summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@proxience.com>2016-01-05 23:29:35 +0100
committerÉtienne Loks <etienne.loks@proxience.com>2016-01-05 23:29:35 +0100
commit70f16a3c305d28586c189349c13c7fa588715e16 (patch)
tree57aab0420ac19b34d50ae8d4b3ab18680919a29a
parent4dd343122548d14f05bcbcb467de8fd52f7b90aa (diff)
downloadChimère-70f16a3c305d28586c189349c13c7fa588715e16.tar.bz2
Chimère-70f16a3c305d28586c189349c13c7fa588715e16.zip
Include ol3 v3.12.1
-rw-r--r--chimere/static/ol3/ol-debug.js133887
-rw-r--r--chimere/static/ol3/ol.css1
-rw-r--r--chimere/static/ol3/ol.js1024
3 files changed, 134912 insertions, 0 deletions
diff --git a/chimere/static/ol3/ol-debug.js b/chimere/static/ol3/ol-debug.js
new file mode 100644
index 0000000..5bf1864
--- /dev/null
+++ b/chimere/static/ol3/ol-debug.js
@@ -0,0 +1,133887 @@
+// OpenLayers 3. See http://openlayers.org/
+// License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md
+// Version: v3.12.1
+
+(function (root, factory) {
+ 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 goog = this.goog = {};
+this.CLOSURE_NO_DEPS = true;
+// 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 Bootstrap for the Google JS Library (Closure).
+ *
+ * In uncompiled mode base.js will write out Closure's deps file, unless the
+ * global <code>CLOSURE_NO_DEPS</code> is set to true. This allows projects to
+ * include their own deps file(s) from different locations.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ *
+ * @provideGoog
+ */
+
+
+/**
+ * @define {boolean} Overridden to true by the compiler when
+ * --process_closure_primitives is specified.
+ */
+var COMPILED = false;
+
+
+/**
+ * Base namespace for the Closure library. Checks to see goog is already
+ * defined in the current scope before assigning to prevent clobbering if
+ * base.js is loaded more than once.
+ *
+ * @const
+ */
+var goog = goog || {};
+
+
+/**
+ * Reference to the global context. In most cases this will be 'window'.
+ */
+goog.global = this;
+
+
+/**
+ * A hook for overriding the define values in uncompiled mode.
+ *
+ * In uncompiled mode, {@code CLOSURE_UNCOMPILED_DEFINES} may be defined before
+ * loading base.js. If a key is defined in {@code CLOSURE_UNCOMPILED_DEFINES},
+ * {@code goog.define} will use the value instead of the default value. This
+ * allows flags to be overwritten without compilation (this is normally
+ * accomplished with the compiler's "define" flag).
+ *
+ * Example:
+ * <pre>
+ * var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false};
+ * </pre>
+ *
+ * @type {Object<string, (string|number|boolean)>|undefined}
+ */
+goog.global.CLOSURE_UNCOMPILED_DEFINES;
+
+
+/**
+ * A hook for overriding the define values in uncompiled or compiled mode,
+ * like CLOSURE_UNCOMPILED_DEFINES but effective in compiled code. In
+ * uncompiled code CLOSURE_UNCOMPILED_DEFINES takes precedence.
+ *
+ * Also unlike CLOSURE_UNCOMPILED_DEFINES the values must be number, boolean or
+ * string literals or the compiler will emit an error.
+ *
+ * While any @define value may be set, only those set with goog.define will be
+ * effective for uncompiled code.
+ *
+ * Example:
+ * <pre>
+ * var CLOSURE_DEFINES = {'goog.DEBUG': false} ;
+ * </pre>
+ *
+ * @type {Object<string, (string|number|boolean)>|undefined}
+ */
+goog.global.CLOSURE_DEFINES;
+
+
+/**
+ * Returns true if the specified value is not undefined.
+ * WARNING: Do not use this to test if an object has a property. Use the in
+ * operator instead.
+ *
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is defined.
+ */
+goog.isDef = function(val) {
+ // void 0 always evaluates to undefined and hence we do not need to depend on
+ // the definition of the global variable named 'undefined'.
+ return val !== void 0;
+};
+
+
+/**
+ * Builds an object structure for the provided namespace path, ensuring that
+ * names that already exist are not overwritten. For example:
+ * "a.b.c" -> a = {};a.b={};a.b.c={};
+ * Used by goog.provide and goog.exportSymbol.
+ * @param {string} name name of the object that this file defines.
+ * @param {*=} opt_object the object to expose at the end of the path.
+ * @param {Object=} opt_objectToExportTo The object to add the path to; default
+ * is |goog.global|.
+ * @private
+ */
+goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
+ var parts = name.split('.');
+ var cur = opt_objectToExportTo || goog.global;
+
+ // Internet Explorer exhibits strange behavior when throwing errors from
+ // methods externed in this manner. See the testExportSymbolExceptions in
+ // base_test.html for an example.
+ if (!(parts[0] in cur) && cur.execScript) {
+ cur.execScript('var ' + parts[0]);
+ }
+
+ // Certain browsers cannot parse code in the form for((a in b); c;);
+ // This pattern is produced by the JSCompiler when it collapses the
+ // statement above into the conditional loop below. To prevent this from
+ // happening, use a for-loop and reserve the init logic as below.
+
+ // Parentheses added to eliminate strict JS warning in Firefox.
+ for (var part; parts.length && (part = parts.shift());) {
+ if (!parts.length && goog.isDef(opt_object)) {
+ // last part and we have an object; use it
+ cur[part] = opt_object;
+ } else if (cur[part]) {
+ cur = cur[part];
+ } else {
+ cur = cur[part] = {};
+ }
+ }
+};
+
+
+/**
+ * Defines a named value. In uncompiled mode, the value is retrieved from
+ * CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and
+ * has the property specified, and otherwise used the defined defaultValue.
+ * When compiled the default can be overridden using the compiler
+ * options or the value set in the CLOSURE_DEFINES object.
+ *
+ * @param {string} name The distinguished name to provide.
+ * @param {string|number|boolean} defaultValue
+ */
+goog.define = function(name, defaultValue) {
+ var value = defaultValue;
+ if (!COMPILED) {
+ if (goog.global.CLOSURE_UNCOMPILED_DEFINES &&
+ Object.prototype.hasOwnProperty.call(
+ goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) {
+ value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name];
+ } else if (goog.global.CLOSURE_DEFINES &&
+ Object.prototype.hasOwnProperty.call(
+ goog.global.CLOSURE_DEFINES, name)) {
+ value = goog.global.CLOSURE_DEFINES[name];
+ }
+ }
+ goog.exportPath_(name, value);
+};
+
+
+/**
+ * @define {boolean} DEBUG is provided as a convenience so that debugging code
+ * that should not be included in a production js_binary can be easily stripped
+ * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
+ * toString() methods should be declared inside an "if (goog.DEBUG)" conditional
+ * because they are generally used for debugging purposes and it is difficult
+ * for the JSCompiler to statically determine whether they are used.
+ */
+goog.define('goog.DEBUG', true);
+
+
+/**
+ * @define {string} LOCALE defines the locale being used for compilation. It is
+ * used to select locale specific data to be compiled in js binary. BUILD rule
+ * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
+ * option.
+ *
+ * Take into account that the locale code format is important. You should use
+ * the canonical Unicode format with hyphen as a delimiter. Language must be
+ * lowercase, Language Script - Capitalized, Region - UPPERCASE.
+ * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
+ *
+ * See more info about locale codes here:
+ * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
+ *
+ * For language codes you should use values defined by ISO 693-1. See it here
+ * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
+ * this rule: the Hebrew language. For legacy reasons the old code (iw) should
+ * be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
+ */
+goog.define('goog.LOCALE', 'en'); // default to en
+
+
+/**
+ * @define {boolean} Whether this code is running on trusted sites.
+ *
+ * On untrusted sites, several native functions can be defined or overridden by
+ * external libraries like Prototype, Datejs, and JQuery and setting this flag
+ * to false forces closure to use its own implementations when possible.
+ *
+ * If your JavaScript can be loaded by a third party site and you are wary about
+ * relying on non-standard implementations, specify
+ * "--define goog.TRUSTED_SITE=false" to the JSCompiler.
+ */
+goog.define('goog.TRUSTED_SITE', true);
+
+
+/**
+ * @define {boolean} Whether a project is expected to be running in strict mode.
+ *
+ * This define can be used to trigger alternate implementations compatible with
+ * running in EcmaScript Strict mode or warn about unavailable functionality.
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/Strict_mode
+ *
+ */
+goog.define('goog.STRICT_MODE_COMPATIBLE', false);
+
+
+/**
+ * @define {boolean} Whether code that calls {@link goog.setTestOnly} should
+ * be disallowed in the compilation unit.
+ */
+goog.define('goog.DISALLOW_TEST_ONLY_CODE', COMPILED && !goog.DEBUG);
+
+
+/**
+ * @define {boolean} Whether to use a Chrome app CSP-compliant method for
+ * loading scripts via goog.require. @see appendScriptSrcNode_.
+ */
+goog.define('goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING', false);
+
+
+/**
+ * Defines a namespace in Closure.
+ *
+ * A namespace may only be defined once in a codebase. It may be defined using
+ * goog.provide() or goog.module().
+ *
+ * The presence of one or more goog.provide() calls in a file indicates
+ * that the file defines the given objects/namespaces.
+ * Provided symbols must not be null or undefined.
+ *
+ * In addition, goog.provide() creates the object stubs for a namespace
+ * (for example, goog.provide("goog.foo.bar") will create the object
+ * goog.foo.bar if it does not already exist).
+ *
+ * Build tools also scan for provide/require/module statements
+ * to discern dependencies, build dependency files (see deps.js), etc.
+ *
+ * @see goog.require
+ * @see goog.module
+ * @param {string} name Namespace provided by this file in the form
+ * "goog.package.part".
+ */
+goog.provide = function(name) {
+ if (!COMPILED) {
+ // Ensure that the same namespace isn't provided twice.
+ // A goog.module/goog.provide maps a goog.require to a specific file
+ if (goog.isProvided_(name)) {
+ throw Error('Namespace "' + name + '" already declared.');
+ }
+ }
+
+ goog.constructNamespace_(name);
+};
+
+
+/**
+ * @param {string} name Namespace provided by this file in the form
+ * "goog.package.part".
+ * @param {Object=} opt_obj The object to embed in the namespace.
+ * @private
+ */
+goog.constructNamespace_ = function(name, opt_obj) {
+ if (!COMPILED) {
+ delete goog.implicitNamespaces_[name];
+
+ var namespace = name;
+ while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
+ if (goog.getObjectByName(namespace)) {
+ break;
+ }
+ goog.implicitNamespaces_[namespace] = true;
+ }
+ }
+
+ goog.exportPath_(name, opt_obj);
+};
+
+
+/**
+ * Module identifier validation regexp.
+ * Note: This is a conservative check, it is very possible to be more lenient,
+ * the primary exclusion here is "/" and "\" and a leading ".", these
+ * restrictions are intended to leave the door open for using goog.require
+ * with relative file paths rather than module identifiers.
+ * @private
+ */
+goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/;
+
+
+/**
+ * Defines a module in Closure.
+ *
+ * Marks that this file must be loaded as a module and claims the namespace.
+ *
+ * A namespace may only be defined once in a codebase. It may be defined using
+ * goog.provide() or goog.module().
+ *
+ * goog.module() has three requirements:
+ * - goog.module may not be used in the same file as goog.provide.
+ * - goog.module must be the first statement in the file.
+ * - only one goog.module is allowed per file.
+ *
+ * When a goog.module annotated file is loaded, it is enclosed in
+ * a strict function closure. This means that:
+ * - any variables declared in a goog.module file are private to the file
+ * (not global), though the compiler is expected to inline the module.
+ * - The code must obey all the rules of "strict" JavaScript.
+ * - the file will be marked as "use strict"
+ *
+ * NOTE: unlike goog.provide, goog.module does not declare any symbols by
+ * itself. If declared symbols are desired, use
+ * goog.module.declareLegacyNamespace().
+ *
+ *
+ * See the public goog.module proposal: http://goo.gl/Va1hin
+ *
+ * @param {string} name Namespace provided by this file in the form
+ * "goog.package.part", is expected but not required.
+ */
+goog.module = function(name) {
+ if (!goog.isString(name) ||
+ !name ||
+ name.search(goog.VALID_MODULE_RE_) == -1) {
+ throw Error('Invalid module identifier');
+ }
+ if (!goog.isInModuleLoader_()) {
+ throw Error('Module ' + name + ' has been loaded incorrectly.');
+ }
+ if (goog.moduleLoaderState_.moduleName) {
+ throw Error('goog.module may only be called once per module.');
+ }
+
+ // Store the module name for the loader.
+ goog.moduleLoaderState_.moduleName = name;
+ if (!COMPILED) {
+ // Ensure that the same namespace isn't provided twice.
+ // A goog.module/goog.provide maps a goog.require to a specific file
+ if (goog.isProvided_(name)) {
+ throw Error('Namespace "' + name + '" already declared.');
+ }
+ delete goog.implicitNamespaces_[name];
+ }
+};
+
+
+/**
+ * @param {string} name The module identifier.
+ * @return {?} The module exports for an already loaded module or null.
+ *
+ * Note: This is not an alternative to goog.require, it does not
+ * indicate a hard dependency, instead it is used to indicate
+ * an optional dependency or to access the exports of a module
+ * that has already been loaded.
+ * @suppress {missingProvide}
+ */
+goog.module.get = function(name) {
+ return goog.module.getInternal_(name);
+};
+
+
+/**
+ * @param {string} name The module identifier.
+ * @return {?} The module exports for an already loaded module or null.
+ * @private
+ */
+goog.module.getInternal_ = function(name) {
+ if (!COMPILED) {
+ if (goog.isProvided_(name)) {
+ // goog.require only return a value with-in goog.module files.
+ return name in goog.loadedModules_ ?
+ goog.loadedModules_[name] :
+ goog.getObjectByName(name);
+ } else {
+ return null;
+ }
+ }
+};
+
+
+/**
+ * @private {?{moduleName: (string|undefined)}}
+ */
+goog.moduleLoaderState_ = null;
+
+
+/**
+ * @private
+ * @return {boolean} Whether a goog.module is currently being initialized.
+ */
+goog.isInModuleLoader_ = function() {
+ return goog.moduleLoaderState_ != null;
+};
+
+
+/**
+ * Provide the module's exports as a globally accessible object under the
+ * module's declared name. This is intended to ease migration to goog.module
+ * for files that have existing usages.
+ * @suppress {missingProvide}
+ */
+goog.module.declareLegacyNamespace = function() {
+ if (!COMPILED && !goog.isInModuleLoader_()) {
+ throw new Error('goog.module.declareLegacyNamespace must be called from ' +
+ 'within a goog.module');
+ }
+ if (!COMPILED && !goog.moduleLoaderState_.moduleName) {
+ throw Error('goog.module must be called prior to ' +
+ 'goog.module.declareLegacyNamespace.');
+ }
+ goog.moduleLoaderState_.declareLegacyNamespace = true;
+};
+
+
+/**
+ * Marks that the current file should only be used for testing, and never for
+ * live code in production.
+ *
+ * In the case of unit tests, the message may optionally be an exact namespace
+ * for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra
+ * provide (if not explicitly defined in the code).
+ *
+ * @param {string=} opt_message Optional message to add to the error that's
+ * raised when used in production code.
+ */
+goog.setTestOnly = function(opt_message) {
+ if (goog.DISALLOW_TEST_ONLY_CODE) {
+ opt_message = opt_message || '';
+ throw Error('Importing test-only code into non-debug environment' +
+ (opt_message ? ': ' + opt_message : '.'));
+ }
+};
+
+
+/**
+ * Forward declares a symbol. This is an indication to the compiler that the
+ * symbol may be used in the source yet is not required and may not be provided
+ * in compilation.
+ *
+ * The most common usage of forward declaration is code that takes a type as a
+ * function parameter but does not need to require it. By forward declaring
+ * instead of requiring, no hard dependency is made, and (if not required
+ * elsewhere) the namespace may never be required and thus, not be pulled
+ * into the JavaScript binary. If it is required elsewhere, it will be type
+ * checked as normal.
+ *
+ *
+ * @param {string} name The namespace to forward declare in the form of
+ * "goog.package.part".
+ */
+goog.forwardDeclare = function(name) {};
+
+
+/**
+ * Forward declare type information. Used to assign types to goog.global
+ * referenced object that would otherwise result in unknown type references
+ * and thus block property disambiguation.
+ */
+goog.forwardDeclare('Document');
+goog.forwardDeclare('XMLHttpRequest');
+
+
+if (!COMPILED) {
+
+ /**
+ * Check if the given name has been goog.provided. This will return false for
+ * names that are available only as implicit namespaces.
+ * @param {string} name name of the object to look for.
+ * @return {boolean} Whether the name has been provided.
+ * @private
+ */
+ goog.isProvided_ = function(name) {
+ return (name in goog.loadedModules_) ||
+ (!goog.implicitNamespaces_[name] &&
+ goog.isDefAndNotNull(goog.getObjectByName(name)));
+ };
+
+ /**
+ * Namespaces implicitly defined by goog.provide. For example,
+ * goog.provide('goog.events.Event') implicitly declares that 'goog' and
+ * 'goog.events' must be namespaces.
+ *
+ * @type {!Object<string, (boolean|undefined)>}
+ * @private
+ */
+ goog.implicitNamespaces_ = {'goog.module': true};
+
+ // NOTE: We add goog.module as an implicit namespace as goog.module is defined
+ // here and because the existing module package has not been moved yet out of
+ // the goog.module namespace. This satisifies both the debug loader and
+ // ahead-of-time dependency management.
+}
+
+
+/**
+ * Returns an object based on its fully qualified external name. The object
+ * is not found if null or undefined. If you are using a compilation pass that
+ * renames property names beware that using this function will not find renamed
+ * properties.
+ *
+ * @param {string} name The fully qualified name.
+ * @param {Object=} opt_obj The object within which to look; default is
+ * |goog.global|.
+ * @return {?} The value (object or primitive) or, if not found, null.
+ */
+goog.getObjectByName = function(name, opt_obj) {
+ var parts = name.split('.');
+ var cur = opt_obj || goog.global;
+ for (var part; part = parts.shift(); ) {
+ if (goog.isDefAndNotNull(cur[part])) {
+ cur = cur[part];
+ } else {
+ return null;
+ }
+ }
+ return cur;
+};
+
+
+/**
+ * Globalizes a whole namespace, such as goog or goog.lang.
+ *
+ * @param {!Object} obj The namespace to globalize.
+ * @param {Object=} opt_global The object to add the properties to.
+ * @deprecated Properties may be explicitly exported to the global scope, but
+ * this should no longer be done in bulk.
+ */
+goog.globalize = function(obj, opt_global) {
+ var global = opt_global || goog.global;
+ for (var x in obj) {
+ global[x] = obj[x];
+ }
+};
+
+
+/**
+ * Adds a dependency from a file to the files it requires.
+ * @param {string} relPath The path to the js file.
+ * @param {!Array<string>} provides An array of strings with
+ * the names of the objects this file provides.
+ * @param {!Array<string>} requires An array of strings with
+ * the names of the objects this file requires.
+ * @param {boolean=} opt_isModule Whether this dependency must be loaded as
+ * a module as declared by goog.module.
+ */
+goog.addDependency = function(relPath, provides, requires, opt_isModule) {
+ if (goog.DEPENDENCIES_ENABLED) {
+ var provide, require;
+ var path = relPath.replace(/\\/g, '/');
+ var deps = goog.dependencies_;
+ for (var i = 0; provide = provides[i]; i++) {
+ deps.nameToPath[provide] = path;
+ deps.pathIsModule[path] = !!opt_isModule;
+ }
+ for (var j = 0; require = requires[j]; j++) {
+ if (!(path in deps.requires)) {
+ deps.requires[path] = {};
+ }
+ deps.requires[path][require] = true;
+ }
+ }
+};
+
+
+
+
+// NOTE(nnaze): The debug DOM loader was included in base.js as an original way
+// to do "debug-mode" development. The dependency system can sometimes be
+// confusing, as can the debug DOM loader's asynchronous nature.
+//
+// With the DOM loader, a call to goog.require() is not blocking -- the script
+// will not load until some point after the current script. If a namespace is
+// needed at runtime, it needs to be defined in a previous script, or loaded via
+// require() with its registered dependencies.
+// User-defined namespaces may need their own deps file. See http://go/js_deps,
+// http://go/genjsdeps, or, externally, DepsWriter.
+// https://developers.google.com/closure/library/docs/depswriter
+//
+// Because of legacy clients, the DOM loader can't be easily removed from
+// base.js. Work is being done to make it disableable or replaceable for
+// different environments (DOM-less JavaScript interpreters like Rhino or V8,
+// for example). See bootstrap/ for more information.
+
+
+/**
+ * @define {boolean} Whether to enable the debug loader.
+ *
+ * If enabled, a call to goog.require() will attempt to load the namespace by
+ * appending a script tag to the DOM (if the namespace has been registered).
+ *
+ * If disabled, goog.require() will simply assert that the namespace has been
+ * provided (and depend on the fact that some outside tool correctly ordered
+ * the script).
+ */
+goog.define('goog.ENABLE_DEBUG_LOADER', true);
+
+
+/**
+ * @param {string} msg
+ * @private
+ */
+goog.logToConsole_ = function(msg) {
+ if (goog.global.console) {
+ goog.global.console['error'](msg);
+ }
+};
+
+
+/**
+ * Implements a system for the dynamic resolution of dependencies that works in
+ * parallel with the BUILD system. Note that all calls to goog.require will be
+ * stripped by the JSCompiler when the --process_closure_primitives option is
+ * used.
+ * @see goog.provide
+ * @param {string} name Namespace to include (as was given in goog.provide()) in
+ * the form "goog.package.part".
+ * @return {?} If called within a goog.module file, the associated namespace or
+ * module otherwise null.
+ */
+goog.require = function(name) {
+ // If the object already exists we do not need do do anything.
+ if (!COMPILED) {
+ if (goog.ENABLE_DEBUG_LOADER && goog.IS_OLD_IE_) {
+ goog.maybeProcessDeferredDep_(name);
+ }
+
+ if (goog.isProvided_(name)) {
+ if (goog.isInModuleLoader_()) {
+ return goog.module.getInternal_(name);
+ } else {
+ return null;
+ }
+ }
+
+ if (goog.ENABLE_DEBUG_LOADER) {
+ var path = goog.getPathFromDeps_(name);
+ if (path) {
+ goog.writeScripts_(path);
+ return null;
+ }
+ }
+
+ var errorMessage = 'goog.require could not find: ' + name;
+ goog.logToConsole_(errorMessage);
+
+ throw Error(errorMessage);
+ }
+};
+
+
+/**
+ * Path for included scripts.
+ * @type {string}
+ */
+goog.basePath = '';
+
+
+/**
+ * A hook for overriding the base path.
+ * @type {string|undefined}
+ */
+goog.global.CLOSURE_BASE_PATH;
+
+
+/**
+ * Whether to write out Closure's deps file. By default, the deps are written.
+ * @type {boolean|undefined}
+ */
+goog.global.CLOSURE_NO_DEPS;
+
+
+/**
+ * A function to import a single script. This is meant to be overridden when
+ * Closure is being run in non-HTML contexts, such as web workers. It's defined
+ * in the global scope so that it can be set before base.js is loaded, which
+ * allows deps.js to be imported properly.
+ *
+ * The function is passed the script source, which is a relative URI. It should
+ * return true if the script was imported, false otherwise.
+ * @type {(function(string): boolean)|undefined}
+ */
+goog.global.CLOSURE_IMPORT_SCRIPT;
+
+
+/**
+ * Null function used for default values of callbacks, etc.
+ * @return {void} Nothing.
+ */
+goog.nullFunction = function() {};
+
+
+/**
+ * When defining a class Foo with an abstract method bar(), you can do:
+ * Foo.prototype.bar = goog.abstractMethod
+ *
+ * Now if a subclass of Foo fails to override bar(), an error will be thrown
+ * when bar() is invoked.
+ *
+ * Note: This does not take the name of the function to override as an argument
+ * because that would make it more difficult to obfuscate our JavaScript code.
+ *
+ * @type {!Function}
+ * @throws {Error} when invoked to indicate the method should be overridden.
+ */
+goog.abstractMethod = function() {
+ throw Error('unimplemented abstract method');
+};
+
+
+/**
+ * Adds a {@code getInstance} static method that always returns the same
+ * instance object.
+ * @param {!Function} ctor The constructor for the class to add the static
+ * method to.
+ */
+goog.addSingletonGetter = function(ctor) {
+ ctor.getInstance = function() {
+ if (ctor.instance_) {
+ return ctor.instance_;
+ }
+ if (goog.DEBUG) {
+ // NOTE: JSCompiler can't optimize away Array#push.
+ goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor;
+ }
+ return ctor.instance_ = new ctor;
+ };
+};
+
+
+/**
+ * All singleton classes that have been instantiated, for testing. Don't read
+ * it directly, use the {@code goog.testing.singleton} module. The compiler
+ * removes this variable if unused.
+ * @type {!Array<!Function>}
+ * @private
+ */
+goog.instantiatedSingletons_ = [];
+
+
+/**
+ * @define {boolean} Whether to load goog.modules using {@code eval} when using
+ * the debug loader. This provides a better debugging experience as the
+ * source is unmodified and can be edited using Chrome Workspaces or similar.
+ * However in some environments the use of {@code eval} is banned
+ * so we provide an alternative.
+ */
+goog.define('goog.LOAD_MODULE_USING_EVAL', true);
+
+
+/**
+ * @define {boolean} Whether the exports of goog.modules should be sealed when
+ * possible.
+ */
+goog.define('goog.SEAL_MODULE_EXPORTS', goog.DEBUG);
+
+
+/**
+ * The registry of initialized modules:
+ * the module identifier to module exports map.
+ * @private @const {!Object<string, ?>}
+ */
+goog.loadedModules_ = {};
+
+
+/**
+ * True if goog.dependencies_ is available.
+ * @const {boolean}
+ */
+goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER;
+
+
+if (goog.DEPENDENCIES_ENABLED) {
+
+ /**
+ * This object is used to keep track of dependencies and other data that is
+ * used for loading scripts.
+ * @private
+ * @type {{
+ * pathIsModule: !Object<string, boolean>,
+ * nameToPath: !Object<string, string>,
+ * requires: !Object<string, !Object<string, boolean>>,
+ * visited: !Object<string, boolean>,
+ * written: !Object<string, boolean>,
+ * deferred: !Object<string, string>
+ * }}
+ */
+ goog.dependencies_ = {
+ pathIsModule: {}, // 1 to 1
+
+ nameToPath: {}, // 1 to 1
+
+ requires: {}, // 1 to many
+
+ // Used when resolving dependencies to prevent us from visiting file twice.
+ visited: {},
+
+ written: {}, // Used to keep track of script files we have written.
+
+ deferred: {} // Used to track deferred module evaluations in old IEs
+ };
+
+
+ /**
+ * Tries to detect whether is in the context of an HTML document.
+ * @return {boolean} True if it looks like HTML document.
+ * @private
+ */
+ goog.inHtmlDocument_ = function() {
+ /** @type {Document} */
+ var doc = goog.global.document;
+ return typeof doc != 'undefined' &&
+ 'write' in doc; // XULDocument misses write.
+ };
+
+
+ /**
+ * Tries to detect the base path of base.js script that bootstraps Closure.
+ * @private
+ */
+ goog.findBasePath_ = function() {
+ if (goog.isDef(goog.global.CLOSURE_BASE_PATH)) {
+ goog.basePath = goog.global.CLOSURE_BASE_PATH;
+ return;
+ } else if (!goog.inHtmlDocument_()) {
+ return;
+ }
+ /** @type {Document} */
+ var doc = goog.global.document;
+ var scripts = doc.getElementsByTagName('SCRIPT');
+ // Search backwards since the current script is in almost all cases the one
+ // that has base.js.
+ for (var i = scripts.length - 1; i >= 0; --i) {
+ var script = /** @type {!HTMLScriptElement} */ (scripts[i]);
+ var src = script.src;
+ var qmark = src.lastIndexOf('?');
+ var l = qmark == -1 ? src.length : qmark;
+ if (src.substr(l - 7, 7) == 'base.js') {
+ goog.basePath = src.substr(0, l - 7);
+ return;
+ }
+ }
+ };
+
+
+ /**
+ * Imports a script if, and only if, that script hasn't already been imported.
+ * (Must be called at execution time)
+ * @param {string} src Script source.
+ * @param {string=} opt_sourceText The optionally source text to evaluate
+ * @private
+ */
+ goog.importScript_ = function(src, opt_sourceText) {
+ var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
+ goog.writeScriptTag_;
+ if (importScript(src, opt_sourceText)) {
+ goog.dependencies_.written[src] = true;
+ }
+ };
+
+
+ /** @const @private {boolean} */
+ goog.IS_OLD_IE_ = !!(!goog.global.atob && goog.global.document &&
+ goog.global.document.all);
+
+
+ /**
+ * Given a URL initiate retrieval and execution of the module.
+ * @param {string} src Script source URL.
+ * @private
+ */
+ goog.importModule_ = function(src) {
+ // In an attempt to keep browsers from timing out loading scripts using
+ // synchronous XHRs, put each load in its own script block.
+ var bootstrap = 'goog.retrieveAndExecModule_("' + src + '");';
+
+ if (goog.importScript_('', bootstrap)) {
+ goog.dependencies_.written[src] = true;
+ }
+ };
+
+
+ /** @private {!Array<string>} */
+ goog.queuedModules_ = [];
+
+
+ /**
+ * Return an appropriate module text. Suitable to insert into
+ * a script tag (that is unescaped).
+ * @param {string} srcUrl
+ * @param {string} scriptText
+ * @return {string}
+ * @private
+ */
+ goog.wrapModule_ = function(srcUrl, scriptText) {
+ if (!goog.LOAD_MODULE_USING_EVAL || !goog.isDef(goog.global.JSON)) {
+ return '' +
+ 'goog.loadModule(function(exports) {' +
+ '"use strict";' +
+ scriptText +
+ '\n' + // terminate any trailing single line comment.
+ ';return exports' +
+ '});' +
+ '\n//# sourceURL=' + srcUrl + '\n';
+ } else {
+ return '' +
+ 'goog.loadModule(' +
+ goog.global.JSON.stringify(
+ scriptText + '\n//# sourceURL=' + srcUrl + '\n') +
+ ');';
+ }
+ };
+
+ // On IE9 and earlier, it is necessary to handle
+ // deferred module loads. In later browsers, the
+ // code to be evaluated is simply inserted as a script
+ // block in the correct order. To eval deferred
+ // code at the right time, we piggy back on goog.require to call
+ // goog.maybeProcessDeferredDep_.
+ //
+ // The goog.requires are used both to bootstrap
+ // the loading process (when no deps are available) and
+ // declare that they should be available.
+ //
+ // Here we eval the sources, if all the deps are available
+ // either already eval'd or goog.require'd. This will
+ // be the case when all the dependencies have already
+ // been loaded, and the dependent module is loaded.
+ //
+ // But this alone isn't sufficient because it is also
+ // necessary to handle the case where there is no root
+ // that is not deferred. For that there we register for an event
+ // and trigger goog.loadQueuedModules_ handle any remaining deferred
+ // evaluations.
+
+ /**
+ * Handle any remaining deferred goog.module evals.
+ * @private
+ */
+ goog.loadQueuedModules_ = function() {
+ var count = goog.queuedModules_.length;
+ if (count > 0) {
+ var queue = goog.queuedModules_;
+ goog.queuedModules_ = [];
+ for (var i = 0; i < count; i++) {
+ var path = queue[i];
+ goog.maybeProcessDeferredPath_(path);
+ }
+ }
+ };
+
+
+ /**
+ * Eval the named module if its dependencies are
+ * available.
+ * @param {string} name The module to load.
+ * @private
+ */
+ goog.maybeProcessDeferredDep_ = function(name) {
+ if (goog.isDeferredModule_(name) &&
+ goog.allDepsAreAvailable_(name)) {
+ var path = goog.getPathFromDeps_(name);
+ goog.maybeProcessDeferredPath_(goog.basePath + path);
+ }
+ };
+
+ /**
+ * @param {string} name The module to check.
+ * @return {boolean} Whether the name represents a
+ * module whose evaluation has been deferred.
+ * @private
+ */
+ goog.isDeferredModule_ = function(name) {
+ var path = goog.getPathFromDeps_(name);
+ if (path && goog.dependencies_.pathIsModule[path]) {
+ var abspath = goog.basePath + path;
+ return (abspath) in goog.dependencies_.deferred;
+ }
+ return false;
+ };
+
+ /**
+ * @param {string} name The module to check.
+ * @return {boolean} Whether the name represents a
+ * module whose declared dependencies have all been loaded
+ * (eval'd or a deferred module load)
+ * @private
+ */
+ goog.allDepsAreAvailable_ = function(name) {
+ var path = goog.getPathFromDeps_(name);
+ if (path && (path in goog.dependencies_.requires)) {
+ for (var requireName in goog.dependencies_.requires[path]) {
+ if (!goog.isProvided_(requireName) &&
+ !goog.isDeferredModule_(requireName)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+
+
+ /**
+ * @param {string} abspath
+ * @private
+ */
+ goog.maybeProcessDeferredPath_ = function(abspath) {
+ if (abspath in goog.dependencies_.deferred) {
+ var src = goog.dependencies_.deferred[abspath];
+ delete goog.dependencies_.deferred[abspath];
+ goog.globalEval(src);
+ }
+ };
+
+
+ /**
+ * @param {function(?):?|string} moduleDef The module definition.
+ */
+ goog.loadModule = function(moduleDef) {
+ // NOTE: we allow function definitions to be either in the from
+ // of a string to eval (which keeps the original source intact) or
+ // in a eval forbidden environment (CSP) we allow a function definition
+ // which in its body must call {@code goog.module}, and return the exports
+ // of the module.
+ var previousState = goog.moduleLoaderState_;
+ try {
+ goog.moduleLoaderState_ = {moduleName: undefined};
+ var exports;
+ if (goog.isFunction(moduleDef)) {
+ exports = moduleDef.call(goog.global, {});
+ } else if (goog.isString(moduleDef)) {
+ exports = goog.loadModuleFromSource_.call(goog.global, moduleDef);
+ } else {
+ throw Error('Invalid module definition');
+ }
+
+ var moduleName = goog.moduleLoaderState_.moduleName;
+ if (!goog.isString(moduleName) || !moduleName) {
+ throw Error('Invalid module name \"' + moduleName + '\"');
+ }
+
+ // Don't seal legacy namespaces as they may be uses as a parent of
+ // another namespace
+ if (goog.moduleLoaderState_.declareLegacyNamespace) {
+ goog.constructNamespace_(moduleName, exports);
+ } else if (goog.SEAL_MODULE_EXPORTS && Object.seal) {
+ Object.seal(exports);
+ }
+
+ goog.loadedModules_[moduleName] = exports;
+ } finally {
+ goog.moduleLoaderState_ = previousState;
+ }
+ };
+
+
+ /**
+ * @private @const {function(string):?}
+ * @suppress {newCheckTypes}
+ */
+ goog.loadModuleFromSource_ = function() {
+ // NOTE: we avoid declaring parameters or local variables here to avoid
+ // masking globals or leaking values into the module definition.
+ 'use strict';
+ var exports = {};
+ eval(arguments[0]);
+ return exports;
+ };
+
+
+ /**
+ * Writes a new script pointing to {@code src} directly into the DOM.
+ *
+ * NOTE: This method is not CSP-compliant. @see goog.appendScriptSrcNode_ for
+ * the fallback mechanism.
+ *
+ * @param {string} src The script URL.
+ * @private
+ */
+ goog.writeScriptSrcNode_ = function(src) {
+ goog.global.document.write(
+ '<script type="text/javascript" src="' + src + '"></' + 'script>');
+ };
+
+
+ /**
+ * Appends a new script node to the DOM using a CSP-compliant mechanism. This
+ * method exists as a fallback for document.write (which is not allowed in a
+ * strict CSP context, e.g., Chrome apps).
+ *
+ * NOTE: This method is not analogous to using document.write to insert a
+ * <script> tag; specifically, the user agent will execute a script added by
+ * document.write immediately after the current script block finishes
+ * executing, whereas the DOM-appended script node will not be executed until
+ * the entire document is parsed and executed. That is to say, this script is
+ * added to the end of the script execution queue.
+ *
+ * The page must not attempt to call goog.required entities until after the
+ * document has loaded, e.g., in or after the window.onload callback.
+ *
+ * @param {string} src The script URL.
+ * @private
+ */
+ goog.appendScriptSrcNode_ = function(src) {
+ /** @type {Document} */
+ var doc = goog.global.document;
+ var scriptEl = doc.createElement('script');
+ scriptEl.type = 'text/javascript';
+ scriptEl.src = src;
+ scriptEl.defer = false;
+ scriptEl.async = false;
+ doc.head.appendChild(scriptEl);
+ };
+
+
+ /**
+ * The default implementation of the import function. Writes a script tag to
+ * import the script.
+ *
+ * @param {string} src The script url.
+ * @param {string=} opt_sourceText The optionally source text to evaluate
+ * @return {boolean} True if the script was imported, false otherwise.
+ * @private
+ */
+ goog.writeScriptTag_ = function(src, opt_sourceText) {
+ if (goog.inHtmlDocument_()) {
+ /** @type {Document} */
+ var doc = goog.global.document;
+
+ // If the user tries to require a new symbol after document load,
+ // something has gone terribly wrong. Doing a document.write would
+ // wipe out the page. This does not apply to the CSP-compliant method
+ // of writing script tags.
+ if (!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING &&
+ doc.readyState == 'complete') {
+ // Certain test frameworks load base.js multiple times, which tries
+ // to write deps.js each time. If that happens, just fail silently.
+ // These frameworks wipe the page between each load of base.js, so this
+ // is OK.
+ var isDeps = /\bdeps.js$/.test(src);
+ if (isDeps) {
+ return false;
+ } else {
+ throw Error('Cannot write "' + src + '" after document load');
+ }
+ }
+
+ var isOldIE = goog.IS_OLD_IE_;
+
+ if (opt_sourceText === undefined) {
+ if (!isOldIE) {
+ if (goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING) {
+ goog.appendScriptSrcNode_(src);
+ } else {
+ goog.writeScriptSrcNode_(src);
+ }
+ } else {
+ var state = " onreadystatechange='goog.onScriptLoad_(this, " +
+ ++goog.lastNonModuleScriptIndex_ + ")' ";
+ doc.write(
+ '<script type="text/javascript" src="' +
+ src + '"' + state + '></' + 'script>');
+ }
+ } else {
+ doc.write(
+ '<script type="text/javascript">' +
+ opt_sourceText +
+ '</' + 'script>');
+ }
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+
+ /** @private {number} */
+ goog.lastNonModuleScriptIndex_ = 0;
+
+
+ /**
+ * A readystatechange handler for legacy IE
+ * @param {!HTMLScriptElement} script
+ * @param {number} scriptIndex
+ * @return {boolean}
+ * @private
+ */
+ goog.onScriptLoad_ = function(script, scriptIndex) {
+ // for now load the modules when we reach the last script,
+ // later allow more inter-mingling.
+ if (script.readyState == 'complete' &&
+ goog.lastNonModuleScriptIndex_ == scriptIndex) {
+ goog.loadQueuedModules_();
+ }
+ return true;
+ };
+
+ /**
+ * Resolves dependencies based on the dependencies added using addDependency
+ * and calls importScript_ in the correct order.
+ * @param {string} pathToLoad The path from which to start discovering
+ * dependencies.
+ * @private
+ */
+ goog.writeScripts_ = function(pathToLoad) {
+ /** @type {!Array<string>} The scripts we need to write this time. */
+ var scripts = [];
+ var seenScript = {};
+ var deps = goog.dependencies_;
+
+ /** @param {string} path */
+ function visitNode(path) {
+ if (path in deps.written) {
+ return;
+ }
+
+ // We have already visited this one. We can get here if we have cyclic
+ // dependencies.
+ if (path in deps.visited) {
+ return;
+ }
+
+ deps.visited[path] = true;
+
+ if (path in deps.requires) {
+ for (var requireName in deps.requires[path]) {
+ // If the required name is defined, we assume that it was already
+ // bootstrapped by other means.
+ if (!goog.isProvided_(requireName)) {
+ if (requireName in deps.nameToPath) {
+ visitNode(deps.nameToPath[requireName]);
+ } else {
+ throw Error('Undefined nameToPath for ' + requireName);
+ }
+ }
+ }
+ }
+
+ if (!(path in seenScript)) {
+ seenScript[path] = true;
+ scripts.push(path);
+ }
+ }
+
+ visitNode(pathToLoad);
+
+ // record that we are going to load all these scripts.
+ for (var i = 0; i < scripts.length; i++) {
+ var path = scripts[i];
+ goog.dependencies_.written[path] = true;
+ }
+
+ // If a module is loaded synchronously then we need to
+ // clear the current inModuleLoader value, and restore it when we are
+ // done loading the current "requires".
+ var moduleState = goog.moduleLoaderState_;
+ goog.moduleLoaderState_ = null;
+
+ for (var i = 0; i < scripts.length; i++) {
+ var path = scripts[i];
+ if (path) {
+ if (!deps.pathIsModule[path]) {
+ goog.importScript_(goog.basePath + path);
+ } else {
+ goog.importModule_(goog.basePath + path);
+ }
+ } else {
+ goog.moduleLoaderState_ = moduleState;
+ throw Error('Undefined script input');
+ }
+ }
+
+ // restore the current "module loading state"
+ goog.moduleLoaderState_ = moduleState;
+ };
+
+
+ /**
+ * Looks at the dependency rules and tries to determine the script file that
+ * fulfills a particular rule.
+ * @param {string} rule In the form goog.namespace.Class or project.script.
+ * @return {?string} Url corresponding to the rule, or null.
+ * @private
+ */
+ goog.getPathFromDeps_ = function(rule) {
+ if (rule in goog.dependencies_.nameToPath) {
+ return goog.dependencies_.nameToPath[rule];
+ } else {
+ return null;
+ }
+ };
+
+ goog.findBasePath_();
+
+ // Allow projects to manage the deps files themselves.
+ if (!goog.global.CLOSURE_NO_DEPS) {
+ goog.importScript_(goog.basePath + 'deps.js');
+ }
+}
+
+
+/**
+ * Normalize a file path by removing redundant ".." and extraneous "." file
+ * path components.
+ * @param {string} path
+ * @return {string}
+ * @private
+ */
+goog.normalizePath_ = function(path) {
+ var components = path.split('/');
+ var i = 0;
+ while (i < components.length) {
+ if (components[i] == '.') {
+ components.splice(i, 1);
+ } else if (i && components[i] == '..' &&
+ components[i - 1] && components[i - 1] != '..') {
+ components.splice(--i, 2);
+ } else {
+ i++;
+ }
+ }
+ return components.join('/');
+};
+
+
+/**
+ * Loads file by synchronous XHR. Should not be used in production environments.
+ * @param {string} src Source URL.
+ * @return {string} File contents.
+ * @private
+ */
+goog.loadFileSync_ = function(src) {
+ if (goog.global.CLOSURE_LOAD_FILE_SYNC) {
+ return goog.global.CLOSURE_LOAD_FILE_SYNC(src);
+ } else {
+ /** @type {XMLHttpRequest} */
+ var xhr = new goog.global['XMLHttpRequest']();
+ xhr.open('get', src, false);
+ xhr.send();
+ return xhr.responseText;
+ }
+};
+
+
+/**
+ * Retrieve and execute a module.
+ * @param {string} src Script source URL.
+ * @private
+ */
+goog.retrieveAndExecModule_ = function(src) {
+ if (!COMPILED) {
+ // The full but non-canonicalized URL for later use.
+ var originalPath = src;
+ // Canonicalize the path, removing any /./ or /../ since Chrome's debugging
+ // console doesn't auto-canonicalize XHR loads as it does <script> srcs.
+ src = goog.normalizePath_(src);
+
+ var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
+ goog.writeScriptTag_;
+
+ var scriptText = goog.loadFileSync_(src);
+
+ if (scriptText != null) {
+ var execModuleScript = goog.wrapModule_(src, scriptText);
+ var isOldIE = goog.IS_OLD_IE_;
+ if (isOldIE) {
+ goog.dependencies_.deferred[originalPath] = execModuleScript;
+ goog.queuedModules_.push(originalPath);
+ } else {
+ importScript(src, execModuleScript);
+ }
+ } else {
+ throw new Error('load of ' + src + 'failed');
+ }
+ }
+};
+
+
+//==============================================================================
+// Language Enhancements
+//==============================================================================
+
+
+/**
+ * This is a "fixed" version of the typeof operator. It differs from the typeof
+ * operator in such a way that null returns 'null' and arrays return 'array'.
+ * @param {*} value The value to get the type of.
+ * @return {string} The name of the type.
+ */
+goog.typeOf = function(value) {
+ var s = typeof value;
+ if (s == 'object') {
+ if (value) {
+ // Check these first, so we can avoid calling Object.prototype.toString if
+ // possible.
+ //
+ // IE improperly marshals tyepof across execution contexts, but a
+ // cross-context object will still return false for "instanceof Object".
+ if (value instanceof Array) {
+ return 'array';
+ } else if (value instanceof Object) {
+ return s;
+ }
+
+ // HACK: In order to use an Object prototype method on the arbitrary
+ // value, the compiler requires the value be cast to type Object,
+ // even though the ECMA spec explicitly allows it.
+ var className = Object.prototype.toString.call(
+ /** @type {Object} */ (value));
+ // In Firefox 3.6, attempting to access iframe window objects' length
+ // property throws an NS_ERROR_FAILURE, so we need to special-case it
+ // here.
+ if (className == '[object Window]') {
+ return 'object';
+ }
+
+ // We cannot always use constructor == Array or instanceof Array because
+ // different frames have different Array objects. In IE6, if the iframe
+ // where the array was created is destroyed, the array loses its
+ // prototype. Then dereferencing val.splice here throws an exception, so
+ // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
+ // so that will work. In this case, this function will return false and
+ // most array functions will still work because the array is still
+ // array-like (supports length and []) even though it has lost its
+ // prototype.
+ // Mark Miller noticed that Object.prototype.toString
+ // allows access to the unforgeable [[Class]] property.
+ // 15.2.4.2 Object.prototype.toString ( )
+ // When the toString method is called, the following steps are taken:
+ // 1. Get the [[Class]] property of this object.
+ // 2. Compute a string value by concatenating the three strings
+ // "[object ", Result(1), and "]".
+ // 3. Return Result(2).
+ // and this behavior survives the destruction of the execution context.
+ if ((className == '[object Array]' ||
+ // In IE all non value types are wrapped as objects across window
+ // boundaries (not iframe though) so we have to do object detection
+ // for this edge case.
+ typeof value.length == 'number' &&
+ typeof value.splice != 'undefined' &&
+ typeof value.propertyIsEnumerable != 'undefined' &&
+ !value.propertyIsEnumerable('splice')
+
+ )) {
+ return 'array';
+ }
+ // HACK: There is still an array case that fails.
+ // function ArrayImpostor() {}
+ // ArrayImpostor.prototype = [];
+ // var impostor = new ArrayImpostor;
+ // this can be fixed by getting rid of the fast path
+ // (value instanceof Array) and solely relying on
+ // (value && Object.prototype.toString.vall(value) === '[object Array]')
+ // but that would require many more function calls and is not warranted
+ // unless closure code is receiving objects from untrusted sources.
+
+ // IE in cross-window calls does not correctly marshal the function type
+ // (it appears just as an object) so we cannot use just typeof val ==
+ // 'function'. However, if the object has a call property, it is a
+ // function.
+ if ((className == '[object Function]' ||
+ typeof value.call != 'undefined' &&
+ typeof value.propertyIsEnumerable != 'undefined' &&
+ !value.propertyIsEnumerable('call'))) {
+ return 'function';
+ }
+
+ } else {
+ return 'null';
+ }
+
+ } else if (s == 'function' && typeof value.call == 'undefined') {
+ // In Safari typeof nodeList returns 'function', and on Firefox typeof
+ // behaves similarly for HTML{Applet,Embed,Object}, Elements and RegExps. We
+ // would like to return object for those and we can detect an invalid
+ // function by making sure that the function object has a call method.
+ return 'object';
+ }
+ return s;
+};
+
+
+/**
+ * Returns true if the specified value is null.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is null.
+ */
+goog.isNull = function(val) {
+ return val === null;
+};
+
+
+/**
+ * Returns true if the specified value is defined and not null.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is defined and not null.
+ */
+goog.isDefAndNotNull = function(val) {
+ // Note that undefined == null.
+ return val != null;
+};
+
+
+/**
+ * Returns true if the specified value is an array.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArray = function(val) {
+ return goog.typeOf(val) == 'array';
+};
+
+
+/**
+ * Returns true if the object looks like an array. To qualify as array like
+ * the value needs to be either a NodeList or an object with a Number length
+ * property. As a special case, a function value is not array like, because its
+ * length property is fixed to correspond to the number of expected arguments.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArrayLike = function(val) {
+ var type = goog.typeOf(val);
+ // We do not use goog.isObject here in order to exclude function values.
+ return type == 'array' || type == 'object' && typeof val.length == 'number';
+};
+
+
+/**
+ * Returns true if the object looks like a Date. To qualify as Date-like the
+ * value needs to be an object and have a getFullYear() function.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a like a Date.
+ */
+goog.isDateLike = function(val) {
+ return goog.isObject(val) && typeof val.getFullYear == 'function';
+};
+
+
+/**
+ * Returns true if the specified value is a string.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a string.
+ */
+goog.isString = function(val) {
+ return typeof val == 'string';
+};
+
+
+/**
+ * Returns true if the specified value is a boolean.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is boolean.
+ */
+goog.isBoolean = function(val) {
+ return typeof val == 'boolean';
+};
+
+
+/**
+ * Returns true if the specified value is a number.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a number.
+ */
+goog.isNumber = function(val) {
+ return typeof val == 'number';
+};
+
+
+/**
+ * Returns true if the specified value is a function.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a function.
+ */
+goog.isFunction = function(val) {
+ return goog.typeOf(val) == 'function';
+};
+
+
+/**
+ * Returns true if the specified value is an object. This includes arrays and
+ * functions.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is an object.
+ */
+goog.isObject = function(val) {
+ var type = typeof val;
+ return type == 'object' && val != null || type == 'function';
+ // return Object(val) === val also works, but is slower, especially if val is
+ // not an object.
+};
+
+
+/**
+ * Gets a unique ID for an object. This mutates the object so that further calls
+ * with the same object as a parameter returns the same value. The unique ID is
+ * guaranteed to be unique across the current session amongst objects that are
+ * passed into {@code getUid}. There is no guarantee that the ID is unique or
+ * consistent across sessions. It is unsafe to generate unique ID for function
+ * prototypes.
+ *
+ * @param {Object} obj The object to get the unique ID for.
+ * @return {number} The unique ID for the object.
+ */
+goog.getUid = function(obj) {
+ // TODO(arv): Make the type stricter, do not accept null.
+
+ // In Opera window.hasOwnProperty exists but always returns false so we avoid
+ // using it. As a consequence the unique ID generated for BaseClass.prototype
+ // and SubClass.prototype will be the same.
+ return obj[goog.UID_PROPERTY_] ||
+ (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_);
+};
+
+
+/**
+ * Whether the given object is already assigned a unique ID.
+ *
+ * This does not modify the object.
+ *
+ * @param {!Object} obj The object to check.
+ * @return {boolean} Whether there is an assigned unique id for the object.
+ */
+goog.hasUid = function(obj) {
+ return !!obj[goog.UID_PROPERTY_];
+};
+
+
+/**
+ * Removes the unique ID from an object. This is useful if the object was
+ * previously mutated using {@code goog.getUid} in which case the mutation is
+ * undone.
+ * @param {Object} obj The object to remove the unique ID field from.
+ */
+goog.removeUid = function(obj) {
+ // TODO(arv): Make the type stricter, do not accept null.
+
+ // In IE, DOM nodes are not instances of Object and throw an exception if we
+ // try to delete. Instead we try to use removeAttribute.
+ if ('removeAttribute' in obj) {
+ obj.removeAttribute(goog.UID_PROPERTY_);
+ }
+ /** @preserveTry */
+ try {
+ delete obj[goog.UID_PROPERTY_];
+ } catch (ex) {
+ }
+};
+
+
+/**
+ * Name for unique ID property. Initialized in a way to help avoid collisions
+ * with other closure JavaScript on the same page.
+ * @type {string}
+ * @private
+ */
+goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0);
+
+
+/**
+ * Counter for UID.
+ * @type {number}
+ * @private
+ */
+goog.uidCounter_ = 0;
+
+
+/**
+ * Adds a hash code field to an object. The hash code is unique for the
+ * given object.
+ * @param {Object} obj The object to get the hash code for.
+ * @return {number} The hash code for the object.
+ * @deprecated Use goog.getUid instead.
+ */
+goog.getHashCode = goog.getUid;
+
+
+/**
+ * Removes the hash code field from an object.
+ * @param {Object} obj The object to remove the field from.
+ * @deprecated Use goog.removeUid instead.
+ */
+goog.removeHashCode = goog.removeUid;
+
+
+/**
+ * Clones a value. The input may be an Object, Array, or basic type. Objects and
+ * arrays will be cloned recursively.
+ *
+ * WARNINGS:
+ * <code>goog.cloneObject</code> does not detect reference loops. Objects that
+ * refer to themselves will cause infinite recursion.
+ *
+ * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies
+ * UIDs created by <code>getUid</code> into cloned results.
+ *
+ * @param {*} obj The value to clone.
+ * @return {*} A clone of the input value.
+ * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods.
+ */
+goog.cloneObject = function(obj) {
+ var type = goog.typeOf(obj);
+ if (type == 'object' || type == 'array') {
+ if (obj.clone) {
+ return obj.clone();
+ }
+ var clone = type == 'array' ? [] : {};
+ for (var key in obj) {
+ clone[key] = goog.cloneObject(obj[key]);
+ }
+ return clone;
+ }
+
+ return obj;
+};
+
+
+/**
+ * A native implementation of goog.bind.
+ * @param {Function} fn A function to partially apply.
+ * @param {Object|undefined} selfObj Specifies the object which this should
+ * point to when the function is run.
+ * @param {...*} var_args Additional arguments that are partially applied to the
+ * function.
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ * @private
+ * @suppress {deprecated} The compiler thinks that Function.prototype.bind is
+ * deprecated because some people have declared a pure-JS version.
+ * Only the pure-JS version is truly deprecated.
+ */
+goog.bindNative_ = function(fn, selfObj, var_args) {
+ return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments));
+};
+
+
+/**
+ * A pure-JS implementation of goog.bind.
+ * @param {Function} fn A function to partially apply.
+ * @param {Object|undefined} selfObj Specifies the object which this should
+ * point to when the function is run.
+ * @param {...*} var_args Additional arguments that are partially applied to the
+ * function.
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ * @private
+ */
+goog.bindJs_ = function(fn, selfObj, var_args) {
+ if (!fn) {
+ throw new Error();
+ }
+
+ if (arguments.length > 2) {
+ var boundArgs = Array.prototype.slice.call(arguments, 2);
+ return function() {
+ // Prepend the bound arguments to the current arguments.
+ var newArgs = Array.prototype.slice.call(arguments);
+ Array.prototype.unshift.apply(newArgs, boundArgs);
+ return fn.apply(selfObj, newArgs);
+ };
+
+ } else {
+ return function() {
+ return fn.apply(selfObj, arguments);
+ };
+ }
+};
+
+
+/**
+ * Partially applies this function to a particular 'this object' and zero or
+ * more arguments. The result is a new function with some arguments of the first
+ * function pre-filled and the value of this 'pre-specified'.
+ *
+ * Remaining arguments specified at call-time are appended to the pre-specified
+ * ones.
+ *
+ * Also see: {@link #partial}.
+ *
+ * Usage:
+ * <pre>var barMethBound = goog.bind(myFunction, myObj, 'arg1', 'arg2');
+ * barMethBound('arg3', 'arg4');</pre>
+ *
+ * @param {?function(this:T, ...)} fn A function to partially apply.
+ * @param {T} selfObj Specifies the object which this should point to when the
+ * function is run.
+ * @param {...*} var_args Additional arguments that are partially applied to the
+ * function.
+ * @return {!Function} A partially-applied form of the function goog.bind() was
+ * invoked as a method of.
+ * @template T
+ * @suppress {deprecated} See above.
+ */
+goog.bind = function(fn, selfObj, var_args) {
+ // TODO(nicksantos): narrow the type signature.
+ if (Function.prototype.bind &&
+ // NOTE(nicksantos): Somebody pulled base.js into the default Chrome
+ // extension environment. This means that for Chrome extensions, they get
+ // the implementation of Function.prototype.bind that calls goog.bind
+ // instead of the native one. Even worse, we don't want to introduce a
+ // circular dependency between goog.bind and Function.prototype.bind, so
+ // we have to hack this to make sure it works correctly.
+ Function.prototype.bind.toString().indexOf('native code') != -1) {
+ goog.bind = goog.bindNative_;
+ } else {
+ goog.bind = goog.bindJs_;
+ }
+ return goog.bind.apply(null, arguments);
+};
+
+
+/**
+ * Like goog.bind(), except that a 'this object' is not required. Useful when
+ * the target function is already bound.
+ *
+ * Usage:
+ * var g = goog.partial(f, arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Function} fn A function to partially apply.
+ * @param {...*} var_args Additional arguments that are partially applied to fn.
+ * @return {!Function} A partially-applied form of the function goog.partial()
+ * was invoked as a method of.
+ */
+goog.partial = function(fn, var_args) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function() {
+ // Clone the array (with slice()) and append additional arguments
+ // to the existing arguments.
+ var newArgs = args.slice();
+ newArgs.push.apply(newArgs, arguments);
+ return fn.apply(this, newArgs);
+ };
+};
+
+
+/**
+ * Copies all the members of a source object to a target object. This method
+ * does not work on all browsers for all objects that contain keys such as
+ * toString or hasOwnProperty. Use goog.object.extend for this purpose.
+ * @param {Object} target Target.
+ * @param {Object} source Source.
+ */
+goog.mixin = function(target, source) {
+ for (var x in source) {
+ target[x] = source[x];
+ }
+
+ // For IE7 or lower, the for-in-loop does not contain any properties that are
+ // not enumerable on the prototype object (for example, isPrototypeOf from
+ // Object.prototype) but also it will not include 'replace' on objects that
+ // extend String and change 'replace' (not that it is common for anyone to
+ // extend anything except Object).
+};
+
+
+/**
+ * @return {number} An integer value representing the number of milliseconds
+ * between midnight, January 1, 1970 and the current time.
+ */
+goog.now = (goog.TRUSTED_SITE && Date.now) || (function() {
+ // Unary plus operator converts its operand to a number which in the case of
+ // a date is done by calling getTime().
+ return +new Date();
+});
+
+
+/**
+ * Evals JavaScript in the global scope. In IE this uses execScript, other
+ * browsers use goog.global.eval. If goog.global.eval does not evaluate in the
+ * global scope (for example, in Safari), appends a script tag instead.
+ * Throws an exception if neither execScript or eval is defined.
+ * @param {string} script JavaScript string.
+ */
+goog.globalEval = function(script) {
+ if (goog.global.execScript) {
+ goog.global.execScript(script, 'JavaScript');
+ } else if (goog.global.eval) {
+ // Test to see if eval works
+ if (goog.evalWorksForGlobals_ == null) {
+ goog.global.eval('var _evalTest_ = 1;');
+ if (typeof goog.global['_evalTest_'] != 'undefined') {
+ try {
+ delete goog.global['_evalTest_'];
+ } catch (ignore) {
+ // Microsoft edge fails the deletion above in strict mode.
+ }
+ goog.evalWorksForGlobals_ = true;
+ } else {
+ goog.evalWorksForGlobals_ = false;
+ }
+ }
+
+ if (goog.evalWorksForGlobals_) {
+ goog.global.eval(script);
+ } else {
+ /** @type {Document} */
+ var doc = goog.global.document;
+ var scriptElt = doc.createElement('SCRIPT');
+ scriptElt.type = 'text/javascript';
+ scriptElt.defer = false;
+ // Note(user): can't use .innerHTML since "t('<test>')" will fail and
+ // .text doesn't work in Safari 2. Therefore we append a text node.
+ scriptElt.appendChild(doc.createTextNode(script));
+ doc.body.appendChild(scriptElt);
+ doc.body.removeChild(scriptElt);
+ }
+ } else {
+ throw Error('goog.globalEval not available');
+ }
+};
+
+
+/**
+ * Indicates whether or not we can call 'eval' directly to eval code in the
+ * global scope. Set to a Boolean by the first call to goog.globalEval (which
+ * empirically tests whether eval works for globals). @see goog.globalEval
+ * @type {?boolean}
+ * @private
+ */
+goog.evalWorksForGlobals_ = null;
+
+
+/**
+ * Optional map of CSS class names to obfuscated names used with
+ * goog.getCssName().
+ * @private {!Object<string, string>|undefined}
+ * @see goog.setCssNameMapping
+ */
+goog.cssNameMapping_;
+
+
+/**
+ * Optional obfuscation style for CSS class names. Should be set to either
+ * 'BY_WHOLE' or 'BY_PART' if defined.
+ * @type {string|undefined}
+ * @private
+ * @see goog.setCssNameMapping
+ */
+goog.cssNameMappingStyle_;
+
+
+/**
+ * Handles strings that are intended to be used as CSS class names.
+ *
+ * This function works in tandem with @see goog.setCssNameMapping.
+ *
+ * Without any mapping set, the arguments are simple joined with a hyphen and
+ * passed through unaltered.
+ *
+ * When there is a mapping, there are two possible styles in which these
+ * mappings are used. In the BY_PART style, each part (i.e. in between hyphens)
+ * of the passed in css name is rewritten according to the map. In the BY_WHOLE
+ * style, the full css name is looked up in the map directly. If a rewrite is
+ * not specified by the map, the compiler will output a warning.
+ *
+ * When the mapping is passed to the compiler, it will replace calls to
+ * goog.getCssName with the strings from the mapping, e.g.
+ * var x = goog.getCssName('foo');
+ * var y = goog.getCssName(this.baseClass, 'active');
+ * becomes:
+ * var x= 'foo';
+ * var y = this.baseClass + '-active';
+ *
+ * If one argument is passed it will be processed, if two are passed only the
+ * modifier will be processed, as it is assumed the first argument was generated
+ * as a result of calling goog.getCssName.
+ *
+ * @param {string} className The class name.
+ * @param {string=} opt_modifier A modifier to be appended to the class name.
+ * @return {string} The class name or the concatenation of the class name and
+ * the modifier.
+ */
+goog.getCssName = function(className, opt_modifier) {
+ var getMapping = function(cssName) {
+ return goog.cssNameMapping_[cssName] || cssName;
+ };
+
+ var renameByParts = function(cssName) {
+ // Remap all the parts individually.
+ var parts = cssName.split('-');
+ var mapped = [];
+ for (var i = 0; i < parts.length; i++) {
+ mapped.push(getMapping(parts[i]));
+ }
+ return mapped.join('-');
+ };
+
+ var rename;
+ if (goog.cssNameMapping_) {
+ rename = goog.cssNameMappingStyle_ == 'BY_WHOLE' ?
+ getMapping : renameByParts;
+ } else {
+ rename = function(a) {
+ return a;
+ };
+ }
+
+ if (opt_modifier) {
+ return className + '-' + rename(opt_modifier);
+ } else {
+ return rename(className);
+ }
+};
+
+
+/**
+ * Sets the map to check when returning a value from goog.getCssName(). Example:
+ * <pre>
+ * goog.setCssNameMapping({
+ * "goog": "a",
+ * "disabled": "b",
+ * });
+ *
+ * var x = goog.getCssName('goog');
+ * // The following evaluates to: "a a-b".
+ * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled')
+ * </pre>
+ * When declared as a map of string literals to string literals, the JSCompiler
+ * will replace all calls to goog.getCssName() using the supplied map if the
+ * --process_closure_primitives flag is set.
+ *
+ * @param {!Object} mapping A map of strings to strings where keys are possible
+ * arguments to goog.getCssName() and values are the corresponding values
+ * that should be returned.
+ * @param {string=} opt_style The style of css name mapping. There are two valid
+ * options: 'BY_PART', and 'BY_WHOLE'.
+ * @see goog.getCssName for a description.
+ */
+goog.setCssNameMapping = function(mapping, opt_style) {
+ goog.cssNameMapping_ = mapping;
+ goog.cssNameMappingStyle_ = opt_style;
+};
+
+
+/**
+ * To use CSS renaming in compiled mode, one of the input files should have a
+ * call to goog.setCssNameMapping() with an object literal that the JSCompiler
+ * can extract and use to replace all calls to goog.getCssName(). In uncompiled
+ * mode, JavaScript code should be loaded before this base.js file that declares
+ * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is
+ * to ensure that the mapping is loaded before any calls to goog.getCssName()
+ * are made in uncompiled mode.
+ *
+ * A hook for overriding the CSS name mapping.
+ * @type {!Object<string, string>|undefined}
+ */
+goog.global.CLOSURE_CSS_NAME_MAPPING;
+
+
+if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
+ // This does not call goog.setCssNameMapping() because the JSCompiler
+ // requires that goog.setCssNameMapping() be called with an object literal.
+ goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING;
+}
+
+
+/**
+ * Gets a localized message.
+ *
+ * This function is a compiler primitive. If you give the compiler a localized
+ * message bundle, it will replace the string at compile-time with a localized
+ * version, and expand goog.getMsg call to a concatenated string.
+ *
+ * Messages must be initialized in the form:
+ * <code>
+ * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'});
+ * </code>
+ *
+ * @param {string} str Translatable string, places holders in the form {$foo}.
+ * @param {Object<string, string>=} opt_values Maps place holder name to value.
+ * @return {string} message with placeholders filled.
+ */
+goog.getMsg = function(str, opt_values) {
+ if (opt_values) {
+ str = str.replace(/\{\$([^}]+)}/g, function(match, key) {
+ return key in opt_values ? opt_values[key] : match;
+ });
+ }
+ return str;
+};
+
+
+/**
+ * Gets a localized message. If the message does not have a translation, gives a
+ * fallback message.
+ *
+ * This is useful when introducing a new message that has not yet been
+ * translated into all languages.
+ *
+ * This function is a compiler primitive. Must be used in the form:
+ * <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code>
+ * where MSG_A and MSG_B were initialized with goog.getMsg.
+ *
+ * @param {string} a The preferred message.
+ * @param {string} b The fallback message.
+ * @return {string} The best translated message.
+ */
+goog.getMsgWithFallback = function(a, b) {
+ return a;
+};
+
+
+/**
+ * Exposes an unobfuscated global namespace path for the given object.
+ * Note that fields of the exported object *will* be obfuscated, unless they are
+ * exported in turn via this function or goog.exportProperty.
+ *
+ * Also handy for making public items that are defined in anonymous closures.
+ *
+ * ex. goog.exportSymbol('public.path.Foo', Foo);
+ *
+ * ex. goog.exportSymbol('public.path.Foo.staticFunction', Foo.staticFunction);
+ * public.path.Foo.staticFunction();
+ *
+ * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
+ * Foo.prototype.myMethod);
+ * new public.path.Foo().myMethod();
+ *
+ * @param {string} publicPath Unobfuscated name to export.
+ * @param {*} object Object the name should point to.
+ * @param {Object=} opt_objectToExportTo The object to add the path to; default
+ * is goog.global.
+ */
+goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
+ goog.exportPath_(publicPath, object, opt_objectToExportTo);
+};
+
+
+/**
+ * Exports a property unobfuscated into the object's namespace.
+ * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
+ * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
+ * @param {Object} object Object whose static property is being exported.
+ * @param {string} publicName Unobfuscated name to export.
+ * @param {*} symbol Object the name should point to.
+ */
+goog.exportProperty = function(object, publicName, symbol) {
+ object[publicName] = symbol;
+};
+
+
+/**
+ * Inherit the prototype methods from one constructor into another.
+ *
+ * Usage:
+ * <pre>
+ * function ParentClass(a, b) { }
+ * ParentClass.prototype.foo = function(a) { };
+ *
+ * function ChildClass(a, b, c) {
+ * ChildClass.base(this, 'constructor', a, b);
+ * }
+ * goog.inherits(ChildClass, ParentClass);
+ *
+ * var child = new ChildClass('a', 'b', 'see');
+ * child.foo(); // This works.
+ * </pre>
+ *
+ * @param {!Function} childCtor Child class.
+ * @param {!Function} parentCtor Parent class.
+ */
+goog.inherits = function(childCtor, parentCtor) {
+ /** @constructor */
+ function tempCtor() {};
+ tempCtor.prototype = parentCtor.prototype;
+ childCtor.superClass_ = parentCtor.prototype;
+ childCtor.prototype = new tempCtor();
+ /** @override */
+ childCtor.prototype.constructor = childCtor;
+
+ /**
+ * Calls superclass constructor/method.
+ *
+ * This function is only available if you use goog.inherits to
+ * express inheritance relationships between classes.
+ *
+ * NOTE: This is a replacement for goog.base and for superClass_
+ * property defined in childCtor.
+ *
+ * @param {!Object} me Should always be "this".
+ * @param {string} methodName The method name to call. Calling
+ * superclass constructor can be done with the special string
+ * 'constructor'.
+ * @param {...*} var_args The arguments to pass to superclass
+ * method/constructor.
+ * @return {*} The return value of the superclass method/constructor.
+ */
+ childCtor.base = function(me, methodName, var_args) {
+ // Copying using loop to avoid deop due to passing arguments object to
+ // function. This is faster in many JS engines as of late 2014.
+ var args = new Array(arguments.length - 2);
+ for (var i = 2; i < arguments.length; i++) {
+ args[i - 2] = arguments[i];
+ }
+ return parentCtor.prototype[methodName].apply(me, args);
+ };
+};
+
+
+/**
+ * Call up to the superclass.
+ *
+ * If this is called from a constructor, then this calls the superclass
+ * constructor with arguments 1-N.
+ *
+ * If this is called from a prototype method, then you must pass the name of the
+ * method as the second argument to this function. If you do not, you will get a
+ * runtime error. This calls the superclass' method with arguments 2-N.
+ *
+ * This function only works if you use goog.inherits to express inheritance
+ * relationships between your classes.
+ *
+ * This function is a compiler primitive. At compile-time, the compiler will do
+ * macro expansion to remove a lot of the extra overhead that this function
+ * introduces. The compiler will also enforce a lot of the assumptions that this
+ * function makes, and treat it as a compiler error if you break them.
+ *
+ * @param {!Object} me Should always be "this".
+ * @param {*=} opt_methodName The method name if calling a super method.
+ * @param {...*} var_args The rest of the arguments.
+ * @return {*} The return value of the superclass method.
+ * @suppress {es5Strict} This method can not be used in strict mode, but
+ * all Closure Library consumers must depend on this file.
+ */
+goog.base = function(me, opt_methodName, var_args) {
+ var caller = arguments.callee.caller;
+
+ if (goog.STRICT_MODE_COMPATIBLE || (goog.DEBUG && !caller)) {
+ throw Error('arguments.caller not defined. goog.base() cannot be used ' +
+ 'with strict mode code. See ' +
+ 'http://www.ecma-international.org/ecma-262/5.1/#sec-C');
+ }
+
+ if (caller.superClass_) {
+ // Copying using loop to avoid deop due to passing arguments object to
+ // function. This is faster in many JS engines as of late 2014.
+ var ctorArgs = new Array(arguments.length - 1);
+ for (var i = 1; i < arguments.length; i++) {
+ ctorArgs[i - 1] = arguments[i];
+ }
+ // This is a constructor. Call the superclass constructor.
+ return caller.superClass_.constructor.apply(me, ctorArgs);
+ }
+
+ // Copying using loop to avoid deop due to passing arguments object to
+ // function. This is faster in many JS engines as of late 2014.
+ var args = new Array(arguments.length - 2);
+ for (var i = 2; i < arguments.length; i++) {
+ args[i - 2] = arguments[i];
+ }
+ var foundCaller = false;
+ for (var ctor = me.constructor;
+ ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
+ if (ctor.prototype[opt_methodName] === caller) {
+ foundCaller = true;
+ } else if (foundCaller) {
+ return ctor.prototype[opt_methodName].apply(me, args);
+ }
+ }
+
+ // If we did not find the caller in the prototype chain, then one of two
+ // things happened:
+ // 1) The caller is an instance method.
+ // 2) This method was not called by the right caller.
+ if (me[opt_methodName] === caller) {
+ return me.constructor.prototype[opt_methodName].apply(me, args);
+ } else {
+ throw Error(
+ 'goog.base called from a method of one name ' +
+ 'to a method of a different name');
+ }
+};
+
+
+/**
+ * Allow for aliasing within scope functions. This function exists for
+ * uncompiled code - in compiled code the calls will be inlined and the aliases
+ * applied. In uncompiled code the function is simply run since the aliases as
+ * written are valid JavaScript.
+ *
+ *
+ * @param {function()} fn Function to call. This function can contain aliases
+ * to namespaces (e.g. "var dom = goog.dom") or classes
+ * (e.g. "var Timer = goog.Timer").
+ */
+goog.scope = function(fn) {
+ fn.call(goog.global);
+};
+
+
+/*
+ * To support uncompiled, strict mode bundles that use eval to divide source
+ * like so:
+ * eval('someSource;//# sourceUrl sourcefile.js');
+ * We need to export the globally defined symbols "goog" and "COMPILED".
+ * Exporting "goog" breaks the compiler optimizations, so we required that
+ * be defined externally.
+ * NOTE: We don't use goog.exportSymbol here because we don't want to trigger
+ * extern generation when that compiler option is enabled.
+ */
+if (!COMPILED) {
+ goog.global['COMPILED'] = COMPILED;
+}
+
+
+
+//==============================================================================
+// goog.defineClass implementation
+//==============================================================================
+
+
+/**
+ * Creates a restricted form of a Closure "class":
+ * - from the compiler's perspective, the instance returned from the
+ * constructor is sealed (no new properties may be added). This enables
+ * better checks.
+ * - the compiler will rewrite this definition to a form that is optimal
+ * for type checking and optimization (initially this will be a more
+ * traditional form).
+ *
+ * @param {Function} superClass The superclass, Object or null.
+ * @param {goog.defineClass.ClassDescriptor} def
+ * An object literal describing
+ * the class. It may have the following properties:
+ * "constructor": the constructor function
+ * "statics": an object literal containing methods to add to the constructor
+ * as "static" methods or a function that will receive the constructor
+ * function as its only parameter to which static properties can
+ * be added.
+ * all other properties are added to the prototype.
+ * @return {!Function} The class constructor.
+ */
+goog.defineClass = function(superClass, def) {
+ // TODO(johnlenz): consider making the superClass an optional parameter.
+ var constructor = def.constructor;
+ var statics = def.statics;
+ // Wrap the constructor prior to setting up the prototype and static methods.
+ if (!constructor || constructor == Object.prototype.constructor) {
+ constructor = function() {
+ throw Error('cannot instantiate an interface (no constructor defined).');
+ };
+ }
+
+ var cls = goog.defineClass.createSealingConstructor_(constructor, superClass);
+ if (superClass) {
+ goog.inherits(cls, superClass);
+ }
+
+ // Remove all the properties that should not be copied to the prototype.
+ delete def.constructor;
+ delete def.statics;
+
+ goog.defineClass.applyProperties_(cls.prototype, def);
+ if (statics != null) {
+ if (statics instanceof Function) {
+ statics(cls);
+ } else {
+ goog.defineClass.applyProperties_(cls, statics);
+ }
+ }
+
+ return cls;
+};
+
+
+/**
+ * @typedef {
+ * !Object|
+ * {constructor:!Function}|
+ * {constructor:!Function, statics:(Object|function(Function):void)}}
+ * @suppress {missingProvide}
+ */
+goog.defineClass.ClassDescriptor;
+
+
+/**
+ * @define {boolean} Whether the instances returned by
+ * goog.defineClass should be sealed when possible.
+ */
+goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG);
+
+
+/**
+ * If goog.defineClass.SEAL_CLASS_INSTANCES is enabled and Object.seal is
+ * defined, this function will wrap the constructor in a function that seals the
+ * results of the provided constructor function.
+ *
+ * @param {!Function} ctr The constructor whose results maybe be sealed.
+ * @param {Function} superClass The superclass constructor.
+ * @return {!Function} The replacement constructor.
+ * @private
+ */
+goog.defineClass.createSealingConstructor_ = function(ctr, superClass) {
+ if (goog.defineClass.SEAL_CLASS_INSTANCES &&
+ Object.seal instanceof Function) {
+ // Don't seal subclasses of unsealable-tagged legacy classes.
+ if (superClass && superClass.prototype &&
+ superClass.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]) {
+ return ctr;
+ }
+ /**
+ * @this {Object}
+ * @return {?}
+ */
+ var wrappedCtr = function() {
+ // Don't seal an instance of a subclass when it calls the constructor of
+ // its super class as there is most likely still setup to do.
+ var instance = ctr.apply(this, arguments) || this;
+ instance[goog.UID_PROPERTY_] = instance[goog.UID_PROPERTY_];
+ if (this.constructor === wrappedCtr) {
+ Object.seal(instance);
+ }
+ return instance;
+ };
+ return wrappedCtr;
+ }
+ return ctr;
+};
+
+
+// TODO(johnlenz): share these values with the goog.object
+/**
+ * The names of the fields that are defined on Object.prototype.
+ * @type {!Array<string>}
+ * @private
+ * @const
+ */
+goog.defineClass.OBJECT_PROTOTYPE_FIELDS_ = [
+ 'constructor',
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'toLocaleString',
+ 'toString',
+ 'valueOf'
+];
+
+
+// TODO(johnlenz): share this function with the goog.object
+/**
+ * @param {!Object} target The object to add properties to.
+ * @param {!Object} source The object to copy properties from.
+ * @private
+ */
+goog.defineClass.applyProperties_ = function(target, source) {
+ // TODO(johnlenz): update this to support ES5 getters/setters
+
+ var key;
+ for (key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+
+ // For IE the for-in-loop does not contain any properties that are not
+ // enumerable on the prototype object (for example isPrototypeOf from
+ // Object.prototype) and it will also not include 'replace' on objects that
+ // extend String and change 'replace' (not that it is common for anyone to
+ // extend anything except Object).
+ for (var i = 0; i < goog.defineClass.OBJECT_PROTOTYPE_FIELDS_.length; i++) {
+ key = goog.defineClass.OBJECT_PROTOTYPE_FIELDS_[i];
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+};
+
+
+/**
+ * Sealing classes breaks the older idiom of assigning properties on the
+ * prototype rather than in the constructor. As such, goog.defineClass
+ * must not seal subclasses of these old-style classes until they are fixed.
+ * Until then, this marks a class as "broken", instructing defineClass
+ * not to seal subclasses.
+ * @param {!Function} ctr The legacy constructor to tag as unsealable.
+ */
+goog.tagUnsealableClass = function(ctr) {
+ if (!COMPILED && goog.defineClass.SEAL_CLASS_INSTANCES) {
+ ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_] = true;
+ }
+};
+
+
+/**
+ * Name for unsealable tag property.
+ * @const @private {string}
+ */
+goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_ = 'goog_defineClass_legacy_unsealable';
+
+goog.provide('ol');
+
+
+/**
+ * Constants defined with the define tag cannot be changed in application
+ * code, but can be set at compile time.
+ * Some reduce the size of the build in advanced compile mode.
+ */
+
+
+/**
+ * @define {boolean} Assume touch. Default is `false`.
+ */
+ol.ASSUME_TOUCH = false;
+
+
+/**
+ * TODO: rename this to something having to do with tile grids
+ * see https://github.com/openlayers/ol3/issues/2076
+ * @define {number} Default maximum zoom for default tile grids.
+ */
+ol.DEFAULT_MAX_ZOOM = 42;
+
+
+/**
+ * @define {number} Default min zoom level for the map view. Default is `0`.
+ */
+ol.DEFAULT_MIN_ZOOM = 0;
+
+
+/**
+ * @define {number} Default maximum allowed threshold (in pixels) for
+ * reprojection triangulation. Default is `0.5`.
+ */
+ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD = 0.5;
+
+
+/**
+ * @define {number} Default high water mark.
+ */
+ol.DEFAULT_TILE_CACHE_HIGH_WATER_MARK = 2048;
+
+
+/**
+ * @define {number} Default tile size.
+ */
+ol.DEFAULT_TILE_SIZE = 256;
+
+
+/**
+ * @define {string} Default WMS version.
+ */
+ol.DEFAULT_WMS_VERSION = '1.3.0';
+
+
+/**
+ * @define {number} Hysteresis pixels.
+ */
+ol.DRAG_BOX_HYSTERESIS_PIXELS = 8;
+
+
+/**
+ * @define {boolean} Enable the Canvas renderer. Default is `true`. Setting
+ * this to false at compile time in advanced mode removes all code
+ * supporting the Canvas renderer from the build.
+ */
+ol.ENABLE_CANVAS = true;
+
+
+/**
+ * @define {boolean} Enable the DOM renderer (used as a fallback where Canvas is
+ * not available). Default is `true`. Setting this to false at compile time
+ * in advanced mode removes all code supporting the DOM renderer from the
+ * build.
+ */
+ol.ENABLE_DOM = true;
+
+
+/**
+ * @define {boolean} Enable rendering of ol.layer.Image based layers. Default
+ * is `true`. Setting this to false at compile time in advanced mode removes
+ * all code supporting Image layers from the build.
+ */
+ol.ENABLE_IMAGE = true;
+
+
+/**
+ * @define {boolean} Enable Closure named colors (`goog.color.names`).
+ * Enabling these colors adds about 3KB uncompressed / 1.5KB compressed to
+ * the final build size. Default is `false`. This setting has no effect
+ * with Canvas renderer, which uses its own names, whether this is true or
+ * false.
+ */
+ol.ENABLE_NAMED_COLORS = false;
+
+
+/**
+ * @define {boolean} Enable integration with the Proj4js library. Default is
+ * `true`.
+ */
+ol.ENABLE_PROJ4JS = true;
+
+
+/**
+ * @define {boolean} Enable automatic reprojection of raster sources. Default is
+ * `true`.
+ */
+ol.ENABLE_RASTER_REPROJECTION = true;
+
+
+/**
+ * @define {boolean} Enable rendering of ol.layer.Tile based layers. Default is
+ * `true`. Setting this to false at compile time in advanced mode removes
+ * all code supporting Tile layers from the build.
+ */
+ol.ENABLE_TILE = true;
+
+
+/**
+ * @define {boolean} Enable rendering of ol.layer.Vector based layers. Default
+ * is `true`. Setting this to false at compile time in advanced mode removes
+ * all code supporting Vector layers from the build.
+ */
+ol.ENABLE_VECTOR = true;
+
+
+/**
+ * @define {boolean} Enable rendering of ol.layer.VectorTile based layers.
+ * Default is `true`. Setting this to false at compile time in advanced mode
+ * removes all code supporting VectorTile layers from the build.
+ */
+ol.ENABLE_VECTOR_TILE = true;
+
+
+/**
+ * @define {boolean} Enable the WebGL renderer. Default is `true`. Setting
+ * this to false at compile time in advanced mode removes all code
+ * supporting the WebGL renderer from the build.
+ */
+ol.ENABLE_WEBGL = true;
+
+
+/**
+ * @define {number} The size in pixels of the first atlas image. Default is
+ * `256`.
+ */
+ol.INITIAL_ATLAS_SIZE = 256;
+
+
+/**
+ * @define {number} The maximum size in pixels of atlas images. Default is
+ * `-1`, meaning it is not used (and `ol.WEBGL_MAX_TEXTURE_SIZE` is
+ * used instead).
+ */
+ol.MAX_ATLAS_SIZE = -1;
+
+
+/**
+ * @define {number} Maximum mouse wheel delta.
+ */
+ol.MOUSEWHEELZOOM_MAXDELTA = 1;
+
+
+/**
+ * @define {number} Mouse wheel timeout duration.
+ */
+ol.MOUSEWHEELZOOM_TIMEOUT_DURATION = 80;
+
+
+/**
+ * @define {number} Maximum width and/or height extent ratio that determines
+ * when the overview map should be zoomed out.
+ */
+ol.OVERVIEWMAP_MAX_RATIO = 0.75;
+
+
+/**
+ * @define {number} Minimum width and/or height extent ratio that determines
+ * when the overview map should be zoomed in.
+ */
+ol.OVERVIEWMAP_MIN_RATIO = 0.1;
+
+
+/**
+ * @define {number} Maximum number of source tiles for raster reprojection of
+ * a single tile.
+ * If too many source tiles are determined to be loaded to create a single
+ * reprojected tile the browser can become unresponsive or even crash.
+ * This can happen if the developer defines projections improperly and/or
+ * with unlimited extents.
+ * If too many tiles are required, no tiles are loaded and
+ * `ol.TileState.ERROR` state is set. Default is `100`.
+ */
+ol.RASTER_REPROJECTION_MAX_SOURCE_TILES = 100;
+
+
+/**
+ * @define {number} Maximum number of subdivision steps during raster
+ * reprojection triangulation. Prevents high memory usage and large
+ * number of proj4 calls (for certain transformations and areas).
+ * At most `2*(2^this)` triangles are created for each triangulated
+ * extent (tile/image). Default is `10`.
+ */
+ol.RASTER_REPROJECTION_MAX_SUBDIVISION = 10;
+
+
+/**
+ * @define {number} Maximum allowed size of triangle relative to world width.
+ * When transforming corners of world extent between certain projections,
+ * the resulting triangulation seems to have zero error and no subdivision
+ * is performed.
+ * If the triangle width is more than this (relative to world width; 0-1),
+ * subdivison is forced (up to `ol.RASTER_REPROJECTION_MAX_SUBDIVISION`).
+ * Default is `0.25`.
+ */
+ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH = 0.25;
+
+
+/**
+ * @define {number} Tolerance for geometry simplification in device pixels.
+ */
+ol.SIMPLIFY_TOLERANCE = 0.5;
+
+
+/**
+ * @define {number} Texture cache high water mark.
+ */
+ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024;
+
+
+/**
+ * The maximum supported WebGL texture size in pixels. If WebGL is not
+ * supported, the value is set to `undefined`.
+ * @const
+ * @type {number|undefined}
+ */
+ol.WEBGL_MAX_TEXTURE_SIZE; // value is set in `ol.has`
+
+
+/**
+ * List of supported WebGL extensions.
+ * @const
+ * @type {Array.<string>}
+ */
+ol.WEBGL_EXTENSIONS; // value is set in `ol.has`
+
+
+/**
+ * Inherit the prototype methods from one constructor into another.
+ *
+ * Usage:
+ *
+ * function ParentClass(a, b) { }
+ * ParentClass.prototype.foo = function(a) { }
+ *
+ * function ChildClass(a, b, c) {
+ * // Call parent constructor
+ * ParentClass.call(this, a, b);
+ * }
+ * ol.inherits(ChildClass, ParentClass);
+ *
+ * var child = new ChildClass('a', 'b', 'see');
+ * child.foo(); // This works.
+ *
+ * In addition, a superclass' implementation of a method can be invoked as
+ * follows:
+ *
+ * ChildClass.prototype.foo = function(a) {
+ * ChildClass.base(this, 'foo', a);
+ * // Other code here.
+ * };
+ *
+ * @param {!Function} childCtor Child constructor.
+ * @param {!Function} parentCtor Parent constructor.
+ * @function
+ * @api
+ */
+ol.inherits =
+ goog.inherits;
+// note that the newline above is necessary to satisfy the linter
+
+
+/**
+ * A reusable function, used e.g. as a default for callbacks.
+ *
+ * @return {undefined} Nothing.
+ */
+ol.nullFunction = function() {};
+
+// FIXME factor out common code between usedTiles and wantedTiles
+
+goog.provide('ol.PostRenderFunction');
+goog.provide('ol.PreRenderFunction');
+
+
+/**
+ * @typedef {function(ol.Map, ?olx.FrameState): boolean}
+ */
+ol.PostRenderFunction;
+
+
+/**
+ * Function to perform manipulations before rendering. This function is called
+ * with the {@link ol.Map} as first and an optional {@link olx.FrameState} as
+ * second argument. Return `true` to keep this function for the next frame,
+ * `false` to remove it.
+ * @typedef {function(ol.Map, ?olx.FrameState): boolean}
+ * @api
+ */
+ol.PreRenderFunction;
+
+// Copyright 2009 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 base class for custom Error objects such that the
+ * stack is correctly maintained.
+ *
+ * You should never need to throw goog.debug.Error(msg) directly, Error(msg) is
+ * sufficient.
+ *
+ */
+
+goog.provide('goog.debug.Error');
+
+
+
+/**
+ * Base class for custom error objects.
+ * @param {*=} opt_msg The message associated with the error.
+ * @constructor
+ * @extends {Error}
+ */
+goog.debug.Error = function(opt_msg) {
+
+ // Attempt to ensure there is a stack trace.
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, goog.debug.Error);
+ } else {
+ var stack = new Error().stack;
+ if (stack) {
+ this.stack = stack;
+ }
+ }
+
+ if (opt_msg) {
+ this.message = String(opt_msg);
+ }
+
+ /**
+ * Whether to report this error to the server. Setting this to false will
+ * cause the error reporter to not report the error back to the server,
+ * which can be useful if the client knows that the error has already been
+ * logged on the server.
+ * @type {boolean}
+ */
+ this.reportErrorToServer = true;
+};
+goog.inherits(goog.debug.Error, Error);
+
+
+/** @override */
+goog.debug.Error.prototype.name = 'CustomError';
+
+// 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 Definition of goog.dom.NodeType.
+ */
+
+goog.provide('goog.dom.NodeType');
+
+
+/**
+ * Constants for the nodeType attribute in the Node interface.
+ *
+ * These constants match those specified in the Node interface. These are
+ * usually present on the Node object in recent browsers, but not in older
+ * browsers (specifically, early IEs) and thus are given here.
+ *
+ * In some browsers (early IEs), these are not defined on the Node object,
+ * so they are provided here.
+ *
+ * See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247
+ * @enum {number}
+ */
+goog.dom.NodeType = {
+ ELEMENT: 1,
+ ATTRIBUTE: 2,
+ TEXT: 3,
+ CDATA_SECTION: 4,
+ ENTITY_REFERENCE: 5,
+ ENTITY: 6,
+ PROCESSING_INSTRUCTION: 7,
+ COMMENT: 8,
+ DOCUMENT: 9,
+ DOCUMENT_TYPE: 10,
+ DOCUMENT_FRAGMENT: 11,
+ NOTATION: 12
+};
+
+// 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 string manipulation.
+ * @author arv@google.com (Erik Arvidsson)
+ */
+
+
+/**
+ * Namespace for string utilities
+ */
+goog.provide('goog.string');
+goog.provide('goog.string.Unicode');
+
+
+/**
+ * @define {boolean} Enables HTML escaping of lowercase letter "e" which helps
+ * with detection of double-escaping as this letter is frequently used.
+ */
+goog.define('goog.string.DETECT_DOUBLE_ESCAPING', false);
+
+
+/**
+ * @define {boolean} Whether to force non-dom html unescaping.
+ */
+goog.define('goog.string.FORCE_NON_DOM_HTML_UNESCAPING', false);
+
+
+/**
+ * Common Unicode string characters.
+ * @enum {string}
+ */
+goog.string.Unicode = {
+ NBSP: '\xa0'
+};
+
+
+/**
+ * Fast prefix-checker.
+ * @param {string} str The string to check.
+ * @param {string} prefix A string to look for at the start of {@code str}.
+ * @return {boolean} True if {@code str} begins with {@code prefix}.
+ */
+goog.string.startsWith = function(str, prefix) {
+ return str.lastIndexOf(prefix, 0) == 0;
+};
+
+
+/**
+ * Fast suffix-checker.
+ * @param {string} str The string to check.
+ * @param {string} suffix A string to look for at the end of {@code str}.
+ * @return {boolean} True if {@code str} ends with {@code suffix}.
+ */
+goog.string.endsWith = function(str, suffix) {
+ var l = str.length - suffix.length;
+ return l >= 0 && str.indexOf(suffix, l) == l;
+};
+
+
+/**
+ * Case-insensitive prefix-checker.
+ * @param {string} str The string to check.
+ * @param {string} prefix A string to look for at the end of {@code str}.
+ * @return {boolean} True if {@code str} begins with {@code prefix} (ignoring
+ * case).
+ */
+goog.string.caseInsensitiveStartsWith = function(str, prefix) {
+ return goog.string.caseInsensitiveCompare(
+ prefix, str.substr(0, prefix.length)) == 0;
+};
+
+
+/**
+ * Case-insensitive suffix-checker.
+ * @param {string} str The string to check.
+ * @param {string} suffix A string to look for at the end of {@code str}.
+ * @return {boolean} True if {@code str} ends with {@code suffix} (ignoring
+ * case).
+ */
+goog.string.caseInsensitiveEndsWith = function(str, suffix) {
+ return goog.string.caseInsensitiveCompare(
+ suffix, str.substr(str.length - suffix.length, suffix.length)) == 0;
+};
+
+
+/**
+ * Case-insensitive equality checker.
+ * @param {string} str1 First string to check.
+ * @param {string} str2 Second string to check.
+ * @return {boolean} True if {@code str1} and {@code str2} are the same string,
+ * ignoring case.
+ */
+goog.string.caseInsensitiveEquals = function(str1, str2) {
+ return str1.toLowerCase() == str2.toLowerCase();
+};
+
+
+/**
+ * Does simple python-style string substitution.
+ * subs("foo%s hot%s", "bar", "dog") becomes "foobar hotdog".
+ * @param {string} str The string containing the pattern.
+ * @param {...*} var_args The items to substitute into the pattern.
+ * @return {string} A copy of {@code str} in which each occurrence of
+ * {@code %s} has been replaced an argument from {@code var_args}.
+ */
+goog.string.subs = function(str, var_args) {
+ var splitParts = str.split('%s');
+ var returnString = '';
+
+ var subsArguments = Array.prototype.slice.call(arguments, 1);
+ while (subsArguments.length &&
+ // Replace up to the last split part. We are inserting in the
+ // positions between split parts.
+ splitParts.length > 1) {
+ returnString += splitParts.shift() + subsArguments.shift();
+ }
+
+ return returnString + splitParts.join('%s'); // Join unused '%s'
+};
+
+
+/**
+ * Converts multiple whitespace chars (spaces, non-breaking-spaces, new lines
+ * and tabs) to a single space, and strips leading and trailing whitespace.
+ * @param {string} str Input string.
+ * @return {string} A copy of {@code str} with collapsed whitespace.
+ */
+goog.string.collapseWhitespace = function(str) {
+ // Since IE doesn't include non-breaking-space (0xa0) in their \s character
+ // class (as required by section 7.2 of the ECMAScript spec), we explicitly
+ // include it in the regexp to enforce consistent cross-browser behavior.
+ return str.replace(/[\s\xa0]+/g, ' ').replace(/^\s+|\s+$/g, '');
+};
+
+
+/**
+ * Checks if a string is empty or contains only whitespaces.
+ * @param {string} str The string to check.
+ * @return {boolean} Whether {@code str} is empty or whitespace only.
+ */
+goog.string.isEmptyOrWhitespace = function(str) {
+ // testing length == 0 first is actually slower in all browsers (about the
+ // same in Opera).
+ // Since IE doesn't include non-breaking-space (0xa0) in their \s character
+ // class (as required by section 7.2 of the ECMAScript spec), we explicitly
+ // include it in the regexp to enforce consistent cross-browser behavior.
+ return /^[\s\xa0]*$/.test(str);
+};
+
+
+/**
+ * Checks if a string is empty.
+ * @param {string} str The string to check.
+ * @return {boolean} Whether {@code str} is empty.
+ */
+goog.string.isEmptyString = function(str) {
+ return str.length == 0;
+};
+
+
+/**
+ * Checks if a string is empty or contains only whitespaces.
+ *
+ * TODO(user): Deprecate this when clients have been switched over to
+ * goog.string.isEmptyOrWhitespace.
+ *
+ * @param {string} str The string to check.
+ * @return {boolean} Whether {@code str} is empty or whitespace only.
+ */
+goog.string.isEmpty = goog.string.isEmptyOrWhitespace;
+
+
+/**
+ * Checks if a string is null, undefined, empty or contains only whitespaces.
+ * @param {*} str The string to check.
+ * @return {boolean} Whether {@code str} is null, undefined, empty, or
+ * whitespace only.
+ * @deprecated Use goog.string.isEmptyOrWhitespace(goog.string.makeSafe(str))
+ * instead.
+ */
+goog.string.isEmptyOrWhitespaceSafe = function(str) {
+ return goog.string.isEmptyOrWhitespace(goog.string.makeSafe(str));
+};
+
+
+/**
+ * Checks if a string is null, undefined, empty or contains only whitespaces.
+ *
+ * TODO(user): Deprecate this when clients have been switched over to
+ * goog.string.isEmptyOrWhitespaceSafe.
+ *
+ * @param {*} str The string to check.
+ * @return {boolean} Whether {@code str} is null, undefined, empty, or
+ * whitespace only.
+ */
+goog.string.isEmptySafe = goog.string.isEmptyOrWhitespaceSafe;
+
+
+/**
+ * Checks if a string is all breaking whitespace.
+ * @param {string} str The string to check.
+ * @return {boolean} Whether the string is all breaking whitespace.
+ */
+goog.string.isBreakingWhitespace = function(str) {
+ return !/[^\t\n\r ]/.test(str);
+};
+
+
+/**
+ * Checks if a string contains all letters.
+ * @param {string} str string to check.
+ * @return {boolean} True if {@code str} consists entirely of letters.
+ */
+goog.string.isAlpha = function(str) {
+ return !/[^a-zA-Z]/.test(str);
+};
+
+
+/**
+ * Checks if a string contains only numbers.
+ * @param {*} str string to check. If not a string, it will be
+ * casted to one.
+ * @return {boolean} True if {@code str} is numeric.
+ */
+goog.string.isNumeric = function(str) {
+ return !/[^0-9]/.test(str);
+};
+
+
+/**
+ * Checks if a string contains only numbers or letters.
+ * @param {string} str string to check.
+ * @return {boolean} True if {@code str} is alphanumeric.
+ */
+goog.string.isAlphaNumeric = function(str) {
+ return !/[^a-zA-Z0-9]/.test(str);
+};
+
+
+/**
+ * Checks if a character is a space character.
+ * @param {string} ch Character to check.
+ * @return {boolean} True if {@code ch} is a space.
+ */
+goog.string.isSpace = function(ch) {
+ return ch == ' ';
+};
+
+
+/**
+ * Checks if a character is a valid unicode character.
+ * @param {string} ch Character to check.
+ * @return {boolean} True if {@code ch} is a valid unicode character.
+ */
+goog.string.isUnicodeChar = function(ch) {
+ return ch.length == 1 && ch >= ' ' && ch <= '~' ||
+ ch >= '\u0080' && ch <= '\uFFFD';
+};
+
+
+/**
+ * Takes a string and replaces newlines with a space. Multiple lines are
+ * replaced with a single space.
+ * @param {string} str The string from which to strip newlines.
+ * @return {string} A copy of {@code str} stripped of newlines.
+ */
+goog.string.stripNewlines = function(str) {
+ return str.replace(/(\r\n|\r|\n)+/g, ' ');
+};
+
+
+/**
+ * Replaces Windows and Mac new lines with unix style: \r or \r\n with \n.
+ * @param {string} str The string to in which to canonicalize newlines.
+ * @return {string} {@code str} A copy of {@code} with canonicalized newlines.
+ */
+goog.string.canonicalizeNewlines = function(str) {
+ return str.replace(/(\r\n|\r|\n)/g, '\n');
+};
+
+
+/**
+ * Normalizes whitespace in a string, replacing all whitespace chars with
+ * a space.
+ * @param {string} str The string in which to normalize whitespace.
+ * @return {string} A copy of {@code str} with all whitespace normalized.
+ */
+goog.string.normalizeWhitespace = function(str) {
+ return str.replace(/\xa0|\s/g, ' ');
+};
+
+
+/**
+ * Normalizes spaces in a string, replacing all consecutive spaces and tabs
+ * with a single space. Replaces non-breaking space with a space.
+ * @param {string} str The string in which to normalize spaces.
+ * @return {string} A copy of {@code str} with all consecutive spaces and tabs
+ * replaced with a single space.
+ */
+goog.string.normalizeSpaces = function(str) {
+ return str.replace(/\xa0|[ \t]+/g, ' ');
+};
+
+
+/**
+ * Removes the breaking spaces from the left and right of the string and
+ * collapses the sequences of breaking spaces in the middle into single spaces.
+ * The original and the result strings render the same way in HTML.
+ * @param {string} str A string in which to collapse spaces.
+ * @return {string} Copy of the string with normalized breaking spaces.
+ */
+goog.string.collapseBreakingSpaces = function(str) {
+ return str.replace(/[\t\r\n ]+/g, ' ').replace(
+ /^[\t\r\n ]+|[\t\r\n ]+$/g, '');
+};
+
+
+/**
+ * Trims white spaces to the left and right of a string.
+ * @param {string} str The string to trim.
+ * @return {string} A trimmed copy of {@code str}.
+ */
+goog.string.trim = (goog.TRUSTED_SITE && String.prototype.trim) ?
+ function(str) {
+ return str.trim();
+ } :
+ function(str) {
+ // Since IE doesn't include non-breaking-space (0xa0) in their \s
+ // character class (as required by section 7.2 of the ECMAScript spec),
+ // we explicitly include it in the regexp to enforce consistent
+ // cross-browser behavior.
+ return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
+ };
+
+
+/**
+ * Trims whitespaces at the left end of a string.
+ * @param {string} str The string to left trim.
+ * @return {string} A trimmed copy of {@code str}.
+ */
+goog.string.trimLeft = function(str) {
+ // Since IE doesn't include non-breaking-space (0xa0) in their \s character
+ // class (as required by section 7.2 of the ECMAScript spec), we explicitly
+ // include it in the regexp to enforce consistent cross-browser behavior.
+ return str.replace(/^[\s\xa0]+/, '');
+};
+
+
+/**
+ * Trims whitespaces at the right end of a string.
+ * @param {string} str The string to right trim.
+ * @return {string} A trimmed copy of {@code str}.
+ */
+goog.string.trimRight = function(str) {
+ // Since IE doesn't include non-breaking-space (0xa0) in their \s character
+ // class (as required by section 7.2 of the ECMAScript spec), we explicitly
+ // include it in the regexp to enforce consistent cross-browser behavior.
+ return str.replace(/[\s\xa0]+$/, '');
+};
+
+
+/**
+ * A string comparator that ignores case.
+ * -1 = str1 less than str2
+ * 0 = str1 equals str2
+ * 1 = str1 greater than str2
+ *
+ * @param {string} str1 The string to compare.
+ * @param {string} str2 The string to compare {@code str1} to.
+ * @return {number} The comparator result, as described above.
+ */
+goog.string.caseInsensitiveCompare = function(str1, str2) {
+ var test1 = String(str1).toLowerCase();
+ var test2 = String(str2).toLowerCase();
+
+ if (test1 < test2) {
+ return -1;
+ } else if (test1 == test2) {
+ return 0;
+ } else {
+ return 1;
+ }
+};
+
+
+/**
+ * Regular expression used for splitting a string into substrings of fractional
+ * numbers, integers, and non-numeric characters.
+ * @type {RegExp}
+ * @private
+ */
+goog.string.numerateCompareRegExp_ = /(\.\d+)|(\d+)|(\D+)/g;
+
+
+/**
+ * String comparison function that handles numbers in a way humans might expect.
+ * Using this function, the string "File 2.jpg" sorts before "File 10.jpg". The
+ * comparison is mostly case-insensitive, though strings that are identical
+ * except for case are sorted with the upper-case strings before lower-case.
+ *
+ * This comparison function is significantly slower (about 500x) than either
+ * the default or the case-insensitive compare. It should not be used in
+ * time-critical code, but should be fast enough to sort several hundred short
+ * strings (like filenames) with a reasonable delay.
+ *
+ * @param {string} str1 The string to compare in a numerically sensitive way.
+ * @param {string} str2 The string to compare {@code str1} to.
+ * @return {number} less than 0 if str1 < str2, 0 if str1 == str2, greater than
+ * 0 if str1 > str2.
+ */
+goog.string.numerateCompare = function(str1, str2) {
+ if (str1 == str2) {
+ return 0;
+ }
+ if (!str1) {
+ return -1;
+ }
+ if (!str2) {
+ return 1;
+ }
+
+ // Using match to split the entire string ahead of time turns out to be faster
+ // for most inputs than using RegExp.exec or iterating over each character.
+ var tokens1 = str1.toLowerCase().match(goog.string.numerateCompareRegExp_);
+ var tokens2 = str2.toLowerCase().match(goog.string.numerateCompareRegExp_);
+
+ var count = Math.min(tokens1.length, tokens2.length);
+
+ for (var i = 0; i < count; i++) {
+ var a = tokens1[i];
+ var b = tokens2[i];
+
+ // Compare pairs of tokens, returning if one token sorts before the other.
+ if (a != b) {
+
+ // Only if both tokens are integers is a special comparison required.
+ // Decimal numbers are sorted as strings (e.g., '.09' < '.1').
+ var num1 = parseInt(a, 10);
+ if (!isNaN(num1)) {
+ var num2 = parseInt(b, 10);
+ if (!isNaN(num2) && num1 - num2) {
+ return num1 - num2;
+ }
+ }
+ return a < b ? -1 : 1;
+ }
+ }
+
+ // If one string is a substring of the other, the shorter string sorts first.
+ if (tokens1.length != tokens2.length) {
+ return tokens1.length - tokens2.length;
+ }
+
+ // The two strings must be equivalent except for case (perfect equality is
+ // tested at the head of the function.) Revert to default ASCII-betical string
+ // comparison to stablize the sort.
+ return str1 < str2 ? -1 : 1;
+};
+
+
+/**
+ * URL-encodes a string
+ * @param {*} str The string to url-encode.
+ * @return {string} An encoded copy of {@code str} that is safe for urls.
+ * Note that '#', ':', and other characters used to delimit portions
+ * of URLs *will* be encoded.
+ */
+goog.string.urlEncode = function(str) {
+ return encodeURIComponent(String(str));
+};
+
+
+/**
+ * URL-decodes the string. We need to specially handle '+'s because
+ * the javascript library doesn't convert them to spaces.
+ * @param {string} str The string to url decode.
+ * @return {string} The decoded {@code str}.
+ */
+goog.string.urlDecode = function(str) {
+ return decodeURIComponent(str.replace(/\+/g, ' '));
+};
+
+
+/**
+ * Converts \n to <br>s or <br />s.
+ * @param {string} str The string in which to convert newlines.
+ * @param {boolean=} opt_xml Whether to use XML compatible tags.
+ * @return {string} A copy of {@code str} with converted newlines.
+ */
+goog.string.newLineToBr = function(str, opt_xml) {
+ return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>');
+};
+
+
+/**
+ * Escapes double quote '"' and single quote '\'' characters in addition to
+ * '&', '<', and '>' so that a string can be included in an HTML tag attribute
+ * value within double or single quotes.
+ *
+ * It should be noted that > doesn't need to be escaped for the HTML or XML to
+ * be valid, but it has been decided to escape it for consistency with other
+ * implementations.
+ *
+ * With goog.string.DETECT_DOUBLE_ESCAPING, this function escapes also the
+ * lowercase letter "e".
+ *
+ * NOTE(user):
+ * HtmlEscape is often called during the generation of large blocks of HTML.
+ * Using statics for the regular expressions and strings is an optimization
+ * that can more than half the amount of time IE spends in this function for
+ * large apps, since strings and regexes both contribute to GC allocations.
+ *
+ * Testing for the presence of a character before escaping increases the number
+ * of function calls, but actually provides a speed increase for the average
+ * case -- since the average case often doesn't require the escaping of all 4
+ * characters and indexOf() is much cheaper than replace().
+ * The worst case does suffer slightly from the additional calls, therefore the
+ * opt_isLikelyToContainHtmlChars option has been included for situations
+ * where all 4 HTML entities are very likely to be present and need escaping.
+ *
+ * Some benchmarks (times tended to fluctuate +-0.05ms):
+ * FireFox IE6
+ * (no chars / average (mix of cases) / all 4 chars)
+ * no checks 0.13 / 0.22 / 0.22 0.23 / 0.53 / 0.80
+ * indexOf 0.08 / 0.17 / 0.26 0.22 / 0.54 / 0.84
+ * indexOf + re test 0.07 / 0.17 / 0.28 0.19 / 0.50 / 0.85
+ *
+ * An additional advantage of checking if replace actually needs to be called
+ * is a reduction in the number of object allocations, so as the size of the
+ * application grows the difference between the various methods would increase.
+ *
+ * @param {string} str string to be escaped.
+ * @param {boolean=} opt_isLikelyToContainHtmlChars Don't perform a check to see
+ * if the character needs replacing - use this option if you expect each of
+ * the characters to appear often. Leave false if you expect few html
+ * characters to occur in your strings, such as if you are escaping HTML.
+ * @return {string} An escaped copy of {@code str}.
+ */
+goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) {
+
+ if (opt_isLikelyToContainHtmlChars) {
+ str = str.replace(goog.string.AMP_RE_, '&amp;')
+ .replace(goog.string.LT_RE_, '&lt;')
+ .replace(goog.string.GT_RE_, '&gt;')
+ .replace(goog.string.QUOT_RE_, '&quot;')
+ .replace(goog.string.SINGLE_QUOTE_RE_, '&#39;')
+ .replace(goog.string.NULL_RE_, '&#0;');
+ if (goog.string.DETECT_DOUBLE_ESCAPING) {
+ str = str.replace(goog.string.E_RE_, '&#101;');
+ }
+ return str;
+
+ } else {
+ // quick test helps in the case when there are no chars to replace, in
+ // worst case this makes barely a difference to the time taken
+ if (!goog.string.ALL_RE_.test(str)) return str;
+
+ // str.indexOf is faster than regex.test in this case
+ if (str.indexOf('&') != -1) {
+ str = str.replace(goog.string.AMP_RE_, '&amp;');
+ }
+ if (str.indexOf('<') != -1) {
+ str = str.replace(goog.string.LT_RE_, '&lt;');
+ }
+ if (str.indexOf('>') != -1) {
+ str = str.replace(goog.string.GT_RE_, '&gt;');
+ }
+ if (str.indexOf('"') != -1) {
+ str = str.replace(goog.string.QUOT_RE_, '&quot;');
+ }
+ if (str.indexOf('\'') != -1) {
+ str = str.replace(goog.string.SINGLE_QUOTE_RE_, '&#39;');
+ }
+ if (str.indexOf('\x00') != -1) {
+ str = str.replace(goog.string.NULL_RE_, '&#0;');
+ }
+ if (goog.string.DETECT_DOUBLE_ESCAPING && str.indexOf('e') != -1) {
+ str = str.replace(goog.string.E_RE_, '&#101;');
+ }
+ return str;
+ }
+};
+
+
+/**
+ * Regular expression that matches an ampersand, for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.AMP_RE_ = /&/g;
+
+
+/**
+ * Regular expression that matches a less than sign, for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.LT_RE_ = /</g;
+
+
+/**
+ * Regular expression that matches a greater than sign, for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.GT_RE_ = />/g;
+
+
+/**
+ * Regular expression that matches a double quote, for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.QUOT_RE_ = /"/g;
+
+
+/**
+ * Regular expression that matches a single quote, for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.SINGLE_QUOTE_RE_ = /'/g;
+
+
+/**
+ * Regular expression that matches null character, for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.NULL_RE_ = /\x00/g;
+
+
+/**
+ * Regular expression that matches a lowercase letter "e", for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.E_RE_ = /e/g;
+
+
+/**
+ * Regular expression that matches any character that needs to be escaped.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.ALL_RE_ = (goog.string.DETECT_DOUBLE_ESCAPING ?
+ /[\x00&<>"'e]/ :
+ /[\x00&<>"']/);
+
+
+/**
+ * Unescapes an HTML string.
+ *
+ * @param {string} str The string to unescape.
+ * @return {string} An unescaped copy of {@code str}.
+ */
+goog.string.unescapeEntities = function(str) {
+ if (goog.string.contains(str, '&')) {
+ // We are careful not to use a DOM if we do not have one or we explicitly
+ // requested non-DOM html unescaping.
+ if (!goog.string.FORCE_NON_DOM_HTML_UNESCAPING &&
+ 'document' in goog.global) {
+ return goog.string.unescapeEntitiesUsingDom_(str);
+ } else {
+ // Fall back on pure XML entities
+ return goog.string.unescapePureXmlEntities_(str);
+ }
+ }
+ return str;
+};
+
+
+/**
+ * Unescapes a HTML string using the provided document.
+ *
+ * @param {string} str The string to unescape.
+ * @param {!Document} document A document to use in escaping the string.
+ * @return {string} An unescaped copy of {@code str}.
+ */
+goog.string.unescapeEntitiesWithDocument = function(str, document) {
+ if (goog.string.contains(str, '&')) {
+ return goog.string.unescapeEntitiesUsingDom_(str, document);
+ }
+ return str;
+};
+
+
+/**
+ * Unescapes an HTML string using a DOM to resolve non-XML, non-numeric
+ * entities. This function is XSS-safe and whitespace-preserving.
+ * @private
+ * @param {string} str The string to unescape.
+ * @param {Document=} opt_document An optional document to use for creating
+ * elements. If this is not specified then the default window.document
+ * will be used.
+ * @return {string} The unescaped {@code str} string.
+ */
+goog.string.unescapeEntitiesUsingDom_ = function(str, opt_document) {
+ /** @type {!Object<string, string>} */
+ var seen = {'&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"'};
+ var div;
+ if (opt_document) {
+ div = opt_document.createElement('div');
+ } else {
+ div = goog.global.document.createElement('div');
+ }
+ // Match as many valid entity characters as possible. If the actual entity
+ // happens to be shorter, it will still work as innerHTML will return the
+ // trailing characters unchanged. Since the entity characters do not include
+ // open angle bracket, there is no chance of XSS from the innerHTML use.
+ // Since no whitespace is passed to innerHTML, whitespace is preserved.
+ return str.replace(goog.string.HTML_ENTITY_PATTERN_, function(s, entity) {
+ // Check for cached entity.
+ var value = seen[s];
+ if (value) {
+ return value;
+ }
+ // Check for numeric entity.
+ if (entity.charAt(0) == '#') {
+ // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex numbers.
+ var n = Number('0' + entity.substr(1));
+ if (!isNaN(n)) {
+ value = String.fromCharCode(n);
+ }
+ }
+ // Fall back to innerHTML otherwise.
+ if (!value) {
+ // Append a non-entity character to avoid a bug in Webkit that parses
+ // an invalid entity at the end of innerHTML text as the empty string.
+ div.innerHTML = s + ' ';
+ // Then remove the trailing character from the result.
+ value = div.firstChild.nodeValue.slice(0, -1);
+ }
+ // Cache and return.
+ return seen[s] = value;
+ });
+};
+
+
+/**
+ * Unescapes XML entities.
+ * @private
+ * @param {string} str The string to unescape.
+ * @return {string} An unescaped copy of {@code str}.
+ */
+goog.string.unescapePureXmlEntities_ = function(str) {
+ return str.replace(/&([^;]+);/g, function(s, entity) {
+ switch (entity) {
+ case 'amp':
+ return '&';
+ case 'lt':
+ return '<';
+ case 'gt':
+ return '>';
+ case 'quot':
+ return '"';
+ default:
+ if (entity.charAt(0) == '#') {
+ // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex.
+ var n = Number('0' + entity.substr(1));
+ if (!isNaN(n)) {
+ return String.fromCharCode(n);
+ }
+ }
+ // For invalid entities we just return the entity
+ return s;
+ }
+ });
+};
+
+
+/**
+ * Regular expression that matches an HTML entity.
+ * See also HTML5: Tokenization / Tokenizing character references.
+ * @private
+ * @type {!RegExp}
+ */
+goog.string.HTML_ENTITY_PATTERN_ = /&([^;\s<&]+);?/g;
+
+
+/**
+ * Do escaping of whitespace to preserve spatial formatting. We use character
+ * entity #160 to make it safer for xml.
+ * @param {string} str The string in which to escape whitespace.
+ * @param {boolean=} opt_xml Whether to use XML compatible tags.
+ * @return {string} An escaped copy of {@code str}.
+ */
+goog.string.whitespaceEscape = function(str, opt_xml) {
+ // This doesn't use goog.string.preserveSpaces for backwards compatibility.
+ return goog.string.newLineToBr(str.replace(/ /g, ' &#160;'), opt_xml);
+};
+
+
+/**
+ * Preserve spaces that would be otherwise collapsed in HTML by replacing them
+ * with non-breaking space Unicode characters.
+ * @param {string} str The string in which to preserve whitespace.
+ * @return {string} A copy of {@code str} with preserved whitespace.
+ */
+goog.string.preserveSpaces = function(str) {
+ return str.replace(/(^|[\n ]) /g, '$1' + goog.string.Unicode.NBSP);
+};
+
+
+/**
+ * Strip quote characters around a string. The second argument is a string of
+ * characters to treat as quotes. This can be a single character or a string of
+ * multiple character and in that case each of those are treated as possible
+ * quote characters. For example:
+ *
+ * <pre>
+ * goog.string.stripQuotes('"abc"', '"`') --> 'abc'
+ * goog.string.stripQuotes('`abc`', '"`') --> 'abc'
+ * </pre>
+ *
+ * @param {string} str The string to strip.
+ * @param {string} quoteChars The quote characters to strip.
+ * @return {string} A copy of {@code str} without the quotes.
+ */
+goog.string.stripQuotes = function(str, quoteChars) {
+ var length = quoteChars.length;
+ for (var i = 0; i < length; i++) {
+ var quoteChar = length == 1 ? quoteChars : quoteChars.charAt(i);
+ if (str.charAt(0) == quoteChar && str.charAt(str.length - 1) == quoteChar) {
+ return str.substring(1, str.length - 1);
+ }
+ }
+ return str;
+};
+
+
+/**
+ * Truncates a string to a certain length and adds '...' if necessary. The
+ * length also accounts for the ellipsis, so a maximum length of 10 and a string
+ * 'Hello World!' produces 'Hello W...'.
+ * @param {string} str The string to truncate.
+ * @param {number} chars Max number of characters.
+ * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
+ * characters from being cut off in the middle.
+ * @return {string} The truncated {@code str} string.
+ */
+goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) {
+ if (opt_protectEscapedCharacters) {
+ str = goog.string.unescapeEntities(str);
+ }
+
+ if (str.length > chars) {
+ str = str.substring(0, chars - 3) + '...';
+ }
+
+ if (opt_protectEscapedCharacters) {
+ str = goog.string.htmlEscape(str);
+ }
+
+ return str;
+};
+
+
+/**
+ * Truncate a string in the middle, adding "..." if necessary,
+ * and favoring the beginning of the string.
+ * @param {string} str The string to truncate the middle of.
+ * @param {number} chars Max number of characters.
+ * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
+ * characters from being cutoff in the middle.
+ * @param {number=} opt_trailingChars Optional number of trailing characters to
+ * leave at the end of the string, instead of truncating as close to the
+ * middle as possible.
+ * @return {string} A truncated copy of {@code str}.
+ */
+goog.string.truncateMiddle = function(str, chars,
+ opt_protectEscapedCharacters, opt_trailingChars) {
+ if (opt_protectEscapedCharacters) {
+ str = goog.string.unescapeEntities(str);
+ }
+
+ if (opt_trailingChars && str.length > chars) {
+ if (opt_trailingChars > chars) {
+ opt_trailingChars = chars;
+ }
+ var endPoint = str.length - opt_trailingChars;
+ var startPoint = chars - opt_trailingChars;
+ str = str.substring(0, startPoint) + '...' + str.substring(endPoint);
+ } else if (str.length > chars) {
+ // Favor the beginning of the string:
+ var half = Math.floor(chars / 2);
+ var endPos = str.length - half;
+ half += chars % 2;
+ str = str.substring(0, half) + '...' + str.substring(endPos);
+ }
+
+ if (opt_protectEscapedCharacters) {
+ str = goog.string.htmlEscape(str);
+ }
+
+ return str;
+};
+
+
+/**
+ * Special chars that need to be escaped for goog.string.quote.
+ * @private {!Object<string, string>}
+ */
+goog.string.specialEscapeChars_ = {
+ '\0': '\\0',
+ '\b': '\\b',
+ '\f': '\\f',
+ '\n': '\\n',
+ '\r': '\\r',
+ '\t': '\\t',
+ '\x0B': '\\x0B', // '\v' is not supported in JScript
+ '"': '\\"',
+ '\\': '\\\\'
+};
+
+
+/**
+ * Character mappings used internally for goog.string.escapeChar.
+ * @private {!Object<string, string>}
+ */
+goog.string.jsEscapeCache_ = {
+ '\'': '\\\''
+};
+
+
+/**
+ * Encloses a string in double quotes and escapes characters so that the
+ * string is a valid JS string.
+ * @param {string} s The string to quote.
+ * @return {string} A copy of {@code s} surrounded by double quotes.
+ */
+goog.string.quote = function(s) {
+ s = String(s);
+ if (s.quote) {
+ return s.quote();
+ } else {
+ var sb = ['"'];
+ for (var i = 0; i < s.length; i++) {
+ var ch = s.charAt(i);
+ var cc = ch.charCodeAt(0);
+ sb[i + 1] = goog.string.specialEscapeChars_[ch] ||
+ ((cc > 31 && cc < 127) ? ch : goog.string.escapeChar(ch));
+ }
+ sb.push('"');
+ return sb.join('');
+ }
+};
+
+
+/**
+ * Takes a string and returns the escaped string for that character.
+ * @param {string} str The string to escape.
+ * @return {string} An escaped string representing {@code str}.
+ */
+goog.string.escapeString = function(str) {
+ var sb = [];
+ for (var i = 0; i < str.length; i++) {
+ sb[i] = goog.string.escapeChar(str.charAt(i));
+ }
+ return sb.join('');
+};
+
+
+/**
+ * Takes a character and returns the escaped string for that character. For
+ * example escapeChar(String.fromCharCode(15)) -> "\\x0E".
+ * @param {string} c The character to escape.
+ * @return {string} An escaped string representing {@code c}.
+ */
+goog.string.escapeChar = function(c) {
+ if (c in goog.string.jsEscapeCache_) {
+ return goog.string.jsEscapeCache_[c];
+ }
+
+ if (c in goog.string.specialEscapeChars_) {
+ return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c];
+ }
+
+ var rv = c;
+ var cc = c.charCodeAt(0);
+ if (cc > 31 && cc < 127) {
+ rv = c;
+ } else {
+ // tab is 9 but handled above
+ if (cc < 256) {
+ rv = '\\x';
+ if (cc < 16 || cc > 256) {
+ rv += '0';
+ }
+ } else {
+ rv = '\\u';
+ if (cc < 4096) { // \u1000
+ rv += '0';
+ }
+ }
+ rv += cc.toString(16).toUpperCase();
+ }
+
+ return goog.string.jsEscapeCache_[c] = rv;
+};
+
+
+/**
+ * Determines whether a string contains a substring.
+ * @param {string} str The string to search.
+ * @param {string} subString The substring to search for.
+ * @return {boolean} Whether {@code str} contains {@code subString}.
+ */
+goog.string.contains = function(str, subString) {
+ return str.indexOf(subString) != -1;
+};
+
+
+/**
+ * Determines whether a string contains a substring, ignoring case.
+ * @param {string} str The string to search.
+ * @param {string} subString The substring to search for.
+ * @return {boolean} Whether {@code str} contains {@code subString}.
+ */
+goog.string.caseInsensitiveContains = function(str, subString) {
+ return goog.string.contains(str.toLowerCase(), subString.toLowerCase());
+};
+
+
+/**
+ * Returns the non-overlapping occurrences of ss in s.
+ * If either s or ss evalutes to false, then returns zero.
+ * @param {string} s The string to look in.
+ * @param {string} ss The string to look for.
+ * @return {number} Number of occurrences of ss in s.
+ */
+goog.string.countOf = function(s, ss) {
+ return s && ss ? s.split(ss).length - 1 : 0;
+};
+
+
+/**
+ * Removes a substring of a specified length at a specific
+ * index in a string.
+ * @param {string} s The base string from which to remove.
+ * @param {number} index The index at which to remove the substring.
+ * @param {number} stringLength The length of the substring to remove.
+ * @return {string} A copy of {@code s} with the substring removed or the full
+ * string if nothing is removed or the input is invalid.
+ */
+goog.string.removeAt = function(s, index, stringLength) {
+ var resultStr = s;
+ // If the index is greater or equal to 0 then remove substring
+ if (index >= 0 && index < s.length && stringLength > 0) {
+ resultStr = s.substr(0, index) +
+ s.substr(index + stringLength, s.length - index - stringLength);
+ }
+ return resultStr;
+};
+
+
+/**
+ * Removes the first occurrence of a substring from a string.
+ * @param {string} s The base string from which to remove.
+ * @param {string} ss The string to remove.
+ * @return {string} A copy of {@code s} with {@code ss} removed or the full
+ * string if nothing is removed.
+ */
+goog.string.remove = function(s, ss) {
+ var re = new RegExp(goog.string.regExpEscape(ss), '');
+ return s.replace(re, '');
+};
+
+
+/**
+ * Removes all occurrences of a substring from a string.
+ * @param {string} s The base string from which to remove.
+ * @param {string} ss The string to remove.
+ * @return {string} A copy of {@code s} with {@code ss} removed or the full
+ * string if nothing is removed.
+ */
+goog.string.removeAll = function(s, ss) {
+ var re = new RegExp(goog.string.regExpEscape(ss), 'g');
+ return s.replace(re, '');
+};
+
+
+/**
+ * Escapes characters in the string that are not safe to use in a RegExp.
+ * @param {*} s The string to escape. If not a string, it will be casted
+ * to one.
+ * @return {string} A RegExp safe, escaped copy of {@code s}.
+ */
+goog.string.regExpEscape = function(s) {
+ return String(s).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
+ replace(/\x08/g, '\\x08');
+};
+
+
+/**
+ * Repeats a string n times.
+ * @param {string} string The string to repeat.
+ * @param {number} length The number of times to repeat.
+ * @return {string} A string containing {@code length} repetitions of
+ * {@code string}.
+ */
+goog.string.repeat = (String.prototype.repeat) ?
+ function(string, length) {
+ // The native method is over 100 times faster than the alternative.
+ return string.repeat(length);
+ } :
+ function(string, length) {
+ return new Array(length + 1).join(string);
+ };
+
+
+/**
+ * Pads number to given length and optionally rounds it to a given precision.
+ * For example:
+ * <pre>padNumber(1.25, 2, 3) -> '01.250'
+ * padNumber(1.25, 2) -> '01.25'
+ * padNumber(1.25, 2, 1) -> '01.3'
+ * padNumber(1.25, 0) -> '1.25'</pre>
+ *
+ * @param {number} num The number to pad.
+ * @param {number} length The desired length.
+ * @param {number=} opt_precision The desired precision.
+ * @return {string} {@code num} as a string with the given options.
+ */
+goog.string.padNumber = function(num, length, opt_precision) {
+ var s = goog.isDef(opt_precision) ? num.toFixed(opt_precision) : String(num);
+ var index = s.indexOf('.');
+ if (index == -1) {
+ index = s.length;
+ }
+ return goog.string.repeat('0', Math.max(0, length - index)) + s;
+};
+
+
+/**
+ * Returns a string representation of the given object, with
+ * null and undefined being returned as the empty string.
+ *
+ * @param {*} obj The object to convert.
+ * @return {string} A string representation of the {@code obj}.
+ */
+goog.string.makeSafe = function(obj) {
+ return obj == null ? '' : String(obj);
+};
+
+
+/**
+ * Concatenates string expressions. This is useful
+ * since some browsers are very inefficient when it comes to using plus to
+ * concat strings. Be careful when using null and undefined here since
+ * these will not be included in the result. If you need to represent these
+ * be sure to cast the argument to a String first.
+ * For example:
+ * <pre>buildString('a', 'b', 'c', 'd') -> 'abcd'
+ * buildString(null, undefined) -> ''
+ * </pre>
+ * @param {...*} var_args A list of strings to concatenate. If not a string,
+ * it will be casted to one.
+ * @return {string} The concatenation of {@code var_args}.
+ */
+goog.string.buildString = function(var_args) {
+ return Array.prototype.join.call(arguments, '');
+};
+
+
+/**
+ * Returns a string with at least 64-bits of randomness.
+ *
+ * Doesn't trust Javascript's random function entirely. Uses a combination of
+ * random and current timestamp, and then encodes the string in base-36 to
+ * make it shorter.
+ *
+ * @return {string} A random string, e.g. sn1s7vb4gcic.
+ */
+goog.string.getRandomString = function() {
+ var x = 2147483648;
+ return Math.floor(Math.random() * x).toString(36) +
+ Math.abs(Math.floor(Math.random() * x) ^ goog.now()).toString(36);
+};
+
+
+/**
+ * Compares two version numbers.
+ *
+ * @param {string|number} version1 Version of first item.
+ * @param {string|number} version2 Version of second item.
+ *
+ * @return {number} 1 if {@code version1} is higher.
+ * 0 if arguments are equal.
+ * -1 if {@code version2} is higher.
+ */
+goog.string.compareVersions = function(version1, version2) {
+ var order = 0;
+ // Trim leading and trailing whitespace and split the versions into
+ // subversions.
+ var v1Subs = goog.string.trim(String(version1)).split('.');
+ var v2Subs = goog.string.trim(String(version2)).split('.');
+ var subCount = Math.max(v1Subs.length, v2Subs.length);
+
+ // Iterate over the subversions, as long as they appear to be equivalent.
+ for (var subIdx = 0; order == 0 && subIdx < subCount; subIdx++) {
+ var v1Sub = v1Subs[subIdx] || '';
+ var v2Sub = v2Subs[subIdx] || '';
+
+ // Split the subversions into pairs of numbers and qualifiers (like 'b').
+ // Two different RegExp objects are needed because they are both using
+ // the 'g' flag.
+ var v1CompParser = new RegExp('(\\d*)(\\D*)', 'g');
+ var v2CompParser = new RegExp('(\\d*)(\\D*)', 'g');
+ do {
+ var v1Comp = v1CompParser.exec(v1Sub) || ['', '', ''];
+ var v2Comp = v2CompParser.exec(v2Sub) || ['', '', ''];
+ // Break if there are no more matches.
+ if (v1Comp[0].length == 0 && v2Comp[0].length == 0) {
+ break;
+ }
+
+ // Parse the numeric part of the subversion. A missing number is
+ // equivalent to 0.
+ var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10);
+ var v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10);
+
+ // Compare the subversion components. The number has the highest
+ // precedence. Next, if the numbers are equal, a subversion without any
+ // qualifier is always higher than a subversion with any qualifier. Next,
+ // the qualifiers are compared as strings.
+ order = goog.string.compareElements_(v1CompNum, v2CompNum) ||
+ goog.string.compareElements_(v1Comp[2].length == 0,
+ v2Comp[2].length == 0) ||
+ goog.string.compareElements_(v1Comp[2], v2Comp[2]);
+ // Stop as soon as an inequality is discovered.
+ } while (order == 0);
+ }
+
+ return order;
+};
+
+
+/**
+ * Compares elements of a version number.
+ *
+ * @param {string|number|boolean} left An element from a version number.
+ * @param {string|number|boolean} right An element from a version number.
+ *
+ * @return {number} 1 if {@code left} is higher.
+ * 0 if arguments are equal.
+ * -1 if {@code right} is higher.
+ * @private
+ */
+goog.string.compareElements_ = function(left, right) {
+ if (left < right) {
+ return -1;
+ } else if (left > right) {
+ return 1;
+ }
+ return 0;
+};
+
+
+/**
+ * String hash function similar to java.lang.String.hashCode().
+ * The hash code for a string is computed as
+ * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1],
+ * where s[i] is the ith character of the string and n is the length of
+ * the string. We mod the result to make it between 0 (inclusive) and 2^32
+ * (exclusive).
+ * @param {string} str A string.
+ * @return {number} Hash value for {@code str}, between 0 (inclusive) and 2^32
+ * (exclusive). The empty string returns 0.
+ */
+goog.string.hashCode = function(str) {
+ var result = 0;
+ for (var i = 0; i < str.length; ++i) {
+ // Normalize to 4 byte range, 0 ... 2^32.
+ result = (31 * result + str.charCodeAt(i)) >>> 0;
+ }
+ return result;
+};
+
+
+/**
+ * The most recent unique ID. |0 is equivalent to Math.floor in this case.
+ * @type {number}
+ * @private
+ */
+goog.string.uniqueStringCounter_ = Math.random() * 0x80000000 | 0;
+
+
+/**
+ * Generates and returns a string which is unique in the current document.
+ * This is useful, for example, to create unique IDs for DOM elements.
+ * @return {string} A unique id.
+ */
+goog.string.createUniqueString = function() {
+ return 'goog_' + goog.string.uniqueStringCounter_++;
+};
+
+
+/**
+ * Converts the supplied string to a number, which may be Infinity or NaN.
+ * This function strips whitespace: (toNumber(' 123') === 123)
+ * This function accepts scientific notation: (toNumber('1e1') === 10)
+ *
+ * This is better than Javascript's built-in conversions because, sadly:
+ * (Number(' ') === 0) and (parseFloat('123a') === 123)
+ *
+ * @param {string} str The string to convert.
+ * @return {number} The number the supplied string represents, or NaN.
+ */
+goog.string.toNumber = function(str) {
+ var num = Number(str);
+ if (num == 0 && goog.string.isEmptyOrWhitespace(str)) {
+ return NaN;
+ }
+ return num;
+};
+
+
+/**
+ * Returns whether the given string is lower camel case (e.g. "isFooBar").
+ *
+ * Note that this assumes the string is entirely letters.
+ * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
+ *
+ * @param {string} str String to test.
+ * @return {boolean} Whether the string is lower camel case.
+ */
+goog.string.isLowerCamelCase = function(str) {
+ return /^[a-z]+([A-Z][a-z]*)*$/.test(str);
+};
+
+
+/**
+ * Returns whether the given string is upper camel case (e.g. "FooBarBaz").
+ *
+ * Note that this assumes the string is entirely letters.
+ * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
+ *
+ * @param {string} str String to test.
+ * @return {boolean} Whether the string is upper camel case.
+ */
+goog.string.isUpperCamelCase = function(str) {
+ return /^([A-Z][a-z]*)+$/.test(str);
+};
+
+
+/**
+ * Converts a string from selector-case to camelCase (e.g. from
+ * "multi-part-string" to "multiPartString"), useful for converting
+ * CSS selectors and HTML dataset keys to their equivalent JS properties.
+ * @param {string} str The string in selector-case form.
+ * @return {string} The string in camelCase form.
+ */
+goog.string.toCamelCase = function(str) {
+ return String(str).replace(/\-([a-z])/g, function(all, match) {
+ return match.toUpperCase();
+ });
+};
+
+
+/**
+ * Converts a string from camelCase to selector-case (e.g. from
+ * "multiPartString" to "multi-part-string"), useful for converting JS
+ * style and dataset properties to equivalent CSS selectors and HTML keys.
+ * @param {string} str The string in camelCase form.
+ * @return {string} The string in selector-case form.
+ */
+goog.string.toSelectorCase = function(str) {
+ return String(str).replace(/([A-Z])/g, '-$1').toLowerCase();
+};
+
+
+/**
+ * Converts a string into TitleCase. First character of the string is always
+ * capitalized in addition to the first letter of every subsequent word.
+ * Words are delimited by one or more whitespaces by default. Custom delimiters
+ * can optionally be specified to replace the default, which doesn't preserve
+ * whitespace delimiters and instead must be explicitly included if needed.
+ *
+ * Default delimiter => " ":
+ * goog.string.toTitleCase('oneTwoThree') => 'OneTwoThree'
+ * goog.string.toTitleCase('one two three') => 'One Two Three'
+ * goog.string.toTitleCase(' one two ') => ' One Two '
+ * goog.string.toTitleCase('one_two_three') => 'One_two_three'
+ * goog.string.toTitleCase('one-two-three') => 'One-two-three'
+ *
+ * Custom delimiter => "_-.":
+ * goog.string.toTitleCase('oneTwoThree', '_-.') => 'OneTwoThree'
+ * goog.string.toTitleCase('one two three', '_-.') => 'One two three'
+ * goog.string.toTitleCase(' one two ', '_-.') => ' one two '
+ * goog.string.toTitleCase('one_two_three', '_-.') => 'One_Two_Three'
+ * goog.string.toTitleCase('one-two-three', '_-.') => 'One-Two-Three'
+ * goog.string.toTitleCase('one...two...three', '_-.') => 'One...Two...Three'
+ * goog.string.toTitleCase('one. two. three', '_-.') => 'One. two. three'
+ * goog.string.toTitleCase('one-two.three', '_-.') => 'One-Two.Three'
+ *
+ * @param {string} str String value in camelCase form.
+ * @param {string=} opt_delimiters Custom delimiter character set used to
+ * distinguish words in the string value. Each character represents a
+ * single delimiter. When provided, default whitespace delimiter is
+ * overridden and must be explicitly included if needed.
+ * @return {string} String value in TitleCase form.
+ */
+goog.string.toTitleCase = function(str, opt_delimiters) {
+ var delimiters = goog.isString(opt_delimiters) ?
+ goog.string.regExpEscape(opt_delimiters) : '\\s';
+
+ // For IE8, we need to prevent using an empty character set. Otherwise,
+ // incorrect matching will occur.
+ delimiters = delimiters ? '|[' + delimiters + ']+' : '';
+
+ var regexp = new RegExp('(^' + delimiters + ')([a-z])', 'g');
+ return str.replace(regexp, function(all, p1, p2) {
+ return p1 + p2.toUpperCase();
+ });
+};
+
+
+/**
+ * Capitalizes a string, i.e. converts the first letter to uppercase
+ * and all other letters to lowercase, e.g.:
+ *
+ * goog.string.capitalize('one') => 'One'
+ * goog.string.capitalize('ONE') => 'One'
+ * goog.string.capitalize('one two') => 'One two'
+ *
+ * Note that this function does not trim initial whitespace.
+ *
+ * @param {string} str String value to capitalize.
+ * @return {string} String value with first letter in uppercase.
+ */
+goog.string.capitalize = function(str) {
+ return String(str.charAt(0)).toUpperCase() +
+ String(str.substr(1)).toLowerCase();
+};
+
+
+/**
+ * Parse a string in decimal or hexidecimal ('0xFFFF') form.
+ *
+ * To parse a particular radix, please use parseInt(string, radix) directly. See
+ * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/parseInt
+ *
+ * This is a wrapper for the built-in parseInt function that will only parse
+ * numbers as base 10 or base 16. Some JS implementations assume strings
+ * starting with "0" are intended to be octal. ES3 allowed but discouraged
+ * this behavior. ES5 forbids it. This function emulates the ES5 behavior.
+ *
+ * For more information, see Mozilla JS Reference: http://goo.gl/8RiFj
+ *
+ * @param {string|number|null|undefined} value The value to be parsed.
+ * @return {number} The number, parsed. If the string failed to parse, this
+ * will be NaN.
+ */
+goog.string.parseInt = function(value) {
+ // Force finite numbers to strings.
+ if (isFinite(value)) {
+ value = String(value);
+ }
+
+ if (goog.isString(value)) {
+ // If the string starts with '0x' or '-0x', parse as hex.
+ return /^\s*-?0x/i.test(value) ?
+ parseInt(value, 16) : parseInt(value, 10);
+ }
+
+ return NaN;
+};
+
+
+/**
+ * Splits a string on a separator a limited number of times.
+ *
+ * This implementation is more similar to Python or Java, where the limit
+ * parameter specifies the maximum number of splits rather than truncating
+ * the number of results.
+ *
+ * See http://docs.python.org/2/library/stdtypes.html#str.split
+ * See JavaDoc: http://goo.gl/F2AsY
+ * See Mozilla reference: http://goo.gl/dZdZs
+ *
+ * @param {string} str String to split.
+ * @param {string} separator The separator.
+ * @param {number} limit The limit to the number of splits. The resulting array
+ * will have a maximum length of limit+1. Negative numbers are the same
+ * as zero.
+ * @return {!Array<string>} The string, split.
+ */
+
+goog.string.splitLimit = function(str, separator, limit) {
+ var parts = str.split(separator);
+ var returnVal = [];
+
+ // Only continue doing this while we haven't hit the limit and we have
+ // parts left.
+ while (limit > 0 && parts.length) {
+ returnVal.push(parts.shift());
+ limit--;
+ }
+
+ // If there are remaining parts, append them to the end.
+ if (parts.length) {
+ returnVal.push(parts.join(separator));
+ }
+
+ return returnVal;
+};
+
+
+/**
+ * Computes the Levenshtein edit distance between two strings.
+ * @param {string} a
+ * @param {string} b
+ * @return {number} The edit distance between the two strings.
+ */
+goog.string.editDistance = function(a, b) {
+ var v0 = [];
+ var v1 = [];
+
+ if (a == b) {
+ return 0;
+ }
+
+ if (!a.length || !b.length) {
+ return Math.max(a.length, b.length);
+ }
+
+ for (var i = 0; i < b.length + 1; i++) {
+ v0[i] = i;
+ }
+
+ for (var i = 0; i < a.length; i++) {
+ v1[0] = i + 1;
+
+ for (var j = 0; j < b.length; j++) {
+ var cost = a[i] != b[j];
+ // Cost for the substring is the minimum of adding one character, removing
+ // one character, or a swap.
+ v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost);
+ }
+
+ for (var j = 0; j < v0.length; j++) {
+ v0[j] = v1[j];
+ }
+ }
+
+ return v1[b.length];
+};
+
+// 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 to check the preconditions, postconditions and
+ * invariants runtime.
+ *
+ * Methods in this package should be given special treatment by the compiler
+ * for type-inference. For example, <code>goog.asserts.assert(foo)</code>
+ * will restrict <code>foo</code> to a truthy value.
+ *
+ * The compiler has an option to disable asserts. So code like:
+ * <code>
+ * var x = goog.asserts.assert(foo()); goog.asserts.assert(bar());
+ * </code>
+ * will be transformed into:
+ * <code>
+ * var x = foo();
+ * </code>
+ * The compiler will leave in foo() (because its return value is used),
+ * but it will remove bar() because it assumes it does not have side-effects.
+ *
+ * @author agrieve@google.com (Andrew Grieve)
+ */
+
+goog.provide('goog.asserts');
+goog.provide('goog.asserts.AssertionError');
+
+goog.require('goog.debug.Error');
+goog.require('goog.dom.NodeType');
+goog.require('goog.string');
+
+
+/**
+ * @define {boolean} Whether to strip out asserts or to leave them in.
+ */
+goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG);
+
+
+
+/**
+ * Error object for failed assertions.
+ * @param {string} messagePattern The pattern that was used to form message.
+ * @param {!Array<*>} messageArgs The items to substitute into the pattern.
+ * @constructor
+ * @extends {goog.debug.Error}
+ * @final
+ */
+goog.asserts.AssertionError = function(messagePattern, messageArgs) {
+ messageArgs.unshift(messagePattern);
+ goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs));
+ // Remove the messagePattern afterwards to avoid permanently modifying the
+ // passed in array.
+ messageArgs.shift();
+
+ /**
+ * The message pattern used to format the error message. Error handlers can
+ * use this to uniquely identify the assertion.
+ * @type {string}
+ */
+ this.messagePattern = messagePattern;
+};
+goog.inherits(goog.asserts.AssertionError, goog.debug.Error);
+
+
+/** @override */
+goog.asserts.AssertionError.prototype.name = 'AssertionError';
+
+
+/**
+ * The default error handler.
+ * @param {!goog.asserts.AssertionError} e The exception to be handled.
+ */
+goog.asserts.DEFAULT_ERROR_HANDLER = function(e) { throw e; };
+
+
+/**
+ * The handler responsible for throwing or logging assertion errors.
+ * @private {function(!goog.asserts.AssertionError)}
+ */
+goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER;
+
+
+/**
+ * Throws an exception with the given message and "Assertion failed" prefixed
+ * onto it.
+ * @param {string} defaultMessage The message to use if givenMessage is empty.
+ * @param {Array<*>} defaultArgs The substitution arguments for defaultMessage.
+ * @param {string|undefined} givenMessage Message supplied by the caller.
+ * @param {Array<*>} givenArgs The substitution arguments for givenMessage.
+ * @throws {goog.asserts.AssertionError} When the value is not a number.
+ * @private
+ */
+goog.asserts.doAssertFailure_ =
+ function(defaultMessage, defaultArgs, givenMessage, givenArgs) {
+ var message = 'Assertion failed';
+ if (givenMessage) {
+ message += ': ' + givenMessage;
+ var args = givenArgs;
+ } else if (defaultMessage) {
+ message += ': ' + defaultMessage;
+ args = defaultArgs;
+ }
+ // The '' + works around an Opera 10 bug in the unit tests. Without it,
+ // a stack trace is added to var message above. With this, a stack trace is
+ // not added until this line (it causes the extra garbage to be added after
+ // the assertion message instead of in the middle of it).
+ var e = new goog.asserts.AssertionError('' + message, args || []);
+ goog.asserts.errorHandler_(e);
+};
+
+
+/**
+ * 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
+ */
+goog.asserts.setErrorHandler = function(errorHandler) {
+ if (goog.asserts.ENABLE_ASSERTS) {
+ goog.asserts.errorHandler_ = errorHandler;
+ }
+};
+
+
+/**
+ * Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is
+ * true.
+ * @template T
+ * @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.
+ * @throws {goog.asserts.AssertionError} When the condition evaluates to false.
+ */
+goog.asserts.assert = function(condition, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !condition) {
+ goog.asserts.doAssertFailure_('', null, opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return condition;
+};
+
+
+/**
+ * Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case
+ * when we want to add a check in the unreachable area like switch-case
+ * statement:
+ *
+ * <pre>
+ * switch(type) {
+ * case FOO: doSomething(); break;
+ * case BAR: doSomethingElse(); break;
+ * default: goog.assert.fail('Unrecognized type: ' + type);
+ * // We have only 2 types - "default:" section is unreachable code.
+ * }
+ * </pre>
+ *
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @throws {goog.asserts.AssertionError} Failure.
+ */
+goog.asserts.fail = function(opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS) {
+ goog.asserts.errorHandler_(new goog.asserts.AssertionError(
+ 'Failure' + (opt_message ? ': ' + opt_message : ''),
+ Array.prototype.slice.call(arguments, 1)));
+ }
+};
+
+
+/**
+ * Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {number} The value, guaranteed to be a number when asserts enabled.
+ * @throws {goog.asserts.AssertionError} When the value is not a number.
+ */
+goog.asserts.assertNumber = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) {
+ goog.asserts.doAssertFailure_('Expected number but got %s: %s.',
+ [goog.typeOf(value), value], opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {number} */ (value);
+};
+
+
+/**
+ * Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {string} The value, guaranteed to be a string when asserts enabled.
+ * @throws {goog.asserts.AssertionError} When the value is not a string.
+ */
+goog.asserts.assertString = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) {
+ goog.asserts.doAssertFailure_('Expected string but got %s: %s.',
+ [goog.typeOf(value), value], opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {string} */ (value);
+};
+
+
+/**
+ * Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {!Function} The value, guaranteed to be a function when asserts
+ * enabled.
+ * @throws {goog.asserts.AssertionError} When the value is not a function.
+ */
+goog.asserts.assertFunction = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) {
+ goog.asserts.doAssertFailure_('Expected function but got %s: %s.',
+ [goog.typeOf(value), value], opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {!Function} */ (value);
+};
+
+
+/**
+ * Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {!Object} The value, guaranteed to be a non-null object.
+ * @throws {goog.asserts.AssertionError} When the value is not an object.
+ */
+goog.asserts.assertObject = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) {
+ goog.asserts.doAssertFailure_('Expected object but got %s: %s.',
+ [goog.typeOf(value), value],
+ opt_message, Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {!Object} */ (value);
+};
+
+
+/**
+ * Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {!Array<?>} The value, guaranteed to be a non-null array.
+ * @throws {goog.asserts.AssertionError} When the value is not an array.
+ */
+goog.asserts.assertArray = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) {
+ goog.asserts.doAssertFailure_('Expected array but got %s: %s.',
+ [goog.typeOf(value), value], opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {!Array<?>} */ (value);
+};
+
+
+/**
+ * Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {boolean} The value, guaranteed to be a boolean when asserts are
+ * enabled.
+ * @throws {goog.asserts.AssertionError} When the value is not a boolean.
+ */
+goog.asserts.assertBoolean = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) {
+ goog.asserts.doAssertFailure_('Expected boolean but got %s: %s.',
+ [goog.typeOf(value), value], opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {boolean} */ (value);
+};
+
+
+/**
+ * Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {!Element} The value, likely to be a DOM Element when asserts are
+ * enabled.
+ * @throws {goog.asserts.AssertionError} When the value is not an Element.
+ */
+goog.asserts.assertElement = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && (!goog.isObject(value) ||
+ value.nodeType != goog.dom.NodeType.ELEMENT)) {
+ goog.asserts.doAssertFailure_('Expected Element but got %s: %s.',
+ [goog.typeOf(value), value], opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {!Element} */ (value);
+};
+
+
+/**
+ * Checks if the value is an instance of the user-defined type if
+ * goog.asserts.ENABLE_ASSERTS is true.
+ *
+ * The compiler may tighten the type returned by this function.
+ *
+ * @param {*} value The value to check.
+ * @param {function(new: T, ...)} type A user-defined constructor.
+ * @param {string=} opt_message Error message in case of failure.
+ * @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}
+ * @template T
+ */
+goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) {
+ goog.asserts.doAssertFailure_('Expected instanceof %s but got %s.',
+ [goog.asserts.getType_(type), goog.asserts.getType_(value)],
+ opt_message, Array.prototype.slice.call(arguments, 3));
+ }
+ return value;
+};
+
+
+/**
+ * Checks that no enumerable keys are present in Object.prototype. Such keys
+ * would break most code that use {@code for (var ... in ...)} loops.
+ */
+goog.asserts.assertObjectPrototypeIsIntact = function() {
+ for (var key in Object.prototype) {
+ goog.asserts.fail(key + ' should not be enumerable in Object.prototype.');
+ }
+};
+
+
+/**
+ * Returns the type of a value. If a constructor is passed, and a suitable
+ * string cannot be found, 'unknown type name' will be returned.
+ * @param {*} value A constructor, object, or primitive.
+ * @return {string} The best display name for the value, or 'unknown type name'.
+ * @private
+ */
+goog.asserts.getType_ = function(value) {
+ if (value instanceof Function) {
+ return value.displayName || value.name || 'unknown type name';
+ } else if (value instanceof Object) {
+ return value.constructor.displayName || value.constructor.name ||
+ Object.prototype.toString.call(value);
+ } else {
+ return value === null ? 'null' : typeof value;
+ }
+};
+
+goog.provide('ol.math');
+
+goog.require('goog.asserts');
+
+
+/**
+ * Takes a number and clamps it to within the provided bounds.
+ * @param {number} value The input number.
+ * @param {number} min The minimum value to return.
+ * @param {number} max The maximum value to return.
+ * @return {number} The input number if it is within bounds, or the nearest
+ * number within the bounds.
+ */
+ol.math.clamp = function(value, min, max) {
+ return Math.min(Math.max(value, min), max);
+};
+
+
+/**
+ * Return the hyperbolic cosine of a given number. The method will use the
+ * native `Math.cosh` function if it is available, otherwise the hyperbolic
+ * cosine will be calculated via the reference implementation of the Mozilla
+ * developer network.
+ *
+ * @param {number} x X.
+ * @return {number} Hyperbolic cosine of x.
+ */
+ol.math.cosh = (function() {
+ // Wrapped in a iife, to save the overhead of checking for the native
+ // implementation on every invocation.
+ var cosh;
+ if ('cosh' in Math) {
+ // The environment supports the native Math.cosh function, use it…
+ cosh = Math.cosh;
+ } else {
+ // … else, use the reference implementation of MDN:
+ cosh = function(x) {
+ var y = Math.exp(x);
+ return (y + 1 / y) / 2;
+ };
+ }
+ return cosh;
+}());
+
+
+/**
+ * @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));
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * Solves system of linear equations using Gaussian elimination method.
+ *
+ * @param {Array.<Array.<number>>} mat Augmented matrix (n x n + 1 column)
+ * in row-major order.
+ * @return {Array.<number>} The resulting vector.
+ */
+ol.math.solveLinearSystem = function(mat) {
+ var n = mat.length;
+
+ if (goog.asserts.ENABLE_ASSERTS) {
+ for (var row = 0; row < n; row++) {
+ goog.asserts.assert(mat[row].length == n + 1,
+ 'every row should have correct number of columns');
+ }
+ }
+
+ for (var i = 0; i < n; i++) {
+ // Find max in the i-th column (ignoring i - 1 first rows)
+ var maxRow = i;
+ var maxEl = Math.abs(mat[i][i]);
+ for (var r = i + 1; r < n; r++) {
+ var absValue = Math.abs(mat[r][i]);
+ if (absValue > maxEl) {
+ maxEl = absValue;
+ maxRow = r;
+ }
+ }
+
+ if (maxEl === 0) {
+ return null; // matrix is singular
+ }
+
+ // Swap max row with i-th (current) row
+ var tmp = mat[maxRow];
+ mat[maxRow] = mat[i];
+ mat[i] = tmp;
+
+ // Subtract the i-th row to make all the remaining rows 0 in the i-th column
+ for (var j = i + 1; j < n; j++) {
+ var coef = -mat[j][i] / mat[i][i];
+ for (var k = i; k < n + 1; k++) {
+ if (i == k) {
+ mat[j][k] = 0;
+ } else {
+ mat[j][k] += coef * mat[i][k];
+ }
+ }
+ }
+ }
+
+ // Solve Ax=b for upper triangular matrix A (mat)
+ var x = new Array(n);
+ for (var l = n - 1; l >= 0; l--) {
+ x[l] = mat[l][n] / mat[l][l];
+ for (var m = l - 1; m >= 0; m--) {
+ mat[m][n] -= mat[m][l] * x[l];
+ }
+ }
+ return x;
+};
+
+
+/**
+ * Converts radians to to degrees.
+ *
+ * @param {number} angleInRadians Angle in radians.
+ * @return {number} Angle in degrees.
+ */
+ol.math.toDegrees = function(angleInRadians) {
+ return angleInRadians * 180 / Math.PI;
+};
+
+
+/**
+ * Converts degrees to radians.
+ *
+ * @param {number} angleInDegrees Angle in degrees.
+ * @return {number} Angle in radians.
+ */
+ol.math.toRadians = function(angleInDegrees) {
+ return angleInDegrees * Math.PI / 180;
+};
+
+goog.provide('ol.CenterConstraint');
+goog.provide('ol.CenterConstraintType');
+
+goog.require('ol.math');
+
+
+/**
+ * @typedef {function((ol.Coordinate|undefined)): (ol.Coordinate|undefined)}
+ */
+ol.CenterConstraintType;
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.CenterConstraintType}
+ */
+ol.CenterConstraint.createExtent = function(extent) {
+ return (
+ /**
+ * @param {ol.Coordinate|undefined} center Center.
+ * @return {ol.Coordinate|undefined} Center.
+ */
+ function(center) {
+ if (center) {
+ return [
+ ol.math.clamp(center[0], extent[0], extent[2]),
+ ol.math.clamp(center[1], extent[1], extent[3])
+ ];
+ } else {
+ return undefined;
+ }
+ });
+};
+
+
+/**
+ * @param {ol.Coordinate|undefined} center Center.
+ * @return {ol.Coordinate|undefined} Center.
+ */
+ol.CenterConstraint.none = function(center) {
+ return center;
+};
+
+// 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 arrays.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ */
+
+
+goog.provide('goog.array');
+goog.provide('goog.array.ArrayLike');
+
+goog.require('goog.asserts');
+
+
+/**
+ * @define {boolean} NATIVE_ARRAY_PROTOTYPES indicates whether the code should
+ * rely on Array.prototype functions, if available.
+ *
+ * The Array.prototype functions can be defined by external libraries like
+ * Prototype and setting this flag to false forces closure to use its own
+ * goog.array implementation.
+ *
+ * If your javascript can be loaded by a third party site and you are wary about
+ * relying on the prototype functions, specify
+ * "--define goog.NATIVE_ARRAY_PROTOTYPES=false" to the JSCompiler.
+ *
+ * Setting goog.TRUSTED_SITE to false will automatically set
+ * NATIVE_ARRAY_PROTOTYPES to false.
+ */
+goog.define('goog.NATIVE_ARRAY_PROTOTYPES', goog.TRUSTED_SITE);
+
+
+/**
+ * @define {boolean} If true, JSCompiler will use the native implementation of
+ * array functions where appropriate (e.g., {@code Array#filter}) and remove the
+ * unused pure JS implementation.
+ */
+goog.define('goog.array.ASSUME_NATIVE_FUNCTIONS', false);
+
+
+/**
+ * @typedef {Array|NodeList|Arguments|{length: number}}
+ */
+goog.array.ArrayLike;
+
+
+/**
+ * Returns the last element in an array without removing it.
+ * Same as goog.array.last.
+ * @param {Array<T>|goog.array.ArrayLike} array The array.
+ * @return {T} Last item in array.
+ * @template T
+ */
+goog.array.peek = function(array) {
+ return array[array.length - 1];
+};
+
+
+/**
+ * Returns the last element in an array without removing it.
+ * Same as goog.array.peek.
+ * @param {Array<T>|goog.array.ArrayLike} array The array.
+ * @return {T} Last item in array.
+ * @template T
+ */
+goog.array.last = goog.array.peek;
+
+
+/**
+ * Reference to the original {@code Array.prototype}.
+ * @private
+ */
+goog.array.ARRAY_PROTOTYPE_ = Array.prototype;
+
+
+// NOTE(arv): Since most of the array functions are generic it allows you to
+// pass an array-like object. Strings have a length and are considered array-
+// like. However, the 'in' operator does not work on strings so we cannot just
+// use the array path even if the browser supports indexing into strings. We
+// therefore end up splitting the string.
+
+
+/**
+ * Returns the index of the first element of an array with a specified value, or
+ * -1 if the element is not present in the array.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-indexof}
+ *
+ * @param {Array<T>|goog.array.ArrayLike} arr The array to be searched.
+ * @param {T} obj The object for which we are searching.
+ * @param {number=} opt_fromIndex The index at which to start the search. If
+ * omitted the search starts at index 0.
+ * @return {number} The index of the first matching array element.
+ * @template T
+ */
+goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.indexOf) ?
+ function(arr, obj, opt_fromIndex) {
+ goog.asserts.assert(arr.length != null);
+
+ return goog.array.ARRAY_PROTOTYPE_.indexOf.call(arr, obj, opt_fromIndex);
+ } :
+ function(arr, obj, opt_fromIndex) {
+ var fromIndex = opt_fromIndex == null ?
+ 0 : (opt_fromIndex < 0 ?
+ Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex);
+
+ if (goog.isString(arr)) {
+ // Array.prototype.indexOf uses === so only strings should be found.
+ if (!goog.isString(obj) || obj.length != 1) {
+ return -1;
+ }
+ return arr.indexOf(obj, fromIndex);
+ }
+
+ for (var i = fromIndex; i < arr.length; i++) {
+ if (i in arr && arr[i] === obj)
+ return i;
+ }
+ return -1;
+ };
+
+
+/**
+ * Returns the index of the last element of an array with a specified value, or
+ * -1 if the element is not present in the array.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-lastindexof}
+ *
+ * @param {!Array<T>|!goog.array.ArrayLike} arr The array to be searched.
+ * @param {T} obj The object for which we are searching.
+ * @param {?number=} opt_fromIndex The index at which to start the search. If
+ * omitted the search starts at the end of the array.
+ * @return {number} The index of the last matching array element.
+ * @template T
+ */
+goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.lastIndexOf) ?
+ function(arr, obj, opt_fromIndex) {
+ goog.asserts.assert(arr.length != null);
+
+ // Firefox treats undefined and null as 0 in the fromIndex argument which
+ // leads it to always return -1
+ var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
+ return goog.array.ARRAY_PROTOTYPE_.lastIndexOf.call(arr, obj, fromIndex);
+ } :
+ function(arr, obj, opt_fromIndex) {
+ var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
+
+ if (fromIndex < 0) {
+ fromIndex = Math.max(0, arr.length + fromIndex);
+ }
+
+ if (goog.isString(arr)) {
+ // Array.prototype.lastIndexOf uses === so only strings should be found.
+ if (!goog.isString(obj) || obj.length != 1) {
+ return -1;
+ }
+ return arr.lastIndexOf(obj, fromIndex);
+ }
+
+ for (var i = fromIndex; i >= 0; i--) {
+ if (i in arr && arr[i] === obj)
+ return i;
+ }
+ return -1;
+ };
+
+
+/**
+ * Calls a function for each element in an array. Skips holes in the array.
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-foreach}
+ *
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array like object over
+ * which to iterate.
+ * @param {?function(this: S, T, number, ?): ?} 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_obj The object to be used as the value of 'this' within f.
+ * @template T,S
+ */
+goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.forEach) ?
+ function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+
+ goog.array.ARRAY_PROTOTYPE_.forEach.call(arr, f, opt_obj);
+ } :
+ function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = 0; i < l; i++) {
+ if (i in arr2) {
+ f.call(opt_obj, arr2[i], i, arr);
+ }
+ }
+ };
+
+
+/**
+ * Calls a function for each element in an array, starting from the last
+ * element rather than the first.
+ *
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this: S, T, number, ?): ?} 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_obj The object to be used as the value of 'this'
+ * within f.
+ * @template T,S
+ */
+goog.array.forEachRight = function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = l - 1; i >= 0; --i) {
+ if (i in arr2) {
+ f.call(opt_obj, arr2[i], i, arr);
+ }
+ }
+};
+
+
+/**
+ * Calls a function for each element in an array, and if the function returns
+ * true adds the element to a new array.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-filter}
+ *
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?):boolean} f The function to call for
+ * every element. This function
+ * takes 3 arguments (the element, the index and the array) and must
+ * return a Boolean. If the return value is true the element is added to the
+ * result array. If it is false the element is not included.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within f.
+ * @return {!Array<T>} a new array in which only elements that passed the test
+ * are present.
+ * @template T,S
+ */
+goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.filter) ?
+ function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+
+ return goog.array.ARRAY_PROTOTYPE_.filter.call(arr, f, opt_obj);
+ } :
+ function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var res = [];
+ var resLength = 0;
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = 0; i < l; i++) {
+ if (i in arr2) {
+ var val = arr2[i]; // in case f mutates arr2
+ if (f.call(opt_obj, val, i, arr)) {
+ res[resLength++] = val;
+ }
+ }
+ }
+ return res;
+ };
+
+
+/**
+ * Calls a function for each element in an array and inserts the result into a
+ * new array.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-map}
+ *
+ * @param {Array<VALUE>|goog.array.ArrayLike} arr Array or array like object
+ * over which to iterate.
+ * @param {function(this:THIS, VALUE, number, ?): RESULT} f The function to call
+ * for every element. This function takes 3 arguments (the element,
+ * the index and the array) and should return something. The result will be
+ * inserted into a new array.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within f.
+ * @return {!Array<RESULT>} a new array with the results from f.
+ * @template THIS, VALUE, RESULT
+ */
+goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.map) ?
+ function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+
+ return goog.array.ARRAY_PROTOTYPE_.map.call(arr, f, opt_obj);
+ } :
+ function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var res = new Array(l);
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = 0; i < l; i++) {
+ if (i in arr2) {
+ res[i] = f.call(opt_obj, arr2[i], i, arr);
+ }
+ }
+ return res;
+ };
+
+
+/**
+ * Passes every element of an array into a function and accumulates the result.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-reduce}
+ *
+ * For example:
+ * var a = [1, 2, 3, 4];
+ * goog.array.reduce(a, function(r, v, i, arr) {return r + v;}, 0);
+ * returns 10
+ *
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {function(this:S, R, T, number, ?) : R} f The function to call for
+ * every element. This function
+ * takes 4 arguments (the function's previous result or the initial value,
+ * the value of the current array element, the current array index, and the
+ * array itself)
+ * function(previousValue, currentValue, index, array).
+ * @param {?} val The initial value to pass into the function on the first call.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within f.
+ * @return {R} Result of evaluating f repeatedly across the values of the array.
+ * @template T,S,R
+ */
+goog.array.reduce = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.reduce) ?
+ function(arr, f, val, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+ if (opt_obj) {
+ f = goog.bind(f, opt_obj);
+ }
+ return goog.array.ARRAY_PROTOTYPE_.reduce.call(arr, f, val);
+ } :
+ function(arr, f, val, opt_obj) {
+ var rval = val;
+ goog.array.forEach(arr, function(val, index) {
+ rval = f.call(opt_obj, rval, val, index, arr);
+ });
+ return rval;
+ };
+
+
+/**
+ * Passes every element of an array into a function and accumulates the result,
+ * starting from the last element and working towards the first.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-reduceright}
+ *
+ * For example:
+ * var a = ['a', 'b', 'c'];
+ * goog.array.reduceRight(a, function(r, v, i, arr) {return r + v;}, '');
+ * returns 'cba'
+ *
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, R, T, number, ?) : R} f The function to call for
+ * every element. This function
+ * takes 4 arguments (the function's previous result or the initial value,
+ * the value of the current array element, the current array index, and the
+ * array itself)
+ * function(previousValue, currentValue, index, array).
+ * @param {?} val The initial value to pass into the function on the first call.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within f.
+ * @return {R} Object returned as a result of evaluating f repeatedly across the
+ * values of the array.
+ * @template T,S,R
+ */
+goog.array.reduceRight = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.reduceRight) ?
+ function(arr, f, val, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+ if (opt_obj) {
+ f = goog.bind(f, opt_obj);
+ }
+ return goog.array.ARRAY_PROTOTYPE_.reduceRight.call(arr, f, val);
+ } :
+ function(arr, f, val, opt_obj) {
+ var rval = val;
+ goog.array.forEachRight(arr, function(val, index) {
+ rval = f.call(opt_obj, rval, val, index, arr);
+ });
+ return rval;
+ };
+
+
+/**
+ * Calls f for each element of an array. If any call returns true, some()
+ * returns true (without checking the remaining elements). If all calls
+ * return false, some() returns false.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-some}
+ *
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
+ * for every element. This function takes 3 arguments (the element, the
+ * index and the array) and should return a boolean.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within f.
+ * @return {boolean} true if any element passes the test.
+ * @template T,S
+ */
+goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.some) ?
+ function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+
+ return goog.array.ARRAY_PROTOTYPE_.some.call(arr, f, opt_obj);
+ } :
+ function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = 0; i < l; i++) {
+ if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+
+/**
+ * Call f for each element of an array. If all calls return true, every()
+ * returns true. If any call returns false, every() returns false and
+ * does not continue to check the remaining elements.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-every}
+ *
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
+ * for every element. This function takes 3 arguments (the element, the
+ * index and the array) and should return a boolean.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within f.
+ * @return {boolean} false if any element fails the test.
+ * @template T,S
+ */
+goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.every) ?
+ function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+
+ return goog.array.ARRAY_PROTOTYPE_.every.call(arr, f, opt_obj);
+ } :
+ function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = 0; i < l; i++) {
+ if (i in arr2 && !f.call(opt_obj, arr2[i], i, arr)) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+
+/**
+ * Counts the array elements that fulfill the predicate, i.e. for which the
+ * callback function returns true. Skips holes in the array.
+ *
+ * @param {!(Array<T>|goog.array.ArrayLike)} arr Array or array like object
+ * over which to iterate.
+ * @param {function(this: S, T, number, ?): boolean} f The function to call for
+ * every element. Takes 3 arguments (the element, the index and the array).
+ * @param {S=} opt_obj The object to be used as the value of 'this' within f.
+ * @return {number} The number of the matching elements.
+ * @template T,S
+ */
+goog.array.count = function(arr, f, opt_obj) {
+ var count = 0;
+ goog.array.forEach(arr, function(element, index, arr) {
+ if (f.call(opt_obj, element, index, arr)) {
+ ++count;
+ }
+ }, opt_obj);
+ return count;
+};
+
+
+/**
+ * Search an array for the first element that satisfies a given condition and
+ * return that element.
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call
+ * 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|null} The first array element that passes the test, or null if no
+ * element is found.
+ * @template T,S
+ */
+goog.array.find = function(arr, f, opt_obj) {
+ var i = goog.array.findIndex(arr, f, opt_obj);
+ return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
+};
+
+
+/**
+ * Search an array for the first element that satisfies a given condition and
+ * return its index.
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call 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 {number} The index of the first array element that passes the test,
+ * or -1 if no element is found.
+ * @template T,S
+ */
+goog.array.findIndex = function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = 0; i < l; i++) {
+ if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
+ return i;
+ }
+ }
+ return -1;
+};
+
+
+/**
+ * Search an array (in reverse order) for the last element that satisfies a
+ * given condition and return that element.
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call
+ * 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|null} The last array element that passes the test, or null if no
+ * element is found.
+ * @template T,S
+ */
+goog.array.findRight = function(arr, f, opt_obj) {
+ var i = goog.array.findIndexRight(arr, f, opt_obj);
+ return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
+};
+
+
+/**
+ * Search an array (in reverse order) for the last element that satisfies a
+ * given condition and return its index.
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call
+ * 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 {number} The index of the last array element that passes the test,
+ * or -1 if no element is found.
+ * @template T,S
+ */
+goog.array.findIndexRight = function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = l - 1; i >= 0; i--) {
+ if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
+ return i;
+ }
+ }
+ return -1;
+};
+
+
+/**
+ * Whether the array contains the given object.
+ * @param {goog.array.ArrayLike} arr The array to test for the presence of the
+ * element.
+ * @param {*} obj The object for which to test.
+ * @return {boolean} true if obj is present.
+ */
+goog.array.contains = function(arr, obj) {
+ return goog.array.indexOf(arr, obj) >= 0;
+};
+
+
+/**
+ * Whether the array is empty.
+ * @param {goog.array.ArrayLike} arr The array to test.
+ * @return {boolean} true if empty.
+ */
+goog.array.isEmpty = function(arr) {
+ return arr.length == 0;
+};
+
+
+/**
+ * Clears the array.
+ * @param {goog.array.ArrayLike} arr Array or array like object to clear.
+ */
+goog.array.clear = function(arr) {
+ // For non real arrays we don't have the magic length so we delete the
+ // indices.
+ if (!goog.isArray(arr)) {
+ for (var i = arr.length - 1; i >= 0; i--) {
+ delete arr[i];
+ }
+ }
+ arr.length = 0;
+};
+
+
+/**
+ * Pushes an item into an array, if it's not already in the array.
+ * @param {Array<T>} arr Array into which to insert the item.
+ * @param {T} obj Value to add.
+ * @template T
+ */
+goog.array.insert = function(arr, obj) {
+ if (!goog.array.contains(arr, obj)) {
+ arr.push(obj);
+ }
+};
+
+
+/**
+ * Inserts an object at the given index of the array.
+ * @param {goog.array.ArrayLike} arr The array to modify.
+ * @param {*} obj The object to insert.
+ * @param {number=} opt_i The index at which to insert the object. If omitted,
+ * treated as 0. A negative index is counted from the end of the array.
+ */
+goog.array.insertAt = function(arr, obj, opt_i) {
+ goog.array.splice(arr, opt_i, 0, obj);
+};
+
+
+/**
+ * Inserts at the given index of the array, all elements of another array.
+ * @param {goog.array.ArrayLike} arr The array to modify.
+ * @param {goog.array.ArrayLike} elementsToAdd The array of elements to add.
+ * @param {number=} opt_i The index at which to insert the object. If omitted,
+ * treated as 0. A negative index is counted from the end of the array.
+ */
+goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) {
+ goog.partial(goog.array.splice, arr, opt_i, 0).apply(null, elementsToAdd);
+};
+
+
+/**
+ * Inserts an object into an array before a specified object.
+ * @param {Array<T>} arr The array to modify.
+ * @param {T} obj The object to insert.
+ * @param {T=} opt_obj2 The object before which obj should be inserted. If obj2
+ * is omitted or not found, obj is inserted at the end of the array.
+ * @template T
+ */
+goog.array.insertBefore = function(arr, obj, opt_obj2) {
+ var i;
+ if (arguments.length == 2 || (i = goog.array.indexOf(arr, opt_obj2)) < 0) {
+ arr.push(obj);
+ } else {
+ goog.array.insertAt(arr, obj, i);
+ }
+};
+
+
+/**
+ * Removes the first occurrence of a particular value from an array.
+ * @param {Array<T>|goog.array.ArrayLike} arr Array from which to remove
+ * value.
+ * @param {T} obj Object to remove.
+ * @return {boolean} True if an element was removed.
+ * @template T
+ */
+goog.array.remove = function(arr, obj) {
+ var i = goog.array.indexOf(arr, obj);
+ var rv;
+ if ((rv = i >= 0)) {
+ goog.array.removeAt(arr, i);
+ }
+ return rv;
+};
+
+
+/**
+ * Removes from an array the element at index i
+ * @param {goog.array.ArrayLike} arr Array or array like object from which to
+ * remove value.
+ * @param {number} i The index to remove.
+ * @return {boolean} True if an element was removed.
+ */
+goog.array.removeAt = function(arr, i) {
+ goog.asserts.assert(arr.length != null);
+
+ // use generic form of splice
+ // splice returns the removed items and if successful the length of that
+ // will be 1
+ return goog.array.ARRAY_PROTOTYPE_.splice.call(arr, i, 1).length == 1;
+};
+
+
+/**
+ * Removes the first value that satisfies the given condition.
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call
+ * 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 {boolean} True if an element was removed.
+ * @template T,S
+ */
+goog.array.removeIf = function(arr, f, opt_obj) {
+ var i = goog.array.findIndex(arr, f, opt_obj);
+ if (i >= 0) {
+ goog.array.removeAt(arr, i);
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Removes all values that satisfy the given condition.
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call
+ * 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 {number} The number of items removed
+ * @template T,S
+ */
+goog.array.removeAllIf = function(arr, f, opt_obj) {
+ var removedCount = 0;
+ goog.array.forEachRight(arr, function(val, index) {
+ if (f.call(opt_obj, val, index, arr)) {
+ if (goog.array.removeAt(arr, index)) {
+ removedCount++;
+ }
+ }
+ });
+ return removedCount;
+};
+
+
+/**
+ * Returns a new array that is the result of joining the arguments. If arrays
+ * are passed then their items are added, however, if non-arrays are passed they
+ * will be added to the return array as is.
+ *
+ * Note that ArrayLike objects will be added as is, rather than having their
+ * items added.
+ *
+ * goog.array.concat([1, 2], [3, 4]) -> [1, 2, 3, 4]
+ * goog.array.concat(0, [1, 2]) -> [0, 1, 2]
+ * goog.array.concat([1, 2], null) -> [1, 2, null]
+ *
+ * There is bug in all current versions of IE (6, 7 and 8) where arrays created
+ * in an iframe become corrupted soon (not immediately) after the iframe is
+ * destroyed. This is common if loading data via goog.net.IframeIo, for example.
+ * This corruption only affects the concat method which will start throwing
+ * Catastrophic Errors (#-2147418113).
+ *
+ * See http://endoflow.com/scratch/corrupted-arrays.html for a test case.
+ *
+ * Internally goog.array should use this, so that all methods will continue to
+ * work on these broken array objects.
+ *
+ * @param {...*} var_args Items to concatenate. Arrays will have each item
+ * added, while primitives and objects will be added as is.
+ * @return {!Array<?>} The new resultant array.
+ */
+goog.array.concat = function(var_args) {
+ return goog.array.ARRAY_PROTOTYPE_.concat.apply(
+ goog.array.ARRAY_PROTOTYPE_, arguments);
+};
+
+
+/**
+ * Returns a new array that contains the contents of all the arrays passed.
+ * @param {...!Array<T>} var_args
+ * @return {!Array<T>}
+ * @template T
+ */
+goog.array.join = function(var_args) {
+ return goog.array.ARRAY_PROTOTYPE_.concat.apply(
+ goog.array.ARRAY_PROTOTYPE_, arguments);
+};
+
+
+/**
+ * Converts an object to an array.
+ * @param {Array<T>|goog.array.ArrayLike} object The object to convert to an
+ * array.
+ * @return {!Array<T>} The object converted into an array. If object has a
+ * length property, every property indexed with a non-negative number
+ * less than length will be included in the result. If object does not
+ * have a length property, an empty array will be returned.
+ * @template T
+ */
+goog.array.toArray = function(object) {
+ var length = object.length;
+
+ // If length is not a number the following it false. This case is kept for
+ // backwards compatibility since there are callers that pass objects that are
+ // not array like.
+ if (length > 0) {
+ var rv = new Array(length);
+ for (var i = 0; i < length; i++) {
+ rv[i] = object[i];
+ }
+ return rv;
+ }
+ return [];
+};
+
+
+/**
+ * Does a shallow copy of an array.
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array-like object to
+ * clone.
+ * @return {!Array<T>} Clone of the input array.
+ * @template T
+ */
+goog.array.clone = goog.array.toArray;
+
+
+/**
+ * Extends an array with another array, element, or "array like" object.
+ * This function operates 'in-place', it does not create a new Array.
+ *
+ * Example:
+ * var a = [];
+ * goog.array.extend(a, [0, 1]);
+ * a; // [0, 1]
+ * goog.array.extend(a, 2);
+ * a; // [0, 1, 2]
+ *
+ * @param {Array<VALUE>} arr1 The array to modify.
+ * @param {...(Array<VALUE>|VALUE)} var_args The elements or arrays of elements
+ * to add to arr1.
+ * @template VALUE
+ */
+goog.array.extend = function(arr1, var_args) {
+ for (var i = 1; i < arguments.length; i++) {
+ var arr2 = arguments[i];
+ if (goog.isArrayLike(arr2)) {
+ var len1 = arr1.length || 0;
+ var len2 = arr2.length || 0;
+ arr1.length = len1 + len2;
+ for (var j = 0; j < len2; j++) {
+ arr1[len1 + j] = arr2[j];
+ }
+ } else {
+ arr1.push(arr2);
+ }
+ }
+};
+
+
+/**
+ * Adds or removes elements from an array. This is a generic version of Array
+ * splice. This means that it might work on other objects similar to arrays,
+ * such as the arguments object.
+ *
+ * @param {Array<T>|goog.array.ArrayLike} arr The array to modify.
+ * @param {number|undefined} index The index at which to start changing the
+ * array. If not defined, treated as 0.
+ * @param {number} howMany How many elements to remove (0 means no removal. A
+ * value below 0 is treated as zero and so is any other non number. Numbers
+ * are floored).
+ * @param {...T} var_args Optional, additional elements to insert into the
+ * array.
+ * @return {!Array<T>} the removed elements.
+ * @template T
+ */
+goog.array.splice = function(arr, index, howMany, var_args) {
+ goog.asserts.assert(arr.length != null);
+
+ return goog.array.ARRAY_PROTOTYPE_.splice.apply(
+ arr, goog.array.slice(arguments, 1));
+};
+
+
+/**
+ * Returns a new array from a segment of an array. This is a generic version of
+ * Array slice. This means that it might work on other objects similar to
+ * arrays, such as the arguments object.
+ *
+ * @param {Array<T>|goog.array.ArrayLike} arr The array from
+ * which to copy a segment.
+ * @param {number} start The index of the first element to copy.
+ * @param {number=} opt_end The index after the last element to copy.
+ * @return {!Array<T>} A new array containing the specified segment of the
+ * original array.
+ * @template T
+ */
+goog.array.slice = function(arr, start, opt_end) {
+ goog.asserts.assert(arr.length != null);
+
+ // passing 1 arg to slice is not the same as passing 2 where the second is
+ // null or undefined (in that case the second argument is treated as 0).
+ // we could use slice on the arguments object and then use apply instead of
+ // testing the length
+ if (arguments.length <= 2) {
+ return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start);
+ } else {
+ return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start, opt_end);
+ }
+};
+
+
+/**
+ * Removes all duplicates from an array (retaining only the first
+ * occurrence of each array element). This function modifies the
+ * array in place and doesn't change the order of the non-duplicate items.
+ *
+ * For objects, duplicates are identified as having the same unique ID as
+ * defined by {@link goog.getUid}.
+ *
+ * Alternatively you can specify a custom hash function that returns a unique
+ * value for each item in the array it should consider unique.
+ *
+ * Runtime: N,
+ * Worstcase space: 2N (no dupes)
+ *
+ * @param {Array<T>|goog.array.ArrayLike} arr The array from which to remove
+ * duplicates.
+ * @param {Array=} opt_rv An optional array in which to return the results,
+ * instead of performing the removal inplace. If specified, the original
+ * array will remain unchanged.
+ * @param {function(T):string=} opt_hashFn An optional function to use to
+ * apply to every item in the array. This function should return a unique
+ * value for each item in the array it should consider unique.
+ * @template T
+ */
+goog.array.removeDuplicates = function(arr, opt_rv, opt_hashFn) {
+ var returnArray = opt_rv || arr;
+ var defaultHashFn = function(item) {
+ // Prefix each type with a single character representing the type to
+ // prevent conflicting keys (e.g. true and 'true').
+ return goog.isObject(item) ? 'o' + goog.getUid(item) :
+ (typeof item).charAt(0) + item;
+ };
+ var hashFn = opt_hashFn || defaultHashFn;
+
+ var seen = {}, cursorInsert = 0, cursorRead = 0;
+ while (cursorRead < arr.length) {
+ var current = arr[cursorRead++];
+ var key = hashFn(current);
+ if (!Object.prototype.hasOwnProperty.call(seen, key)) {
+ seen[key] = true;
+ returnArray[cursorInsert++] = current;
+ }
+ }
+ returnArray.length = cursorInsert;
+};
+
+
+/**
+ * Searches the specified array for the specified target using the binary
+ * search algorithm. If no opt_compareFn is specified, elements are compared
+ * using <code>goog.array.defaultCompare</code>, which compares the elements
+ * using the built in < and > operators. This will produce the expected
+ * behavior for homogeneous arrays of String(s) and Number(s). The array
+ * specified <b>must</b> be sorted in ascending order (as defined by the
+ * comparison function). If the array is not sorted, results are undefined.
+ * If the array contains multiple instances of the specified target value, any
+ * of these instances may be found.
+ *
+ * Runtime: O(log n)
+ *
+ * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to be searched.
+ * @param {TARGET} target The sought value.
+ * @param {function(TARGET, VALUE): number=} opt_compareFn Optional comparison
+ * function by which the array is ordered. Should take 2 arguments to
+ * compare, and return a negative number, zero, or a positive number
+ * depending on whether the first argument is less than, equal to, or
+ * greater than the second.
+ * @return {number} Lowest index of the target value if found, otherwise
+ * (-(insertion point) - 1). The insertion point is where the value should
+ * be inserted into arr to preserve the sorted property. Return value >= 0
+ * iff target is found.
+ * @template TARGET, VALUE
+ */
+goog.array.binarySearch = function(arr, target, opt_compareFn) {
+ return goog.array.binarySearch_(arr,
+ opt_compareFn || goog.array.defaultCompare, false /* isEvaluator */,
+ target);
+};
+
+
+/**
+ * Selects an index in the specified array using the binary search algorithm.
+ * The evaluator receives an element and determines whether the desired index
+ * is before, at, or after it. The evaluator must be consistent (formally,
+ * goog.array.map(goog.array.map(arr, evaluator, opt_obj), goog.math.sign)
+ * must be monotonically non-increasing).
+ *
+ * Runtime: O(log n)
+ *
+ * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to be searched.
+ * @param {function(this:THIS, VALUE, number, ?): number} evaluator
+ * Evaluator function that receives 3 arguments (the element, the index and
+ * the array). Should return a negative number, zero, or a positive number
+ * depending on whether the desired index is before, at, or after the
+ * element passed to it.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this'
+ * within evaluator.
+ * @return {number} Index of the leftmost element matched by the evaluator, if
+ * such exists; otherwise (-(insertion point) - 1). The insertion point is
+ * the index of the first element for which the evaluator returns negative,
+ * or arr.length if no such element exists. The return value is non-negative
+ * iff a match is found.
+ * @template THIS, VALUE
+ */
+goog.array.binarySelect = function(arr, evaluator, opt_obj) {
+ return goog.array.binarySearch_(arr, evaluator, true /* isEvaluator */,
+ undefined /* opt_target */, opt_obj);
+};
+
+
+/**
+ * Implementation of a binary search algorithm which knows how to use both
+ * comparison functions and evaluators. If an evaluator is provided, will call
+ * the evaluator with the given optional data object, conforming to the
+ * interface defined in binarySelect. Otherwise, if a comparison function is
+ * provided, will call the comparison function against the given data object.
+ *
+ * This implementation purposefully does not use goog.bind or goog.partial for
+ * performance reasons.
+ *
+ * Runtime: O(log n)
+ *
+ * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to be searched.
+ * @param {function(TARGET, VALUE): number|
+ * function(this:THIS, VALUE, number, ?): number} compareFn Either an
+ * evaluator or a comparison function, as defined by binarySearch
+ * and binarySelect above.
+ * @param {boolean} isEvaluator Whether the function is an evaluator or a
+ * comparison function.
+ * @param {TARGET=} opt_target If the function is a comparison function, then
+ * this is the target to binary search for.
+ * @param {THIS=} opt_selfObj If the function is an evaluator, this is an
+ * optional this object for the evaluator.
+ * @return {number} Lowest index of the target value if found, otherwise
+ * (-(insertion point) - 1). The insertion point is where the value should
+ * be inserted into arr to preserve the sorted property. Return value >= 0
+ * iff target is found.
+ * @template THIS, VALUE, TARGET
+ * @private
+ */
+goog.array.binarySearch_ = function(arr, compareFn, isEvaluator, opt_target,
+ opt_selfObj) {
+ var left = 0; // inclusive
+ var right = arr.length; // exclusive
+ var found;
+ while (left < right) {
+ var middle = (left + right) >> 1;
+ var compareResult;
+ if (isEvaluator) {
+ compareResult = compareFn.call(opt_selfObj, arr[middle], middle, arr);
+ } else {
+ compareResult = compareFn(opt_target, arr[middle]);
+ }
+ if (compareResult > 0) {
+ left = middle + 1;
+ } else {
+ right = middle;
+ // We are looking for the lowest index so we can't return immediately.
+ found = !compareResult;
+ }
+ }
+ // left is the index if found, or the insertion point otherwise.
+ // ~left is a shorthand for -left - 1.
+ return found ? left : ~left;
+};
+
+
+/**
+ * Sorts the specified array into ascending order. If no opt_compareFn is
+ * specified, elements are compared using
+ * <code>goog.array.defaultCompare</code>, which compares the elements using
+ * the built in < and > operators. This will produce the expected behavior
+ * for homogeneous arrays of String(s) and Number(s), unlike the native sort,
+ * but will give unpredictable results for heterogenous lists of strings and
+ * numbers with different numbers of digits.
+ *
+ * This sort is not guaranteed to be stable.
+ *
+ * Runtime: Same as <code>Array.prototype.sort</code>
+ *
+ * @param {Array<T>} arr The array to be sorted.
+ * @param {?function(T,T):number=} opt_compareFn Optional comparison
+ * function by which the
+ * array is to be ordered. Should take 2 arguments to compare, and return a
+ * negative number, zero, or a positive number depending on whether the
+ * first argument is less than, equal to, or greater than the second.
+ * @template T
+ */
+goog.array.sort = function(arr, opt_compareFn) {
+ // TODO(arv): Update type annotation since null is not accepted.
+ arr.sort(opt_compareFn || goog.array.defaultCompare);
+};
+
+
+/**
+ * Sorts the specified array into ascending order in a stable way. If no
+ * opt_compareFn is specified, elements are compared using
+ * <code>goog.array.defaultCompare</code>, which compares the elements using
+ * the built in < and > operators. This will produce the expected behavior
+ * for homogeneous arrays of String(s) and Number(s).
+ *
+ * Runtime: Same as <code>Array.prototype.sort</code>, plus an additional
+ * O(n) overhead of copying the array twice.
+ *
+ * @param {Array<T>} arr The array to be sorted.
+ * @param {?function(T, T): number=} opt_compareFn Optional comparison function
+ * by which the array is to be ordered. Should take 2 arguments to compare,
+ * and return a negative number, zero, or a positive number depending on
+ * whether the first argument is less than, equal to, or greater than the
+ * second.
+ * @template T
+ */
+goog.array.stableSort = function(arr, opt_compareFn) {
+ for (var i = 0; i < arr.length; i++) {
+ arr[i] = {index: i, value: arr[i]};
+ }
+ var valueCompareFn = opt_compareFn || goog.array.defaultCompare;
+ function stableCompareFn(obj1, obj2) {
+ return valueCompareFn(obj1.value, obj2.value) || obj1.index - obj2.index;
+ };
+ goog.array.sort(arr, stableCompareFn);
+ for (var i = 0; i < arr.length; i++) {
+ arr[i] = arr[i].value;
+ }
+};
+
+
+/**
+ * Sort the specified array into ascending order based on item keys
+ * returned by the specified key function.
+ * If no opt_compareFn is specified, the keys are compared in ascending order
+ * using <code>goog.array.defaultCompare</code>.
+ *
+ * Runtime: O(S(f(n)), where S is runtime of <code>goog.array.sort</code>
+ * and f(n) is runtime of the key function.
+ *
+ * @param {Array<T>} arr The array to be sorted.
+ * @param {function(T): K} keyFn Function taking array element and returning
+ * a key used for sorting this element.
+ * @param {?function(K, K): number=} opt_compareFn Optional comparison function
+ * by which the keys are to be ordered. Should take 2 arguments to compare,
+ * and return a negative number, zero, or a positive number depending on
+ * whether the first argument is less than, equal to, or greater than the
+ * second.
+ * @template T,K
+ */
+goog.array.sortByKey = function(arr, keyFn, opt_compareFn) {
+ var keyCompareFn = opt_compareFn || goog.array.defaultCompare;
+ goog.array.sort(arr, function(a, b) {
+ return keyCompareFn(keyFn(a), keyFn(b));
+ });
+};
+
+
+/**
+ * Sorts an array of objects by the specified object key and compare
+ * function. If no compare function is provided, the key values are
+ * compared in ascending order using <code>goog.array.defaultCompare</code>.
+ * This won't work for keys that get renamed by the compiler. So use
+ * {'foo': 1, 'bar': 2} rather than {foo: 1, bar: 2}.
+ * @param {Array<Object>} arr An array of objects to sort.
+ * @param {string} key The object key to sort by.
+ * @param {Function=} opt_compareFn The function to use to compare key
+ * values.
+ */
+goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) {
+ goog.array.sortByKey(arr,
+ function(obj) { return obj[key]; },
+ opt_compareFn);
+};
+
+
+/**
+ * Tells if the array is sorted.
+ * @param {!Array<T>} arr The array.
+ * @param {?function(T,T):number=} opt_compareFn Function to compare the
+ * array elements.
+ * Should take 2 arguments to compare, and return a negative number, zero,
+ * or a positive number depending on whether the first argument is less
+ * than, equal to, or greater than the second.
+ * @param {boolean=} opt_strict If true no equal elements are allowed.
+ * @return {boolean} Whether the array is sorted.
+ * @template T
+ */
+goog.array.isSorted = function(arr, opt_compareFn, opt_strict) {
+ var compare = opt_compareFn || goog.array.defaultCompare;
+ for (var i = 1; i < arr.length; i++) {
+ var compareResult = compare(arr[i - 1], arr[i]);
+ if (compareResult > 0 || compareResult == 0 && opt_strict) {
+ return false;
+ }
+ }
+ return true;
+};
+
+
+/**
+ * Compares two arrays for equality. Two arrays are considered equal if they
+ * have the same length and their corresponding elements are equal according to
+ * the comparison function.
+ *
+ * @param {goog.array.ArrayLike} arr1 The first array to compare.
+ * @param {goog.array.ArrayLike} arr2 The second array to compare.
+ * @param {Function=} opt_equalsFn Optional comparison function.
+ * Should take 2 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} Whether the two arrays are equal.
+ */
+goog.array.equals = function(arr1, arr2, opt_equalsFn) {
+ if (!goog.isArrayLike(arr1) || !goog.isArrayLike(arr2) ||
+ arr1.length != arr2.length) {
+ return false;
+ }
+ var l = arr1.length;
+ var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality;
+ for (var i = 0; i < l; i++) {
+ if (!equalsFn(arr1[i], arr2[i])) {
+ return false;
+ }
+ }
+ return true;
+};
+
+
+/**
+ * 3-way array compare function.
+ * @param {!Array<VALUE>|!goog.array.ArrayLike} arr1 The first array to
+ * compare.
+ * @param {!Array<VALUE>|!goog.array.ArrayLike} arr2 The second array to
+ * compare.
+ * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
+ * function by which the array is to be ordered. Should take 2 arguments to
+ * compare, and return a negative number, zero, or a positive number
+ * depending on whether the first argument is less than, equal to, or
+ * greater than the second.
+ * @return {number} Negative number, zero, or a positive number depending on
+ * whether the first argument is less than, equal to, or greater than the
+ * second.
+ * @template VALUE
+ */
+goog.array.compare3 = function(arr1, arr2, opt_compareFn) {
+ var compare = opt_compareFn || goog.array.defaultCompare;
+ var l = Math.min(arr1.length, arr2.length);
+ for (var i = 0; i < l; i++) {
+ var result = compare(arr1[i], arr2[i]);
+ if (result != 0) {
+ return result;
+ }
+ }
+ return goog.array.defaultCompare(arr1.length, arr2.length);
+};
+
+
+/**
+ * Compares its two arguments for 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 less than, equal to, or greater than the second,
+ * respectively.
+ * @template VALUE
+ */
+goog.array.defaultCompare = function(a, b) {
+ return a > b ? 1 : a < b ? -1 : 0;
+};
+
+
+/**
+ * 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.
+ * @param {*} b The second object to compare.
+ * @return {boolean} True if the two arguments are equal, false otherwise.
+ */
+goog.array.defaultCompareEquality = function(a, b) {
+ return a === b;
+};
+
+
+/**
+ * Inserts a value into a sorted array. The array is not modified if the
+ * value is already present.
+ * @param {Array<VALUE>|goog.array.ArrayLike} array The array to modify.
+ * @param {VALUE} value The object to insert.
+ * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
+ * function by which the array is ordered. Should take 2 arguments to
+ * compare, and return a negative number, zero, or a positive number
+ * depending on whether the first argument is less than, equal to, or
+ * greater than the second.
+ * @return {boolean} True if an element was inserted.
+ * @template VALUE
+ */
+goog.array.binaryInsert = function(array, value, opt_compareFn) {
+ var index = goog.array.binarySearch(array, value, opt_compareFn);
+ if (index < 0) {
+ goog.array.insertAt(array, value, -(index + 1));
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Removes a value from a sorted array.
+ * @param {!Array<VALUE>|!goog.array.ArrayLike} array The array to modify.
+ * @param {VALUE} value The object to remove.
+ * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
+ * function by which the array is ordered. Should take 2 arguments to
+ * compare, and return a negative number, zero, or a positive number
+ * depending on whether the first argument is less than, equal to, or
+ * greater than the second.
+ * @return {boolean} True if an element was removed.
+ * @template VALUE
+ */
+goog.array.binaryRemove = function(array, value, opt_compareFn) {
+ var index = goog.array.binarySearch(array, value, opt_compareFn);
+ return (index >= 0) ? goog.array.removeAt(array, index) : false;
+};
+
+
+/**
+ * Splits an array into disjoint buckets according to a splitting function.
+ * @param {Array<T>} array The array.
+ * @param {function(this:S, T,number,Array<T>):?} sorter Function to call for
+ * every element. This takes 3 arguments (the element, the index and the
+ * array) and must return a valid object key (a string, number, etc), or
+ * undefined, if that object should not be placed in a bucket.
+ * @param {S=} opt_obj The object to be used as the value of 'this' within
+ * sorter.
+ * @return {!Object} An object, with keys being all of the unique return values
+ * of sorter, and values being arrays containing the items for
+ * which the splitter returned that key.
+ * @template T,S
+ */
+goog.array.bucket = function(array, sorter, opt_obj) {
+ var buckets = {};
+
+ for (var i = 0; i < array.length; i++) {
+ var value = array[i];
+ var key = sorter.call(opt_obj, value, i, array);
+ if (goog.isDef(key)) {
+ // Push the value to the right bucket, creating it if necessary.
+ var bucket = buckets[key] || (buckets[key] = []);
+ bucket.push(value);
+ }
+ }
+
+ return buckets;
+};
+
+
+/**
+ * Creates a new object built from the provided array and the key-generation
+ * function.
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array like object over
+ * which to iterate whose elements will be the values in the new object.
+ * @param {?function(this:S, T, number, ?) : string} keyFunc The function to
+ * call for every element. This function takes 3 arguments (the element, the
+ * index and the array) and should return a string that will be used as the
+ * key for the element in the new object. If the function returns the same
+ * key for more than one element, the value for that key is
+ * implementation-defined.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within keyFunc.
+ * @return {!Object<T>} The new object.
+ * @template T,S
+ */
+goog.array.toObject = function(arr, keyFunc, opt_obj) {
+ var ret = {};
+ goog.array.forEach(arr, function(element, index) {
+ ret[keyFunc.call(opt_obj, element, index, arr)] = element;
+ });
+ return ret;
+};
+
+
+/**
+ * Creates a range of numbers in an arithmetic progression.
+ *
+ * Range takes 1, 2, or 3 arguments:
+ * <pre>
+ * range(5) is the same as range(0, 5, 1) and produces [0, 1, 2, 3, 4]
+ * range(2, 5) is the same as range(2, 5, 1) and produces [2, 3, 4]
+ * range(-2, -5, -1) produces [-2, -3, -4]
+ * range(-2, -5, 1) produces [], since stepping by 1 wouldn't ever reach -5.
+ * </pre>
+ *
+ * @param {number} startOrEnd The starting value of the range if an end argument
+ * is provided. Otherwise, the start value is 0, and this is the end value.
+ * @param {number=} opt_end The optional end value of the range.
+ * @param {number=} opt_step The step size between range values. Defaults to 1
+ * if opt_step is undefined or 0.
+ * @return {!Array<number>} An array of numbers for the requested range. May be
+ * an empty array if adding the step would not converge toward the end
+ * value.
+ */
+goog.array.range = function(startOrEnd, opt_end, opt_step) {
+ var array = [];
+ var start = 0;
+ var end = startOrEnd;
+ var step = opt_step || 1;
+ if (opt_end !== undefined) {
+ start = startOrEnd;
+ end = opt_end;
+ }
+
+ if (step * (end - start) < 0) {
+ // Sign mismatch: start + step will never reach the end value.
+ return [];
+ }
+
+ if (step > 0) {
+ for (var i = start; i < end; i += step) {
+ array.push(i);
+ }
+ } else {
+ for (var i = start; i > end; i += step) {
+ array.push(i);
+ }
+ }
+ return array;
+};
+
+
+/**
+ * Returns an array consisting of the given value repeated N times.
+ *
+ * @param {VALUE} value The value to repeat.
+ * @param {number} n The repeat count.
+ * @return {!Array<VALUE>} An array with the repeated value.
+ * @template VALUE
+ */
+goog.array.repeat = function(value, n) {
+ var array = [];
+ for (var i = 0; i < n; i++) {
+ array[i] = value;
+ }
+ return array;
+};
+
+
+/**
+ * Returns an array consisting of every argument with all arrays
+ * expanded in-place recursively.
+ *
+ * @param {...*} var_args The values to flatten.
+ * @return {!Array<?>} An array containing the flattened values.
+ */
+goog.array.flatten = function(var_args) {
+ var CHUNK_SIZE = 8192;
+
+ var result = [];
+ for (var i = 0; i < arguments.length; i++) {
+ var element = arguments[i];
+ if (goog.isArray(element)) {
+ for (var c = 0; c < element.length; c += CHUNK_SIZE) {
+ var chunk = goog.array.slice(element, c, c + CHUNK_SIZE);
+ var recurseResult = goog.array.flatten.apply(null, chunk);
+ for (var r = 0; r < recurseResult.length; r++) {
+ result.push(recurseResult[r]);
+ }
+ }
+ } else {
+ result.push(element);
+ }
+ }
+ return result;
+};
+
+
+/**
+ * Rotates an array in-place. After calling this method, the element at
+ * index i will be the element previously at index (i - n) %
+ * array.length, for all values of i between 0 and array.length - 1,
+ * inclusive.
+ *
+ * For example, suppose list comprises [t, a, n, k, s]. After invoking
+ * rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k].
+ *
+ * @param {!Array<T>} array The array to rotate.
+ * @param {number} n The amount to rotate.
+ * @return {!Array<T>} The array.
+ * @template T
+ */
+goog.array.rotate = function(array, n) {
+ goog.asserts.assert(array.length != null);
+
+ if (array.length) {
+ n %= array.length;
+ if (n > 0) {
+ goog.array.ARRAY_PROTOTYPE_.unshift.apply(array, array.splice(-n, n));
+ } else if (n < 0) {
+ goog.array.ARRAY_PROTOTYPE_.push.apply(array, array.splice(0, -n));
+ }
+ }
+ return array;
+};
+
+
+/**
+ * Moves one item of an array to a new position keeping the order of the rest
+ * of the items. Example use case: keeping a list of JavaScript objects
+ * synchronized with the corresponding list of DOM elements after one of the
+ * elements has been dragged to a new position.
+ * @param {!(Array|Arguments|{length:number})} arr The array to modify.
+ * @param {number} fromIndex Index of the item to move between 0 and
+ * {@code arr.length - 1}.
+ * @param {number} toIndex Target index between 0 and {@code arr.length - 1}.
+ */
+goog.array.moveItem = function(arr, fromIndex, toIndex) {
+ goog.asserts.assert(fromIndex >= 0 && fromIndex < arr.length);
+ goog.asserts.assert(toIndex >= 0 && toIndex < arr.length);
+ // Remove 1 item at fromIndex.
+ var removedItems = goog.array.ARRAY_PROTOTYPE_.splice.call(arr, fromIndex, 1);
+ // Insert the removed item at toIndex.
+ goog.array.ARRAY_PROTOTYPE_.splice.call(arr, toIndex, 0, removedItems[0]);
+ // We don't use goog.array.insertAt and goog.array.removeAt, because they're
+ // significantly slower than splice.
+};
+
+
+/**
+ * Creates a new array for which the element at position i is an array of the
+ * ith element of the provided arrays. The returned array will only be as long
+ * as the shortest array provided; additional values are ignored. For example,
+ * the result of zipping [1, 2] and [3, 4, 5] is [[1,3], [2, 4]].
+ *
+ * This is similar to the zip() function in Python. See {@link
+ * http://docs.python.org/library/functions.html#zip}
+ *
+ * @param {...!goog.array.ArrayLike} var_args Arrays to be combined.
+ * @return {!Array<!Array<?>>} A new array of arrays created from
+ * provided arrays.
+ */
+goog.array.zip = function(var_args) {
+ if (!arguments.length) {
+ return [];
+ }
+ var result = [];
+ for (var i = 0; true; i++) {
+ var value = [];
+ for (var j = 0; j < arguments.length; j++) {
+ var arr = arguments[j];
+ // If i is larger than the array length, this is the shortest array.
+ if (i >= arr.length) {
+ return result;
+ }
+ value.push(arr[i]);
+ }
+ result.push(value);
+ }
+};
+
+
+/**
+ * Shuffles the values in the specified array using the Fisher-Yates in-place
+ * shuffle (also known as the Knuth Shuffle). By default, calls Math.random()
+ * and so resets the state of that random number generator. Similarly, may reset
+ * the state of the any other specified random number generator.
+ *
+ * Runtime: O(n)
+ *
+ * @param {!Array<?>} arr The array to be shuffled.
+ * @param {function():number=} opt_randFn Optional random function to use for
+ * shuffling.
+ * Takes no arguments, and returns a random number on the interval [0, 1).
+ * Defaults to Math.random() using JavaScript's built-in Math library.
+ */
+goog.array.shuffle = function(arr, opt_randFn) {
+ var randFn = opt_randFn || Math.random;
+
+ for (var i = arr.length - 1; i > 0; i--) {
+ // Choose a random array index in [0, i] (inclusive with i).
+ var j = Math.floor(randFn() * (i + 1));
+
+ var tmp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = tmp;
+ }
+};
+
+
+/**
+ * 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;
+};
+
+goog.provide('ol.array');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+
+
+/**
+ * @param {Array.<number>} arr Array.
+ * @param {number} target Target.
+ * @return {number} Index.
+ */
+ol.array.binaryFindNearest = function(arr, target) {
+ var index = goog.array.binarySearch(arr, target,
+ /**
+ * @param {number} a A.
+ * @param {number} b B.
+ * @return {number} b minus a.
+ */
+ function(a, b) {
+ return b - a;
+ });
+ if (index >= 0) {
+ return index;
+ } else if (index == -1) {
+ return 0;
+ } else if (index == -arr.length - 1) {
+ return arr.length - 1;
+ } else {
+ var left = -index - 2;
+ var right = -index - 1;
+ if (arr[left] - target < target - arr[right]) {
+ return left;
+ } else {
+ return right;
+ }
+ }
+};
+
+
+/**
+ * Compare function for array sort that is safe for numbers.
+ * @param {*} a The first object to be compared.
+ * @param {*} 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.
+ */
+ol.array.numberSafeCompareFunction = function(a, b) {
+ return a > b ? 1 : a < b ? -1 : 0;
+};
+
+
+/**
+ * Whether the array contains the given object.
+ * @param {Array.<*>} arr The array to test for the presence of the element.
+ * @param {*} obj The object for which to test.
+ * @return {boolean} The object is in the array.
+ */
+ol.array.includes = function(arr, obj) {
+ return arr.indexOf(obj) >= 0;
+};
+
+
+/**
+ * @param {Array.<number>} arr Array.
+ * @param {number} target Target.
+ * @param {number} direction 0 means return the nearest, > 0
+ * means return the largest nearest, < 0 means return the
+ * smallest nearest.
+ * @return {number} Index.
+ */
+ol.array.linearFindNearest = function(arr, target, direction) {
+ var n = arr.length;
+ if (arr[0] <= target) {
+ return 0;
+ } else if (target <= arr[n - 1]) {
+ return n - 1;
+ } else {
+ var i;
+ if (direction > 0) {
+ for (i = 1; i < n; ++i) {
+ if (arr[i] < target) {
+ return i - 1;
+ }
+ }
+ } else if (direction < 0) {
+ for (i = 1; i < n; ++i) {
+ if (arr[i] <= target) {
+ return i;
+ }
+ }
+ } else {
+ for (i = 1; i < n; ++i) {
+ if (arr[i] == target) {
+ return i;
+ } else if (arr[i] < target) {
+ if (arr[i - 1] - target < target - arr[i]) {
+ return i - 1;
+ } else {
+ return i;
+ }
+ }
+ }
+ }
+ // We should never get here, but the compiler complains
+ // if it finds a path for which no number is returned.
+ goog.asserts.fail();
+ return n - 1;
+ }
+};
+
+
+/**
+ * @param {Array.<*>} arr Array.
+ * @param {number} begin Begin index.
+ * @param {number} end End index.
+ */
+ol.array.reverseSubArray = function(arr, begin, end) {
+ 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];
+ arr[end] = tmp;
+ ++begin;
+ --end;
+ }
+};
+
+goog.provide('ol.ResolutionConstraint');
+goog.provide('ol.ResolutionConstraintType');
+
+goog.require('ol.array');
+goog.require('ol.math');
+
+
+/**
+ * @typedef {function((number|undefined), number, number): (number|undefined)}
+ */
+ol.ResolutionConstraintType;
+
+
+/**
+ * @param {Array.<number>} resolutions Resolutions.
+ * @return {ol.ResolutionConstraintType} Zoom function.
+ */
+ol.ResolutionConstraint.createSnapToResolutions =
+ function(resolutions) {
+ return (
+ /**
+ * @param {number|undefined} resolution Resolution.
+ * @param {number} delta Delta.
+ * @param {number} direction Direction.
+ * @return {number|undefined} Resolution.
+ */
+ function(resolution, delta, direction) {
+ if (resolution !== undefined) {
+ var z =
+ ol.array.linearFindNearest(resolutions, resolution, direction);
+ z = ol.math.clamp(z + delta, 0, resolutions.length - 1);
+ return resolutions[z];
+ } else {
+ return undefined;
+ }
+ });
+};
+
+
+/**
+ * @param {number} power Power.
+ * @param {number} maxResolution Maximum resolution.
+ * @param {number=} opt_maxLevel Maximum level.
+ * @return {ol.ResolutionConstraintType} Zoom function.
+ */
+ol.ResolutionConstraint.createSnapToPower =
+ function(power, maxResolution, opt_maxLevel) {
+ return (
+ /**
+ * @param {number|undefined} resolution Resolution.
+ * @param {number} delta Delta.
+ * @param {number} direction Direction.
+ * @return {number|undefined} Resolution.
+ */
+ function(resolution, delta, direction) {
+ if (resolution !== undefined) {
+ var offset;
+ if (direction > 0) {
+ offset = 0;
+ } else if (direction < 0) {
+ offset = 1;
+ } else {
+ offset = 0.5;
+ }
+ var oldLevel = Math.floor(
+ Math.log(maxResolution / resolution) / Math.log(power) + offset);
+ var newLevel = Math.max(oldLevel + delta, 0);
+ if (opt_maxLevel !== undefined) {
+ newLevel = Math.min(newLevel, opt_maxLevel);
+ }
+ return maxResolution / Math.pow(power, newLevel);
+ } else {
+ return undefined;
+ }
+ });
+};
+
+goog.provide('ol.RotationConstraint');
+goog.provide('ol.RotationConstraintType');
+
+goog.require('ol.math');
+
+
+/**
+ * @typedef {function((number|undefined), number): (number|undefined)}
+ */
+ol.RotationConstraintType;
+
+
+/**
+ * @param {number|undefined} rotation Rotation.
+ * @param {number} delta Delta.
+ * @return {number|undefined} Rotation.
+ */
+ol.RotationConstraint.disable = function(rotation, delta) {
+ if (rotation !== undefined) {
+ return 0;
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {number|undefined} rotation Rotation.
+ * @param {number} delta Delta.
+ * @return {number|undefined} Rotation.
+ */
+ol.RotationConstraint.none = function(rotation, delta) {
+ if (rotation !== undefined) {
+ return rotation + delta;
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {number} n N.
+ * @return {ol.RotationConstraintType} Rotation constraint.
+ */
+ol.RotationConstraint.createSnapToN = function(n) {
+ var theta = 2 * Math.PI / n;
+ return (
+ /**
+ * @param {number|undefined} rotation Rotation.
+ * @param {number} delta Delta.
+ * @return {number|undefined} Rotation.
+ */
+ function(rotation, delta) {
+ if (rotation !== undefined) {
+ rotation = Math.floor((rotation + delta) / theta + 0.5) * theta;
+ return rotation;
+ } else {
+ return undefined;
+ }
+ });
+};
+
+
+/**
+ * @param {number=} opt_tolerance Tolerance.
+ * @return {ol.RotationConstraintType} Rotation constraint.
+ */
+ol.RotationConstraint.createSnapToZero = function(opt_tolerance) {
+ var tolerance = opt_tolerance || ol.math.toRadians(5);
+ return (
+ /**
+ * @param {number|undefined} rotation Rotation.
+ * @param {number} delta Delta.
+ * @return {number|undefined} Rotation.
+ */
+ function(rotation, delta) {
+ if (rotation !== undefined) {
+ if (Math.abs(rotation + delta) <= tolerance) {
+ return 0;
+ } else {
+ return rotation + delta;
+ }
+ } else {
+ return undefined;
+ }
+ });
+};
+
+goog.provide('ol.Constraints');
+
+goog.require('ol.CenterConstraintType');
+goog.require('ol.ResolutionConstraintType');
+goog.require('ol.RotationConstraintType');
+
+
+
+/**
+ * @constructor
+ * @param {ol.CenterConstraintType} centerConstraint Center constraint.
+ * @param {ol.ResolutionConstraintType} resolutionConstraint
+ * Resolution constraint.
+ * @param {ol.RotationConstraintType} rotationConstraint
+ * Rotation constraint.
+ */
+ol.Constraints =
+ function(centerConstraint, resolutionConstraint, rotationConstraint) {
+
+ /**
+ * @type {ol.CenterConstraintType}
+ */
+ this.center = centerConstraint;
+
+ /**
+ * @type {ol.ResolutionConstraintType}
+ */
+ this.resolution = resolutionConstraint;
+
+ /**
+ * @type {ol.RotationConstraintType}
+ */
+ this.rotation = rotationConstraint;
+
+};
+
+// 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 A global registry for entry points into a program,
+ * so that they can be instrumented. Each module should register their
+ * entry points with this registry. Designed to be compiled out
+ * if no instrumentation is requested.
+ *
+ * Entry points may be registered before or after a call to
+ * goog.debug.entryPointRegistry.monitorAll. If an entry point is registered
+ * later, the existing monitor will instrument the new entry point.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ */
+
+goog.provide('goog.debug.EntryPointMonitor');
+goog.provide('goog.debug.entryPointRegistry');
+
+goog.require('goog.asserts');
+
+
+
+/**
+ * @interface
+ */
+goog.debug.EntryPointMonitor = function() {};
+
+
+/**
+ * Instruments a function.
+ *
+ * @param {!Function} fn A function to instrument.
+ * @return {!Function} The instrumented function.
+ */
+goog.debug.EntryPointMonitor.prototype.wrap;
+
+
+/**
+ * Try to remove an instrumentation wrapper created by this monitor.
+ * If the function passed to unwrap is not a wrapper created by this
+ * monitor, then we will do nothing.
+ *
+ * Notice that some wrappers may not be unwrappable. For example, if other
+ * monitors have applied their own wrappers, then it will be impossible to
+ * unwrap them because their wrappers will have captured our wrapper.
+ *
+ * So it is important that entry points are unwrapped in the reverse
+ * order that they were wrapped.
+ *
+ * @param {!Function} fn A function to unwrap.
+ * @return {!Function} The unwrapped function, or {@code fn} if it was not
+ * a wrapped function created by this monitor.
+ */
+goog.debug.EntryPointMonitor.prototype.unwrap;
+
+
+/**
+ * An array of entry point callbacks.
+ * @type {!Array<function(!Function)>}
+ * @private
+ */
+goog.debug.entryPointRegistry.refList_ = [];
+
+
+/**
+ * Monitors that should wrap all the entry points.
+ * @type {!Array<!goog.debug.EntryPointMonitor>}
+ * @private
+ */
+goog.debug.entryPointRegistry.monitors_ = [];
+
+
+/**
+ * Whether goog.debug.entryPointRegistry.monitorAll has ever been called.
+ * Checking this allows the compiler to optimize out the registrations.
+ * @type {boolean}
+ * @private
+ */
+goog.debug.entryPointRegistry.monitorsMayExist_ = false;
+
+
+/**
+ * Register an entry point with this module.
+ *
+ * The entry point will be instrumented when a monitor is passed to
+ * goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the
+ * entry point is instrumented immediately.
+ *
+ * @param {function(!Function)} callback A callback function which is called
+ * with a transforming function to instrument the entry point. The callback
+ * is responsible for wrapping the relevant entry point with the
+ * transforming function.
+ */
+goog.debug.entryPointRegistry.register = function(callback) {
+ // Don't use push(), so that this can be compiled out.
+ goog.debug.entryPointRegistry.refList_[
+ goog.debug.entryPointRegistry.refList_.length] = callback;
+ // If no one calls monitorAll, this can be compiled out.
+ if (goog.debug.entryPointRegistry.monitorsMayExist_) {
+ var monitors = goog.debug.entryPointRegistry.monitors_;
+ for (var i = 0; i < monitors.length; i++) {
+ callback(goog.bind(monitors[i].wrap, monitors[i]));
+ }
+ }
+};
+
+
+/**
+ * Configures a monitor to wrap all entry points.
+ *
+ * Entry points that have already been registered are immediately wrapped by
+ * the monitor. When an entry point is registered in the future, it will also
+ * be wrapped by the monitor when it is registered.
+ *
+ * @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor.
+ */
+goog.debug.entryPointRegistry.monitorAll = function(monitor) {
+ goog.debug.entryPointRegistry.monitorsMayExist_ = true;
+ var transformer = goog.bind(monitor.wrap, monitor);
+ for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
+ goog.debug.entryPointRegistry.refList_[i](transformer);
+ }
+ goog.debug.entryPointRegistry.monitors_.push(monitor);
+};
+
+
+/**
+ * Try to unmonitor all the entry points that have already been registered. If
+ * an entry point is registered in the future, it will not be wrapped by the
+ * monitor when it is registered. Note that this may fail if the entry points
+ * have additional wrapping.
+ *
+ * @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap
+ * the entry points.
+ * @throws {Error} If the monitor is not the most recently configured monitor.
+ */
+goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) {
+ var monitors = goog.debug.entryPointRegistry.monitors_;
+ goog.asserts.assert(monitor == monitors[monitors.length - 1],
+ 'Only the most recent monitor can be unwrapped.');
+ var transformer = goog.bind(monitor.unwrap, monitor);
+ for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
+ goog.debug.entryPointRegistry.refList_[i](transformer);
+ }
+ monitors.length--;
+};
+
+// 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 used by goog.labs.userAgent tools. These functions
+ * should not be used outside of goog.labs.userAgent.*.
+ *
+ *
+ * @author nnaze@google.com (Nathan Naze)
+ */
+
+goog.provide('goog.labs.userAgent.util');
+
+goog.require('goog.string');
+
+
+/**
+ * Gets the native userAgent string from navigator if it exists.
+ * If navigator or navigator.userAgent string is missing, returns an empty
+ * string.
+ * @return {string}
+ * @private
+ */
+goog.labs.userAgent.util.getNativeUserAgentString_ = function() {
+ var navigator = goog.labs.userAgent.util.getNavigator_();
+ if (navigator) {
+ var userAgent = navigator.userAgent;
+ if (userAgent) {
+ return userAgent;
+ }
+ }
+ return '';
+};
+
+
+/**
+ * Getter for the native navigator.
+ * This is a separate function so it can be stubbed out in testing.
+ * @return {Navigator}
+ * @private
+ */
+goog.labs.userAgent.util.getNavigator_ = function() {
+ return goog.global.navigator;
+};
+
+
+/**
+ * A possible override for applications which wish to not check
+ * navigator.userAgent but use a specified value for detection instead.
+ * @private {string}
+ */
+goog.labs.userAgent.util.userAgent_ =
+ goog.labs.userAgent.util.getNativeUserAgentString_();
+
+
+/**
+ * Applications may override browser detection on the built in
+ * navigator.userAgent object by setting this string. Set to null to use the
+ * browser object instead.
+ * @param {?string=} opt_userAgent The User-Agent override.
+ */
+goog.labs.userAgent.util.setUserAgent = function(opt_userAgent) {
+ goog.labs.userAgent.util.userAgent_ = opt_userAgent ||
+ goog.labs.userAgent.util.getNativeUserAgentString_();
+};
+
+
+/**
+ * @return {string} The user agent string.
+ */
+goog.labs.userAgent.util.getUserAgent = function() {
+ return goog.labs.userAgent.util.userAgent_;
+};
+
+
+/**
+ * @param {string} str
+ * @return {boolean} Whether the user agent contains the given string, ignoring
+ * case.
+ */
+goog.labs.userAgent.util.matchUserAgent = function(str) {
+ var userAgent = goog.labs.userAgent.util.getUserAgent();
+ return goog.string.contains(userAgent, str);
+};
+
+
+/**
+ * @param {string} str
+ * @return {boolean} Whether the user agent contains the given string.
+ */
+goog.labs.userAgent.util.matchUserAgentIgnoreCase = function(str) {
+ var userAgent = goog.labs.userAgent.util.getUserAgent();
+ return goog.string.caseInsensitiveContains(userAgent, str);
+};
+
+
+/**
+ * Parses the user agent into tuples for each section.
+ * @param {string} userAgent
+ * @return {!Array<!Array<string>>} Tuples of key, version, and the contents
+ * of the parenthetical.
+ */
+goog.labs.userAgent.util.extractVersionTuples = function(userAgent) {
+ // Matches each section of a user agent string.
+ // Example UA:
+ // Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us)
+ // AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405
+ // This has three version tuples: Mozilla, AppleWebKit, and Mobile.
+
+ var versionRegExp = new RegExp(
+ // Key. Note that a key may have a space.
+ // (i.e. 'Mobile Safari' in 'Mobile Safari/5.0')
+ '(\\w[\\w ]+)' +
+
+ '/' + // slash
+ '([^\\s]+)' + // version (i.e. '5.0b')
+ '\\s*' + // whitespace
+ '(?:\\((.*?)\\))?', // parenthetical info. parentheses not matched.
+ 'g');
+
+ var data = [];
+ var match;
+
+ // Iterate and collect the version tuples. Each iteration will be the
+ // next regex match.
+ while (match = versionRegExp.exec(userAgent)) {
+ data.push([
+ match[1], // key
+ match[2], // value
+ // || undefined as this is not undefined in IE7 and IE8
+ match[3] || undefined // info
+ ]);
+ }
+
+ return data;
+};
+
+
+// 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 objects/maps/hashes.
+ * @author arv@google.com (Erik Arvidsson)
+ */
+
+goog.provide('goog.object');
+
+
+/**
+ * Calls a function for each element in an object/map/hash.
+ *
+ * @param {Object<K,V>} obj The object over which to iterate.
+ * @param {function(this:T,V,?,Object<K,V>):?} f The function to call
+ * for every element. This function takes 3 arguments (the value, the
+ * key and the object) and the return value is ignored.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @template T,K,V
+ */
+goog.object.forEach = function(obj, f, opt_obj) {
+ for (var key in obj) {
+ f.call(opt_obj, obj[key], key, obj);
+ }
+};
+
+
+/**
+ * Calls a function for each element in an object/map/hash. If that call returns
+ * true, adds the element to a new object.
+ *
+ * @param {Object<K,V>} obj The object over which to iterate.
+ * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to call
+ * for every element. This
+ * function takes 3 arguments (the value, the key and the object)
+ * and should return a boolean. If the return value is true the
+ * element is added to the result object. If it is false the
+ * element is not included.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @return {!Object<K,V>} a new object in which only elements that passed the
+ * test are present.
+ * @template T,K,V
+ */
+goog.object.filter = function(obj, f, opt_obj) {
+ var res = {};
+ for (var key in obj) {
+ if (f.call(opt_obj, obj[key], key, obj)) {
+ res[key] = obj[key];
+ }
+ }
+ return res;
+};
+
+
+/**
+ * For every element in an object/map/hash calls a function and inserts the
+ * result into a new object.
+ *
+ * @param {Object<K,V>} obj The object over which to iterate.
+ * @param {function(this:T,V,?,Object<K,V>):R} f The function to call
+ * for every element. This function
+ * takes 3 arguments (the value, the key and the object)
+ * and should return something. The result will be inserted
+ * into a new object.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @return {!Object<K,R>} a new object with the results from f.
+ * @template T,K,V,R
+ */
+goog.object.map = function(obj, f, opt_obj) {
+ var res = {};
+ for (var key in obj) {
+ res[key] = f.call(opt_obj, obj[key], key, obj);
+ }
+ return res;
+};
+
+
+/**
+ * Calls a function for each element in an object/map/hash. If any
+ * call returns true, returns true (without checking the rest). If
+ * all calls return false, returns false.
+ *
+ * @param {Object<K,V>} obj The object to check.
+ * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to
+ * call for every element. This function
+ * takes 3 arguments (the value, the key and the object) and should
+ * return a boolean.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @return {boolean} true if any element passes the test.
+ * @template T,K,V
+ */
+goog.object.some = function(obj, f, opt_obj) {
+ for (var key in obj) {
+ if (f.call(opt_obj, obj[key], key, obj)) {
+ return true;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Calls a function for each element in an object/map/hash. If
+ * all calls return true, returns true. If any call returns false, returns
+ * false at this point and does not continue to check the remaining elements.
+ *
+ * @param {Object<K,V>} obj The object to check.
+ * @param {?function(this:T,V,?,Object<K,V>):boolean} f The function to
+ * call for every element. This function
+ * takes 3 arguments (the value, the key and the object) and should
+ * return a boolean.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @return {boolean} false if any element fails the test.
+ * @template T,K,V
+ */
+goog.object.every = function(obj, f, opt_obj) {
+ for (var key in obj) {
+ if (!f.call(opt_obj, obj[key], key, obj)) {
+ return false;
+ }
+ }
+ return true;
+};
+
+
+/**
+ * Returns the number of key-value pairs in the object map.
+ *
+ * @param {Object} obj The object for which to get the number of key-value
+ * pairs.
+ * @return {number} The number of key-value pairs in the object map.
+ */
+goog.object.getCount = function(obj) {
+ // JS1.5 has __count__ but it has been deprecated so it raises a warning...
+ // in other words do not use. Also __count__ only includes the fields on the
+ // actual object and not in the prototype chain.
+ var rv = 0;
+ for (var key in obj) {
+ rv++;
+ }
+ return rv;
+};
+
+
+/**
+ * Returns one key from the object map, if any exists.
+ * For map literals the returned key will be the first one in most of the
+ * browsers (a know exception is Konqueror).
+ *
+ * @param {Object} obj The object to pick a key from.
+ * @return {string|undefined} The key or undefined if the object is empty.
+ */
+goog.object.getAnyKey = function(obj) {
+ for (var key in obj) {
+ return key;
+ }
+};
+
+
+/**
+ * Returns one value from the object map, if any exists.
+ * For map literals the returned value will be the first one in most of the
+ * browsers (a know exception is Konqueror).
+ *
+ * @param {Object<K,V>} obj The object to pick a value from.
+ * @return {V|undefined} The value or undefined if the object is empty.
+ * @template K,V
+ */
+goog.object.getAnyValue = function(obj) {
+ for (var key in obj) {
+ return obj[key];
+ }
+};
+
+
+/**
+ * Whether the object/hash/map contains the given object as a value.
+ * An alias for goog.object.containsValue(obj, val).
+ *
+ * @param {Object<K,V>} obj The object in which to look for val.
+ * @param {V} val The object for which to check.
+ * @return {boolean} true if val is present.
+ * @template K,V
+ */
+goog.object.contains = function(obj, val) {
+ return goog.object.containsValue(obj, val);
+};
+
+
+/**
+ * Returns the values of the object/map/hash.
+ *
+ * @param {Object<K,V>} obj The object from which to get the values.
+ * @return {!Array<V>} The values in the object/map/hash.
+ * @template K,V
+ */
+goog.object.getValues = function(obj) {
+ var res = [];
+ var i = 0;
+ for (var key in obj) {
+ res[i++] = obj[key];
+ }
+ return res;
+};
+
+
+/**
+ * Returns the keys of the object/map/hash.
+ *
+ * @param {Object} obj The object from which to get the keys.
+ * @return {!Array<string>} Array of property keys.
+ */
+goog.object.getKeys = function(obj) {
+ var res = [];
+ var i = 0;
+ for (var key in obj) {
+ res[i++] = key;
+ }
+ return res;
+};
+
+
+/**
+ * Get a value from an object multiple levels deep. This is useful for
+ * pulling values from deeply nested objects, such as JSON responses.
+ * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
+ *
+ * @param {!Object} obj An object to get the value from. Can be array-like.
+ * @param {...(string|number|!Array<number|string>)} var_args A number of keys
+ * (as strings, or numbers, for array-like objects). Can also be
+ * specified as a single array of keys.
+ * @return {*} The resulting value. If, at any point, the value for a key
+ * is undefined, returns undefined.
+ */
+goog.object.getValueByKeys = function(obj, var_args) {
+ var isArrayLike = goog.isArrayLike(var_args);
+ var keys = isArrayLike ? var_args : arguments;
+
+ // Start with the 2nd parameter for the variable parameters syntax.
+ for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
+ obj = obj[keys[i]];
+ if (!goog.isDef(obj)) {
+ break;
+ }
+ }
+
+ return obj;
+};
+
+
+/**
+ * Whether the object/map/hash contains the given key.
+ *
+ * @param {Object} obj The object in which to look for key.
+ * @param {*} key The key for which to check.
+ * @return {boolean} true If the map contains the key.
+ */
+goog.object.containsKey = function(obj, key) {
+ return key in obj;
+};
+
+
+/**
+ * Whether the object/map/hash contains the given value. This is O(n).
+ *
+ * @param {Object<K,V>} obj The object in which to look for val.
+ * @param {V} val The value for which to check.
+ * @return {boolean} true If the map contains the value.
+ * @template K,V
+ */
+goog.object.containsValue = function(obj, val) {
+ for (var key in obj) {
+ if (obj[key] == val) {
+ return true;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Searches an object for an element that satisfies the given condition and
+ * returns its key.
+ * @param {Object<K,V>} obj The object to search in.
+ * @param {function(this:T,V,string,Object<K,V>):boolean} f The
+ * function to call for every element. Takes 3 arguments (the value,
+ * the key and the object) and should return a boolean.
+ * @param {T=} opt_this An optional "this" context for the function.
+ * @return {string|undefined} The key of an element for which the function
+ * returns true or undefined if no such element is found.
+ * @template T,K,V
+ */
+goog.object.findKey = function(obj, f, opt_this) {
+ for (var key in obj) {
+ if (f.call(opt_this, obj[key], key, obj)) {
+ return key;
+ }
+ }
+ return undefined;
+};
+
+
+/**
+ * Searches an object for an element that satisfies the given condition and
+ * returns its value.
+ * @param {Object<K,V>} obj The object to search in.
+ * @param {function(this:T,V,string,Object<K,V>):boolean} f The function
+ * to call for every element. Takes 3 arguments (the value, the key
+ * and the object) and should return a boolean.
+ * @param {T=} opt_this An optional "this" context for the function.
+ * @return {V} The value of an element for which the function returns true or
+ * undefined if no such element is found.
+ * @template T,K,V
+ */
+goog.object.findValue = function(obj, f, opt_this) {
+ var key = goog.object.findKey(obj, f, opt_this);
+ return key && obj[key];
+};
+
+
+/**
+ * Whether the object/map/hash is empty.
+ *
+ * @param {Object} obj The object to test.
+ * @return {boolean} true if obj is empty.
+ */
+goog.object.isEmpty = function(obj) {
+ for (var key in obj) {
+ return false;
+ }
+ return true;
+};
+
+
+/**
+ * Removes all key value pairs from the object/map/hash.
+ *
+ * @param {Object} obj The object to clear.
+ */
+goog.object.clear = function(obj) {
+ for (var i in obj) {
+ delete obj[i];
+ }
+};
+
+
+/**
+ * Removes a key-value pair based on the key.
+ *
+ * @param {Object} obj The object from which to remove the key.
+ * @param {*} key The key to remove.
+ * @return {boolean} Whether an element was removed.
+ */
+goog.object.remove = function(obj, key) {
+ var rv;
+ if ((rv = key in obj)) {
+ delete obj[key];
+ }
+ return rv;
+};
+
+
+/**
+ * Adds a key-value pair to the object. Throws an exception if the key is
+ * already in use. Use set if you want to change an existing pair.
+ *
+ * @param {Object<K,V>} obj The object to which to add the key-value pair.
+ * @param {string} key The key to add.
+ * @param {V} val The value to add.
+ * @template K,V
+ */
+goog.object.add = function(obj, key, val) {
+ if (key in obj) {
+ throw Error('The object already contains the key "' + key + '"');
+ }
+ goog.object.set(obj, key, val);
+};
+
+
+/**
+ * Returns the value for the given key.
+ *
+ * @param {Object<K,V>} obj The object from which to get the value.
+ * @param {string} key The key for which to get the value.
+ * @param {R=} opt_val The value to return if no item is found for the given
+ * key (default is undefined).
+ * @return {V|R|undefined} The value for the given key.
+ * @template K,V,R
+ */
+goog.object.get = function(obj, key, opt_val) {
+ if (key in obj) {
+ return obj[key];
+ }
+ return opt_val;
+};
+
+
+/**
+ * Adds a key-value pair to the object/map/hash.
+ *
+ * @param {Object<K,V>} obj The object to which to add the key-value pair.
+ * @param {string} key The key to add.
+ * @param {V} value The value to add.
+ * @template K,V
+ */
+goog.object.set = function(obj, key, value) {
+ obj[key] = value;
+};
+
+
+/**
+ * Adds a key-value pair to the object/map/hash if it doesn't exist yet.
+ *
+ * @param {Object<K,V>} obj The object to which to add the key-value pair.
+ * @param {string} key The key to add.
+ * @param {V} value The value to add if the key wasn't present.
+ * @return {V} The value of the entry at the end of the function.
+ * @template K,V
+ */
+goog.object.setIfUndefined = function(obj, key, value) {
+ return key in obj ? obj[key] : (obj[key] = value);
+};
+
+
+/**
+ * Sets a key and value to an object if the key is not set. The value will be
+ * the return value of the given function. If the key already exists, the
+ * object will not be changed and the function will not be called (the function
+ * will be lazily evaluated -- only called if necessary).
+ *
+ * This function is particularly useful for use with a map used a as a cache.
+ *
+ * @param {!Object<K,V>} obj The object to which to add the key-value pair.
+ * @param {string} key The key to add.
+ * @param {function():V} f The value to add if the key wasn't present.
+ * @return {V} The value of the entry at the end of the function.
+ * @template K,V
+ */
+goog.object.setWithReturnValueIfNotSet = function(obj, key, f) {
+ if (key in obj) {
+ return obj[key];
+ }
+
+ var val = f();
+ obj[key] = val;
+ return val;
+};
+
+
+/**
+ * Compares two objects for equality using === on the values.
+ *
+ * @param {!Object<K,V>} a
+ * @param {!Object<K,V>} b
+ * @return {boolean}
+ * @template K,V
+ */
+goog.object.equals = function(a, b) {
+ for (var k in a) {
+ if (!(k in b) || a[k] !== b[k]) {
+ return false;
+ }
+ }
+ for (var k in b) {
+ if (!(k in a)) {
+ return false;
+ }
+ }
+ return true;
+};
+
+
+/**
+ * Does a flat clone of the object.
+ *
+ * @param {Object<K,V>} obj Object to clone.
+ * @return {!Object<K,V>} Clone of the input object.
+ * @template K,V
+ */
+goog.object.clone = function(obj) {
+ // We cannot use the prototype trick because a lot of methods depend on where
+ // the actual key is set.
+
+ var res = {};
+ for (var key in obj) {
+ res[key] = obj[key];
+ }
+ return res;
+ // We could also use goog.mixin but I wanted this to be independent from that.
+};
+
+
+/**
+ * Clones a value. The input may be an Object, Array, or basic type. Objects and
+ * arrays will be cloned recursively.
+ *
+ * WARNINGS:
+ * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects
+ * that refer to themselves will cause infinite recursion.
+ *
+ * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and
+ * copies UIDs created by <code>getUid</code> into cloned results.
+ *
+ * @param {*} obj The value to clone.
+ * @return {*} A clone of the input value.
+ */
+goog.object.unsafeClone = function(obj) {
+ var type = goog.typeOf(obj);
+ if (type == 'object' || type == 'array') {
+ if (goog.isFunction(obj.clone)) {
+ return obj.clone();
+ }
+ var clone = type == 'array' ? [] : {};
+ for (var key in obj) {
+ clone[key] = goog.object.unsafeClone(obj[key]);
+ }
+ return clone;
+ }
+
+ return obj;
+};
+
+
+/**
+ * Returns a new object 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.
+ *
+ * @param {Object} obj The object to transpose.
+ * @return {!Object} The transposed object.
+ */
+goog.object.transpose = function(obj) {
+ var transposed = {};
+ for (var key in obj) {
+ transposed[obj[key]] = key;
+ }
+ return transposed;
+};
+
+
+/**
+ * The names of the fields that are defined on Object.prototype.
+ * @type {Array<string>}
+ * @private
+ */
+goog.object.PROTOTYPE_FIELDS_ = [
+ 'constructor',
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'toLocaleString',
+ 'toString',
+ 'valueOf'
+];
+
+
+/**
+ * Extends an object with another object.
+ * This operates 'in-place'; it does not create a new Object.
+ *
+ * Example:
+ * var o = {};
+ * goog.object.extend(o, {a: 0, b: 1});
+ * o; // {a: 0, b: 1}
+ * goog.object.extend(o, {b: 2, c: 3});
+ * o; // {a: 0, b: 2, c: 3}
+ *
+ * @param {Object} target The object to modify. Existing properties will be
+ * overwritten if they are also present in one of the objects in
+ * {@code var_args}.
+ * @param {...Object} var_args The objects from which values will be copied.
+ */
+goog.object.extend = function(target, var_args) {
+ var key, source;
+ for (var i = 1; i < arguments.length; i++) {
+ source = arguments[i];
+ for (key in source) {
+ target[key] = source[key];
+ }
+
+ // For IE the for-in-loop does not contain any properties that are not
+ // enumerable on the prototype object (for example isPrototypeOf from
+ // Object.prototype) and it will also not include 'replace' on objects that
+ // extend String and change 'replace' (not that it is common for anyone to
+ // extend anything except Object).
+
+ for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
+ key = goog.object.PROTOTYPE_FIELDS_[j];
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+ }
+};
+
+
+/**
+ * Creates a new object built from the key-value pairs provided as arguments.
+ * @param {...*} var_args If only one argument is provided and it is an array
+ * then this is used as the arguments, otherwise even arguments are used as
+ * the property names and odd arguments are used as the property values.
+ * @return {!Object} The new object.
+ * @throws {Error} If there are uneven number of arguments or there is only one
+ * non array argument.
+ */
+goog.object.create = function(var_args) {
+ var argLength = arguments.length;
+ if (argLength == 1 && goog.isArray(arguments[0])) {
+ return goog.object.create.apply(null, arguments[0]);
+ }
+
+ if (argLength % 2) {
+ throw Error('Uneven number of arguments');
+ }
+
+ var rv = {};
+ for (var i = 0; i < argLength; i += 2) {
+ rv[arguments[i]] = arguments[i + 1];
+ }
+ return rv;
+};
+
+
+/**
+ * Creates a new object where the property names come from the arguments but
+ * the value is always set to true
+ * @param {...*} var_args If only one argument is provided and it is an array
+ * then this is used as the arguments, otherwise the arguments are used
+ * as the property names.
+ * @return {!Object} The new object.
+ */
+goog.object.createSet = function(var_args) {
+ var argLength = arguments.length;
+ if (argLength == 1 && goog.isArray(arguments[0])) {
+ return goog.object.createSet.apply(null, arguments[0]);
+ }
+
+ var rv = {};
+ for (var i = 0; i < argLength; i++) {
+ rv[arguments[i]] = true;
+ }
+ return rv;
+};
+
+
+/**
+ * Creates an immutable view of the underlying object, if the browser
+ * supports immutable objects.
+ *
+ * In default mode, writes to this view will fail silently. In strict mode,
+ * they will throw an error.
+ *
+ * @param {!Object<K,V>} obj An object.
+ * @return {!Object<K,V>} An immutable view of that object, or the
+ * original object if this browser does not support immutables.
+ * @template K,V
+ */
+goog.object.createImmutableView = function(obj) {
+ var result = obj;
+ if (Object.isFrozen && !Object.isFrozen(obj)) {
+ result = Object.create(obj);
+ Object.freeze(result);
+ }
+ return result;
+};
+
+
+/**
+ * @param {!Object} obj An object.
+ * @return {boolean} Whether this is an immutable view of the object.
+ */
+goog.object.isImmutableView = function(obj) {
+ return !!Object.isFrozen && Object.isFrozen(obj);
+};
+
+// 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 Closure user agent detection (Browser).
+ * @see <a href="http://www.useragentstring.com/">User agent strings</a>
+ * For more information on rendering engine, platform, or device see the other
+ * sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform,
+ * goog.labs.userAgent.device respectively.)
+ *
+ * @author martone@google.com (Andy Martone)
+ */
+
+goog.provide('goog.labs.userAgent.browser');
+
+goog.require('goog.array');
+goog.require('goog.labs.userAgent.util');
+goog.require('goog.object');
+goog.require('goog.string');
+
+
+// TODO(nnaze): Refactor to remove excessive exclusion logic in matching
+// functions.
+
+
+/**
+ * @return {boolean} Whether the user's browser is Opera.
+ * @private
+ */
+goog.labs.userAgent.browser.matchOpera_ = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Opera') ||
+ goog.labs.userAgent.util.matchUserAgent('OPR');
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is IE.
+ * @private
+ */
+goog.labs.userAgent.browser.matchIE_ = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Trident') ||
+ goog.labs.userAgent.util.matchUserAgent('MSIE');
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is Edge.
+ * @private
+ */
+goog.labs.userAgent.browser.matchEdge_ = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Edge');
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is Firefox.
+ * @private
+ */
+goog.labs.userAgent.browser.matchFirefox_ = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Firefox');
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is Safari.
+ * @private
+ */
+goog.labs.userAgent.browser.matchSafari_ = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Safari') &&
+ !(goog.labs.userAgent.browser.matchChrome_() ||
+ goog.labs.userAgent.browser.matchCoast_() ||
+ goog.labs.userAgent.browser.matchOpera_() ||
+ goog.labs.userAgent.browser.matchEdge_() ||
+ goog.labs.userAgent.browser.isSilk() ||
+ goog.labs.userAgent.util.matchUserAgent('Android'));
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
+ * iOS browser).
+ * @private
+ */
+goog.labs.userAgent.browser.matchCoast_ = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Coast');
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is iOS Webview.
+ * @private
+ */
+goog.labs.userAgent.browser.matchIosWebview_ = function() {
+ // iOS Webview does not show up as Chrome or Safari. Also check for Opera's
+ // WebKit-based iOS browser, Coast.
+ return (goog.labs.userAgent.util.matchUserAgent('iPad') ||
+ goog.labs.userAgent.util.matchUserAgent('iPhone')) &&
+ !goog.labs.userAgent.browser.matchSafari_() &&
+ !goog.labs.userAgent.browser.matchChrome_() &&
+ !goog.labs.userAgent.browser.matchCoast_() &&
+ goog.labs.userAgent.util.matchUserAgent('AppleWebKit');
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is Chrome.
+ * @private
+ */
+goog.labs.userAgent.browser.matchChrome_ = function() {
+ return (goog.labs.userAgent.util.matchUserAgent('Chrome') ||
+ goog.labs.userAgent.util.matchUserAgent('CriOS')) &&
+ !goog.labs.userAgent.browser.matchOpera_() &&
+ !goog.labs.userAgent.browser.matchEdge_();
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is the Android browser.
+ * @private
+ */
+goog.labs.userAgent.browser.matchAndroidBrowser_ = function() {
+ // Android can appear in the user agent string for Chrome on Android.
+ // This is not the Android standalone browser if it does.
+ return goog.labs.userAgent.util.matchUserAgent('Android') &&
+ !(goog.labs.userAgent.browser.isChrome() ||
+ goog.labs.userAgent.browser.isFirefox() ||
+ goog.labs.userAgent.browser.isOpera() ||
+ goog.labs.userAgent.browser.isSilk());
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is Opera.
+ */
+goog.labs.userAgent.browser.isOpera = goog.labs.userAgent.browser.matchOpera_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is IE.
+ */
+goog.labs.userAgent.browser.isIE = goog.labs.userAgent.browser.matchIE_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is Edge.
+ */
+goog.labs.userAgent.browser.isEdge = goog.labs.userAgent.browser.matchEdge_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is Firefox.
+ */
+goog.labs.userAgent.browser.isFirefox =
+ goog.labs.userAgent.browser.matchFirefox_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is Safari.
+ */
+goog.labs.userAgent.browser.isSafari =
+ goog.labs.userAgent.browser.matchSafari_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
+ * iOS browser).
+ */
+goog.labs.userAgent.browser.isCoast =
+ goog.labs.userAgent.browser.matchCoast_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is iOS Webview.
+ */
+goog.labs.userAgent.browser.isIosWebview =
+ goog.labs.userAgent.browser.matchIosWebview_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is Chrome.
+ */
+goog.labs.userAgent.browser.isChrome =
+ goog.labs.userAgent.browser.matchChrome_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is the Android browser.
+ */
+goog.labs.userAgent.browser.isAndroidBrowser =
+ goog.labs.userAgent.browser.matchAndroidBrowser_;
+
+
+/**
+ * For more information, see:
+ * http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
+ * @return {boolean} Whether the user's browser is Silk.
+ */
+goog.labs.userAgent.browser.isSilk = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Silk');
+};
+
+
+/**
+ * @return {string} The browser version or empty string if version cannot be
+ * determined. Note that for Internet Explorer, this returns the version of
+ * the browser, not the version of the rendering engine. (IE 8 in
+ * compatibility mode will return 8.0 rather than 7.0. To determine the
+ * rendering engine version, look at document.documentMode instead. See
+ * http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more
+ * details.)
+ */
+goog.labs.userAgent.browser.getVersion = function() {
+ var userAgentString = goog.labs.userAgent.util.getUserAgent();
+ // Special case IE since IE's version is inside the parenthesis and
+ // without the '/'.
+ if (goog.labs.userAgent.browser.isIE()) {
+ return goog.labs.userAgent.browser.getIEVersion_(userAgentString);
+ }
+
+ var versionTuples = goog.labs.userAgent.util.extractVersionTuples(
+ userAgentString);
+
+ // Construct a map for easy lookup.
+ var versionMap = {};
+ goog.array.forEach(versionTuples, function(tuple) {
+ // Note that the tuple is of length three, but we only care about the
+ // first two.
+ var key = tuple[0];
+ var value = tuple[1];
+ versionMap[key] = value;
+ });
+
+ var versionMapHasKey = goog.partial(goog.object.containsKey, versionMap);
+
+ // Gives the value with the first key it finds, otherwise empty string.
+ function lookUpValueWithKeys(keys) {
+ var key = goog.array.find(keys, versionMapHasKey);
+ return versionMap[key] || '';
+ }
+
+ // Check Opera before Chrome since Opera 15+ has "Chrome" in the string.
+ // See
+ // http://my.opera.com/ODIN/blog/2013/07/15/opera-user-agent-strings-opera-15-and-beyond
+ if (goog.labs.userAgent.browser.isOpera()) {
+ // Opera 10 has Version/10.0 but Opera/9.8, so look for "Version" first.
+ // Opera uses 'OPR' for more recent UAs.
+ return lookUpValueWithKeys(['Version', 'Opera', 'OPR']);
+ }
+
+ // Check Edge before Chrome since it has Chrome in the string.
+ if (goog.labs.userAgent.browser.isEdge()) {
+ return lookUpValueWithKeys(['Edge']);
+ }
+
+ if (goog.labs.userAgent.browser.isChrome()) {
+ return lookUpValueWithKeys(['Chrome', 'CriOS']);
+ }
+
+ // Usually products browser versions are in the third tuple after "Mozilla"
+ // and the engine.
+ var tuple = versionTuples[2];
+ return tuple && tuple[1] || '';
+};
+
+
+/**
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the browser version is higher or the same as the
+ * given version.
+ */
+goog.labs.userAgent.browser.isVersionOrHigher = function(version) {
+ return goog.string.compareVersions(goog.labs.userAgent.browser.getVersion(),
+ version) >= 0;
+};
+
+
+/**
+ * Determines IE version. More information:
+ * http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString
+ * http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
+ * http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx
+ * http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx
+ *
+ * @param {string} userAgent the User-Agent.
+ * @return {string}
+ * @private
+ */
+goog.labs.userAgent.browser.getIEVersion_ = function(userAgent) {
+ // IE11 may identify itself as MSIE 9.0 or MSIE 10.0 due to an IE 11 upgrade
+ // bug. Example UA:
+ // Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)
+ // like Gecko.
+ // See http://www.whatismybrowser.com/developers/unknown-user-agent-fragments.
+ var rv = /rv: *([\d\.]*)/.exec(userAgent);
+ if (rv && rv[1]) {
+ return rv[1];
+ }
+
+ var version = '';
+ var msie = /MSIE +([\d\.]+)/.exec(userAgent);
+ if (msie && msie[1]) {
+ // IE in compatibility mode usually identifies itself as MSIE 7.0; in this
+ // case, use the Trident version to determine the version of IE. For more
+ // details, see the links above.
+ var tridentVersion = /Trident\/(\d.\d)/.exec(userAgent);
+ if (msie[1] == '7.0') {
+ if (tridentVersion && tridentVersion[1]) {
+ switch (tridentVersion[1]) {
+ case '4.0':
+ version = '8.0';
+ break;
+ case '5.0':
+ version = '9.0';
+ break;
+ case '6.0':
+ version = '10.0';
+ break;
+ case '7.0':
+ version = '11.0';
+ break;
+ }
+ } else {
+ version = '7.0';
+ }
+ } else {
+ version = msie[1];
+ }
+ }
+ return version;
+};
+
+// 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 Closure user agent detection.
+ * @see http://en.wikipedia.org/wiki/User_agent
+ * For more information on browser brand, platform, or device see the other
+ * sub-namespaces in goog.labs.userAgent (browser, platform, and device).
+ *
+ */
+
+goog.provide('goog.labs.userAgent.engine');
+
+goog.require('goog.array');
+goog.require('goog.labs.userAgent.util');
+goog.require('goog.string');
+
+
+/**
+ * @return {boolean} Whether the rendering engine is Presto.
+ */
+goog.labs.userAgent.engine.isPresto = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Presto');
+};
+
+
+/**
+ * @return {boolean} Whether the rendering engine is Trident.
+ */
+goog.labs.userAgent.engine.isTrident = function() {
+ // IE only started including the Trident token in IE8.
+ return goog.labs.userAgent.util.matchUserAgent('Trident') ||
+ goog.labs.userAgent.util.matchUserAgent('MSIE');
+};
+
+
+/**
+ * @return {boolean} Whether the rendering engine is Edge.
+ */
+goog.labs.userAgent.engine.isEdge = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Edge');
+};
+
+
+/**
+ * @return {boolean} Whether the rendering engine is WebKit.
+ */
+goog.labs.userAgent.engine.isWebKit = function() {
+ return goog.labs.userAgent.util.matchUserAgentIgnoreCase('WebKit') &&
+ !goog.labs.userAgent.engine.isEdge();
+};
+
+
+/**
+ * @return {boolean} Whether the rendering engine is Gecko.
+ */
+goog.labs.userAgent.engine.isGecko = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Gecko') &&
+ !goog.labs.userAgent.engine.isWebKit() &&
+ !goog.labs.userAgent.engine.isTrident() &&
+ !goog.labs.userAgent.engine.isEdge();
+};
+
+
+/**
+ * @return {string} The rendering engine's version or empty string if version
+ * can't be determined.
+ */
+goog.labs.userAgent.engine.getVersion = function() {
+ var userAgentString = goog.labs.userAgent.util.getUserAgent();
+ if (userAgentString) {
+ var tuples = goog.labs.userAgent.util.extractVersionTuples(
+ userAgentString);
+
+ var engineTuple = goog.labs.userAgent.engine.getEngineTuple_(tuples);
+ if (engineTuple) {
+ // In Gecko, the version string is either in the browser info or the
+ // Firefox version. See Gecko user agent string reference:
+ // http://goo.gl/mULqa
+ if (engineTuple[0] == 'Gecko') {
+ return goog.labs.userAgent.engine.getVersionForKey_(
+ tuples, 'Firefox');
+ }
+
+ return engineTuple[1];
+ }
+
+ // MSIE has only one version identifier, and the Trident version is
+ // specified in the parenthetical. IE Edge is covered in the engine tuple
+ // detection.
+ var browserTuple = tuples[0];
+ var info;
+ if (browserTuple && (info = browserTuple[2])) {
+ var match = /Trident\/([^\s;]+)/.exec(info);
+ if (match) {
+ return match[1];
+ }
+ }
+ }
+ return '';
+};
+
+
+/**
+ * @param {!Array<!Array<string>>} tuples Extracted version tuples.
+ * @return {!Array<string>|undefined} The engine tuple or undefined if not
+ * found.
+ * @private
+ */
+goog.labs.userAgent.engine.getEngineTuple_ = function(tuples) {
+ if (!goog.labs.userAgent.engine.isEdge()) {
+ return tuples[1];
+ }
+ for (var i = 0; i < tuples.length; i++) {
+ var tuple = tuples[i];
+ if (tuple[0] == 'Edge') {
+ return tuple;
+ }
+ }
+};
+
+
+/**
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the rendering engine version is higher or the same
+ * as the given version.
+ */
+goog.labs.userAgent.engine.isVersionOrHigher = function(version) {
+ return goog.string.compareVersions(goog.labs.userAgent.engine.getVersion(),
+ version) >= 0;
+};
+
+
+/**
+ * @param {!Array<!Array<string>>} tuples Version tuples.
+ * @param {string} key The key to look for.
+ * @return {string} The version string of the given key, if present.
+ * Otherwise, the empty string.
+ * @private
+ */
+goog.labs.userAgent.engine.getVersionForKey_ = function(tuples, key) {
+ // TODO(nnaze): Move to util if useful elsewhere.
+
+ var pair = goog.array.find(tuples, function(pair) {
+ return key == pair[0];
+ });
+
+ return pair && pair[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 Closure user agent platform detection.
+ * @see <a href="http://www.useragentstring.com/">User agent strings</a>
+ * For more information on browser brand, rendering engine, or device see the
+ * other sub-namespaces in goog.labs.userAgent (browser, engine, and device
+ * respectively).
+ *
+ */
+
+goog.provide('goog.labs.userAgent.platform');
+
+goog.require('goog.labs.userAgent.util');
+goog.require('goog.string');
+
+
+/**
+ * @return {boolean} Whether the platform is Android.
+ */
+goog.labs.userAgent.platform.isAndroid = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Android');
+};
+
+
+/**
+ * @return {boolean} Whether the platform is iPod.
+ */
+goog.labs.userAgent.platform.isIpod = function() {
+ return goog.labs.userAgent.util.matchUserAgent('iPod');
+};
+
+
+/**
+ * @return {boolean} Whether the platform is iPhone.
+ */
+goog.labs.userAgent.platform.isIphone = function() {
+ return goog.labs.userAgent.util.matchUserAgent('iPhone') &&
+ !goog.labs.userAgent.util.matchUserAgent('iPod') &&
+ !goog.labs.userAgent.util.matchUserAgent('iPad');
+};
+
+
+/**
+ * @return {boolean} Whether the platform is iPad.
+ */
+goog.labs.userAgent.platform.isIpad = function() {
+ return goog.labs.userAgent.util.matchUserAgent('iPad');
+};
+
+
+/**
+ * @return {boolean} Whether the platform is iOS.
+ */
+goog.labs.userAgent.platform.isIos = function() {
+ return goog.labs.userAgent.platform.isIphone() ||
+ goog.labs.userAgent.platform.isIpad() ||
+ goog.labs.userAgent.platform.isIpod();
+};
+
+
+/**
+ * @return {boolean} Whether the platform is Mac.
+ */
+goog.labs.userAgent.platform.isMacintosh = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Macintosh');
+};
+
+
+/**
+ * Note: ChromeOS is not considered to be Linux as it does not report itself
+ * as Linux in the user agent string.
+ * @return {boolean} Whether the platform is Linux.
+ */
+goog.labs.userAgent.platform.isLinux = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Linux');
+};
+
+
+/**
+ * @return {boolean} Whether the platform is Windows.
+ */
+goog.labs.userAgent.platform.isWindows = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Windows');
+};
+
+
+/**
+ * @return {boolean} Whether the platform is ChromeOS.
+ */
+goog.labs.userAgent.platform.isChromeOS = function() {
+ return goog.labs.userAgent.util.matchUserAgent('CrOS');
+};
+
+
+/**
+ * The version of the platform. We only determine the version for Windows,
+ * Mac, and Chrome OS. It doesn't make much sense on Linux. For Windows, we only
+ * look at the NT version. Non-NT-based versions (e.g. 95, 98, etc.) are given
+ * version 0.0.
+ *
+ * @return {string} The platform version or empty string if version cannot be
+ * determined.
+ */
+goog.labs.userAgent.platform.getVersion = function() {
+ var userAgentString = goog.labs.userAgent.util.getUserAgent();
+ var version = '', re;
+ if (goog.labs.userAgent.platform.isWindows()) {
+ re = /Windows (?:NT|Phone) ([0-9.]+)/;
+ var match = re.exec(userAgentString);
+ if (match) {
+ version = match[1];
+ } else {
+ version = '0.0';
+ }
+ } else if (goog.labs.userAgent.platform.isIos()) {
+ re = /(?:iPhone|iPod|iPad|CPU)\s+OS\s+(\S+)/;
+ var match = re.exec(userAgentString);
+ // Report the version as x.y.z and not x_y_z
+ version = match && match[1].replace(/_/g, '.');
+ } else if (goog.labs.userAgent.platform.isMacintosh()) {
+ re = /Mac OS X ([0-9_.]+)/;
+ var match = re.exec(userAgentString);
+ // Note: some old versions of Camino do not report an OSX version.
+ // Default to 10.
+ version = match ? match[1].replace(/_/g, '.') : '10';
+ } else if (goog.labs.userAgent.platform.isAndroid()) {
+ re = /Android\s+([^\);]+)(\)|;)/;
+ var match = re.exec(userAgentString);
+ version = match && match[1];
+ } else if (goog.labs.userAgent.platform.isChromeOS()) {
+ re = /(?:CrOS\s+(?:i686|x86_64)\s+([0-9.]+))/;
+ var match = re.exec(userAgentString);
+ version = match && match[1];
+ }
+ return version || '';
+};
+
+
+/**
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the browser version is higher or the same as the
+ * given version.
+ */
+goog.labs.userAgent.platform.isVersionOrHigher = function(version) {
+ return goog.string.compareVersions(goog.labs.userAgent.platform.getVersion(),
+ version) >= 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 Rendering engine detection.
+ * @see <a href="http://www.useragentstring.com/">User agent strings</a>
+ * For information on the browser brand (such as Safari versus Chrome), see
+ * goog.userAgent.product.
+ * @author arv@google.com (Erik Arvidsson)
+ * @see ../demos/useragent.html
+ */
+
+goog.provide('goog.userAgent');
+
+goog.require('goog.labs.userAgent.browser');
+goog.require('goog.labs.userAgent.engine');
+goog.require('goog.labs.userAgent.platform');
+goog.require('goog.labs.userAgent.util');
+goog.require('goog.string');
+
+
+/**
+ * @define {boolean} Whether we know at compile-time that the browser is IE.
+ */
+goog.define('goog.userAgent.ASSUME_IE', false);
+
+
+/**
+ * @define {boolean} Whether we know at compile-time that the browser is EDGE.
+ */
+goog.define('goog.userAgent.ASSUME_EDGE', false);
+
+
+/**
+ * @define {boolean} Whether we know at compile-time that the browser is GECKO.
+ */
+goog.define('goog.userAgent.ASSUME_GECKO', false);
+
+
+/**
+ * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
+ */
+goog.define('goog.userAgent.ASSUME_WEBKIT', false);
+
+
+/**
+ * @define {boolean} Whether we know at compile-time that the browser is a
+ * mobile device running WebKit e.g. iPhone or Android.
+ */
+goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false);
+
+
+/**
+ * @define {boolean} Whether we know at compile-time that the browser is OPERA.
+ */
+goog.define('goog.userAgent.ASSUME_OPERA', false);
+
+
+/**
+ * @define {boolean} Whether the
+ * {@code goog.userAgent.isVersionOrHigher}
+ * function will return true for any version.
+ */
+goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);
+
+
+/**
+ * Whether we know the browser engine at compile-time.
+ * @type {boolean}
+ * @private
+ */
+goog.userAgent.BROWSER_KNOWN_ =
+ goog.userAgent.ASSUME_IE ||
+ goog.userAgent.ASSUME_EDGE ||
+ goog.userAgent.ASSUME_GECKO ||
+ goog.userAgent.ASSUME_MOBILE_WEBKIT ||
+ goog.userAgent.ASSUME_WEBKIT ||
+ goog.userAgent.ASSUME_OPERA;
+
+
+/**
+ * Returns the userAgent string for the current browser.
+ *
+ * @return {string} The userAgent string.
+ */
+goog.userAgent.getUserAgentString = function() {
+ return goog.labs.userAgent.util.getUserAgent();
+};
+
+
+/**
+ * TODO(nnaze): Change type to "Navigator" and update compilation targets.
+ * @return {Object} The native navigator object.
+ */
+goog.userAgent.getNavigator = function() {
+ // Need a local navigator reference instead of using the global one,
+ // to avoid the rare case where they reference different objects.
+ // (in a WorkerPool, for example).
+ return goog.global['navigator'] || null;
+};
+
+
+/**
+ * Whether the user agent is Opera.
+ * @type {boolean}
+ */
+goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
+ goog.userAgent.ASSUME_OPERA :
+ goog.labs.userAgent.browser.isOpera();
+
+
+/**
+ * Whether the user agent is Internet Explorer.
+ * @type {boolean}
+ */
+goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
+ goog.userAgent.ASSUME_IE :
+ goog.labs.userAgent.browser.isIE();
+
+
+/**
+ * Whether the user agent is Microsoft Edge.
+ * @type {boolean}
+ */
+goog.userAgent.EDGE = goog.userAgent.BROWSER_KNOWN_ ?
+ goog.userAgent.ASSUME_EDGE :
+ goog.labs.userAgent.engine.isEdge();
+
+
+/**
+ * Whether the user agent is MS Internet Explorer or MS Edge.
+ * @type {boolean}
+ */
+goog.userAgent.EDGE_OR_IE = goog.userAgent.EDGE || goog.userAgent.IE;
+
+
+/**
+ * Whether the user agent is Gecko. Gecko is the rendering engine used by
+ * Mozilla, Firefox, and others.
+ * @type {boolean}
+ */
+goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
+ goog.userAgent.ASSUME_GECKO :
+ goog.labs.userAgent.engine.isGecko();
+
+
+/**
+ * Whether the user agent is WebKit. WebKit is the rendering engine that
+ * Safari, Android and others use.
+ * @type {boolean}
+ */
+goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
+ goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
+ goog.labs.userAgent.engine.isWebKit();
+
+
+/**
+ * Whether the user agent is running on a mobile device.
+ *
+ * This is a separate function so that the logic can be tested.
+ *
+ * TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile().
+ *
+ * @return {boolean} Whether the user agent is running on a mobile device.
+ * @private
+ */
+goog.userAgent.isMobile_ = function() {
+ return goog.userAgent.WEBKIT &&
+ goog.labs.userAgent.util.matchUserAgent('Mobile');
+};
+
+
+/**
+ * Whether the user agent is running on a mobile device.
+ *
+ * TODO(nnaze): Consider deprecating MOBILE when labs.userAgent
+ * is promoted as the gecko/webkit logic is likely inaccurate.
+ *
+ * @type {boolean}
+ */
+goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT ||
+ goog.userAgent.isMobile_();
+
+
+/**
+ * Used while transitioning code to use WEBKIT instead.
+ * @type {boolean}
+ * @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
+ * TODO(nicksantos): Delete this from goog.userAgent.
+ */
+goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
+
+
+/**
+ * @return {string} the platform (operating system) the user agent is running
+ * on. Default to empty string because navigator.platform may not be defined
+ * (on Rhino, for example).
+ * @private
+ */
+goog.userAgent.determinePlatform_ = function() {
+ var navigator = goog.userAgent.getNavigator();
+ return navigator && navigator.platform || '';
+};
+
+
+/**
+ * The platform (operating system) the user agent is running on. Default to
+ * empty string because navigator.platform may not be defined (on Rhino, for
+ * example).
+ * @type {string}
+ */
+goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
+
+
+/**
+ * @define {boolean} Whether the user agent is running on a Macintosh operating
+ * system.
+ */
+goog.define('goog.userAgent.ASSUME_MAC', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on a Windows operating
+ * system.
+ */
+goog.define('goog.userAgent.ASSUME_WINDOWS', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on a Linux operating
+ * system.
+ */
+goog.define('goog.userAgent.ASSUME_LINUX', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on a X11 windowing
+ * system.
+ */
+goog.define('goog.userAgent.ASSUME_X11', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on Android.
+ */
+goog.define('goog.userAgent.ASSUME_ANDROID', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on an iPhone.
+ */
+goog.define('goog.userAgent.ASSUME_IPHONE', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on an iPad.
+ */
+goog.define('goog.userAgent.ASSUME_IPAD', false);
+
+
+/**
+ * @type {boolean}
+ * @private
+ */
+goog.userAgent.PLATFORM_KNOWN_ =
+ goog.userAgent.ASSUME_MAC ||
+ goog.userAgent.ASSUME_WINDOWS ||
+ goog.userAgent.ASSUME_LINUX ||
+ goog.userAgent.ASSUME_X11 ||
+ goog.userAgent.ASSUME_ANDROID ||
+ goog.userAgent.ASSUME_IPHONE ||
+ goog.userAgent.ASSUME_IPAD;
+
+
+/**
+ * Whether the user agent is running on a Macintosh operating system.
+ * @type {boolean}
+ */
+goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_MAC : goog.labs.userAgent.platform.isMacintosh();
+
+
+/**
+ * Whether the user agent is running on a Windows operating system.
+ * @type {boolean}
+ */
+goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_WINDOWS :
+ goog.labs.userAgent.platform.isWindows();
+
+
+/**
+ * Whether the user agent is Linux per the legacy behavior of
+ * goog.userAgent.LINUX, which considered ChromeOS to also be
+ * Linux.
+ * @return {boolean}
+ * @private
+ */
+goog.userAgent.isLegacyLinux_ = function() {
+ return goog.labs.userAgent.platform.isLinux() ||
+ goog.labs.userAgent.platform.isChromeOS();
+};
+
+
+/**
+ * Whether the user agent is running on a Linux operating system.
+ *
+ * Note that goog.userAgent.LINUX considers ChromeOS to be Linux,
+ * while goog.labs.userAgent.platform considers ChromeOS and
+ * Linux to be different OSes.
+ *
+ * @type {boolean}
+ */
+goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_LINUX :
+ goog.userAgent.isLegacyLinux_();
+
+
+/**
+ * @return {boolean} Whether the user agent is an X11 windowing system.
+ * @private
+ */
+goog.userAgent.isX11_ = function() {
+ var navigator = goog.userAgent.getNavigator();
+ return !!navigator &&
+ goog.string.contains(navigator['appVersion'] || '', 'X11');
+};
+
+
+/**
+ * Whether the user agent is running on a X11 windowing system.
+ * @type {boolean}
+ */
+goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_X11 :
+ goog.userAgent.isX11_();
+
+
+/**
+ * Whether the user agent is running on Android.
+ * @type {boolean}
+ */
+goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_ANDROID :
+ goog.labs.userAgent.platform.isAndroid();
+
+
+/**
+ * Whether the user agent is running on an iPhone.
+ * @type {boolean}
+ */
+goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_IPHONE :
+ goog.labs.userAgent.platform.isIphone();
+
+
+/**
+ * Whether the user agent is running on an iPad.
+ * @type {boolean}
+ */
+goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_IPAD :
+ goog.labs.userAgent.platform.isIpad();
+
+
+/**
+ * @return {string} The string that describes the version number of the user
+ * agent.
+ * Assumes user agent is opera.
+ * @private
+ */
+goog.userAgent.operaVersion_ = function() {
+ var version = goog.global.opera.version;
+ try {
+ return version();
+ } catch (e) {
+ return version;
+ }
+};
+
+
+/**
+ * @return {string} The string that describes the version number of the user
+ * agent.
+ * @private
+ */
+goog.userAgent.determineVersion_ = function() {
+ // All browsers have different ways to detect the version and they all have
+ // different naming schemes.
+
+ if (goog.userAgent.OPERA && goog.global['opera']) {
+ return goog.userAgent.operaVersion_();
+ }
+
+ // version is a string rather than a number because it may contain 'b', 'a',
+ // and so on.
+ var version = '';
+ var arr = goog.userAgent.getVersionRegexResult_();
+ if (arr) {
+ version = arr ? arr[1] : '';
+ }
+
+ if (goog.userAgent.IE) {
+ // IE9 can be in document mode 9 but be reporting an inconsistent user agent
+ // version. If it is identifying as a version lower than 9 we take the
+ // documentMode as the version instead. IE8 has similar behavior.
+ // It is recommended to set the X-UA-Compatible header to ensure that IE9
+ // uses documentMode 9.
+ var docMode = goog.userAgent.getDocumentMode_();
+ if (docMode > parseFloat(version)) {
+ return String(docMode);
+ }
+ }
+
+ return version;
+};
+
+
+/**
+ * @return {Array|undefined} The version regex matches from parsing the user
+ * agent string. These regex statements must be executed inline so they can
+ * be compiled out by the closure compiler with the rest of the useragent
+ * detection logic when ASSUME_* is specified.
+ * @private
+ */
+goog.userAgent.getVersionRegexResult_ = function() {
+ var userAgent = goog.userAgent.getUserAgentString();
+ if (goog.userAgent.GECKO) {
+ return /rv\:([^\);]+)(\)|;)/.exec(userAgent);
+ }
+ if (goog.userAgent.EDGE) {
+ return /Edge\/([\d\.]+)/.exec(userAgent);
+ }
+ if (goog.userAgent.IE) {
+ return /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(userAgent);
+ }
+ if (goog.userAgent.WEBKIT) {
+ // WebKit/125.4
+ return /WebKit\/(\S+)/.exec(userAgent);
+ }
+};
+
+
+/**
+ * @return {number|undefined} Returns the document mode (for testing).
+ * @private
+ */
+goog.userAgent.getDocumentMode_ = function() {
+ // NOTE(user): goog.userAgent may be used in context where there is no DOM.
+ var doc = goog.global['document'];
+ return doc ? doc['documentMode'] : undefined;
+};
+
+
+/**
+ * The version of the user agent. This is a string because it might contain
+ * 'b' (as in beta) as well as multiple dots.
+ * @type {string}
+ */
+goog.userAgent.VERSION = goog.userAgent.determineVersion_();
+
+
+/**
+ * Compares two version numbers.
+ *
+ * @param {string} v1 Version of first item.
+ * @param {string} v2 Version of second item.
+ *
+ * @return {number} 1 if first argument is higher
+ * 0 if arguments are equal
+ * -1 if second argument is higher.
+ * @deprecated Use goog.string.compareVersions.
+ */
+goog.userAgent.compare = function(v1, v2) {
+ return goog.string.compareVersions(v1, v2);
+};
+
+
+/**
+ * Cache for {@link goog.userAgent.isVersionOrHigher}.
+ * Calls to compareVersions are surprisingly expensive and, as a browser's
+ * version number is unlikely to change during a session, we cache the results.
+ * @const
+ * @private
+ */
+goog.userAgent.isVersionOrHigherCache_ = {};
+
+
+/**
+ * Whether the user agent version is higher or the same as the given version.
+ * NOTE: When checking the version numbers for Firefox and Safari, be sure to
+ * use the engine's version, not the browser's version number. For example,
+ * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
+ * Opera and Internet Explorer versions match the product release number.<br>
+ * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
+ * Webkit</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
+ *
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the user agent version is higher or the same as
+ * the given version.
+ */
+goog.userAgent.isVersionOrHigher = function(version) {
+ return goog.userAgent.ASSUME_ANY_VERSION ||
+ goog.userAgent.isVersionOrHigherCache_[version] ||
+ (goog.userAgent.isVersionOrHigherCache_[version] =
+ goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0);
+};
+
+
+/**
+ * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the user agent version is higher or the same as
+ * the given version.
+ * @deprecated Use goog.userAgent.isVersionOrHigher().
+ */
+goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;
+
+
+/**
+ * Whether the IE effective document mode is higher or the same as the given
+ * document mode version.
+ * NOTE: Only for IE, return false for another browser.
+ *
+ * @param {number} documentMode The document mode version to check.
+ * @return {boolean} Whether the IE effective document mode is higher or the
+ * same as the given version.
+ */
+goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
+ return goog.userAgent.DOCUMENT_MODE >= documentMode;
+};
+
+
+/**
+ * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
+ * @param {number} version The version to check.
+ * @return {boolean} Whether the IE effective document mode is higher or the
+ * same as the given version.
+ * @deprecated Use goog.userAgent.isDocumentModeOrHigher().
+ */
+goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
+
+
+/**
+ * For IE version < 7, documentMode is undefined, so attempt to use the
+ * CSS1Compat property to see if we are in standards mode. If we are in
+ * standards mode, treat the browser version as the document mode. Otherwise,
+ * IE is emulating version 5.
+ * @type {number|undefined}
+ * @const
+ */
+goog.userAgent.DOCUMENT_MODE = (function() {
+ var doc = goog.global['document'];
+ var mode = goog.userAgent.getDocumentMode_();
+ if (!doc || !goog.userAgent.IE) {
+ return undefined;
+ }
+ return mode || (doc['compatMode'] == 'CSS1Compat' ?
+ parseInt(goog.userAgent.VERSION, 10) : 5);
+})();
+
+// 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 events package.
+ *
+ */
+
+
+goog.provide('goog.events.BrowserFeature');
+
+goog.require('goog.userAgent');
+
+
+/**
+ * Enum of browser capabilities.
+ * @enum {boolean}
+ */
+goog.events.BrowserFeature = {
+ /**
+ * Whether the button attribute of the event is W3C compliant. False in
+ * Internet Explorer prior to version 9; document-version dependent.
+ */
+ HAS_W3C_BUTTON: !goog.userAgent.IE ||
+ goog.userAgent.isDocumentModeOrHigher(9),
+
+ /**
+ * Whether the browser supports full W3C event model.
+ */
+ HAS_W3C_EVENT_SUPPORT: !goog.userAgent.IE ||
+ goog.userAgent.isDocumentModeOrHigher(9),
+
+ /**
+ * To prevent default in IE7-8 for certain keydown events we need set the
+ * keyCode to -1.
+ */
+ SET_KEY_CODE_TO_PREVENT_DEFAULT: goog.userAgent.IE &&
+ !goog.userAgent.isVersionOrHigher('9'),
+
+ /**
+ * Whether the {@code navigator.onLine} property is supported.
+ */
+ HAS_NAVIGATOR_ONLINE_PROPERTY: !goog.userAgent.WEBKIT ||
+ goog.userAgent.isVersionOrHigher('528'),
+
+ /**
+ * Whether HTML5 network online/offline events are supported.
+ */
+ HAS_HTML5_NETWORK_EVENT_SUPPORT:
+ goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9b') ||
+ goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8') ||
+ goog.userAgent.OPERA && goog.userAgent.isVersionOrHigher('9.5') ||
+ goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('528'),
+
+ /**
+ * Whether HTML5 network events fire on document.body, or otherwise the
+ * window.
+ */
+ HTML5_NETWORK_EVENTS_FIRE_ON_BODY:
+ goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('8') ||
+ goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'),
+
+ /**
+ * Whether touch is enabled in the browser.
+ */
+ TOUCH_ENABLED:
+ ('ontouchstart' in goog.global ||
+ !!(goog.global['document'] &&
+ document.documentElement &&
+ 'ontouchstart' in document.documentElement) ||
+ // IE10 uses non-standard touch events, so it has a different check.
+ !!(goog.global['navigator'] &&
+ goog.global['navigator']['msMaxTouchPoints']))
+};
+
+// 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 Definition of the disposable interface. A disposable object
+ * has a dispose method to to clean up references and resources.
+ * @author nnaze@google.com (Nathan Naze)
+ */
+
+
+goog.provide('goog.disposable.IDisposable');
+
+
+
+/**
+ * Interface for a disposable object. If a instance requires cleanup
+ * (references COM objects, DOM notes, or other disposable objects), it should
+ * implement this interface (it may subclass goog.Disposable).
+ * @interface
+ */
+goog.disposable.IDisposable = function() {};
+
+
+/**
+ * Disposes of the object and its resources.
+ * @return {void} Nothing.
+ */
+goog.disposable.IDisposable.prototype.dispose = goog.abstractMethod;
+
+
+/**
+ * @return {boolean} Whether the object has been disposed of.
+ */
+goog.disposable.IDisposable.prototype.isDisposed = goog.abstractMethod;
+
+// 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.
+
+/**
+ * @fileoverview Implements the disposable interface. The dispose method is used
+ * to clean up references and resources.
+ * @author arv@google.com (Erik Arvidsson)
+ */
+
+
+goog.provide('goog.Disposable');
+/** @suppress {extraProvide} */
+goog.provide('goog.dispose');
+/** @suppress {extraProvide} */
+goog.provide('goog.disposeAll');
+
+goog.require('goog.disposable.IDisposable');
+
+
+
+/**
+ * Class that provides the basic implementation for disposable objects. If your
+ * class holds one or more references to COM objects, DOM nodes, or other
+ * disposable objects, it should extend this class or implement the disposable
+ * interface (defined in goog.disposable.IDisposable).
+ * @constructor
+ * @implements {goog.disposable.IDisposable}
+ */
+goog.Disposable = function() {
+ if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
+ if (goog.Disposable.INCLUDE_STACK_ON_CREATION) {
+ this.creationStack = new Error().stack;
+ }
+ goog.Disposable.instances_[goog.getUid(this)] = this;
+ }
+ // Support sealing
+ this.disposed_ = this.disposed_;
+ this.onDisposeCallbacks_ = this.onDisposeCallbacks_;
+};
+
+
+/**
+ * @enum {number} Different monitoring modes for Disposable.
+ */
+goog.Disposable.MonitoringMode = {
+ /**
+ * No monitoring.
+ */
+ OFF: 0,
+ /**
+ * Creating and disposing the goog.Disposable instances is monitored. All
+ * disposable objects need to call the {@code goog.Disposable} base
+ * constructor. The PERMANENT mode must be switched on before creating any
+ * goog.Disposable instances.
+ */
+ PERMANENT: 1,
+ /**
+ * INTERACTIVE mode can be switched on and off on the fly without producing
+ * errors. It also doesn't warn if the disposable objects don't call the
+ * {@code goog.Disposable} base constructor.
+ */
+ INTERACTIVE: 2
+};
+
+
+/**
+ * @define {number} The monitoring mode of the goog.Disposable
+ * instances. Default is OFF. Switching on the monitoring is only
+ * recommended for debugging because it has a significant impact on
+ * performance and memory usage. If switched off, the monitoring code
+ * compiles down to 0 bytes.
+ */
+goog.define('goog.Disposable.MONITORING_MODE', 0);
+
+
+/**
+ * @define {boolean} Whether to attach creation stack to each created disposable
+ * instance; This is only relevant for when MonitoringMode != OFF.
+ */
+goog.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true);
+
+
+/**
+ * Maps the unique ID of every undisposed {@code goog.Disposable} object to
+ * the object itself.
+ * @type {!Object<number, !goog.Disposable>}
+ * @private
+ */
+goog.Disposable.instances_ = {};
+
+
+/**
+ * @return {!Array<!goog.Disposable>} All {@code goog.Disposable} objects that
+ * haven't been disposed of.
+ */
+goog.Disposable.getUndisposedObjects = function() {
+ var ret = [];
+ for (var id in goog.Disposable.instances_) {
+ if (goog.Disposable.instances_.hasOwnProperty(id)) {
+ ret.push(goog.Disposable.instances_[Number(id)]);
+ }
+ }
+ return ret;
+};
+
+
+/**
+ * Clears the registry of undisposed objects but doesn't dispose of them.
+ */
+goog.Disposable.clearUndisposedObjects = function() {
+ goog.Disposable.instances_ = {};
+};
+
+
+/**
+ * Whether the object has been disposed of.
+ * @type {boolean}
+ * @private
+ */
+goog.Disposable.prototype.disposed_ = false;
+
+
+/**
+ * Callbacks to invoke when this object is disposed.
+ * @type {Array<!Function>}
+ * @private
+ */
+goog.Disposable.prototype.onDisposeCallbacks_;
+
+
+/**
+ * If monitoring the goog.Disposable instances is enabled, stores the creation
+ * stack trace of the Disposable instance.
+ * @const {string}
+ */
+goog.Disposable.prototype.creationStack;
+
+
+/**
+ * @return {boolean} Whether the object has been disposed of.
+ * @override
+ */
+goog.Disposable.prototype.isDisposed = function() {
+ return this.disposed_;
+};
+
+
+/**
+ * @return {boolean} Whether the object has been disposed of.
+ * @deprecated Use {@link #isDisposed} instead.
+ */
+goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed;
+
+
+/**
+ * Disposes of the object. If the object hasn't already been disposed of, calls
+ * {@link #disposeInternal}. Classes that extend {@code goog.Disposable} should
+ * override {@link #disposeInternal} in order to delete references to COM
+ * objects, DOM nodes, and other disposable objects. Reentrant.
+ *
+ * @return {void} Nothing.
+ * @override
+ */
+goog.Disposable.prototype.dispose = function() {
+ if (!this.disposed_) {
+ // Set disposed_ to true first, in case during the chain of disposal this
+ // gets disposed recursively.
+ this.disposed_ = true;
+ this.disposeInternal();
+ if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
+ var uid = goog.getUid(this);
+ if (goog.Disposable.MONITORING_MODE ==
+ goog.Disposable.MonitoringMode.PERMANENT &&
+ !goog.Disposable.instances_.hasOwnProperty(uid)) {
+ throw Error(this + ' did not call the goog.Disposable base ' +
+ 'constructor or was disposed of after a clearUndisposedObjects ' +
+ 'call');
+ }
+ delete goog.Disposable.instances_[uid];
+ }
+ }
+};
+
+
+/**
+ * Associates a disposable object with this object so that they will be disposed
+ * together.
+ * @param {goog.disposable.IDisposable} disposable that will be disposed when
+ * this object is disposed.
+ */
+goog.Disposable.prototype.registerDisposable = function(disposable) {
+ this.addOnDisposeCallback(goog.partial(goog.dispose, disposable));
+};
+
+
+/**
+ * Invokes a callback function when this object is disposed. Callbacks are
+ * 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_ = [];
+ }
+
+ this.onDisposeCallbacks_.push(
+ goog.isDef(opt_scope) ? goog.bind(callback, opt_scope) : callback);
+};
+
+
+/**
+ * Deletes or nulls out any references to COM objects, DOM nodes, or other
+ * disposable objects. Classes that extend {@code goog.Disposable} should
+ * override this method.
+ * Not reentrant. To avoid calling it twice, it must only be called from the
+ * subclass' {@code disposeInternal} method. Everywhere else the public
+ * {@code dispose} method must be used.
+ * For example:
+ * <pre>
+ * mypackage.MyClass = function() {
+ * mypackage.MyClass.base(this, 'constructor');
+ * // Constructor logic specific to MyClass.
+ * ...
+ * };
+ * goog.inherits(mypackage.MyClass, goog.Disposable);
+ *
+ * mypackage.MyClass.prototype.disposeInternal = function() {
+ * // Dispose logic specific to MyClass.
+ * ...
+ * // Call superclass's disposeInternal at the end of the subclass's, like
+ * // in C++, to avoid hard-to-catch issues.
+ * mypackage.MyClass.base(this, 'disposeInternal');
+ * };
+ * </pre>
+ * @protected
+ */
+goog.Disposable.prototype.disposeInternal = function() {
+ if (this.onDisposeCallbacks_) {
+ while (this.onDisposeCallbacks_.length) {
+ this.onDisposeCallbacks_.shift()();
+ }
+ }
+};
+
+
+/**
+ * Returns True if we can verify the object is disposed.
+ * Calls {@code isDisposed} on the argument if it supports it. If obj
+ * is not an object with an isDisposed() method, return false.
+ * @param {*} obj The object to investigate.
+ * @return {boolean} True if we can verify the object is disposed.
+ */
+goog.Disposable.isDisposed = function(obj) {
+ if (obj && typeof obj.isDisposed == 'function') {
+ return obj.isDisposed();
+ }
+ return false;
+};
+
+
+/**
+ * Calls {@code dispose} on the argument if it supports it. If obj is not an
+ * object with a dispose() method, this is a no-op.
+ * @param {*} obj The object to dispose of.
+ */
+goog.dispose = function(obj) {
+ if (obj && typeof obj.dispose == 'function') {
+ obj.dispose();
+ }
+};
+
+
+/**
+ * Calls {@code dispose} on each member of the list that supports it. (If the
+ * member is an ArrayLike, then {@code goog.disposeAll()} will be called
+ * recursively on each of its members.) If the member is not an object with a
+ * {@code dispose()} method, then it is ignored.
+ * @param {...*} var_args The list.
+ */
+goog.disposeAll = function(var_args) {
+ for (var i = 0, len = arguments.length; i < len; ++i) {
+ var disposable = arguments[i];
+ if (goog.isArrayLike(disposable)) {
+ goog.disposeAll.apply(null, disposable);
+ } else {
+ goog.dispose(disposable);
+ }
+ }
+};
+
+// 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.events.EventId');
+
+
+
+/**
+ * A templated class that is used when registering for events. Typical usage:
+ * <code>
+ * /** @type {goog.events.EventId<MyEventObj>}
+ * var myEventId = new goog.events.EventId(
+ * goog.events.getUniqueId(('someEvent'));
+ *
+ * // No need to cast or declare here since the compiler knows the correct
+ * // type of 'evt' (MyEventObj).
+ * something.listen(myEventId, function(evt) {});
+ * </code>
+ *
+ * @param {string} eventId
+ * @template T
+ * @constructor
+ * @struct
+ * @final
+ */
+goog.events.EventId = function(eventId) {
+ /** @const */ this.id = eventId;
+};
+
+
+/**
+ * @override
+ */
+goog.events.EventId.prototype.toString = function() {
+ return this.id;
+};
+
+// 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.
+
+/**
+ * @fileoverview A base class for event objects.
+ *
+ */
+
+
+goog.provide('goog.events.Event');
+goog.provide('goog.events.EventLike');
+
+/**
+ * goog.events.Event no longer depends on goog.Disposable. Keep requiring
+ * goog.Disposable here to not break projects which assume this dependency.
+ * @suppress {extraRequire}
+ */
+goog.require('goog.Disposable');
+goog.require('goog.events.EventId');
+
+
+/**
+ * A typedef for event like objects that are dispatchable via the
+ * goog.events.dispatchEvent function. strings are treated as the type for a
+ * goog.events.Event. Objects are treated as an extension of a new
+ * goog.events.Event with the type property of the object being used as the type
+ * of the Event.
+ * @typedef {string|Object|goog.events.Event|goog.events.EventId}
+ */
+goog.events.EventLike;
+
+
+
+/**
+ * A base class for event objects, so that they can support preventDefault and
+ * stopPropagation.
+ *
+ * @param {string|!goog.events.EventId} type Event Type.
+ * @param {Object=} opt_target Reference to the object that is the target of
+ * this event. It has to implement the {@code EventTarget} interface
+ * declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}.
+ * @constructor
+ */
+goog.events.Event = function(type, opt_target) {
+ /**
+ * Event type.
+ * @type {string}
+ */
+ this.type = type instanceof goog.events.EventId ? String(type) : type;
+
+ /**
+ * TODO(tbreisacher): The type should probably be
+ * EventTarget|goog.events.EventTarget.
+ *
+ * Target of the event.
+ * @type {Object|undefined}
+ */
+ this.target = opt_target;
+
+ /**
+ * Object that had the listener attached.
+ * @type {Object|undefined}
+ */
+ this.currentTarget = this.target;
+
+ /**
+ * Whether to cancel the event in internal capture/bubble processing for IE.
+ * @type {boolean}
+ * @public
+ * @suppress {underscore|visibility} Technically public, but referencing this
+ * outside this package is strongly discouraged.
+ */
+ this.propagationStopped_ = false;
+
+ /**
+ * Whether the default action has been prevented.
+ * This is a property to match the W3C specification at
+ * {@link http://www.w3.org/TR/DOM-Level-3-Events/
+ * #events-event-type-defaultPrevented}.
+ * Must be treated as read-only outside the class.
+ * @type {boolean}
+ */
+ this.defaultPrevented = false;
+
+ /**
+ * Return value for in internal capture/bubble processing for IE.
+ * @type {boolean}
+ * @public
+ * @suppress {underscore|visibility} Technically public, but referencing this
+ * outside this package is strongly discouraged.
+ */
+ this.returnValue_ = true;
+};
+
+
+/**
+ * Stops event propagation.
+ */
+goog.events.Event.prototype.stopPropagation = function() {
+ this.propagationStopped_ = true;
+};
+
+
+/**
+ * Prevents the default action, for example a link redirecting to a url.
+ */
+goog.events.Event.prototype.preventDefault = function() {
+ this.defaultPrevented = true;
+ this.returnValue_ = false;
+};
+
+
+/**
+ * Stops the propagation of the event. It is equivalent to
+ * {@code e.stopPropagation()}, but can be used as the callback argument of
+ * {@link goog.events.listen} without declaring another function.
+ * @param {!goog.events.Event} e An event.
+ */
+goog.events.Event.stopPropagation = function(e) {
+ e.stopPropagation();
+};
+
+
+/**
+ * Prevents the default action. It is equivalent to
+ * {@code e.preventDefault()}, but can be used as the callback argument of
+ * {@link goog.events.listen} without declaring another function.
+ * @param {!goog.events.Event} e An event.
+ */
+goog.events.Event.preventDefault = function(e) {
+ e.preventDefault();
+};
+
+// 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 Event Types.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ */
+
+
+goog.provide('goog.events.EventType');
+
+goog.require('goog.userAgent');
+
+
+/**
+ * Returns a prefixed event name for the current browser.
+ * @param {string} eventName The name of the event.
+ * @return {string} The prefixed event name.
+ * @suppress {missingRequire|missingProvide}
+ * @private
+ */
+goog.events.getVendorPrefixedName_ = function(eventName) {
+ return goog.userAgent.WEBKIT ? 'webkit' + eventName :
+ (goog.userAgent.OPERA ? 'o' + eventName.toLowerCase() :
+ eventName.toLowerCase());
+};
+
+
+/**
+ * Constants for event names.
+ * @enum {string}
+ */
+goog.events.EventType = {
+ // Mouse events
+ CLICK: 'click',
+ RIGHTCLICK: 'rightclick',
+ DBLCLICK: 'dblclick',
+ MOUSEDOWN: 'mousedown',
+ MOUSEUP: 'mouseup',
+ MOUSEOVER: 'mouseover',
+ MOUSEOUT: 'mouseout',
+ MOUSEMOVE: 'mousemove',
+ MOUSEENTER: 'mouseenter',
+ MOUSELEAVE: 'mouseleave',
+ // Select start is non-standard.
+ // See http://msdn.microsoft.com/en-us/library/ie/ms536969(v=vs.85).aspx.
+ SELECTSTART: 'selectstart', // IE, Safari, Chrome
+
+ // Wheel events
+ // http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents
+ WHEEL: 'wheel',
+
+ // Key events
+ KEYPRESS: 'keypress',
+ KEYDOWN: 'keydown',
+ KEYUP: 'keyup',
+
+ // Focus
+ BLUR: 'blur',
+ FOCUS: 'focus',
+ DEACTIVATE: 'deactivate', // IE only
+ // NOTE: The following two events are not stable in cross-browser usage.
+ // WebKit and Opera implement DOMFocusIn/Out.
+ // IE implements focusin/out.
+ // Gecko implements neither see bug at
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=396927.
+ // The DOM Events Level 3 Draft deprecates DOMFocusIn in favor of focusin:
+ // http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html
+ // You can use FOCUS in Capture phase until implementations converge.
+ FOCUSIN: goog.userAgent.IE ? 'focusin' : 'DOMFocusIn',
+ FOCUSOUT: goog.userAgent.IE ? 'focusout' : 'DOMFocusOut',
+
+ // Forms
+ CHANGE: 'change',
+ RESET: 'reset',
+ SELECT: 'select',
+ SUBMIT: 'submit',
+ INPUT: 'input',
+ PROPERTYCHANGE: 'propertychange', // IE only
+
+ // Drag and drop
+ DRAGSTART: 'dragstart',
+ DRAG: 'drag',
+ DRAGENTER: 'dragenter',
+ DRAGOVER: 'dragover',
+ DRAGLEAVE: 'dragleave',
+ DROP: 'drop',
+ DRAGEND: 'dragend',
+
+ // Touch events
+ // Note that other touch events exist, but we should follow the W3C list here.
+ // http://www.w3.org/TR/touch-events/#list-of-touchevent-types
+ TOUCHSTART: 'touchstart',
+ TOUCHMOVE: 'touchmove',
+ TOUCHEND: 'touchend',
+ TOUCHCANCEL: 'touchcancel',
+
+ // Misc
+ BEFOREUNLOAD: 'beforeunload',
+ CONSOLEMESSAGE: 'consolemessage',
+ CONTEXTMENU: 'contextmenu',
+ DOMCONTENTLOADED: 'DOMContentLoaded',
+ ERROR: 'error',
+ HELP: 'help',
+ LOAD: 'load',
+ LOSECAPTURE: 'losecapture',
+ ORIENTATIONCHANGE: 'orientationchange',
+ READYSTATECHANGE: 'readystatechange',
+ RESIZE: 'resize',
+ SCROLL: 'scroll',
+ UNLOAD: 'unload',
+
+ // HTML 5 History events
+ // See http://www.w3.org/TR/html5/browsers.html#event-definitions-0
+ HASHCHANGE: 'hashchange',
+ PAGEHIDE: 'pagehide',
+ PAGESHOW: 'pageshow',
+ POPSTATE: 'popstate',
+
+ // Copy and Paste
+ // Support is limited. Make sure it works on your favorite browser
+ // before using.
+ // http://www.quirksmode.org/dom/events/cutcopypaste.html
+ COPY: 'copy',
+ PASTE: 'paste',
+ CUT: 'cut',
+ BEFORECOPY: 'beforecopy',
+ BEFORECUT: 'beforecut',
+ BEFOREPASTE: 'beforepaste',
+
+ // HTML5 online/offline events.
+ // http://www.w3.org/TR/offline-webapps/#related
+ ONLINE: 'online',
+ OFFLINE: 'offline',
+
+ // HTML 5 worker events
+ MESSAGE: 'message',
+ CONNECT: 'connect',
+
+ // CSS animation events.
+ /** @suppress {missingRequire} */
+ ANIMATIONSTART: goog.events.getVendorPrefixedName_('AnimationStart'),
+ /** @suppress {missingRequire} */
+ ANIMATIONEND: goog.events.getVendorPrefixedName_('AnimationEnd'),
+ /** @suppress {missingRequire} */
+ ANIMATIONITERATION: goog.events.getVendorPrefixedName_('AnimationIteration'),
+
+ // CSS transition events. Based on the browser support described at:
+ // https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility
+ /** @suppress {missingRequire} */
+ TRANSITIONEND: goog.events.getVendorPrefixedName_('TransitionEnd'),
+
+ // W3C Pointer Events
+ // http://www.w3.org/TR/pointerevents/
+ POINTERDOWN: 'pointerdown',
+ POINTERUP: 'pointerup',
+ POINTERCANCEL: 'pointercancel',
+ POINTERMOVE: 'pointermove',
+ POINTEROVER: 'pointerover',
+ POINTEROUT: 'pointerout',
+ POINTERENTER: 'pointerenter',
+ POINTERLEAVE: 'pointerleave',
+ GOTPOINTERCAPTURE: 'gotpointercapture',
+ LOSTPOINTERCAPTURE: 'lostpointercapture',
+
+ // IE specific events.
+ // See http://msdn.microsoft.com/en-us/library/ie/hh772103(v=vs.85).aspx
+ // Note: these events will be supplanted in IE11.
+ MSGESTURECHANGE: 'MSGestureChange',
+ MSGESTUREEND: 'MSGestureEnd',
+ MSGESTUREHOLD: 'MSGestureHold',
+ MSGESTURESTART: 'MSGestureStart',
+ MSGESTURETAP: 'MSGestureTap',
+ MSGOTPOINTERCAPTURE: 'MSGotPointerCapture',
+ MSINERTIASTART: 'MSInertiaStart',
+ MSLOSTPOINTERCAPTURE: 'MSLostPointerCapture',
+ MSPOINTERCANCEL: 'MSPointerCancel',
+ MSPOINTERDOWN: 'MSPointerDown',
+ MSPOINTERENTER: 'MSPointerEnter',
+ MSPOINTERHOVER: 'MSPointerHover',
+ MSPOINTERLEAVE: 'MSPointerLeave',
+ MSPOINTERMOVE: 'MSPointerMove',
+ MSPOINTEROUT: 'MSPointerOut',
+ MSPOINTEROVER: 'MSPointerOver',
+ MSPOINTERUP: 'MSPointerUp',
+
+ // Native IMEs/input tools events.
+ TEXT: 'text',
+ TEXTINPUT: 'textInput',
+ COMPOSITIONSTART: 'compositionstart',
+ COMPOSITIONUPDATE: 'compositionupdate',
+ COMPOSITIONEND: 'compositionend',
+
+ // Webview tag events
+ // See http://developer.chrome.com/dev/apps/webview_tag.html
+ EXIT: 'exit',
+ LOADABORT: 'loadabort',
+ LOADCOMMIT: 'loadcommit',
+ LOADREDIRECT: 'loadredirect',
+ LOADSTART: 'loadstart',
+ LOADSTOP: 'loadstop',
+ RESPONSIVE: 'responsive',
+ SIZECHANGED: 'sizechanged',
+ UNRESPONSIVE: 'unresponsive',
+
+ // HTML5 Page Visibility API. See details at
+ // {@code goog.labs.dom.PageVisibilityMonitor}.
+ VISIBILITYCHANGE: 'visibilitychange',
+
+ // LocalStorage event.
+ STORAGE: 'storage',
+
+ // DOM Level 2 mutation events (deprecated).
+ DOMSUBTREEMODIFIED: 'DOMSubtreeModified',
+ DOMNODEINSERTED: 'DOMNodeInserted',
+ DOMNODEREMOVED: 'DOMNodeRemoved',
+ DOMNODEREMOVEDFROMDOCUMENT: 'DOMNodeRemovedFromDocument',
+ DOMNODEINSERTEDINTODOCUMENT: 'DOMNodeInsertedIntoDocument',
+ DOMATTRMODIFIED: 'DOMAttrModified',
+ DOMCHARACTERDATAMODIFIED: 'DOMCharacterDataModified',
+
+ // Print events.
+ BEFOREPRINT: 'beforeprint',
+ AFTERPRINT: 'afterprint'
+};
+
+// Copyright 2009 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 Useful compiler idioms.
+ *
+ * @author johnlenz@google.com (John Lenz)
+ */
+
+goog.provide('goog.reflect');
+
+
+/**
+ * Syntax for object literal casts.
+ * @see http://go/jscompiler-renaming
+ * @see https://github.com/google/closure-compiler/wiki/Type-Based-Property-Renaming
+ *
+ * Use this if you have an object literal whose keys need to have the same names
+ * as the properties of some class even after they are renamed by the compiler.
+ *
+ * @param {!Function} type Type to cast to.
+ * @param {Object} object Object literal to cast.
+ * @return {Object} The object literal.
+ */
+goog.reflect.object = function(type, object) {
+ return object;
+};
+
+
+/**
+ * To assert to the compiler that an operation is needed when it would
+ * otherwise be stripped. For example:
+ * <code>
+ * // Force a layout
+ * goog.reflect.sinkValue(dialog.offsetHeight);
+ * </code>
+ * @type {!Function}
+ */
+goog.reflect.sinkValue = function(x) {
+ goog.reflect.sinkValue[' '](x);
+ return x;
+};
+
+
+/**
+ * The compiler should optimize this function away iff no one ever uses
+ * goog.reflect.sinkValue.
+ */
+goog.reflect.sinkValue[' '] = goog.nullFunction;
+
+
+/**
+ * Check if a property can be accessed without throwing an exception.
+ * @param {Object} obj The owner of the property.
+ * @param {string} prop The property name.
+ * @return {boolean} Whether the property is accessible. Will also return true
+ * if obj is null.
+ */
+goog.reflect.canAccessProperty = function(obj, prop) {
+ /** @preserveTry */
+ try {
+ goog.reflect.sinkValue(obj[prop]);
+ return true;
+ } catch (e) {}
+ return false;
+};
+
+// 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.
+
+/**
+ * @fileoverview A patched, standardized event object for browser events.
+ *
+ * <pre>
+ * The patched event object contains the following members:
+ * - type {string} Event type, e.g. 'click'
+ * - target {Object} The element that actually triggered the event
+ * - currentTarget {Object} The element the listener is attached to
+ * - relatedTarget {Object} For mouseover and mouseout, the previous object
+ * - offsetX {number} X-coordinate relative to target
+ * - offsetY {number} Y-coordinate relative to target
+ * - clientX {number} X-coordinate relative to viewport
+ * - clientY {number} Y-coordinate relative to viewport
+ * - screenX {number} X-coordinate relative to the edge of the screen
+ * - screenY {number} Y-coordinate relative to the edge of the screen
+ * - button {number} Mouse button. Use isButton() to test.
+ * - keyCode {number} Key-code
+ * - ctrlKey {boolean} Was ctrl key depressed
+ * - altKey {boolean} Was alt key depressed
+ * - shiftKey {boolean} Was shift key depressed
+ * - metaKey {boolean} Was meta key depressed
+ * - defaultPrevented {boolean} Whether the default action has been prevented
+ * - state {Object} History state object
+ *
+ * NOTE: The keyCode member contains the raw browser keyCode. For normalized
+ * key and character code use {@link goog.events.KeyHandler}.
+ * </pre>
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ */
+
+goog.provide('goog.events.BrowserEvent');
+goog.provide('goog.events.BrowserEvent.MouseButton');
+
+goog.require('goog.events.BrowserFeature');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventType');
+goog.require('goog.reflect');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * Accepts a browser event object and creates a patched, cross browser event
+ * object.
+ * The content of this object will not be initialized if no event object is
+ * provided. If this is the case, init() needs to be invoked separately.
+ * @param {Event=} opt_e Browser event object.
+ * @param {EventTarget=} opt_currentTarget Current target for event.
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+goog.events.BrowserEvent = function(opt_e, opt_currentTarget) {
+ goog.events.BrowserEvent.base(this, 'constructor', opt_e ? opt_e.type : '');
+
+ /**
+ * Target that fired the event.
+ * @override
+ * @type {Node}
+ */
+ this.target = null;
+
+ /**
+ * Node that had the listener attached.
+ * @override
+ * @type {Node|undefined}
+ */
+ this.currentTarget = null;
+
+ /**
+ * For mouseover and mouseout events, the related object for the event.
+ * @type {Node}
+ */
+ this.relatedTarget = null;
+
+ /**
+ * X-coordinate relative to target.
+ * @type {number}
+ */
+ this.offsetX = 0;
+
+ /**
+ * Y-coordinate relative to target.
+ * @type {number}
+ */
+ this.offsetY = 0;
+
+ /**
+ * X-coordinate relative to the window.
+ * @type {number}
+ */
+ this.clientX = 0;
+
+ /**
+ * Y-coordinate relative to the window.
+ * @type {number}
+ */
+ this.clientY = 0;
+
+ /**
+ * X-coordinate relative to the monitor.
+ * @type {number}
+ */
+ this.screenX = 0;
+
+ /**
+ * Y-coordinate relative to the monitor.
+ * @type {number}
+ */
+ this.screenY = 0;
+
+ /**
+ * Which mouse button was pressed.
+ * @type {number}
+ */
+ this.button = 0;
+
+ /**
+ * Keycode of key press.
+ * @type {number}
+ */
+ this.keyCode = 0;
+
+ /**
+ * Keycode of key press.
+ * @type {number}
+ */
+ this.charCode = 0;
+
+ /**
+ * Whether control was pressed at time of event.
+ * @type {boolean}
+ */
+ this.ctrlKey = false;
+
+ /**
+ * Whether alt was pressed at time of event.
+ * @type {boolean}
+ */
+ this.altKey = false;
+
+ /**
+ * Whether shift was pressed at time of event.
+ * @type {boolean}
+ */
+ this.shiftKey = false;
+
+ /**
+ * Whether the meta key was pressed at time of event.
+ * @type {boolean}
+ */
+ this.metaKey = false;
+
+ /**
+ * History state object, only set for PopState events where it's a copy of the
+ * state object provided to pushState or replaceState.
+ * @type {Object}
+ */
+ this.state = null;
+
+ /**
+ * Whether the default platform modifier key was pressed at time of event.
+ * (This is control for all platforms except Mac, where it's Meta.)
+ * @type {boolean}
+ */
+ this.platformModifierKey = false;
+
+ /**
+ * The browser event object.
+ * @private {Event}
+ */
+ this.event_ = null;
+
+ if (opt_e) {
+ this.init(opt_e, opt_currentTarget);
+ }
+};
+goog.inherits(goog.events.BrowserEvent, goog.events.Event);
+
+
+/**
+ * Normalized button constants for the mouse.
+ * @enum {number}
+ */
+goog.events.BrowserEvent.MouseButton = {
+ LEFT: 0,
+ MIDDLE: 1,
+ RIGHT: 2
+};
+
+
+/**
+ * Static data for mapping mouse buttons.
+ * @type {!Array<number>}
+ */
+goog.events.BrowserEvent.IEButtonMap = [
+ 1, // LEFT
+ 4, // MIDDLE
+ 2 // RIGHT
+];
+
+
+/**
+ * Accepts a browser event object and creates a patched, cross browser event
+ * object.
+ * @param {Event} e Browser event object.
+ * @param {EventTarget=} opt_currentTarget Current target for event.
+ */
+goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) {
+ var type = this.type = e.type;
+
+ /**
+ * On touch devices use the first "changed touch" as the relevant touch.
+ * @type {Touch}
+ */
+ var relevantTouch = e.changedTouches ? e.changedTouches[0] : null;
+
+ // TODO(nicksantos): Change this.target to type EventTarget.
+ this.target = /** @type {Node} */ (e.target) || e.srcElement;
+
+ // TODO(nicksantos): Change this.currentTarget to type EventTarget.
+ this.currentTarget = /** @type {Node} */ (opt_currentTarget);
+
+ var relatedTarget = /** @type {Node} */ (e.relatedTarget);
+ if (relatedTarget) {
+ // There's a bug in FireFox where sometimes, relatedTarget will be a
+ // chrome element, and accessing any property of it will get a permission
+ // denied exception. See:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=497780
+ if (goog.userAgent.GECKO) {
+ if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) {
+ relatedTarget = null;
+ }
+ }
+ // TODO(arv): Use goog.events.EventType when it has been refactored into its
+ // own file.
+ } else if (type == goog.events.EventType.MOUSEOVER) {
+ relatedTarget = e.fromElement;
+ } else if (type == goog.events.EventType.MOUSEOUT) {
+ relatedTarget = e.toElement;
+ }
+
+ this.relatedTarget = relatedTarget;
+
+ if (!goog.isNull(relevantTouch)) {
+ this.clientX = relevantTouch.clientX !== undefined ?
+ relevantTouch.clientX : relevantTouch.pageX;
+ this.clientY = relevantTouch.clientY !== undefined ?
+ relevantTouch.clientY : relevantTouch.pageY;
+ this.screenX = relevantTouch.screenX || 0;
+ this.screenY = relevantTouch.screenY || 0;
+ } else {
+ // Webkit emits a lame warning whenever layerX/layerY is accessed.
+ // http://code.google.com/p/chromium/issues/detail?id=101733
+ this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ?
+ e.offsetX : e.layerX;
+ this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ?
+ e.offsetY : e.layerY;
+ this.clientX = e.clientX !== undefined ? e.clientX : e.pageX;
+ this.clientY = e.clientY !== undefined ? e.clientY : e.pageY;
+ this.screenX = e.screenX || 0;
+ this.screenY = e.screenY || 0;
+ }
+
+ this.button = e.button;
+
+ this.keyCode = e.keyCode || 0;
+ this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0);
+ this.ctrlKey = e.ctrlKey;
+ this.altKey = e.altKey;
+ this.shiftKey = e.shiftKey;
+ this.metaKey = e.metaKey;
+ this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey;
+ this.state = e.state;
+ this.event_ = e;
+ if (e.defaultPrevented) {
+ this.preventDefault();
+ }
+};
+
+
+/**
+ * Tests to see which button was pressed during the event. This is really only
+ * useful in IE and Gecko browsers. And in IE, it's only useful for
+ * mousedown/mouseup events, because click only fires for the left mouse button.
+ *
+ * Safari 2 only reports the left button being clicked, and uses the value '1'
+ * instead of 0. Opera only reports a mousedown event for the middle button, and
+ * no mouse events for the right button. Opera has default behavior for left and
+ * middle click that can only be overridden via a configuration setting.
+ *
+ * There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html.
+ *
+ * @param {goog.events.BrowserEvent.MouseButton} button The button
+ * to test for.
+ * @return {boolean} True if button was pressed.
+ */
+goog.events.BrowserEvent.prototype.isButton = function(button) {
+ if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) {
+ if (this.type == 'click') {
+ return button == goog.events.BrowserEvent.MouseButton.LEFT;
+ } else {
+ return !!(this.event_.button &
+ goog.events.BrowserEvent.IEButtonMap[button]);
+ }
+ } else {
+ return this.event_.button == button;
+ }
+};
+
+
+/**
+ * Whether this has an "action"-producing mouse button.
+ *
+ * By definition, this includes left-click on windows/linux, and left-click
+ * without the ctrl key on Macs.
+ *
+ * @return {boolean} The result.
+ */
+goog.events.BrowserEvent.prototype.isMouseActionButton = function() {
+ // Webkit does not ctrl+click to be a right-click, so we
+ // normalize it to behave like Gecko and Opera.
+ return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) &&
+ !(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey);
+};
+
+
+/**
+ * @override
+ */
+goog.events.BrowserEvent.prototype.stopPropagation = function() {
+ goog.events.BrowserEvent.superClass_.stopPropagation.call(this);
+ if (this.event_.stopPropagation) {
+ this.event_.stopPropagation();
+ } else {
+ this.event_.cancelBubble = true;
+ }
+};
+
+
+/**
+ * @override
+ */
+goog.events.BrowserEvent.prototype.preventDefault = function() {
+ goog.events.BrowserEvent.superClass_.preventDefault.call(this);
+ var be = this.event_;
+ if (!be.preventDefault) {
+ be.returnValue = false;
+ if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) {
+ /** @preserveTry */
+ try {
+ // Most keys can be prevented using returnValue. Some special keys
+ // require setting the keyCode to -1 as well:
+ //
+ // In IE7:
+ // F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6)
+ //
+ // In IE8:
+ // Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event)
+ //
+ // We therefore do this for all function keys as well as when Ctrl key
+ // is pressed.
+ var VK_F1 = 112;
+ var VK_F12 = 123;
+ if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) {
+ be.keyCode = -1;
+ }
+ } catch (ex) {
+ // IE throws an 'access denied' exception when trying to change
+ // keyCode in some situations (e.g. srcElement is input[type=file],
+ // or srcElement is an anchor tag rewritten by parent's innerHTML).
+ // Do nothing in this case.
+ }
+ }
+ } else {
+ be.preventDefault();
+ }
+};
+
+
+/**
+ * @return {Event} The underlying browser event object.
+ */
+goog.events.BrowserEvent.prototype.getBrowserEvent = function() {
+ return this.event_;
+};
+
+// 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 An interface for a listenable JavaScript object.
+ * @author chrishenry@google.com (Chris Henry)
+ */
+
+goog.provide('goog.events.Listenable');
+goog.provide('goog.events.ListenableKey');
+
+/** @suppress {extraRequire} */
+goog.require('goog.events.EventId');
+
+
+
+/**
+ * A listenable interface. A listenable is an object with the ability
+ * to dispatch/broadcast events to "event listeners" registered via
+ * listen/listenOnce.
+ *
+ * The interface allows for an event propagation mechanism similar
+ * to one offered by native browser event targets, such as
+ * capture/bubble mechanism, stopping propagation, and preventing
+ * default actions. Capture/bubble mechanism depends on the ancestor
+ * tree constructed via {@code #getParentEventTarget}; this tree
+ * must be directed acyclic graph. The meaning of default action(s)
+ * in preventDefault is specific to a particular use case.
+ *
+ * Implementations that do not support capture/bubble or can not have
+ * a parent listenable can simply not implement any ability to set the
+ * parent listenable (and have {@code #getParentEventTarget} return
+ * null).
+ *
+ * Implementation of this class can be used with or independently from
+ * goog.events.
+ *
+ * Implementation must call {@code #addImplementation(implClass)}.
+ *
+ * @interface
+ * @see goog.events
+ * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html
+ */
+goog.events.Listenable = function() {};
+
+
+/**
+ * An expando property to indicate that an object implements
+ * goog.events.Listenable.
+ *
+ * See addImplementation/isImplementedBy.
+ *
+ * @type {string}
+ * @const
+ */
+goog.events.Listenable.IMPLEMENTED_BY_PROP =
+ 'closure_listenable_' + ((Math.random() * 1e6) | 0);
+
+
+/**
+ * Marks a given class (constructor) as an implementation of
+ * Listenable, do that we can query that fact at runtime. The class
+ * must have already implemented the interface.
+ * @param {!Function} cls The class constructor. The corresponding
+ * class must have already implemented the interface.
+ */
+goog.events.Listenable.addImplementation = function(cls) {
+ cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true;
+};
+
+
+/**
+ * @param {Object} obj The object to check.
+ * @return {boolean} Whether a given instance implements Listenable. The
+ * class/superclass of the instance must call addImplementation.
+ */
+goog.events.Listenable.isImplementedBy = function(obj) {
+ return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]);
+};
+
+
+/**
+ * Adds an event listener. A listener can only be added once to an
+ * object and if it is added again the key for the listener is
+ * returned. Note that if the existing listener is a one-off listener
+ * (registered via listenOnce), it will no longer be a one-off
+ * listener after a call to listen().
+ *
+ * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
+ * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
+ * method.
+ * @param {boolean=} opt_useCapture Whether to fire in capture phase
+ * (defaults to false).
+ * @param {SCOPE=} opt_listenerScope Object in whose scope to call the
+ * listener.
+ * @return {goog.events.ListenableKey} Unique key for the listener.
+ * @template SCOPE,EVENTOBJ
+ */
+goog.events.Listenable.prototype.listen;
+
+
+/**
+ * Adds an event listener that is removed automatically after the
+ * listener fired once.
+ *
+ * If an existing listener already exists, listenOnce will do
+ * nothing. In particular, if the listener was previously registered
+ * via listen(), listenOnce() will not turn the listener into a
+ * one-off listener. Similarly, if there is already an existing
+ * one-off listener, listenOnce does not modify the listeners (it is
+ * still a once listener).
+ *
+ * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
+ * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
+ * method.
+ * @param {boolean=} opt_useCapture Whether to fire in capture phase
+ * (defaults to false).
+ * @param {SCOPE=} opt_listenerScope Object in whose scope to call the
+ * listener.
+ * @return {goog.events.ListenableKey} Unique key for the listener.
+ * @template SCOPE,EVENTOBJ
+ */
+goog.events.Listenable.prototype.listenOnce;
+
+
+/**
+ * Removes an event listener which was added with listen() or listenOnce().
+ *
+ * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
+ * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
+ * method.
+ * @param {boolean=} opt_useCapture Whether to fire in capture phase
+ * (defaults to false).
+ * @param {SCOPE=} opt_listenerScope Object in whose scope to call
+ * the listener.
+ * @return {boolean} Whether any listener was removed.
+ * @template SCOPE,EVENTOBJ
+ */
+goog.events.Listenable.prototype.unlisten;
+
+
+/**
+ * Removes an event listener which was added with listen() by the key
+ * returned by listen().
+ *
+ * @param {goog.events.ListenableKey} key The key returned by
+ * listen() or listenOnce().
+ * @return {boolean} Whether any listener was removed.
+ */
+goog.events.Listenable.prototype.unlistenByKey;
+
+
+/**
+ * Dispatches an event (or event like object) and calls all listeners
+ * listening for events of this type. The type of the event is decided by the
+ * type property on the event object.
+ *
+ * If any of the listeners returns false OR calls preventDefault then this
+ * function will return false. If one of the capture listeners calls
+ * stopPropagation, then the bubble listeners won't fire.
+ *
+ * @param {goog.events.EventLike} e Event object.
+ * @return {boolean} If anyone called preventDefault on the event object (or
+ * if any of the listeners returns false) this will also return false.
+ */
+goog.events.Listenable.prototype.dispatchEvent;
+
+
+/**
+ * Removes all listeners from this listenable. If type is specified,
+ * it will only remove listeners of the particular type. otherwise all
+ * registered listeners will be removed.
+ *
+ * @param {string=} opt_type Type of event to remove, default is to
+ * remove all types.
+ * @return {number} Number of listeners removed.
+ */
+goog.events.Listenable.prototype.removeAllListeners;
+
+
+/**
+ * Returns the parent of this event target to use for capture/bubble
+ * mechanism.
+ *
+ * NOTE(chrishenry): The name reflects the original implementation of
+ * custom event target ({@code goog.events.EventTarget}). We decided
+ * that changing the name is not worth it.
+ *
+ * @return {goog.events.Listenable} The parent EventTarget or null if
+ * there is no parent.
+ */
+goog.events.Listenable.prototype.getParentEventTarget;
+
+
+/**
+ * Fires all registered listeners in this listenable for the given
+ * type and capture mode, passing them the given eventObject. This
+ * does not perform actual capture/bubble. Only implementors of the
+ * interface should be using this.
+ *
+ * @param {string|!goog.events.EventId<EVENTOBJ>} type The type of the
+ * listeners to fire.
+ * @param {boolean} capture The capture mode of the listeners to fire.
+ * @param {EVENTOBJ} eventObject The event object to fire.
+ * @return {boolean} Whether all listeners succeeded without
+ * attempting to prevent default behavior. If any listener returns
+ * false or called goog.events.Event#preventDefault, this returns
+ * false.
+ * @template EVENTOBJ
+ */
+goog.events.Listenable.prototype.fireListeners;
+
+
+/**
+ * Gets all listeners in this listenable for the given type and
+ * capture mode.
+ *
+ * @param {string|!goog.events.EventId} type The type of the listeners to fire.
+ * @param {boolean} capture The capture mode of the listeners to fire.
+ * @return {!Array<goog.events.ListenableKey>} An array of registered
+ * listeners.
+ * @template EVENTOBJ
+ */
+goog.events.Listenable.prototype.getListeners;
+
+
+/**
+ * Gets the goog.events.ListenableKey for the event or null if no such
+ * listener is in use.
+ *
+ * @param {string|!goog.events.EventId<EVENTOBJ>} type The name of the event
+ * without the 'on' prefix.
+ * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The
+ * listener function to get.
+ * @param {boolean} capture Whether the listener is a capturing listener.
+ * @param {SCOPE=} opt_listenerScope Object in whose scope to call the
+ * listener.
+ * @return {goog.events.ListenableKey} the found listener or null if not found.
+ * @template SCOPE,EVENTOBJ
+ */
+goog.events.Listenable.prototype.getListener;
+
+
+/**
+ * Whether there is any active listeners matching the specified
+ * signature. If either the type or capture parameters are
+ * unspecified, the function will match on the remaining criteria.
+ *
+ * @param {string|!goog.events.EventId<EVENTOBJ>=} opt_type Event type.
+ * @param {boolean=} opt_capture Whether to check for capture or bubble
+ * listeners.
+ * @return {boolean} Whether there is any active listeners matching
+ * the requested type and/or capture phase.
+ * @template EVENTOBJ
+ */
+goog.events.Listenable.prototype.hasListener;
+
+
+
+/**
+ * An interface that describes a single registered listener.
+ * @interface
+ */
+goog.events.ListenableKey = function() {};
+
+
+/**
+ * Counter used to create a unique key
+ * @type {number}
+ * @private
+ */
+goog.events.ListenableKey.counter_ = 0;
+
+
+/**
+ * Reserves a key to be used for ListenableKey#key field.
+ * @return {number} A number to be used to fill ListenableKey#key
+ * field.
+ */
+goog.events.ListenableKey.reserveKey = function() {
+ return ++goog.events.ListenableKey.counter_;
+};
+
+
+/**
+ * The source event target.
+ * @type {!(Object|goog.events.Listenable|goog.events.EventTarget)}
+ */
+goog.events.ListenableKey.prototype.src;
+
+
+/**
+ * The event type the listener is listening to.
+ * @type {string}
+ */
+goog.events.ListenableKey.prototype.type;
+
+
+/**
+ * The listener function.
+ * @type {function(?):?|{handleEvent:function(?):?}|null}
+ */
+goog.events.ListenableKey.prototype.listener;
+
+
+/**
+ * Whether the listener works on capture phase.
+ * @type {boolean}
+ */
+goog.events.ListenableKey.prototype.capture;
+
+
+/**
+ * The 'this' object for the listener function's scope.
+ * @type {Object}
+ */
+goog.events.ListenableKey.prototype.handler;
+
+
+/**
+ * A globally unique number to identify the key.
+ * @type {number}
+ */
+goog.events.ListenableKey.prototype.key;
+
+// 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.
+
+/**
+ * @fileoverview Listener object.
+ * @see ../demos/events.html
+ */
+
+goog.provide('goog.events.Listener');
+
+goog.require('goog.events.ListenableKey');
+
+
+
+/**
+ * Simple class that stores information about a listener
+ * @param {!Function} listener Callback function.
+ * @param {Function} proxy Wrapper for the listener that patches the event.
+ * @param {EventTarget|goog.events.Listenable} src Source object for
+ * the event.
+ * @param {string} type Event type.
+ * @param {boolean} capture Whether in capture or bubble phase.
+ * @param {Object=} opt_handler Object in whose context to execute the callback.
+ * @implements {goog.events.ListenableKey}
+ * @constructor
+ */
+goog.events.Listener = function(
+ listener, proxy, src, type, capture, opt_handler) {
+ if (goog.events.Listener.ENABLE_MONITORING) {
+ this.creationStack = new Error().stack;
+ }
+
+ /**
+ * Callback function.
+ * @type {Function}
+ */
+ this.listener = listener;
+
+ /**
+ * A wrapper over the original listener. This is used solely to
+ * handle native browser events (it is used to simulate the capture
+ * phase and to patch the event object).
+ * @type {Function}
+ */
+ this.proxy = proxy;
+
+ /**
+ * Object or node that callback is listening to
+ * @type {EventTarget|goog.events.Listenable}
+ */
+ this.src = src;
+
+ /**
+ * The event type.
+ * @const {string}
+ */
+ this.type = type;
+
+ /**
+ * Whether the listener is being called in the capture or bubble phase
+ * @const {boolean}
+ */
+ this.capture = !!capture;
+
+ /**
+ * Optional object whose context to execute the listener in
+ * @type {Object|undefined}
+ */
+ this.handler = opt_handler;
+
+ /**
+ * The key of the listener.
+ * @const {number}
+ * @override
+ */
+ this.key = goog.events.ListenableKey.reserveKey();
+
+ /**
+ * Whether to remove the listener after it has been called.
+ * @type {boolean}
+ */
+ this.callOnce = false;
+
+ /**
+ * Whether the listener has been removed.
+ * @type {boolean}
+ */
+ this.removed = false;
+};
+
+
+/**
+ * @define {boolean} Whether to enable the monitoring of the
+ * goog.events.Listener instances. Switching on the monitoring is only
+ * recommended for debugging because it has a significant impact on
+ * performance and memory usage. If switched off, the monitoring code
+ * compiles down to 0 bytes.
+ */
+goog.define('goog.events.Listener.ENABLE_MONITORING', false);
+
+
+/**
+ * If monitoring the goog.events.Listener instances is enabled, stores the
+ * creation stack trace of the Disposable instance.
+ * @type {string}
+ */
+goog.events.Listener.prototype.creationStack;
+
+
+/**
+ * Marks this listener as removed. This also remove references held by
+ * this listener object (such as listener and event source).
+ */
+goog.events.Listener.prototype.markAsRemoved = function() {
+ this.removed = true;
+ this.listener = null;
+ this.proxy = null;
+ this.src = null;
+ this.handler = 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.
+
+/**
+ * @fileoverview A map of listeners that provides utility functions to
+ * deal with listeners on an event target. Used by
+ * {@code goog.events.EventTarget}.
+ *
+ * WARNING: Do not use this class from outside goog.events package.
+ *
+ * @visibility {//closure/goog/bin/sizetests:__pkg__}
+ * @visibility {//closure/goog/events:__pkg__}
+ * @visibility {//closure/goog/labs/events:__pkg__}
+ */
+
+goog.provide('goog.events.ListenerMap');
+
+goog.require('goog.array');
+goog.require('goog.events.Listener');
+goog.require('goog.object');
+
+
+
+/**
+ * Creates a new listener map.
+ * @param {EventTarget|goog.events.Listenable} src The src object.
+ * @constructor
+ * @final
+ */
+goog.events.ListenerMap = function(src) {
+ /** @type {EventTarget|goog.events.Listenable} */
+ this.src = src;
+
+ /**
+ * Maps of event type to an array of listeners.
+ * @type {Object<string, !Array<!goog.events.Listener>>}
+ */
+ this.listeners = {};
+
+ /**
+ * The count of types in this map that have registered listeners.
+ * @private {number}
+ */
+ this.typeCount_ = 0;
+};
+
+
+/**
+ * @return {number} The count of event types in this map that actually
+ * have registered listeners.
+ */
+goog.events.ListenerMap.prototype.getTypeCount = function() {
+ return this.typeCount_;
+};
+
+
+/**
+ * @return {number} Total number of registered listeners.
+ */
+goog.events.ListenerMap.prototype.getListenerCount = function() {
+ var count = 0;
+ for (var type in this.listeners) {
+ count += this.listeners[type].length;
+ }
+ return count;
+};
+
+
+/**
+ * Adds an event listener. A listener can only be added once to an
+ * object and if it is added again the key for the listener is
+ * returned.
+ *
+ * Note that a one-off listener will not change an existing listener,
+ * if any. On the other hand a normal listener will change existing
+ * one-off listener to become a normal listener.
+ *
+ * @param {string|!goog.events.EventId} type The listener event type.
+ * @param {!Function} listener This listener callback method.
+ * @param {boolean} callOnce Whether the listener is a one-off
+ * listener.
+ * @param {boolean=} opt_useCapture The capture mode of the listener.
+ * @param {Object=} opt_listenerScope Object in whose scope to call the
+ * listener.
+ * @return {goog.events.ListenableKey} Unique key for the listener.
+ */
+goog.events.ListenerMap.prototype.add = function(
+ type, listener, callOnce, opt_useCapture, opt_listenerScope) {
+ var typeStr = type.toString();
+ var listenerArray = this.listeners[typeStr];
+ if (!listenerArray) {
+ listenerArray = this.listeners[typeStr] = [];
+ this.typeCount_++;
+ }
+
+ var listenerObj;
+ var index = goog.events.ListenerMap.findListenerIndex_(
+ listenerArray, listener, opt_useCapture, opt_listenerScope);
+ if (index > -1) {
+ listenerObj = listenerArray[index];
+ if (!callOnce) {
+ // Ensure that, if there is an existing callOnce listener, it is no
+ // longer a callOnce listener.
+ listenerObj.callOnce = false;
+ }
+ } else {
+ listenerObj = new goog.events.Listener(
+ listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope);
+ listenerObj.callOnce = callOnce;
+ listenerArray.push(listenerObj);
+ }
+ return listenerObj;
+};
+
+
+/**
+ * Removes a matching listener.
+ * @param {string|!goog.events.EventId} type The listener event type.
+ * @param {!Function} listener This listener callback method.
+ * @param {boolean=} opt_useCapture The capture mode of the listener.
+ * @param {Object=} opt_listenerScope Object in whose scope to call the
+ * listener.
+ * @return {boolean} Whether any listener was removed.
+ */
+goog.events.ListenerMap.prototype.remove = function(
+ type, listener, opt_useCapture, opt_listenerScope) {
+ var typeStr = type.toString();
+ if (!(typeStr in this.listeners)) {
+ return false;
+ }
+
+ var listenerArray = this.listeners[typeStr];
+ var index = goog.events.ListenerMap.findListenerIndex_(
+ listenerArray, listener, opt_useCapture, opt_listenerScope);
+ if (index > -1) {
+ var listenerObj = listenerArray[index];
+ listenerObj.markAsRemoved();
+ goog.array.removeAt(listenerArray, index);
+ if (listenerArray.length == 0) {
+ delete this.listeners[typeStr];
+ this.typeCount_--;
+ }
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Removes the given listener object.
+ * @param {goog.events.ListenableKey} listener The listener to remove.
+ * @return {boolean} Whether the listener is removed.
+ */
+goog.events.ListenerMap.prototype.removeByKey = function(listener) {
+ var type = listener.type;
+ if (!(type in this.listeners)) {
+ return false;
+ }
+
+ var removed = goog.array.remove(this.listeners[type], listener);
+ if (removed) {
+ listener.markAsRemoved();
+ if (this.listeners[type].length == 0) {
+ delete this.listeners[type];
+ this.typeCount_--;
+ }
+ }
+ return removed;
+};
+
+
+/**
+ * Removes all listeners from this map. If opt_type is provided, only
+ * listeners that match the given type are removed.
+ * @param {string|!goog.events.EventId=} opt_type Type of event to remove.
+ * @return {number} Number of listeners removed.
+ */
+goog.events.ListenerMap.prototype.removeAll = function(opt_type) {
+ var typeStr = opt_type && opt_type.toString();
+ var count = 0;
+ for (var type in this.listeners) {
+ if (!typeStr || type == typeStr) {
+ var listenerArray = this.listeners[type];
+ for (var i = 0; i < listenerArray.length; i++) {
+ ++count;
+ listenerArray[i].markAsRemoved();
+ }
+ delete this.listeners[type];
+ this.typeCount_--;
+ }
+ }
+ return count;
+};
+
+
+/**
+ * Gets all listeners that match the given type and capture mode. The
+ * returned array is a copy (but the listener objects are not).
+ * @param {string|!goog.events.EventId} type The type of the listeners
+ * to retrieve.
+ * @param {boolean} capture The capture mode of the listeners to retrieve.
+ * @return {!Array<goog.events.ListenableKey>} An array of matching
+ * listeners.
+ */
+goog.events.ListenerMap.prototype.getListeners = function(type, capture) {
+ var listenerArray = this.listeners[type.toString()];
+ var rv = [];
+ if (listenerArray) {
+ for (var i = 0; i < listenerArray.length; ++i) {
+ var listenerObj = listenerArray[i];
+ if (listenerObj.capture == capture) {
+ rv.push(listenerObj);
+ }
+ }
+ }
+ return rv;
+};
+
+
+/**
+ * Gets the goog.events.ListenableKey for the event or null if no such
+ * listener is in use.
+ *
+ * @param {string|!goog.events.EventId} type The type of the listener
+ * to retrieve.
+ * @param {!Function} listener The listener function to get.
+ * @param {boolean} capture Whether the listener is a capturing listener.
+ * @param {Object=} opt_listenerScope Object in whose scope to call the
+ * listener.
+ * @return {goog.events.ListenableKey} the found listener or null if not found.
+ */
+goog.events.ListenerMap.prototype.getListener = function(
+ type, listener, capture, opt_listenerScope) {
+ var listenerArray = this.listeners[type.toString()];
+ var i = -1;
+ if (listenerArray) {
+ i = goog.events.ListenerMap.findListenerIndex_(
+ listenerArray, listener, capture, opt_listenerScope);
+ }
+ return i > -1 ? listenerArray[i] : null;
+};
+
+
+/**
+ * Whether there is a matching listener. If either the type or capture
+ * parameters are unspecified, the function will match on the
+ * remaining criteria.
+ *
+ * @param {string|!goog.events.EventId=} opt_type The type of the listener.
+ * @param {boolean=} opt_capture The capture mode of the listener.
+ * @return {boolean} Whether there is an active listener matching
+ * the requested type and/or capture phase.
+ */
+goog.events.ListenerMap.prototype.hasListener = function(
+ opt_type, opt_capture) {
+ var hasType = goog.isDef(opt_type);
+ var typeStr = hasType ? opt_type.toString() : '';
+ var hasCapture = goog.isDef(opt_capture);
+
+ return goog.object.some(
+ this.listeners, function(listenerArray, type) {
+ for (var i = 0; i < listenerArray.length; ++i) {
+ if ((!hasType || listenerArray[i].type == typeStr) &&
+ (!hasCapture || listenerArray[i].capture == opt_capture)) {
+ return true;
+ }
+ }
+
+ return false;
+ });
+};
+
+
+/**
+ * Finds the index of a matching goog.events.Listener in the given
+ * listenerArray.
+ * @param {!Array<!goog.events.Listener>} listenerArray Array of listener.
+ * @param {!Function} listener The listener function.
+ * @param {boolean=} opt_useCapture The capture flag for the listener.
+ * @param {Object=} opt_listenerScope The listener scope.
+ * @return {number} The index of the matching listener within the
+ * listenerArray.
+ * @private
+ */
+goog.events.ListenerMap.findListenerIndex_ = function(
+ listenerArray, listener, opt_useCapture, opt_listenerScope) {
+ for (var i = 0; i < listenerArray.length; ++i) {
+ var listenerObj = listenerArray[i];
+ if (!listenerObj.removed &&
+ listenerObj.listener == listener &&
+ listenerObj.capture == !!opt_useCapture &&
+ listenerObj.handler == opt_listenerScope) {
+ return i;
+ }
+ }
+ return -1;
+};
+
+// 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.
+
+/**
+ * @fileoverview An event manager for both native browser event
+ * targets and custom JavaScript event targets
+ * ({@code goog.events.Listenable}). This provides an abstraction
+ * over browsers' event systems.
+ *
+ * It also provides a simulation of W3C event model's capture phase in
+ * Internet Explorer (IE 8 and below). Caveat: the simulation does not
+ * interact well with listeners registered directly on the elements
+ * (bypassing goog.events) or even with listeners registered via
+ * goog.events in a separate JS binary. In these cases, we provide
+ * no ordering guarantees.
+ *
+ * The listeners will receive a "patched" event object. Such event object
+ * contains normalized values for certain event properties that differs in
+ * different browsers.
+ *
+ * Example usage:
+ * <pre>
+ * goog.events.listen(myNode, 'click', function(e) { alert('woo') });
+ * goog.events.listen(myNode, 'mouseover', mouseHandler, true);
+ * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
+ * goog.events.removeAll(myNode);
+ * </pre>
+ *
+ * in IE and event object patching]
+ * @author arv@google.com (Erik Arvidsson)
+ *
+ * @see ../demos/events.html
+ * @see ../demos/event-propagation.html
+ * @see ../demos/stopevent.html
+ */
+
+// IMPLEMENTATION NOTES:
+// goog.events stores an auxiliary data structure on each EventTarget
+// source being listened on. This allows us to take advantage of GC,
+// having the data structure GC'd when the EventTarget is GC'd. This
+// GC behavior is equivalent to using W3C DOM Events directly.
+
+goog.provide('goog.events');
+goog.provide('goog.events.CaptureSimulationMode');
+goog.provide('goog.events.Key');
+goog.provide('goog.events.ListenableType');
+
+goog.require('goog.asserts');
+goog.require('goog.debug.entryPointRegistry');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.BrowserFeature');
+goog.require('goog.events.Listenable');
+goog.require('goog.events.ListenerMap');
+
+goog.forwardDeclare('goog.debug.ErrorHandler');
+goog.forwardDeclare('goog.events.EventWrapper');
+
+
+/**
+ * @typedef {number|goog.events.ListenableKey}
+ */
+goog.events.Key;
+
+
+/**
+ * @typedef {EventTarget|goog.events.Listenable}
+ */
+goog.events.ListenableType;
+
+
+/**
+ * Property name on a native event target for the listener map
+ * associated with the event target.
+ * @private @const {string}
+ */
+goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0);
+
+
+/**
+ * String used to prepend to IE event types.
+ * @const
+ * @private
+ */
+goog.events.onString_ = 'on';
+
+
+/**
+ * Map of computed "on<eventname>" strings for IE event types. Caching
+ * this removes an extra object allocation in goog.events.listen which
+ * improves IE6 performance.
+ * @const
+ * @dict
+ * @private
+ */
+goog.events.onStringMap_ = {};
+
+
+/**
+ * @enum {number} Different capture simulation mode for IE8-.
+ */
+goog.events.CaptureSimulationMode = {
+ /**
+ * Does not perform capture simulation. Will asserts in IE8- when you
+ * add capture listeners.
+ */
+ OFF_AND_FAIL: 0,
+
+ /**
+ * Does not perform capture simulation, silently ignore capture
+ * listeners.
+ */
+ OFF_AND_SILENT: 1,
+
+ /**
+ * Performs capture simulation.
+ */
+ ON: 2
+};
+
+
+/**
+ * @define {number} The capture simulation mode for IE8-. By default,
+ * this is ON.
+ */
+goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2);
+
+
+/**
+ * Estimated count of total native listeners.
+ * @private {number}
+ */
+goog.events.listenerCountEstimate_ = 0;
+
+
+/**
+ * Adds an event listener for a specific event on a native event
+ * target (such as a DOM element) or an object that has implemented
+ * {@link goog.events.Listenable}. A listener can only be added once
+ * to an object and if it is added again the key for the listener is
+ * returned. Note that if the existing listener is a one-off listener
+ * (registered via listenOnce), it will no longer be a one-off
+ * listener after a call to listen().
+ *
+ * @param {EventTarget|goog.events.Listenable} src The node to listen
+ * to events on.
+ * @param {string|Array<string>|
+ * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
+ * type Event type or array of event types.
+ * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
+ * listener Callback method, or an object with a handleEvent function.
+ * WARNING: passing an Object is now softly deprecated.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ * false).
+ * @param {T=} opt_handler Element in whose scope to call the listener.
+ * @return {goog.events.Key} Unique key for the listener.
+ * @template T,EVENTOBJ
+ */
+goog.events.listen = function(src, type, listener, opt_capt, opt_handler) {
+ if (goog.isArray(type)) {
+ for (var i = 0; i < type.length; i++) {
+ goog.events.listen(src, type[i], listener, opt_capt, opt_handler);
+ }
+ return null;
+ }
+
+ listener = goog.events.wrapListener(listener);
+ if (goog.events.Listenable.isImplementedBy(src)) {
+ return src.listen(
+ /** @type {string|!goog.events.EventId} */ (type),
+ listener, opt_capt, opt_handler);
+ } else {
+ return goog.events.listen_(
+ /** @type {!EventTarget} */ (src),
+ /** @type {string|!goog.events.EventId} */ (type),
+ listener, /* callOnce */ false, opt_capt, opt_handler);
+ }
+};
+
+
+/**
+ * Adds an event listener for a specific event on a native event
+ * target. A listener can only be added once to an object and if it
+ * is added again the key for the listener is returned.
+ *
+ * Note that a one-off listener will not change an existing listener,
+ * if any. On the other hand a normal listener will change existing
+ * one-off listener to become a normal listener.
+ *
+ * @param {EventTarget} src The node to listen to events on.
+ * @param {string|!goog.events.EventId} type Event type.
+ * @param {!Function} listener Callback function.
+ * @param {boolean} callOnce Whether the listener is a one-off
+ * listener or otherwise.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ * false).
+ * @param {Object=} opt_handler Element in whose scope to call the listener.
+ * @return {goog.events.ListenableKey} Unique key for the listener.
+ * @private
+ */
+goog.events.listen_ = function(
+ src, type, listener, callOnce, opt_capt, opt_handler) {
+ if (!type) {
+ throw Error('Invalid event type');
+ }
+
+ var capture = !!opt_capt;
+ if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
+ if (goog.events.CAPTURE_SIMULATION_MODE ==
+ goog.events.CaptureSimulationMode.OFF_AND_FAIL) {
+ goog.asserts.fail('Can not register capture listener in IE8-.');
+ return null;
+ } else if (goog.events.CAPTURE_SIMULATION_MODE ==
+ goog.events.CaptureSimulationMode.OFF_AND_SILENT) {
+ return null;
+ }
+ }
+
+ var listenerMap = goog.events.getListenerMap_(src);
+ if (!listenerMap) {
+ src[goog.events.LISTENER_MAP_PROP_] = listenerMap =
+ new goog.events.ListenerMap(src);
+ }
+
+ var listenerObj = listenerMap.add(
+ type, listener, callOnce, opt_capt, opt_handler);
+
+ // If the listenerObj already has a proxy, it has been set up
+ // previously. We simply return.
+ if (listenerObj.proxy) {
+ return listenerObj;
+ }
+
+ var proxy = goog.events.getProxy();
+ listenerObj.proxy = proxy;
+
+ proxy.src = src;
+ proxy.listener = listenerObj;
+
+ // Attach the proxy through the browser's API
+ if (src.addEventListener) {
+ src.addEventListener(type.toString(), proxy, capture);
+ } else if (src.attachEvent) {
+ // The else if above used to be an unconditional else. It would call
+ // exception on IE11, spoiling the day of some callers. The previous
+ // incarnation of this code, from 2007, indicates that it replaced an
+ // earlier still version that caused excess allocations on IE6.
+ src.attachEvent(goog.events.getOnString_(type.toString()), proxy);
+ } else {
+ throw Error('addEventListener and attachEvent are unavailable.');
+ }
+
+ goog.events.listenerCountEstimate_++;
+ return listenerObj;
+};
+
+
+/**
+ * Helper function for returning a proxy function.
+ * @return {!Function} A new or reused function object.
+ */
+goog.events.getProxy = function() {
+ var proxyCallbackFunction = goog.events.handleBrowserEvent_;
+ // Use a local var f to prevent one allocation.
+ var f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ?
+ function(eventObject) {
+ return proxyCallbackFunction.call(f.src, f.listener, eventObject);
+ } :
+ function(eventObject) {
+ var v = proxyCallbackFunction.call(f.src, f.listener, eventObject);
+ // NOTE(chrishenry): In IE, we hack in a capture phase. However, if
+ // there is inline event handler which tries to prevent default (for
+ // example <a href="..." onclick="return false">...</a>) in a
+ // descendant element, the prevent default will be overridden
+ // by this listener if this listener were to return true. Hence, we
+ // return undefined.
+ if (!v) return v;
+ };
+ return f;
+};
+
+
+/**
+ * Adds an event listener for a specific event on a native event
+ * target (such as a DOM element) or an object that has implemented
+ * {@link goog.events.Listenable}. After the event has fired the event
+ * listener is removed from the target.
+ *
+ * If an existing listener already exists, listenOnce will do
+ * nothing. In particular, if the listener was previously registered
+ * via listen(), listenOnce() will not turn the listener into a
+ * one-off listener. Similarly, if there is already an existing
+ * one-off listener, listenOnce does not modify the listeners (it is
+ * still a once listener).
+ *
+ * @param {EventTarget|goog.events.Listenable} src The node to listen
+ * to events on.
+ * @param {string|Array<string>|
+ * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
+ * type Event type or array of event types.
+ * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
+ * listener Callback method.
+ * @param {boolean=} opt_capt Fire in capture phase?.
+ * @param {T=} opt_handler Element in whose scope to call the listener.
+ * @return {goog.events.Key} Unique key for the listener.
+ * @template T,EVENTOBJ
+ */
+goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) {
+ if (goog.isArray(type)) {
+ for (var i = 0; i < type.length; i++) {
+ goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler);
+ }
+ return null;
+ }
+
+ listener = goog.events.wrapListener(listener);
+ if (goog.events.Listenable.isImplementedBy(src)) {
+ return src.listenOnce(
+ /** @type {string|!goog.events.EventId} */ (type),
+ listener, opt_capt, opt_handler);
+ } else {
+ return goog.events.listen_(
+ /** @type {!EventTarget} */ (src),
+ /** @type {string|!goog.events.EventId} */ (type),
+ listener, /* callOnce */ true, opt_capt, opt_handler);
+ }
+};
+
+
+/**
+ * Adds an event listener with a specific event wrapper on a DOM Node or an
+ * object that has implemented {@link goog.events.Listenable}. A listener can
+ * only be added once to an object.
+ *
+ * @param {EventTarget|goog.events.Listenable} src The target to
+ * listen to events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(this:T, ?):?|{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 {T=} opt_handler Element in whose scope to call the listener.
+ * @template T
+ */
+goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt,
+ opt_handler) {
+ wrapper.listen(src, listener, opt_capt, opt_handler);
+};
+
+
+/**
+ * Removes an event listener which was added with listen().
+ *
+ * @param {EventTarget|goog.events.Listenable} src The target to stop
+ * listening to events on.
+ * @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(?):?|{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_handler Element in whose scope to call the listener.
+ * @return {?boolean} indicating whether the listener was there to remove.
+ * @template EVENTOBJ
+ */
+goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) {
+ if (goog.isArray(type)) {
+ for (var i = 0; i < type.length; i++) {
+ goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler);
+ }
+ return null;
+ }
+
+ listener = goog.events.wrapListener(listener);
+ if (goog.events.Listenable.isImplementedBy(src)) {
+ return src.unlisten(
+ /** @type {string|!goog.events.EventId} */ (type),
+ listener, opt_capt, opt_handler);
+ }
+
+ if (!src) {
+ // TODO(chrishenry): We should tighten the API to only accept
+ // non-null objects, or add an assertion here.
+ return false;
+ }
+
+ var capture = !!opt_capt;
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {!EventTarget} */ (src));
+ if (listenerMap) {
+ var listenerObj = listenerMap.getListener(
+ /** @type {string|!goog.events.EventId} */ (type),
+ listener, capture, opt_handler);
+ if (listenerObj) {
+ return goog.events.unlistenByKey(listenerObj);
+ }
+ }
+
+ return false;
+};
+
+
+/**
+ * Removes an event listener which was added with listen() by the key
+ * returned by listen().
+ *
+ * @param {goog.events.Key} key The key returned by listen() for this
+ * event listener.
+ * @return {boolean} indicating whether the listener was there to remove.
+ */
+goog.events.unlistenByKey = function(key) {
+ // TODO(chrishenry): Remove this check when tests that rely on this
+ // are fixed.
+ if (goog.isNumber(key)) {
+ return false;
+ }
+
+ var listener = key;
+ if (!listener || listener.removed) {
+ return false;
+ }
+
+ var src = listener.src;
+ if (goog.events.Listenable.isImplementedBy(src)) {
+ return src.unlistenByKey(listener);
+ }
+
+ var type = listener.type;
+ var proxy = listener.proxy;
+ if (src.removeEventListener) {
+ src.removeEventListener(type, proxy, listener.capture);
+ } else if (src.detachEvent) {
+ src.detachEvent(goog.events.getOnString_(type), proxy);
+ }
+ goog.events.listenerCountEstimate_--;
+
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {!EventTarget} */ (src));
+ // TODO(chrishenry): Try to remove this conditional and execute the
+ // first branch always. This should be safe.
+ if (listenerMap) {
+ listenerMap.removeByKey(listener);
+ if (listenerMap.getTypeCount() == 0) {
+ // Null the src, just because this is simple to do (and useful
+ // for IE <= 7).
+ listenerMap.src = null;
+ // We don't use delete here because IE does not allow delete
+ // on a window object.
+ src[goog.events.LISTENER_MAP_PROP_] = null;
+ }
+ } else {
+ listener.markAsRemoved();
+ }
+
+ return true;
+};
+
+
+/**
+ * Removes an event listener which was added with listenWithWrapper().
+ *
+ * @param {EventTarget|goog.events.Listenable} 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_handler Element in whose scope to call the listener.
+ */
+goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt,
+ opt_handler) {
+ wrapper.unlisten(src, listener, opt_capt, opt_handler);
+};
+
+
+/**
+ * Removes all listeners from an object. You can also optionally
+ * remove listeners of a particular type.
+ *
+ * @param {Object|undefined} obj Object to remove listeners from. Must be an
+ * EventTarget or a goog.events.Listenable.
+ * @param {string|!goog.events.EventId=} opt_type Type of event to remove.
+ * Default is all types.
+ * @return {number} Number of listeners removed.
+ */
+goog.events.removeAll = function(obj, opt_type) {
+ // TODO(chrishenry): Change the type of obj to
+ // (!EventTarget|!goog.events.Listenable).
+
+ if (!obj) {
+ return 0;
+ }
+
+ if (goog.events.Listenable.isImplementedBy(obj)) {
+ return obj.removeAllListeners(opt_type);
+ }
+
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {!EventTarget} */ (obj));
+ if (!listenerMap) {
+ return 0;
+ }
+
+ var count = 0;
+ var typeStr = opt_type && opt_type.toString();
+ for (var type in listenerMap.listeners) {
+ if (!typeStr || type == typeStr) {
+ // Clone so that we don't need to worry about unlistenByKey
+ // changing the content of the ListenerMap.
+ var listeners = listenerMap.listeners[type].concat();
+ for (var i = 0; i < listeners.length; ++i) {
+ if (goog.events.unlistenByKey(listeners[i])) {
+ ++count;
+ }
+ }
+ }
+ }
+ return count;
+};
+
+
+/**
+ * Gets the listeners for a given object, type and capture phase.
+ *
+ * @param {Object} obj Object to get listeners for.
+ * @param {string|!goog.events.EventId} type Event type.
+ * @param {boolean} capture Capture phase?.
+ * @return {Array<goog.events.Listener>} Array of listener objects.
+ */
+goog.events.getListeners = function(obj, type, capture) {
+ if (goog.events.Listenable.isImplementedBy(obj)) {
+ return obj.getListeners(type, capture);
+ } else {
+ if (!obj) {
+ // TODO(chrishenry): We should tighten the API to accept
+ // !EventTarget|goog.events.Listenable, and add an assertion here.
+ return [];
+ }
+
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {!EventTarget} */ (obj));
+ return listenerMap ? listenerMap.getListeners(type, capture) : [];
+ }
+};
+
+
+/**
+ * Gets the goog.events.Listener for the event or null if no such listener is
+ * in use.
+ *
+ * @param {EventTarget|goog.events.Listenable} src The target from
+ * which to get listeners.
+ * @param {?string|!goog.events.EventId<EVENTOBJ>} type The type of the event.
+ * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null} listener The
+ * listener function to get.
+ * @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_handler Element in whose scope to call the listener.
+ * @return {goog.events.ListenableKey} the found listener or null if not found.
+ * @template EVENTOBJ
+ */
+goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {
+ // TODO(chrishenry): Change type from ?string to string, or add assertion.
+ type = /** @type {string} */ (type);
+ listener = goog.events.wrapListener(listener);
+ var capture = !!opt_capt;
+ if (goog.events.Listenable.isImplementedBy(src)) {
+ return src.getListener(type, listener, capture, opt_handler);
+ }
+
+ if (!src) {
+ // TODO(chrishenry): We should tighten the API to only accept
+ // non-null objects, or add an assertion here.
+ return null;
+ }
+
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {!EventTarget} */ (src));
+ if (listenerMap) {
+ return listenerMap.getListener(type, listener, capture, opt_handler);
+ }
+ return null;
+};
+
+
+/**
+ * Returns whether an event target has any active listeners matching the
+ * specified signature. If either the type or capture parameters are
+ * unspecified, the function will match on the remaining criteria.
+ *
+ * @param {EventTarget|goog.events.Listenable} obj Target to get
+ * listeners for.
+ * @param {string|!goog.events.EventId=} opt_type Event type.
+ * @param {boolean=} opt_capture Whether to check for capture or bubble-phase
+ * listeners.
+ * @return {boolean} Whether an event target has one or more listeners matching
+ * the requested type and/or capture phase.
+ */
+goog.events.hasListener = function(obj, opt_type, opt_capture) {
+ if (goog.events.Listenable.isImplementedBy(obj)) {
+ return obj.hasListener(opt_type, opt_capture);
+ }
+
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {!EventTarget} */ (obj));
+ return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture);
+};
+
+
+/**
+ * Provides a nice string showing the normalized event objects public members
+ * @param {Object} e Event Object.
+ * @return {string} String of the public members of the normalized event object.
+ */
+goog.events.expose = function(e) {
+ var str = [];
+ for (var key in e) {
+ if (e[key] && e[key].id) {
+ str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')');
+ } else {
+ str.push(key + ' = ' + e[key]);
+ }
+ }
+ return str.join('\n');
+};
+
+
+/**
+ * Returns a string with on prepended to the specified type. This is used for IE
+ * which expects "on" to be prepended. This function caches the string in order
+ * to avoid extra allocations in steady state.
+ * @param {string} type Event type.
+ * @return {string} The type string with 'on' prepended.
+ * @private
+ */
+goog.events.getOnString_ = function(type) {
+ if (type in goog.events.onStringMap_) {
+ return goog.events.onStringMap_[type];
+ }
+ return goog.events.onStringMap_[type] = goog.events.onString_ + type;
+};
+
+
+/**
+ * Fires an object's listeners of a particular type and phase
+ *
+ * @param {Object} obj Object whose listeners to call.
+ * @param {string|!goog.events.EventId} type Event type.
+ * @param {boolean} capture Which event phase.
+ * @param {Object} eventObject Event object to be passed to listener.
+ * @return {boolean} True if all listeners returned true else false.
+ */
+goog.events.fireListeners = function(obj, type, capture, eventObject) {
+ if (goog.events.Listenable.isImplementedBy(obj)) {
+ return obj.fireListeners(type, capture, eventObject);
+ }
+
+ return goog.events.fireListeners_(obj, type, capture, eventObject);
+};
+
+
+/**
+ * Fires an object's listeners of a particular type and phase.
+ * @param {Object} obj Object whose listeners to call.
+ * @param {string|!goog.events.EventId} type Event type.
+ * @param {boolean} capture Which event phase.
+ * @param {Object} eventObject Event object to be passed to listener.
+ * @return {boolean} True if all listeners returned true else false.
+ * @private
+ */
+goog.events.fireListeners_ = function(obj, type, capture, eventObject) {
+ /** @type {boolean} */
+ var retval = true;
+
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {EventTarget} */ (obj));
+ if (listenerMap) {
+ // TODO(chrishenry): Original code avoids array creation when there
+ // is no listener, so we do the same. If this optimization turns
+ // out to be not required, we can replace this with
+ // listenerMap.getListeners(type, capture) instead, which is simpler.
+ var listenerArray = listenerMap.listeners[type.toString()];
+ if (listenerArray) {
+ listenerArray = listenerArray.concat();
+ for (var i = 0; i < listenerArray.length; i++) {
+ var listener = listenerArray[i];
+ // We might not have a listener if the listener was removed.
+ if (listener && listener.capture == capture && !listener.removed) {
+ var result = goog.events.fireListener(listener, eventObject);
+ retval = retval && (result !== false);
+ }
+ }
+ }
+ }
+ return retval;
+};
+
+
+/**
+ * Fires a listener with a set of arguments
+ *
+ * @param {goog.events.Listener} listener The listener object to call.
+ * @param {Object} eventObject The event object to pass to the listener.
+ * @return {boolean} Result of listener.
+ */
+goog.events.fireListener = function(listener, eventObject) {
+ var listenerFn = listener.listener;
+ var listenerHandler = listener.handler || listener.src;
+
+ if (listener.callOnce) {
+ goog.events.unlistenByKey(listener);
+ }
+ return listenerFn.call(listenerHandler, eventObject);
+};
+
+
+/**
+ * Gets the total number of listeners currently in the system.
+ * @return {number} Number of listeners.
+ * @deprecated This returns estimated count, now that Closure no longer
+ * stores a central listener registry. We still return an estimation
+ * to keep existing listener-related tests passing. In the near future,
+ * this function will be removed.
+ */
+goog.events.getTotalListenerCount = function() {
+ return goog.events.listenerCountEstimate_;
+};
+
+
+/**
+ * Dispatches an event (or event like object) and calls all listeners
+ * listening for events of this type. The type of the event is decided by the
+ * type property on the event object.
+ *
+ * If any of the listeners returns false OR calls preventDefault then this
+ * function will return false. If one of the capture listeners calls
+ * stopPropagation, then the bubble listeners won't fire.
+ *
+ * @param {goog.events.Listenable} src The event target.
+ * @param {goog.events.EventLike} e Event object.
+ * @return {boolean} If anyone called preventDefault on the event object (or
+ * if any of the handlers returns false) this will also return false.
+ * If there are no handlers, or if all handlers return true, this returns
+ * true.
+ */
+goog.events.dispatchEvent = function(src, e) {
+ goog.asserts.assert(
+ goog.events.Listenable.isImplementedBy(src),
+ 'Can not use goog.events.dispatchEvent with ' +
+ 'non-goog.events.Listenable instance.');
+ return src.dispatchEvent(e);
+};
+
+
+/**
+ * Installs exception protection for the browser event entry point using the
+ * given error handler.
+ *
+ * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
+ * protect the entry point.
+ */
+goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
+ goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint(
+ goog.events.handleBrowserEvent_);
+};
+
+
+/**
+ * Handles an event and dispatches it to the correct listeners. This
+ * function is a proxy for the real listener the user specified.
+ *
+ * @param {goog.events.Listener} listener The listener object.
+ * @param {Event=} opt_evt Optional event object that gets passed in via the
+ * native event handlers.
+ * @return {boolean} Result of the event handler.
+ * @this {EventTarget} The object or Element that fired the event.
+ * @private
+ */
+goog.events.handleBrowserEvent_ = function(listener, opt_evt) {
+ if (listener.removed) {
+ return true;
+ }
+
+ // Synthesize event propagation if the browser does not support W3C
+ // event model.
+ if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
+ var ieEvent = opt_evt ||
+ /** @type {Event} */ (goog.getObjectByName('window.event'));
+ var evt = new goog.events.BrowserEvent(ieEvent, this);
+ /** @type {boolean} */
+ var retval = true;
+
+ if (goog.events.CAPTURE_SIMULATION_MODE ==
+ goog.events.CaptureSimulationMode.ON) {
+ // If we have not marked this event yet, we should perform capture
+ // simulation.
+ if (!goog.events.isMarkedIeEvent_(ieEvent)) {
+ goog.events.markIeEvent_(ieEvent);
+
+ var ancestors = [];
+ for (var parent = evt.currentTarget; parent;
+ parent = parent.parentNode) {
+ ancestors.push(parent);
+ }
+
+ // Fire capture listeners.
+ var type = listener.type;
+ for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0;
+ i--) {
+ evt.currentTarget = ancestors[i];
+ var result = goog.events.fireListeners_(ancestors[i], type, true, evt);
+ retval = retval && result;
+ }
+
+ // Fire bubble listeners.
+ //
+ // We can technically rely on IE to perform bubble event
+ // propagation. However, it turns out that IE fires events in
+ // opposite order of attachEvent registration, which broke
+ // some code and tests that rely on the order. (While W3C DOM
+ // Level 2 Events TR leaves the event ordering unspecified,
+ // modern browsers and W3C DOM Level 3 Events Working Draft
+ // actually specify the order as the registration order.)
+ for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) {
+ evt.currentTarget = ancestors[i];
+ var result = goog.events.fireListeners_(ancestors[i], type, false, evt);
+ retval = retval && result;
+ }
+ }
+ } else {
+ retval = goog.events.fireListener(listener, evt);
+ }
+ return retval;
+ }
+
+ // Otherwise, simply fire the listener.
+ return goog.events.fireListener(
+ listener, new goog.events.BrowserEvent(opt_evt, this));
+};
+
+
+/**
+ * This is used to mark the IE event object so we do not do the Closure pass
+ * twice for a bubbling event.
+ * @param {Event} e The IE browser event.
+ * @private
+ */
+goog.events.markIeEvent_ = function(e) {
+ // Only the keyCode and the returnValue can be changed. We use keyCode for
+ // non keyboard events.
+ // event.returnValue is a bit more tricky. It is undefined by default. A
+ // boolean false prevents the default action. In a window.onbeforeunload and
+ // the returnValue is non undefined it will be alerted. However, we will only
+ // modify the returnValue for keyboard events. We can get a problem if non
+ // closure events sets the keyCode or the returnValue
+
+ var useReturnValue = false;
+
+ if (e.keyCode == 0) {
+ // We cannot change the keyCode in case that srcElement is input[type=file].
+ // We could test that that is the case but that would allocate 3 objects.
+ // If we use try/catch we will only allocate extra objects in the case of a
+ // failure.
+ /** @preserveTry */
+ try {
+ e.keyCode = -1;
+ return;
+ } catch (ex) {
+ useReturnValue = true;
+ }
+ }
+
+ if (useReturnValue ||
+ /** @type {boolean|undefined} */ (e.returnValue) == undefined) {
+ e.returnValue = true;
+ }
+};
+
+
+/**
+ * This is used to check if an IE event has already been handled by the Closure
+ * system so we do not do the Closure pass twice for a bubbling event.
+ * @param {Event} e The IE browser event.
+ * @return {boolean} True if the event object has been marked.
+ * @private
+ */
+goog.events.isMarkedIeEvent_ = function(e) {
+ return e.keyCode < 0 || e.returnValue != undefined;
+};
+
+
+/**
+ * Counter to create unique event ids.
+ * @private {number}
+ */
+goog.events.uniqueIdCounter_ = 0;
+
+
+/**
+ * Creates a unique event id.
+ *
+ * @param {string} identifier The identifier.
+ * @return {string} A unique identifier.
+ * @idGenerator
+ */
+goog.events.getUniqueId = function(identifier) {
+ return identifier + '_' + goog.events.uniqueIdCounter_++;
+};
+
+
+/**
+ * @param {EventTarget} src The source object.
+ * @return {goog.events.ListenerMap} A listener map for the given
+ * source object, or null if none exists.
+ * @private
+ */
+goog.events.getListenerMap_ = function(src) {
+ var listenerMap = src[goog.events.LISTENER_MAP_PROP_];
+ // IE serializes the property as well (e.g. when serializing outer
+ // HTML). So we must check that the value is of the correct type.
+ return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null;
+};
+
+
+/**
+ * Expando property for listener function wrapper for Object with
+ * handleEvent.
+ * @private @const {string}
+ */
+goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' +
+ ((Math.random() * 1e9) >>> 0);
+
+
+/**
+ * @param {Object|Function} listener The listener function or an
+ * object that contains handleEvent method.
+ * @return {!Function} Either the original function or a function that
+ * calls obj.handleEvent. If the same listener is passed to this
+ * function more than once, the same function is guaranteed to be
+ * returned.
+ */
+goog.events.wrapListener = function(listener) {
+ goog.asserts.assert(listener, 'Listener can not be null.');
+
+ if (goog.isFunction(listener)) {
+ return listener;
+ }
+
+ goog.asserts.assert(
+ listener.handleEvent, 'An object listener must have handleEvent method.');
+ if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) {
+ listener[goog.events.LISTENER_WRAPPER_PROP_] =
+ function(e) { return listener.handleEvent(e); };
+ }
+ return listener[goog.events.LISTENER_WRAPPER_PROP_];
+};
+
+
+// Register the browser event 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.events.handleBrowserEvent_ = transformer(
+ goog.events.handleBrowserEvent_);
+ });
+
+// 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.
+
+/**
+ * @fileoverview A disposable implementation of a custom
+ * listenable/event target. See also: documentation for
+ * {@code goog.events.Listenable}.
+ *
+ * @author arv@google.com (Erik Arvidsson) [Original implementation]
+ * @see ../demos/eventtarget.html
+ * @see goog.events.Listenable
+ */
+
+goog.provide('goog.events.EventTarget');
+
+goog.require('goog.Disposable');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.events.Listenable');
+goog.require('goog.events.ListenerMap');
+goog.require('goog.object');
+
+
+
+/**
+ * An implementation of {@code goog.events.Listenable} with full W3C
+ * EventTarget-like support (capture/bubble mechanism, stopping event
+ * propagation, preventing default actions).
+ *
+ * You may subclass this class to turn your class into a Listenable.
+ *
+ * Unless propagation is stopped, an event dispatched by an
+ * EventTarget will bubble to the parent returned by
+ * {@code getParentEventTarget}. To set the parent, call
+ * {@code setParentEventTarget}. Subclasses that don't support
+ * changing the parent can override the setter to throw an error.
+ *
+ * Example usage:
+ * <pre>
+ * var source = new goog.events.EventTarget();
+ * function handleEvent(e) {
+ * alert('Type: ' + e.type + '; Target: ' + e.target);
+ * }
+ * source.listen('foo', handleEvent);
+ * // Or: goog.events.listen(source, 'foo', handleEvent);
+ * ...
+ * source.dispatchEvent('foo'); // will call handleEvent
+ * ...
+ * source.unlisten('foo', handleEvent);
+ * // Or: goog.events.unlisten(source, 'foo', handleEvent);
+ * </pre>
+ *
+ * @constructor
+ * @extends {goog.Disposable}
+ * @implements {goog.events.Listenable}
+ */
+goog.events.EventTarget = function() {
+ goog.Disposable.call(this);
+
+ /**
+ * Maps of event type to an array of listeners.
+ * @private {!goog.events.ListenerMap}
+ */
+ this.eventTargetListeners_ = new goog.events.ListenerMap(this);
+
+ /**
+ * The object to use for event.target. Useful when mixing in an
+ * EventTarget to another object.
+ * @private {!Object}
+ */
+ this.actualEventTarget_ = this;
+
+ /**
+ * Parent event target, used during event bubbling.
+ *
+ * TODO(chrishenry): Change this to goog.events.Listenable. This
+ * currently breaks people who expect getParentEventTarget to return
+ * goog.events.EventTarget.
+ *
+ * @private {goog.events.EventTarget}
+ */
+ this.parentEventTarget_ = null;
+};
+goog.inherits(goog.events.EventTarget, goog.Disposable);
+goog.events.Listenable.addImplementation(goog.events.EventTarget);
+
+
+/**
+ * An artificial cap on the number of ancestors you can have. This is mainly
+ * for loop detection.
+ * @const {number}
+ * @private
+ */
+goog.events.EventTarget.MAX_ANCESTORS_ = 1000;
+
+
+/**
+ * Returns the parent of this event target to use for bubbling.
+ *
+ * @return {goog.events.EventTarget} The parent EventTarget or null if
+ * there is no parent.
+ * @override
+ */
+goog.events.EventTarget.prototype.getParentEventTarget = function() {
+ return this.parentEventTarget_;
+};
+
+
+/**
+ * Sets the parent of this event target to use for capture/bubble
+ * mechanism.
+ * @param {goog.events.EventTarget} parent Parent listenable (null if none).
+ */
+goog.events.EventTarget.prototype.setParentEventTarget = function(parent) {
+ this.parentEventTarget_ = parent;
+};
+
+
+/**
+ * Adds an event listener to the event target. The same handler can only be
+ * added once per the type. Even if you add the same handler multiple times
+ * using the same type then it will only be called once when the event is
+ * dispatched.
+ *
+ * @param {string} type The type of the event to listen for.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
+ * to handle the event. The handler can also be an object that implements
+ * the handleEvent method which takes the event object as argument.
+ * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
+ * whether the listener is fired during the capture or bubble phase
+ * of the event.
+ * @param {Object=} opt_handlerScope Object in whose scope to call
+ * the listener.
+ * @deprecated Use {@code #listen} instead, when possible. Otherwise, use
+ * {@code goog.events.listen} if you are passing Object
+ * (instead of Function) as handler.
+ */
+goog.events.EventTarget.prototype.addEventListener = function(
+ type, handler, opt_capture, opt_handlerScope) {
+ goog.events.listen(this, type, handler, opt_capture, opt_handlerScope);
+};
+
+
+/**
+ * Removes an event listener from the event target. The handler must be the
+ * same object as the one added. If the handler has not been added then
+ * nothing is done.
+ *
+ * @param {string} type The type of the event to listen for.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
+ * to handle the event. The handler can also be an object that implements
+ * the handleEvent method which takes the event object as argument.
+ * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
+ * whether the listener is fired during the capture or bubble phase
+ * of the event.
+ * @param {Object=} opt_handlerScope Object in whose scope to call
+ * the listener.
+ * @deprecated Use {@code #unlisten} instead, when possible. Otherwise, use
+ * {@code goog.events.unlisten} if you are passing Object
+ * (instead of Function) as handler.
+ */
+goog.events.EventTarget.prototype.removeEventListener = function(
+ type, handler, opt_capture, opt_handlerScope) {
+ goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.dispatchEvent = function(e) {
+ this.assertInitialized_();
+
+ var ancestorsTree, ancestor = this.getParentEventTarget();
+ if (ancestor) {
+ ancestorsTree = [];
+ var ancestorCount = 1;
+ for (; ancestor; ancestor = ancestor.getParentEventTarget()) {
+ ancestorsTree.push(ancestor);
+ goog.asserts.assert(
+ (++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_),
+ 'infinite loop');
+ }
+ }
+
+ return goog.events.EventTarget.dispatchEventInternal_(
+ this.actualEventTarget_, e, ancestorsTree);
+};
+
+
+/**
+ * Removes listeners from this object. Classes that extend EventTarget may
+ * need to override this method in order to remove references to DOM Elements
+ * and additional listeners.
+ * @override
+ */
+goog.events.EventTarget.prototype.disposeInternal = function() {
+ goog.events.EventTarget.superClass_.disposeInternal.call(this);
+
+ this.removeAllListeners();
+ this.parentEventTarget_ = null;
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.listen = function(
+ type, listener, opt_useCapture, opt_listenerScope) {
+ this.assertInitialized_();
+ return this.eventTargetListeners_.add(
+ String(type), listener, false /* callOnce */, opt_useCapture,
+ opt_listenerScope);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.listenOnce = function(
+ type, listener, opt_useCapture, opt_listenerScope) {
+ return this.eventTargetListeners_.add(
+ String(type), listener, true /* callOnce */, opt_useCapture,
+ opt_listenerScope);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.unlisten = function(
+ type, listener, opt_useCapture, opt_listenerScope) {
+ return this.eventTargetListeners_.remove(
+ String(type), listener, opt_useCapture, opt_listenerScope);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.unlistenByKey = function(key) {
+ return this.eventTargetListeners_.removeByKey(key);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) {
+ // TODO(chrishenry): Previously, removeAllListeners can be called on
+ // uninitialized EventTarget, so we preserve that behavior. We
+ // should remove this when usages that rely on that fact are purged.
+ if (!this.eventTargetListeners_) {
+ return 0;
+ }
+ return this.eventTargetListeners_.removeAll(opt_type);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.fireListeners = function(
+ type, capture, eventObject) {
+ // TODO(chrishenry): Original code avoids array creation when there
+ // is no listener, so we do the same. If this optimization turns
+ // out to be not required, we can replace this with
+ // getListeners(type, capture) instead, which is simpler.
+ var listenerArray = this.eventTargetListeners_.listeners[String(type)];
+ if (!listenerArray) {
+ return true;
+ }
+ listenerArray = listenerArray.concat();
+
+ var rv = true;
+ for (var i = 0; i < listenerArray.length; ++i) {
+ var listener = listenerArray[i];
+ // We might not have a listener if the listener was removed.
+ if (listener && !listener.removed && listener.capture == capture) {
+ var listenerFn = listener.listener;
+ var listenerHandler = listener.handler || listener.src;
+
+ if (listener.callOnce) {
+ this.unlistenByKey(listener);
+ }
+ rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;
+ }
+ }
+
+ return rv && eventObject.returnValue_ != false;
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.getListeners = function(type, capture) {
+ return this.eventTargetListeners_.getListeners(String(type), capture);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.getListener = function(
+ type, listener, capture, opt_listenerScope) {
+ return this.eventTargetListeners_.getListener(
+ String(type), listener, capture, opt_listenerScope);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.hasListener = function(
+ opt_type, opt_capture) {
+ var id = goog.isDef(opt_type) ? String(opt_type) : undefined;
+ return this.eventTargetListeners_.hasListener(id, opt_capture);
+};
+
+
+/**
+ * Sets the target to be used for {@code event.target} when firing
+ * event. Mainly used for testing. For example, see
+ * {@code goog.testing.events.mixinListenable}.
+ * @param {!Object} target The target.
+ */
+goog.events.EventTarget.prototype.setTargetForTesting = function(target) {
+ this.actualEventTarget_ = target;
+};
+
+
+/**
+ * Asserts that the event target instance is initialized properly.
+ * @private
+ */
+goog.events.EventTarget.prototype.assertInitialized_ = function() {
+ goog.asserts.assert(
+ this.eventTargetListeners_,
+ 'Event target is not initialized. Did you call the superclass ' +
+ '(goog.events.EventTarget) constructor?');
+};
+
+
+/**
+ * Dispatches the given event on the ancestorsTree.
+ *
+ * @param {!Object} target The target to dispatch on.
+ * @param {goog.events.Event|Object|string} e The event object.
+ * @param {Array<goog.events.Listenable>=} opt_ancestorsTree The ancestors
+ * tree of the target, in reverse order from the closest ancestor
+ * to the root event target. May be null if the target has no ancestor.
+ * @return {boolean} If anyone called preventDefault on the event object (or
+ * if any of the listeners returns false) this will also return false.
+ * @private
+ */
+goog.events.EventTarget.dispatchEventInternal_ = function(
+ target, e, opt_ancestorsTree) {
+ var type = e.type || /** @type {string} */ (e);
+
+ // If accepting a string or object, create a custom event object so that
+ // preventDefault and stopPropagation work with the event.
+ if (goog.isString(e)) {
+ e = new goog.events.Event(e, target);
+ } else if (!(e instanceof goog.events.Event)) {
+ var oldEvent = e;
+ e = new goog.events.Event(type, target);
+ goog.object.extend(e, oldEvent);
+ } else {
+ e.target = e.target || target;
+ }
+
+ var rv = true, currentTarget;
+
+ // Executes all capture listeners on the ancestors, if any.
+ if (opt_ancestorsTree) {
+ for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0;
+ i--) {
+ currentTarget = e.currentTarget = opt_ancestorsTree[i];
+ rv = currentTarget.fireListeners(type, true, e) && rv;
+ }
+ }
+
+ // Executes capture and bubble listeners on the target.
+ if (!e.propagationStopped_) {
+ currentTarget = e.currentTarget = target;
+ rv = currentTarget.fireListeners(type, true, e) && rv;
+ if (!e.propagationStopped_) {
+ rv = currentTarget.fireListeners(type, false, e) && rv;
+ }
+ }
+
+ // Executes all bubble listeners on the ancestors, if any.
+ if (opt_ancestorsTree) {
+ for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) {
+ currentTarget = e.currentTarget = opt_ancestorsTree[i];
+ rv = currentTarget.fireListeners(type, false, e) && rv;
+ }
+ }
+
+ return rv;
+};
+
+goog.provide('ol.Observable');
+
+goog.require('goog.events');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+
+
+
+/**
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * An event target providing convenient methods for listener registration
+ * and unregistration. A generic `change` event is always available through
+ * {@link ol.Observable#changed}.
+ *
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @fires change
+ * @struct
+ * @api stable
+ */
+ol.Observable = function() {
+
+ goog.base(this);
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.revision_ = 0;
+
+};
+goog.inherits(ol.Observable, goog.events.EventTarget);
+
+
+/**
+ * Removes an event listener using the key returned by `on()` or `once()`.
+ * @param {goog.events.Key} key The key returned by `on()` or `once()`.
+ * @api stable
+ */
+ol.Observable.unByKey = function(key) {
+ goog.events.unlistenByKey(key);
+};
+
+
+/**
+ * Increases the revision counter and dispatches a 'change' event.
+ * @api
+ */
+ol.Observable.prototype.changed = function() {
+ ++this.revision_;
+ this.dispatchEvent(goog.events.EventType.CHANGE);
+};
+
+
+/**
+ * Triggered when the revision counter is increased.
+ * @event change
+ * @api
+ */
+
+
+/**
+ * Dispatches an event and calls all listeners listening for events
+ * of this type. The event parameter can either be a string or an
+ * Object with a `type` property.
+ *
+ * @param {goog.events.EventLike} event Event object.
+ * @function
+ * @api
+ */
+ol.Observable.prototype.dispatchEvent;
+
+
+/**
+ * Get the version number for this object. Each time the object is modified,
+ * its version number will be incremented.
+ * @return {number} Revision.
+ * @api
+ */
+ol.Observable.prototype.getRevision = function() {
+ return this.revision_;
+};
+
+
+/**
+ * Listen for a certain type of event.
+ * @param {string|Array.<string>} type The event type or array of event types.
+ * @param {function(?): ?} listener The listener function.
+ * @param {Object=} opt_this The object to use as `this` in `listener`.
+ * @return {goog.events.Key} Unique key for the listener.
+ * @api stable
+ */
+ol.Observable.prototype.on = function(type, listener, opt_this) {
+ return goog.events.listen(this, type, listener, false, opt_this);
+};
+
+
+/**
+ * Listen once for a certain type of event.
+ * @param {string|Array.<string>} type The event type or array of event types.
+ * @param {function(?): ?} listener The listener function.
+ * @param {Object=} opt_this The object to use as `this` in `listener`.
+ * @return {goog.events.Key} Unique key for the listener.
+ * @api stable
+ */
+ol.Observable.prototype.once = function(type, listener, opt_this) {
+ return goog.events.listenOnce(this, type, listener, false, opt_this);
+};
+
+
+/**
+ * Unlisten for a certain type of event.
+ * @param {string|Array.<string>} type The event type or array of event types.
+ * @param {function(?): ?} listener The listener function.
+ * @param {Object=} opt_this The object which was used as `this` by the
+ * `listener`.
+ * @api stable
+ */
+ol.Observable.prototype.un = function(type, listener, opt_this) {
+ goog.events.unlisten(this, type, listener, false, opt_this);
+};
+
+
+/**
+ * Removes an event listener using the key returned by `on()` or `once()`.
+ * Note that using the {@link ol.Observable.unByKey} static function is to
+ * be preferred.
+ * @param {goog.events.Key} key The key returned by `on()` or `once()`.
+ * @function
+ * @api stable
+ */
+ol.Observable.prototype.unByKey = ol.Observable.unByKey;
+
+goog.provide('ol.Object');
+goog.provide('ol.ObjectEvent');
+goog.provide('ol.ObjectEventType');
+
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('ol.Observable');
+
+
+/**
+ * @enum {string}
+ */
+ol.ObjectEventType = {
+ /**
+ * Triggered when a property is changed.
+ * @event ol.ObjectEvent#propertychange
+ * @api stable
+ */
+ PROPERTYCHANGE: 'propertychange'
+};
+
+
+
+/**
+ * @classdesc
+ * Events emitted by {@link ol.Object} instances are instances of this type.
+ *
+ * @param {string} type The event type.
+ * @param {string} key The property name.
+ * @param {*} oldValue The old value for `key`.
+ * @extends {goog.events.Event}
+ * @implements {oli.ObjectEvent}
+ * @constructor
+ */
+ol.ObjectEvent = function(type, key, oldValue) {
+ goog.base(this, type);
+
+ /**
+ * The name of the property whose value is changing.
+ * @type {string}
+ * @api stable
+ */
+ this.key = key;
+
+ /**
+ * The old value. To get the new value use `e.target.get(e.key)` where
+ * `e` is the event object.
+ * @type {*}
+ * @api stable
+ */
+ this.oldValue = oldValue;
+
+};
+goog.inherits(ol.ObjectEvent, goog.events.Event);
+
+
+
+/**
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Most non-trivial classes inherit from this.
+ *
+ * This extends {@link ol.Observable} with observable properties, where each
+ * 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 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
+ * are also general-purpose accessors `get()` and `set()`. For example,
+ * `get('target')` is equivalent to `getTarget()`.
+ *
+ * The `set` accessors trigger a change event, and you can monitor this by
+ * registering a listener. For example, {@link ol.View} has a `center`
+ * property, so `view.on('change:center', function(evt) {...});` would call the
+ * function whenever the value of the center property changes. Within the
+ * function, `evt.target` would be the view, so `evt.target.getCenter()` would
+ * return the new center.
+ *
+ * You can add your own observable properties with
+ * `object.set('prop', 'value')`, and retrieve that with `object.get('prop')`.
+ * You can listen for changes on that property value with
+ * `object.on('change:prop', listener)`. You can get a list of all
+ * properties with {@link ol.Object#getProperties object.getProperties()}.
+ *
+ * Note that the observable properties are separate from standard JS properties.
+ * You can, for example, give your map object a title with
+ * `map.title='New title'` and with `map.set('title', 'Another title')`. The
+ * first will be a `hasOwnProperty`; the second will appear in
+ * `getProperties()`. Only the second is observable.
+ *
+ * Properties can be deleted by using the unset method. E.g.
+ * object.unset('foo').
+ *
+ * @constructor
+ * @extends {ol.Observable}
+ * @param {Object.<string, *>=} opt_values An object with key-value pairs.
+ * @fires ol.ObjectEvent
+ * @api
+ */
+ol.Object = function(opt_values) {
+ goog.base(this);
+
+ // Call goog.getUid to ensure that the order of objects' ids is the same as
+ // the order in which they were created. This also helps to ensure that
+ // object properties are always added in the same order, which helps many
+ // JavaScript engines generate faster code.
+ goog.getUid(this);
+
+ /**
+ * @private
+ * @type {!Object.<string, *>}
+ */
+ this.values_ = {};
+
+ if (opt_values !== undefined) {
+ this.setProperties(opt_values);
+ }
+};
+goog.inherits(ol.Object, ol.Observable);
+
+
+/**
+ * @private
+ * @type {Object.<string, string>}
+ */
+ol.Object.changeEventTypeCache_ = {};
+
+
+/**
+ * @param {string} key Key name.
+ * @return {string} Change name.
+ */
+ol.Object.getChangeEventType = function(key) {
+ return ol.Object.changeEventTypeCache_.hasOwnProperty(key) ?
+ ol.Object.changeEventTypeCache_[key] :
+ (ol.Object.changeEventTypeCache_[key] = 'change:' + key);
+};
+
+
+/**
+ * Gets a value.
+ * @param {string} key Key name.
+ * @return {*} Value.
+ * @api stable
+ */
+ol.Object.prototype.get = function(key) {
+ var value;
+ if (this.values_.hasOwnProperty(key)) {
+ value = this.values_[key];
+ }
+ return value;
+};
+
+
+/**
+ * Get a list of object property names.
+ * @return {Array.<string>} List of property names.
+ * @api stable
+ */
+ol.Object.prototype.getKeys = function() {
+ return Object.keys(this.values_);
+};
+
+
+/**
+ * Get an object of all property names and values.
+ * @return {Object.<string, *>} Object.
+ * @api stable
+ */
+ol.Object.prototype.getProperties = function() {
+ var properties = {};
+ var key;
+ for (key in this.values_) {
+ properties[key] = this.values_[key];
+ }
+ return properties;
+};
+
+
+/**
+ * @param {string} key Key name.
+ * @param {*} oldValue Old value.
+ */
+ol.Object.prototype.notify = function(key, oldValue) {
+ var eventType;
+ eventType = ol.Object.getChangeEventType(key);
+ this.dispatchEvent(new ol.ObjectEvent(eventType, key, oldValue));
+ eventType = ol.ObjectEventType.PROPERTYCHANGE;
+ this.dispatchEvent(new ol.ObjectEvent(eventType, key, oldValue));
+};
+
+
+/**
+ * Sets a value.
+ * @param {string} key Key name.
+ * @param {*} value Value.
+ * @param {boolean=} opt_silent Update without triggering an event.
+ * @api stable
+ */
+ol.Object.prototype.set = function(key, value, opt_silent) {
+ if (opt_silent) {
+ this.values_[key] = value;
+ } else {
+ var oldValue = this.values_[key];
+ this.values_[key] = value;
+ if (oldValue !== value) {
+ this.notify(key, oldValue);
+ }
+ }
+};
+
+
+/**
+ * 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.
+ * @param {boolean=} opt_silent Update without triggering an event.
+ * @api stable
+ */
+ol.Object.prototype.setProperties = function(values, opt_silent) {
+ var key;
+ for (key in values) {
+ this.set(key, values[key], opt_silent);
+ }
+};
+
+
+/**
+ * Unsets a property.
+ * @param {string} key Key name.
+ * @param {boolean=} opt_silent Unset without triggering an event.
+ * @api stable
+ */
+ol.Object.prototype.unset = function(key, opt_silent) {
+ if (key in this.values_) {
+ var oldValue = this.values_[key];
+ delete this.values_[key];
+ if (!opt_silent) {
+ this.notify(key, oldValue);
+ }
+ }
+};
+
+goog.provide('ol.Size');
+goog.provide('ol.size');
+
+
+goog.require('goog.asserts');
+
+
+/**
+ * An array of numbers representing a size: `[width, height]`.
+ * @typedef {Array.<number>}
+ * @api stable
+ */
+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 (opt_size === undefined) {
+ 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.
+ * @param {ol.Size} b Size.
+ * @return {boolean} Equals.
+ */
+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 (opt_size === undefined) {
+ 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 (opt_size === undefined) {
+ opt_size = [size, size];
+ } else {
+ opt_size[0] = size;
+ opt_size[1] = size;
+ }
+ return opt_size;
+ }
+};
+
+// 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 Additional mathematical functions.
+ */
+
+goog.provide('goog.math');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+
+
+/**
+ * Returns a random integer greater than or equal to 0 and less than {@code a}.
+ * @param {number} a The upper bound for the random integer (exclusive).
+ * @return {number} A random integer N such that 0 <= N < a.
+ */
+goog.math.randomInt = function(a) {
+ return Math.floor(Math.random() * a);
+};
+
+
+/**
+ * Returns a random number greater than or equal to {@code a} and less than
+ * {@code b}.
+ * @param {number} a The lower bound for the random number (inclusive).
+ * @param {number} b The upper bound for the random number (exclusive).
+ * @return {number} A random number N such that a <= N < b.
+ */
+goog.math.uniformRandom = function(a, b) {
+ return a + Math.random() * (b - a);
+};
+
+
+/**
+ * Takes a number and clamps it to within the provided bounds.
+ * @param {number} value The input number.
+ * @param {number} min The minimum value to return.
+ * @param {number} max The maximum value to return.
+ * @return {number} The input number if it is within bounds, or the nearest
+ * number within the bounds.
+ */
+goog.math.clamp = function(value, min, max) {
+ return Math.min(Math.max(value, min), max);
+};
+
+
+/**
+ * The % operator in JavaScript returns the remainder of a / b, but differs from
+ * some other languages in that the result will have the same sign as the
+ * dividend. For example, -1 % 8 == -1, whereas in some other languages
+ * (such as Python) the result would be 7. This function emulates the more
+ * correct modulo behavior, which is useful for certain applications such as
+ * calculating an offset index in a circular list.
+ *
+ * @param {number} a The dividend.
+ * @param {number} b The divisor.
+ * @return {number} a % b where the result is between 0 and b (either 0 <= x < b
+ * or b < x <= 0, depending on the sign of b).
+ */
+goog.math.modulo = function(a, b) {
+ var r = a % b;
+ // If r and b differ in sign, add b to wrap the result to the correct sign.
+ return (r * b < 0) ? r + b : r;
+};
+
+
+/**
+ * Performs linear interpolation between values a and b. Returns the value
+ * between a and b proportional to x (when x is between 0 and 1. When x is
+ * outside this range, the return value is a linear extrapolation).
+ * @param {number} a A number.
+ * @param {number} b A number.
+ * @param {number} x The proportion between a and b.
+ * @return {number} The interpolated value between a and b.
+ */
+goog.math.lerp = function(a, b, x) {
+ return a + x * (b - a);
+};
+
+
+/**
+ * Tests whether the two values are equal to each other, within a certain
+ * tolerance to adjust for floating point errors.
+ * @param {number} a A number.
+ * @param {number} b A number.
+ * @param {number=} opt_tolerance Optional tolerance range. Defaults
+ * to 0.000001. If specified, should be greater than 0.
+ * @return {boolean} Whether {@code a} and {@code b} are nearly equal.
+ */
+goog.math.nearlyEquals = function(a, b, opt_tolerance) {
+ return Math.abs(a - b) <= (opt_tolerance || 0.000001);
+};
+
+
+// TODO(user): Rename to normalizeAngle, retaining old name as deprecated
+// alias.
+/**
+ * Normalizes an angle to be in range [0-360). Angles outside this range will
+ * be normalized to be the equivalent angle with that range.
+ * @param {number} angle Angle in degrees.
+ * @return {number} Standardized angle.
+ */
+goog.math.standardAngle = function(angle) {
+ return goog.math.modulo(angle, 360);
+};
+
+
+/**
+ * Normalizes an angle to be in range [0-2*PI). Angles outside this range will
+ * be normalized to be the equivalent angle with that range.
+ * @param {number} angle Angle in radians.
+ * @return {number} Standardized angle.
+ */
+goog.math.standardAngleInRadians = function(angle) {
+ return goog.math.modulo(angle, 2 * Math.PI);
+};
+
+
+/**
+ * Converts degrees to radians.
+ * @param {number} angleDegrees Angle in degrees.
+ * @return {number} Angle in radians.
+ */
+goog.math.toRadians = function(angleDegrees) {
+ return angleDegrees * Math.PI / 180;
+};
+
+
+/**
+ * Converts radians to degrees.
+ * @param {number} angleRadians Angle in radians.
+ * @return {number} Angle in degrees.
+ */
+goog.math.toDegrees = function(angleRadians) {
+ return angleRadians * 180 / Math.PI;
+};
+
+
+/**
+ * For a given angle and radius, finds the X portion of the offset.
+ * @param {number} degrees Angle in degrees (zero points in +X direction).
+ * @param {number} radius Radius.
+ * @return {number} The x-distance for the angle and radius.
+ */
+goog.math.angleDx = function(degrees, radius) {
+ return radius * Math.cos(goog.math.toRadians(degrees));
+};
+
+
+/**
+ * For a given angle and radius, finds the Y portion of the offset.
+ * @param {number} degrees Angle in degrees (zero points in +X direction).
+ * @param {number} radius Radius.
+ * @return {number} The y-distance for the angle and radius.
+ */
+goog.math.angleDy = function(degrees, radius) {
+ return radius * Math.sin(goog.math.toRadians(degrees));
+};
+
+
+/**
+ * Computes the angle between two points (x1,y1) and (x2,y2).
+ * Angle zero points in the +X direction, 90 degrees points in the +Y
+ * direction (down) and from there we grow clockwise towards 360 degrees.
+ * @param {number} x1 x of first point.
+ * @param {number} y1 y of first point.
+ * @param {number} x2 x of second point.
+ * @param {number} y2 y of second point.
+ * @return {number} Standardized angle in degrees of the vector from
+ * x1,y1 to x2,y2.
+ */
+goog.math.angle = function(x1, y1, x2, y2) {
+ return goog.math.standardAngle(goog.math.toDegrees(Math.atan2(y2 - y1,
+ x2 - x1)));
+};
+
+
+/**
+ * Computes the difference between startAngle and endAngle (angles in degrees).
+ * @param {number} startAngle Start angle in degrees.
+ * @param {number} endAngle End angle in degrees.
+ * @return {number} The number of degrees that when added to
+ * startAngle will result in endAngle. Positive numbers mean that the
+ * direction is clockwise. Negative numbers indicate a counter-clockwise
+ * direction.
+ * The shortest route (clockwise vs counter-clockwise) between the angles
+ * is used.
+ * When the difference is 180 degrees, the function returns 180 (not -180)
+ * angleDifference(30, 40) is 10, and angleDifference(40, 30) is -10.
+ * angleDifference(350, 10) is 20, and angleDifference(10, 350) is -20.
+ */
+goog.math.angleDifference = function(startAngle, endAngle) {
+ var d = goog.math.standardAngle(endAngle) -
+ goog.math.standardAngle(startAngle);
+ if (d > 180) {
+ d = d - 360;
+ } else if (d <= -180) {
+ d = 360 + d;
+ }
+ return d;
+};
+
+
+/**
+ * Returns the sign of a number as per the "sign" or "signum" function.
+ * @param {number} x The number to take the sign of.
+ * @return {number} -1 when negative, 1 when positive, 0 when 0. Preserves
+ * signed zeros and NaN.
+ */
+goog.math.sign = Math.sign || function(x) {
+ if (x > 0) {
+ return 1;
+ }
+ if (x < 0) {
+ return -1;
+ }
+ return x; // Preserves signed zeros and NaN.
+};
+
+
+/**
+ * JavaScript implementation of Longest Common Subsequence problem.
+ * http://en.wikipedia.org/wiki/Longest_common_subsequence
+ *
+ * Returns the longest possible array that is subarray of both of given arrays.
+ *
+ * @param {Array<Object>} array1 First array of objects.
+ * @param {Array<Object>} array2 Second array of objects.
+ * @param {Function=} opt_compareFn Function that acts as a custom comparator
+ * for the array ojects. Function should return true if objects are equal,
+ * otherwise false.
+ * @param {Function=} opt_collectorFn Function used to decide what to return
+ * as a result subsequence. It accepts 2 arguments: index of common element
+ * in the first array and index in the second. The default function returns
+ * element from the first array.
+ * @return {!Array<Object>} A list of objects that are common to both arrays
+ * such that there is no common subsequence with size greater than the
+ * length of the list.
+ */
+goog.math.longestCommonSubsequence = function(
+ array1, array2, opt_compareFn, opt_collectorFn) {
+
+ var compare = opt_compareFn || function(a, b) {
+ return a == b;
+ };
+
+ var collect = opt_collectorFn || function(i1, i2) {
+ return array1[i1];
+ };
+
+ var length1 = array1.length;
+ var length2 = array2.length;
+
+ var arr = [];
+ for (var i = 0; i < length1 + 1; i++) {
+ arr[i] = [];
+ arr[i][0] = 0;
+ }
+
+ for (var j = 0; j < length2 + 1; j++) {
+ arr[0][j] = 0;
+ }
+
+ for (i = 1; i <= length1; i++) {
+ for (j = 1; j <= length2; j++) {
+ if (compare(array1[i - 1], array2[j - 1])) {
+ arr[i][j] = arr[i - 1][j - 1] + 1;
+ } else {
+ arr[i][j] = Math.max(arr[i - 1][j], arr[i][j - 1]);
+ }
+ }
+ }
+
+ // Backtracking
+ var result = [];
+ var i = length1, j = length2;
+ while (i > 0 && j > 0) {
+ if (compare(array1[i - 1], array2[j - 1])) {
+ result.unshift(collect(i - 1, j - 1));
+ i--;
+ j--;
+ } else {
+ if (arr[i - 1][j] > arr[i][j - 1]) {
+ i--;
+ } else {
+ j--;
+ }
+ }
+ }
+
+ return result;
+};
+
+
+/**
+ * Returns the sum of the arguments.
+ * @param {...number} var_args Numbers to add.
+ * @return {number} The sum of the arguments (0 if no arguments were provided,
+ * {@code NaN} if any of the arguments is not a valid number).
+ */
+goog.math.sum = function(var_args) {
+ return /** @type {number} */ (goog.array.reduce(arguments,
+ function(sum, value) {
+ return sum + value;
+ }, 0));
+};
+
+
+/**
+ * Returns the arithmetic mean of the arguments.
+ * @param {...number} var_args Numbers to average.
+ * @return {number} The average of the arguments ({@code NaN} if no arguments
+ * were provided or any of the arguments is not a valid number).
+ */
+goog.math.average = function(var_args) {
+ return goog.math.sum.apply(null, arguments) / arguments.length;
+};
+
+
+/**
+ * Returns the unbiased sample variance of the arguments. For a definition,
+ * see e.g. http://en.wikipedia.org/wiki/Variance
+ * @param {...number} var_args Number samples to analyze.
+ * @return {number} The unbiased sample variance of the arguments (0 if fewer
+ * than two samples were provided, or {@code NaN} if any of the samples is
+ * not a valid number).
+ */
+goog.math.sampleVariance = function(var_args) {
+ var sampleSize = arguments.length;
+ if (sampleSize < 2) {
+ return 0;
+ }
+
+ var mean = goog.math.average.apply(null, arguments);
+ var variance = goog.math.sum.apply(null, goog.array.map(arguments,
+ function(val) {
+ return Math.pow(val - mean, 2);
+ })) / (sampleSize - 1);
+
+ return variance;
+};
+
+
+/**
+ * Returns the sample standard deviation of the arguments. For a definition of
+ * sample standard deviation, see e.g.
+ * http://en.wikipedia.org/wiki/Standard_deviation
+ * @param {...number} var_args Number samples to analyze.
+ * @return {number} The sample standard deviation of the arguments (0 if fewer
+ * than two samples were provided, or {@code NaN} if any of the samples is
+ * not a valid number).
+ */
+goog.math.standardDeviation = function(var_args) {
+ return Math.sqrt(goog.math.sampleVariance.apply(null, arguments));
+};
+
+
+/**
+ * Returns whether the supplied number represents an integer, i.e. that is has
+ * no fractional component. No range-checking is performed on the number.
+ * @param {number} num The number to test.
+ * @return {boolean} Whether {@code num} is an integer.
+ */
+goog.math.isInt = function(num) {
+ return isFinite(num) && num % 1 == 0;
+};
+
+
+/**
+ * Returns whether the supplied number is finite and not NaN.
+ * @param {number} num The number to test.
+ * @return {boolean} Whether {@code num} is a finite number.
+ */
+goog.math.isFiniteNumber = function(num) {
+ return isFinite(num) && !isNaN(num);
+};
+
+
+/**
+ * @param {number} num The number to test.
+ * @return {boolean} Whether it is negative zero.
+ */
+goog.math.isNegativeZero = function(num) {
+ return num == 0 && 1 / num < 0;
+};
+
+
+/**
+ * Returns the precise value of floor(log10(num)).
+ * Simpler implementations didn't work because of floating point rounding
+ * errors. For example
+ * <ul>
+ * <li>Math.floor(Math.log(num) / Math.LN10) is off by one for num == 1e+3.
+ * <li>Math.floor(Math.log(num) * Math.LOG10E) is off by one for num == 1e+15.
+ * <li>Math.floor(Math.log10(num)) is off by one for num == 1e+15 - 1.
+ * </ul>
+ * @param {number} num A floating point number.
+ * @return {number} Its logarithm to base 10 rounded down to the nearest
+ * integer if num > 0. -Infinity if num == 0. NaN if num < 0.
+ */
+goog.math.log10Floor = function(num) {
+ if (num > 0) {
+ var x = Math.round(Math.log(num) * Math.LOG10E);
+ return x - (parseFloat('1e' + x) > num);
+ }
+ return num == 0 ? -Infinity : NaN;
+};
+
+
+/**
+ * A tweaked variant of {@code Math.floor} which tolerates if the passed number
+ * is infinitesimally smaller than the closest integer. It often happens with
+ * the results of floating point calculations because of the finite precision
+ * of the intermediate results. For example {@code Math.floor(Math.log(1000) /
+ * Math.LN10) == 2}, not 3 as one would expect.
+ * @param {number} num A number.
+ * @param {number=} opt_epsilon An infinitesimally small positive number, the
+ * rounding error to tolerate.
+ * @return {number} The largest integer less than or equal to {@code num}.
+ */
+goog.math.safeFloor = function(num, opt_epsilon) {
+ goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
+ return Math.floor(num + (opt_epsilon || 2e-15));
+};
+
+
+/**
+ * A tweaked variant of {@code Math.ceil}. See {@code goog.math.safeFloor} for
+ * details.
+ * @param {number} num A number.
+ * @param {number=} opt_epsilon An infinitesimally small positive number, the
+ * rounding error to tolerate.
+ * @return {number} The smallest integer greater than or equal to {@code num}.
+ */
+goog.math.safeCeil = function(num, opt_epsilon) {
+ goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
+ return Math.ceil(num - (opt_epsilon || 2e-15));
+};
+
+goog.provide('ol.Coordinate');
+goog.provide('ol.CoordinateFormatType');
+goog.provide('ol.coordinate');
+
+goog.require('goog.math');
+goog.require('goog.string');
+
+
+/**
+ * A function that takes a {@link ol.Coordinate} and transforms it into a
+ * `{string}`.
+ *
+ * @typedef {function((ol.Coordinate|undefined)): string}
+ * @api stable
+ */
+ol.CoordinateFormatType;
+
+
+/**
+ * An array of numbers representing an xy coordinate. Example: `[16, 48]`.
+ * @typedef {Array.<number>} ol.Coordinate
+ * @api stable
+ */
+ol.Coordinate;
+
+
+/**
+ * Add `delta` to `coordinate`. `coordinate` is modified in place and returned
+ * by the function.
+ *
+ * Example:
+ *
+ * var coord = [7.85, 47.983333];
+ * ol.coordinate.add(coord, [-2, 4]);
+ * // coord is now [5.85, 51.983333]
+ *
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {ol.Coordinate} delta Delta.
+ * @return {ol.Coordinate} The input coordinate adjusted by the given delta.
+ * @api stable
+ */
+ol.coordinate.add = function(coordinate, delta) {
+ coordinate[0] += delta[0];
+ coordinate[1] += delta[1];
+ return coordinate;
+};
+
+
+/**
+ * Calculates the point closest to the passed coordinate on the passed segment.
+ * This is the foot of the perpendicular of the coordinate to the segment when
+ * the foot is on the segment, or the closest segment coordinate when the foot
+ * is outside the segment.
+ *
+ * @param {ol.Coordinate} coordinate The coordinate.
+ * @param {Array.<ol.Coordinate>} segment The two coordinates of the segment.
+ * @return {ol.Coordinate} The foot of the perpendicular of the coordinate to
+ * the segment.
+ */
+ol.coordinate.closestOnSegment = function(coordinate, segment) {
+ var x0 = coordinate[0];
+ var y0 = coordinate[1];
+ var start = segment[0];
+ var end = segment[1];
+ var x1 = start[0];
+ var y1 = start[1];
+ var x2 = end[0];
+ var y2 = end[1];
+ var dx = x2 - x1;
+ var dy = y2 - y1;
+ var along = (dx === 0 && dy === 0) ? 0 :
+ ((dx * (x0 - x1)) + (dy * (y0 - y1))) / ((dx * dx + dy * dy) || 0);
+ var x, y;
+ if (along <= 0) {
+ x = x1;
+ y = y1;
+ } else if (along >= 1) {
+ x = x2;
+ y = y2;
+ } else {
+ x = x1 + along * dx;
+ y = y1 + along * dy;
+ }
+ return [x, y];
+};
+
+
+/**
+ * Returns a {@link ol.CoordinateFormatType} function that can be used to format
+ * a {ol.Coordinate} to a string.
+ *
+ * Example without specifying the fractional digits:
+ *
+ * var coord = [7.85, 47.983333];
+ * var stringifyFunc = ol.coordinate.createStringXY();
+ * var out = stringifyFunc(coord);
+ * // out is now '8, 48'
+ *
+ * Example with explicitly specifying 2 fractional digits:
+ *
+ * var coord = [7.85, 47.983333];
+ * var stringifyFunc = ol.coordinate.createStringXY(2);
+ * var out = stringifyFunc(coord);
+ * // out is now '7.85, 47.98'
+ *
+ * @param {number=} opt_fractionDigits The number of digits to include
+ * after the decimal point. Default is `0`.
+ * @return {ol.CoordinateFormatType} Coordinate format.
+ * @api stable
+ */
+ol.coordinate.createStringXY = function(opt_fractionDigits) {
+ return (
+ /**
+ * @param {ol.Coordinate|undefined} coordinate Coordinate.
+ * @return {string} String XY.
+ */
+ function(coordinate) {
+ return ol.coordinate.toStringXY(coordinate, opt_fractionDigits);
+ });
+};
+
+
+/**
+ * @private
+ * @param {number} degrees Degrees.
+ * @param {string} hemispheres Hemispheres.
+ * @return {string} String.
+ */
+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 ' +
+ 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);
+};
+
+
+/**
+ * Transforms the given {@link ol.Coordinate} to a string using the given string
+ * template. The strings `{x}` and `{y}` in the template will be replaced with
+ * the first and second coordinate values respectively.
+ *
+ * Example without specifying the fractional digits:
+ *
+ * var coord = [7.85, 47.983333];
+ * var template = 'Coordinate is ({x}|{y}).';
+ * var out = ol.coordinate.format(coord, template);
+ * // out is now 'Coordinate is (8|48).'
+ *
+ * Example explicitly specifying the fractional digits:
+ *
+ * var coord = [7.85, 47.983333];
+ * var template = 'Coordinate is ({x}|{y}).';
+ * var out = ol.coordinate.format(coord, template, 2);
+ * // out is now 'Coordinate is (7.85|47.98).'
+ *
+ * @param {ol.Coordinate|undefined} coordinate Coordinate.
+ * @param {string} template A template string with `{x}` and `{y}` placeholders
+ * 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} Formatted coordinate.
+ * @api stable
+ */
+ol.coordinate.format = function(coordinate, template, opt_fractionDigits) {
+ if (coordinate) {
+ return template
+ .replace('{x}', coordinate[0].toFixed(opt_fractionDigits))
+ .replace('{y}', coordinate[1].toFixed(opt_fractionDigits));
+ } else {
+ return '';
+ }
+};
+
+
+/**
+ * @param {ol.Coordinate} coordinate1 First coordinate.
+ * @param {ol.Coordinate} coordinate2 Second coordinate.
+ * @return {boolean} Whether the passed coordinates are equal.
+ */
+ol.coordinate.equals = function(coordinate1, coordinate2) {
+ var equals = true;
+ for (var i = coordinate1.length - 1; i >= 0; --i) {
+ if (coordinate1[i] != coordinate2[i]) {
+ equals = false;
+ break;
+ }
+ }
+ return equals;
+};
+
+
+/**
+ * Rotate `coordinate` by `angle`. `coordinate` is modified in place and
+ * returned by the function.
+ *
+ * Example:
+ *
+ * var coord = [7.85, 47.983333];
+ * var rotateRadians = Math.PI / 2; // 90 degrees
+ * ol.coordinate.rotate(coord, rotateRadians);
+ * // coord is now [-47.983333, 7.85]
+ *
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} angle Angle in radian.
+ * @return {ol.Coordinate} Coordinate.
+ * @api stable
+ */
+ol.coordinate.rotate = function(coordinate, angle) {
+ var cosAngle = Math.cos(angle);
+ var sinAngle = Math.sin(angle);
+ var x = coordinate[0] * cosAngle - coordinate[1] * sinAngle;
+ var y = coordinate[1] * cosAngle + coordinate[0] * sinAngle;
+ coordinate[0] = x;
+ coordinate[1] = y;
+ return coordinate;
+};
+
+
+/**
+ * Scale `coordinate` by `scale`. `coordinate` is modified in place and returned
+ * by the function.
+ *
+ * Example:
+ *
+ * var coord = [7.85, 47.983333];
+ * var scale = 1.2;
+ * ol.coordinate.scale(coord, scale);
+ * // coord is now [9.42, 57.5799996]
+ *
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} scale Scale factor.
+ * @return {ol.Coordinate} Coordinate.
+ */
+ol.coordinate.scale = function(coordinate, scale) {
+ coordinate[0] *= scale;
+ coordinate[1] *= scale;
+ return coordinate;
+};
+
+
+/**
+ * Subtract `delta` to `coordinate`. `coordinate` is modified in place and
+ * returned by the function.
+ *
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {ol.Coordinate} delta Delta.
+ * @return {ol.Coordinate} Coordinate.
+ */
+ol.coordinate.sub = function(coordinate, delta) {
+ coordinate[0] -= delta[0];
+ coordinate[1] -= delta[1];
+ return coordinate;
+};
+
+
+/**
+ * @param {ol.Coordinate} coord1 First coordinate.
+ * @param {ol.Coordinate} coord2 Second coordinate.
+ * @return {number} Squared distance between coord1 and coord2.
+ */
+ol.coordinate.squaredDistance = function(coord1, coord2) {
+ var dx = coord1[0] - coord2[0];
+ var dy = coord1[1] - coord2[1];
+ return dx * dx + dy * dy;
+};
+
+
+/**
+ * Calculate the squared distance from a coordinate to a line segment.
+ *
+ * @param {ol.Coordinate} coordinate Coordinate of the point.
+ * @param {Array.<ol.Coordinate>} segment Line segment (2 coordinates).
+ * @return {number} Squared distance from the point to the line segment.
+ */
+ol.coordinate.squaredDistanceToSegment = function(coordinate, segment) {
+ return ol.coordinate.squaredDistance(coordinate,
+ ol.coordinate.closestOnSegment(coordinate, segment));
+};
+
+
+/**
+ * Format a geographic coordinate with the hemisphere, degrees, minutes, and
+ * seconds.
+ *
+ * Example:
+ *
+ * var coord = [7.85, 47.983333];
+ * var out = ol.coordinate.toStringHDMS(coord);
+ * // out is now '47° 59′ 0″ N 7° 51′ 0″ E'
+ *
+ * @param {ol.Coordinate|undefined} coordinate Coordinate.
+ * @return {string} Hemisphere, degrees, minutes and seconds.
+ * @api stable
+ */
+ol.coordinate.toStringHDMS = function(coordinate) {
+ if (coordinate) {
+ return ol.coordinate.degreesToStringHDMS_(coordinate[1], 'NS') + ' ' +
+ ol.coordinate.degreesToStringHDMS_(coordinate[0], 'EW');
+ } else {
+ return '';
+ }
+};
+
+
+/**
+ * Format a coordinate as a comma delimited string.
+ *
+ * Example without specifying fractional digits:
+ *
+ * var coord = [7.85, 47.983333];
+ * var out = ol.coordinate.toStringXY(coord);
+ * // out is now '8, 48'
+ *
+ * Example explicitly specifying 1 fractional digit:
+ *
+ * var coord = [7.85, 47.983333];
+ * var out = ol.coordinate.toStringXY(coord, 1);
+ * // out is now '7.8, 48.0'
+ *
+ * @param {ol.Coordinate|undefined} coordinate Coordinate.
+ * @param {number=} opt_fractionDigits The number of digits to include
+ * after the decimal point. Default is `0`.
+ * @return {string} XY.
+ * @api stable
+ */
+ol.coordinate.toStringXY = function(coordinate, opt_fractionDigits) {
+ return ol.coordinate.format(coordinate, '{x}, {y}', opt_fractionDigits);
+};
+
+
+/**
+ * Create an ol.Coordinate from an Array and take into account axis order.
+ *
+ * Examples:
+ *
+ * var northCoord = ol.coordinate.fromProjectedArray([1, 2], 'n');
+ * // northCoord is now [2, 1]
+ *
+ * var eastCoord = ol.coordinate.fromProjectedArray([1, 2], 'e');
+ * // eastCoord is now [1, 2]
+ *
+ * @param {Array} array The array with coordinates.
+ * @param {string} axis the axis info.
+ * @return {ol.Coordinate} The coordinate created.
+ */
+ol.coordinate.fromProjectedArray = function(array, axis) {
+ var firstAxis = axis.charAt(0);
+ if (firstAxis === 'n' || firstAxis === 's') {
+ return [array[1], array[0]];
+ } else {
+ return array;
+ }
+};
+
+// 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 Supplies a Float32Array implementation that implements
+ * most of the Float32Array spec and that can be used when a built-in
+ * implementation is not available.
+ *
+ * Note that if no existing Float32Array implementation is found then
+ * this class and all its public properties are exported as Float32Array.
+ *
+ * Adding support for the other TypedArray classes here does not make sense
+ * since this vector math library only needs Float32Array.
+ *
+ */
+goog.provide('goog.vec.Float32Array');
+
+
+
+/**
+ * Constructs a new Float32Array. The new array is initialized to all zeros.
+ *
+ * @param {goog.vec.Float32Array|Array|ArrayBuffer|number} p0
+ * The length of the array, or an array to initialize the contents of the
+ * new Float32Array.
+ * @constructor
+ * @final
+ */
+goog.vec.Float32Array = function(p0) {
+ this.length = /** @type {number} */ (p0.length || p0);
+ for (var i = 0; i < this.length; i++) {
+ this[i] = p0[i] || 0;
+ }
+};
+
+
+/**
+ * The number of bytes in an element (as defined by the Typed Array
+ * specification).
+ *
+ * @type {number}
+ */
+goog.vec.Float32Array.BYTES_PER_ELEMENT = 4;
+
+
+/**
+ * The number of bytes in an element (as defined by the Typed Array
+ * specification).
+ *
+ * @type {number}
+ */
+goog.vec.Float32Array.prototype.BYTES_PER_ELEMENT = 4;
+
+
+/**
+ * Sets elements of the array.
+ * @param {Array<number>|Float32Array} values The array of values.
+ * @param {number=} opt_offset The offset in this array to start.
+ */
+goog.vec.Float32Array.prototype.set = function(values, opt_offset) {
+ opt_offset = opt_offset || 0;
+ for (var i = 0; i < values.length && opt_offset + i < this.length; i++) {
+ this[opt_offset + i] = values[i];
+ }
+};
+
+
+/**
+ * Creates a string representation of this array.
+ * @return {string} The string version of this array.
+ * @override
+ */
+goog.vec.Float32Array.prototype.toString = Array.prototype.join;
+
+
+/**
+ * Note that we cannot implement the subarray() or (deprecated) slice()
+ * methods properly since doing so would require being able to overload
+ * the [] operator which is not possible in javascript. So we leave
+ * them unimplemented. Any attempt to call these methods will just result
+ * in a javascript error since we leave them undefined.
+ */
+
+
+/**
+ * If no existing Float32Array implementation is found then we export
+ * goog.vec.Float32Array as Float32Array.
+ */
+if (typeof Float32Array == 'undefined') {
+ goog.exportProperty(goog.vec.Float32Array, 'BYTES_PER_ELEMENT',
+ goog.vec.Float32Array.BYTES_PER_ELEMENT);
+ goog.exportProperty(goog.vec.Float32Array.prototype, 'BYTES_PER_ELEMENT',
+ goog.vec.Float32Array.prototype.BYTES_PER_ELEMENT);
+ goog.exportProperty(goog.vec.Float32Array.prototype, 'set',
+ goog.vec.Float32Array.prototype.set);
+ goog.exportProperty(goog.vec.Float32Array.prototype, 'toString',
+ goog.vec.Float32Array.prototype.toString);
+ goog.exportSymbol('Float32Array', goog.vec.Float32Array);
+}
+
+// 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 Supplies a Float64Array implementation that implements
+ * most of the Float64Array spec and that can be used when a built-in
+ * implementation is not available.
+ *
+ * Note that if no existing Float64Array implementation is found then this
+ * class and all its public properties are exported as Float64Array.
+ *
+ * Adding support for the other TypedArray classes here does not make sense
+ * since this vector math library only needs Float32Array and Float64Array.
+ *
+ */
+goog.provide('goog.vec.Float64Array');
+
+
+
+/**
+ * Constructs a new Float64Array. The new array is initialized to all zeros.
+ *
+ * @param {goog.vec.Float64Array|Array|ArrayBuffer|number} p0
+ * The length of the array, or an array to initialize the contents of the
+ * new Float64Array.
+ * @constructor
+ * @final
+ */
+goog.vec.Float64Array = function(p0) {
+ this.length = /** @type {number} */ (p0.length || p0);
+ for (var i = 0; i < this.length; i++) {
+ this[i] = p0[i] || 0;
+ }
+};
+
+
+/**
+ * The number of bytes in an element (as defined by the Typed Array
+ * specification).
+ *
+ * @type {number}
+ */
+goog.vec.Float64Array.BYTES_PER_ELEMENT = 8;
+
+
+/**
+ * The number of bytes in an element (as defined by the Typed Array
+ * specification).
+ *
+ * @type {number}
+ */
+goog.vec.Float64Array.prototype.BYTES_PER_ELEMENT = 8;
+
+
+/**
+ * Sets elements of the array.
+ * @param {Array<number>|Float64Array} values The array of values.
+ * @param {number=} opt_offset The offset in this array to start.
+ */
+goog.vec.Float64Array.prototype.set = function(values, opt_offset) {
+ opt_offset = opt_offset || 0;
+ for (var i = 0; i < values.length && opt_offset + i < this.length; i++) {
+ this[opt_offset + i] = values[i];
+ }
+};
+
+
+/**
+ * Creates a string representation of this array.
+ * @return {string} The string version of this array.
+ * @override
+ */
+goog.vec.Float64Array.prototype.toString = Array.prototype.join;
+
+
+/**
+ * Note that we cannot implement the subarray() or (deprecated) slice()
+ * methods properly since doing so would require being able to overload
+ * the [] operator which is not possible in javascript. So we leave
+ * them unimplemented. Any attempt to call these methods will just result
+ * in a javascript error since we leave them undefined.
+ */
+
+
+/**
+ * If no existing Float64Array implementation is found then we export
+ * goog.vec.Float64Array as Float64Array.
+ */
+if (typeof Float64Array == 'undefined') {
+ try {
+ goog.exportProperty(goog.vec.Float64Array, 'BYTES_PER_ELEMENT',
+ goog.vec.Float64Array.BYTES_PER_ELEMENT);
+ } catch (float64ArrayError) {
+ // Do nothing. This code is in place to fix b/7225850, in which an error
+ // is incorrectly thrown for Google TV on an old Chrome.
+ // TODO(user): remove after that version is retired.
+ }
+
+ goog.exportProperty(goog.vec.Float64Array.prototype, 'BYTES_PER_ELEMENT',
+ goog.vec.Float64Array.prototype.BYTES_PER_ELEMENT);
+ goog.exportProperty(goog.vec.Float64Array.prototype, 'set',
+ goog.vec.Float64Array.prototype.set);
+ goog.exportProperty(goog.vec.Float64Array.prototype, 'toString',
+ goog.vec.Float64Array.prototype.toString);
+ goog.exportSymbol('Float64Array', goog.vec.Float64Array);
+}
+
+// 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 Supplies global data types and constants for the vector math
+ * library.
+ */
+goog.provide('goog.vec');
+goog.provide('goog.vec.AnyType');
+goog.provide('goog.vec.ArrayType');
+goog.provide('goog.vec.Float32');
+goog.provide('goog.vec.Float64');
+goog.provide('goog.vec.Number');
+
+
+/**
+ * On platforms that don't have native Float32Array or Float64Array support we
+ * use a javascript implementation so that this math library can be used on all
+ * platforms.
+ * @suppress {extraRequire}
+ */
+goog.require('goog.vec.Float32Array');
+/** @suppress {extraRequire} */
+goog.require('goog.vec.Float64Array');
+
+// All vector and matrix operations are based upon arrays of numbers using
+// either Float32Array, Float64Array, or a standard Javascript Array of
+// Numbers.
+
+
+/** @typedef {!Float32Array} */
+goog.vec.Float32;
+
+
+/** @typedef {!Float64Array} */
+goog.vec.Float64;
+
+
+/** @typedef {!Array<number>} */
+goog.vec.Number;
+
+
+/** @typedef {!goog.vec.Float32|!goog.vec.Float64|!goog.vec.Number} */
+goog.vec.AnyType;
+
+
+/**
+ * @deprecated Use AnyType.
+ * @typedef {!Float32Array|!Array<number>}
+ */
+goog.vec.ArrayType;
+
+
+/**
+ * For graphics work, 6 decimal places of accuracy are typically all that is
+ * required.
+ *
+ * @type {number}
+ * @const
+ */
+goog.vec.EPSILON = 1e-6;
+
+// 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 Supplies 3 element vectors that are compatible with WebGL.
+ * Each element is a float32 since that is typically the desired size of a
+ * 3-vector in the GPU. The API is structured to avoid unnecessary memory
+ * allocations. The last parameter will typically be the output vector and
+ * an object can be both an input and output parameter to all methods except
+ * where noted.
+ *
+ */
+goog.provide('goog.vec.Vec3');
+
+/** @suppress {extraRequire} */
+goog.require('goog.vec');
+
+/** @typedef {goog.vec.Float32} */ goog.vec.Vec3.Float32;
+/** @typedef {goog.vec.Float64} */ goog.vec.Vec3.Float64;
+/** @typedef {goog.vec.Number} */ goog.vec.Vec3.Number;
+/** @typedef {goog.vec.AnyType} */ goog.vec.Vec3.AnyType;
+
+// The following two types are deprecated - use the above types instead.
+/** @typedef {Float32Array} */ goog.vec.Vec3.Type;
+/** @typedef {goog.vec.ArrayType} */ goog.vec.Vec3.Vec3Like;
+
+
+/**
+ * Creates a 3 element vector of Float32. The array is initialized to zero.
+ *
+ * @return {!goog.vec.Vec3.Float32} The new 3 element array.
+ */
+goog.vec.Vec3.createFloat32 = function() {
+ return new Float32Array(3);
+};
+
+
+/**
+ * Creates a 3 element vector of Float64. The array is initialized to zero.
+ *
+ * @return {!goog.vec.Vec3.Float64} The new 3 element array.
+ */
+goog.vec.Vec3.createFloat64 = function() {
+ return new Float64Array(3);
+};
+
+
+/**
+ * Creates a 3 element vector of Number. The array is initialized to zero.
+ *
+ * @return {!goog.vec.Vec3.Number} The new 3 element array.
+ */
+goog.vec.Vec3.createNumber = function() {
+ var a = new Array(3);
+ goog.vec.Vec3.setFromValues(a, 0, 0, 0);
+ return a;
+};
+
+
+/**
+ * Creates a 3 element vector of Float32Array. The array is initialized to zero.
+ *
+ * @deprecated Use createFloat32.
+ * @return {!goog.vec.Vec3.Type} The new 3 element array.
+ */
+goog.vec.Vec3.create = function() {
+ return new Float32Array(3);
+};
+
+
+/**
+ * Creates a new 3 element FLoat32 vector initialized with the value from the
+ * given array.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec The source 3 element array.
+ * @return {!goog.vec.Vec3.Float32} The new 3 element array.
+ */
+goog.vec.Vec3.createFloat32FromArray = function(vec) {
+ var newVec = goog.vec.Vec3.createFloat32();
+ goog.vec.Vec3.setFromArray(newVec, vec);
+ return newVec;
+};
+
+
+/**
+ * Creates a new 3 element Float32 vector initialized with the supplied values.
+ *
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @return {!goog.vec.Vec3.Float32} The new vector.
+ */
+goog.vec.Vec3.createFloat32FromValues = function(v0, v1, v2) {
+ var a = goog.vec.Vec3.createFloat32();
+ goog.vec.Vec3.setFromValues(a, v0, v1, v2);
+ return a;
+};
+
+
+/**
+ * Creates a clone of the given 3 element Float32 vector.
+ *
+ * @param {goog.vec.Vec3.Float32} vec The source 3 element vector.
+ * @return {!goog.vec.Vec3.Float32} The new cloned vector.
+ */
+goog.vec.Vec3.cloneFloat32 = goog.vec.Vec3.createFloat32FromArray;
+
+
+/**
+ * Creates a new 3 element Float64 vector initialized with the value from the
+ * given array.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec The source 3 element array.
+ * @return {!goog.vec.Vec3.Float64} The new 3 element array.
+ */
+goog.vec.Vec3.createFloat64FromArray = function(vec) {
+ var newVec = goog.vec.Vec3.createFloat64();
+ goog.vec.Vec3.setFromArray(newVec, vec);
+ return newVec;
+};
+
+
+/**
+* Creates a new 3 element Float64 vector initialized with the supplied values.
+*
+* @param {number} v0 The value for element at index 0.
+* @param {number} v1 The value for element at index 1.
+* @param {number} v2 The value for element at index 2.
+* @return {!goog.vec.Vec3.Float64} The new vector.
+*/
+goog.vec.Vec3.createFloat64FromValues = function(v0, v1, v2) {
+ var vec = goog.vec.Vec3.createFloat64();
+ goog.vec.Vec3.setFromValues(vec, v0, v1, v2);
+ return vec;
+};
+
+
+/**
+ * Creates a clone of the given 3 element vector.
+ *
+ * @param {goog.vec.Vec3.Float64} vec The source 3 element vector.
+ * @return {!goog.vec.Vec3.Float64} The new cloned vector.
+ */
+goog.vec.Vec3.cloneFloat64 = goog.vec.Vec3.createFloat64FromArray;
+
+
+/**
+ * Creates a new 3 element vector initialized with the value from the given
+ * array.
+ *
+ * @deprecated Use createFloat32FromArray.
+ * @param {goog.vec.Vec3.Vec3Like} vec The source 3 element array.
+ * @return {!goog.vec.Vec3.Type} The new 3 element array.
+ */
+goog.vec.Vec3.createFromArray = function(vec) {
+ var newVec = goog.vec.Vec3.create();
+ goog.vec.Vec3.setFromArray(newVec, vec);
+ return newVec;
+};
+
+
+/**
+ * Creates a new 3 element vector initialized with the supplied values.
+ *
+ * @deprecated Use createFloat32FromValues.
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @return {!goog.vec.Vec3.Type} The new vector.
+ */
+goog.vec.Vec3.createFromValues = function(v0, v1, v2) {
+ var vec = goog.vec.Vec3.create();
+ goog.vec.Vec3.setFromValues(vec, v0, v1, v2);
+ return vec;
+};
+
+
+/**
+ * Creates a clone of the given 3 element vector.
+ *
+ * @deprecated Use cloneFloat32.
+ * @param {goog.vec.Vec3.Vec3Like} vec The source 3 element vector.
+ * @return {!goog.vec.Vec3.Type} The new cloned vector.
+ */
+goog.vec.Vec3.clone = function(vec) {
+ var newVec = goog.vec.Vec3.create();
+ goog.vec.Vec3.setFromArray(newVec, vec);
+ return newVec;
+};
+
+
+/**
+ * Initializes the vector with the given values.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec The vector to receive the values.
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @return {!goog.vec.Vec3.AnyType} Return vec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.setFromValues = function(vec, v0, v1, v2) {
+ vec[0] = v0;
+ vec[1] = v1;
+ vec[2] = v2;
+ return vec;
+};
+
+
+/**
+ * Initializes the vector with the given array of values.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec The vector to receive the
+ * values.
+ * @param {goog.vec.Vec3.AnyType} values The array of values.
+ * @return {!goog.vec.Vec3.AnyType} Return vec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.setFromArray = function(vec, values) {
+ vec[0] = values[0];
+ vec[1] = values[1];
+ vec[2] = values[2];
+ return vec;
+};
+
+
+/**
+ * Performs a component-wise addition of vec0 and vec1 together storing the
+ * result into resultVec.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The first addend.
+ * @param {goog.vec.Vec3.AnyType} vec1 The second addend.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to
+ * receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.add = function(vec0, vec1, resultVec) {
+ resultVec[0] = vec0[0] + vec1[0];
+ resultVec[1] = vec0[1] + vec1[1];
+ resultVec[2] = vec0[2] + vec1[2];
+ return resultVec;
+};
+
+
+/**
+ * Performs a component-wise subtraction of vec1 from vec0 storing the
+ * result into resultVec.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The minuend.
+ * @param {goog.vec.Vec3.AnyType} vec1 The subtrahend.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to
+ * receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.subtract = function(vec0, vec1, resultVec) {
+ resultVec[0] = vec0[0] - vec1[0];
+ resultVec[1] = vec0[1] - vec1[1];
+ resultVec[2] = vec0[2] - vec1[2];
+ return resultVec;
+};
+
+
+/**
+ * Negates vec0, storing the result into resultVec.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The vector to negate.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to
+ * receive the result. May be vec0.
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.negate = function(vec0, resultVec) {
+ resultVec[0] = -vec0[0];
+ resultVec[1] = -vec0[1];
+ resultVec[2] = -vec0[2];
+ return resultVec;
+};
+
+
+/**
+ * Takes the absolute value of each component of vec0 storing the result in
+ * resultVec.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to receive the result.
+ * May be vec0.
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.abs = function(vec0, resultVec) {
+ resultVec[0] = Math.abs(vec0[0]);
+ resultVec[1] = Math.abs(vec0[1]);
+ resultVec[2] = Math.abs(vec0[2]);
+ return resultVec;
+};
+
+
+/**
+ * Multiplies each component of vec0 with scalar storing the product into
+ * resultVec.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The source vector.
+ * @param {number} scalar The value to multiply with each component of vec0.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to
+ * receive the result. May be vec0.
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.scale = function(vec0, scalar, resultVec) {
+ resultVec[0] = vec0[0] * scalar;
+ resultVec[1] = vec0[1] * scalar;
+ resultVec[2] = vec0[2] * scalar;
+ return resultVec;
+};
+
+
+/**
+ * Returns the magnitudeSquared of the given vector.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.Vec3.magnitudeSquared = function(vec0) {
+ var x = vec0[0], y = vec0[1], z = vec0[2];
+ return x * x + y * y + z * z;
+};
+
+
+/**
+ * Returns the magnitude of the given vector.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.Vec3.magnitude = function(vec0) {
+ var x = vec0[0], y = vec0[1], z = vec0[2];
+ return Math.sqrt(x * x + y * y + z * z);
+};
+
+
+/**
+ * Normalizes the given vector storing the result into resultVec.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The vector to normalize.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to
+ * receive the result. May be vec0.
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.normalize = function(vec0, resultVec) {
+ var ilen = 1 / goog.vec.Vec3.magnitude(vec0);
+ resultVec[0] = vec0[0] * ilen;
+ resultVec[1] = vec0[1] * ilen;
+ resultVec[2] = vec0[2] * ilen;
+ return resultVec;
+};
+
+
+/**
+ * Returns the scalar product of vectors v0 and v1.
+ *
+ * @param {goog.vec.Vec3.AnyType} v0 The first vector.
+ * @param {goog.vec.Vec3.AnyType} v1 The second vector.
+ * @return {number} The scalar product.
+ */
+goog.vec.Vec3.dot = function(v0, v1) {
+ return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2];
+};
+
+
+/**
+ * Computes the vector (cross) product of v0 and v1 storing the result into
+ * resultVec.
+ *
+ * @param {goog.vec.Vec3.AnyType} v0 The first vector.
+ * @param {goog.vec.Vec3.AnyType} v1 The second vector.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to receive the
+ * results. May be either v0 or v1.
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.cross = function(v0, v1, resultVec) {
+ var x0 = v0[0], y0 = v0[1], z0 = v0[2];
+ var x1 = v1[0], y1 = v1[1], z1 = v1[2];
+ resultVec[0] = y0 * z1 - z0 * y1;
+ resultVec[1] = z0 * x1 - x0 * z1;
+ resultVec[2] = x0 * y1 - y0 * x1;
+ return resultVec;
+};
+
+
+/**
+ * Returns the squared distance between two points.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 First point.
+ * @param {goog.vec.Vec3.AnyType} vec1 Second point.
+ * @return {number} The squared distance between the points.
+ */
+goog.vec.Vec3.distanceSquared = function(vec0, vec1) {
+ var x = vec0[0] - vec1[0];
+ var y = vec0[1] - vec1[1];
+ var z = vec0[2] - vec1[2];
+ return x * x + y * y + z * z;
+};
+
+
+/**
+ * Returns the distance between two points.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 First point.
+ * @param {goog.vec.Vec3.AnyType} vec1 Second point.
+ * @return {number} The distance between the points.
+ */
+goog.vec.Vec3.distance = function(vec0, vec1) {
+ return Math.sqrt(goog.vec.Vec3.distanceSquared(vec0, vec1));
+};
+
+
+/**
+ * Returns a unit vector pointing from one point to another.
+ * If the input points are equal then the result will be all zeros.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 Origin point.
+ * @param {goog.vec.Vec3.AnyType} vec1 Target point.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to receive the
+ * results (may be vec0 or vec1).
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.direction = function(vec0, vec1, resultVec) {
+ var x = vec1[0] - vec0[0];
+ var y = vec1[1] - vec0[1];
+ var z = vec1[2] - vec0[2];
+ var d = Math.sqrt(x * x + y * y + z * z);
+ if (d) {
+ d = 1 / d;
+ resultVec[0] = x * d;
+ resultVec[1] = y * d;
+ resultVec[2] = z * d;
+ } else {
+ resultVec[0] = resultVec[1] = resultVec[2] = 0;
+ }
+ return resultVec;
+};
+
+
+/**
+ * Linearly interpolate from vec0 to v1 according to f. The value of f should be
+ * in the range [0..1] otherwise the results are undefined.
+ *
+ * @param {goog.vec.Vec3.AnyType} v0 The first vector.
+ * @param {goog.vec.Vec3.AnyType} v1 The second vector.
+ * @param {number} f The interpolation factor.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to receive the
+ * results (may be v0 or v1).
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.lerp = function(v0, v1, f, resultVec) {
+ var x = v0[0], y = v0[1], z = v0[2];
+ resultVec[0] = (v1[0] - x) * f + x;
+ resultVec[1] = (v1[1] - y) * f + y;
+ resultVec[2] = (v1[2] - z) * f + z;
+ return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the larger values in resultVec.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec3.AnyType|number} limit The limit vector or scalar.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to receive the
+ * results (may be vec0 or limit).
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.max = function(vec0, limit, resultVec) {
+ if (goog.isNumber(limit)) {
+ resultVec[0] = Math.max(vec0[0], limit);
+ resultVec[1] = Math.max(vec0[1], limit);
+ resultVec[2] = Math.max(vec0[2], limit);
+ } else {
+ resultVec[0] = Math.max(vec0[0], limit[0]);
+ resultVec[1] = Math.max(vec0[1], limit[1]);
+ resultVec[2] = Math.max(vec0[2], limit[2]);
+ }
+ return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the smaller values in resultVec.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec3.AnyType|number} limit The limit vector or scalar.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to receive the
+ * results (may be vec0 or limit).
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec3.min = function(vec0, limit, resultVec) {
+ if (goog.isNumber(limit)) {
+ resultVec[0] = Math.min(vec0[0], limit);
+ resultVec[1] = Math.min(vec0[1], limit);
+ resultVec[2] = Math.min(vec0[2], limit);
+ } else {
+ resultVec[0] = Math.min(vec0[0], limit[0]);
+ resultVec[1] = Math.min(vec0[1], limit[1]);
+ resultVec[2] = Math.min(vec0[2], limit[2]);
+ }
+ return resultVec;
+};
+
+
+/**
+ * Returns true if the components of v0 are equal to the components of v1.
+ *
+ * @param {goog.vec.Vec3.AnyType} v0 The first vector.
+ * @param {goog.vec.Vec3.AnyType} v1 The second vector.
+ * @return {boolean} True if the vectors are equal, false otherwise.
+ */
+goog.vec.Vec3.equals = function(v0, v1) {
+ return v0.length == v1.length &&
+ v0[0] == v1[0] && v0[1] == v1[1] && v0[2] == v1[2];
+};
+
+// 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 Supplies 4 element vectors that are compatible with WebGL.
+ * Each element is a float32 since that is typically the desired size of a
+ * 4-vector in the GPU. The API is structured to avoid unnecessary memory
+ * allocations. The last parameter will typically be the output vector and
+ * an object can be both an input and output parameter to all methods except
+ * where noted.
+ *
+ */
+goog.provide('goog.vec.Vec4');
+
+/** @suppress {extraRequire} */
+goog.require('goog.vec');
+
+/** @typedef {goog.vec.Float32} */ goog.vec.Vec4.Float32;
+/** @typedef {goog.vec.Float64} */ goog.vec.Vec4.Float64;
+/** @typedef {goog.vec.Number} */ goog.vec.Vec4.Number;
+/** @typedef {goog.vec.AnyType} */ goog.vec.Vec4.AnyType;
+
+// The following two types are deprecated - use the above types instead.
+/** @typedef {Float32Array} */ goog.vec.Vec4.Type;
+/** @typedef {goog.vec.ArrayType} */ goog.vec.Vec4.Vec4Like;
+
+
+/**
+ * Creates a 4 element vector of Float32. The array is initialized to zero.
+ *
+ * @return {!goog.vec.Vec4.Float32} The new 3 element array.
+ */
+goog.vec.Vec4.createFloat32 = function() {
+ return new Float32Array(4);
+};
+
+
+/**
+ * Creates a 4 element vector of Float64. The array is initialized to zero.
+ *
+ * @return {!goog.vec.Vec4.Float64} The new 4 element array.
+ */
+goog.vec.Vec4.createFloat64 = function() {
+ return new Float64Array(4);
+};
+
+
+/**
+ * Creates a 4 element vector of Number. The array is initialized to zero.
+ *
+ * @return {!goog.vec.Vec4.Number} The new 4 element array.
+ */
+goog.vec.Vec4.createNumber = function() {
+ var v = new Array(4);
+ goog.vec.Vec4.setFromValues(v, 0, 0, 0, 0);
+ return v;
+};
+
+
+/**
+ * Creates a 4 element vector of Float32Array. The array is initialized to zero.
+ *
+ * @deprecated Use createFloat32.
+ * @return {!goog.vec.Vec4.Type} The new 4 element array.
+ */
+goog.vec.Vec4.create = function() {
+ return new Float32Array(4);
+};
+
+
+/**
+ * Creates a new 4 element vector initialized with the value from the given
+ * array.
+ *
+ * @deprecated Use createFloat32FromArray.
+ * @param {goog.vec.Vec4.Vec4Like} vec The source 4 element array.
+ * @return {!goog.vec.Vec4.Type} The new 4 element array.
+ */
+goog.vec.Vec4.createFromArray = function(vec) {
+ var newVec = goog.vec.Vec4.create();
+ goog.vec.Vec4.setFromArray(newVec, vec);
+ return newVec;
+};
+
+
+/**
+ * Creates a new 4 element FLoat32 vector initialized with the value from the
+ * given array.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec The source 3 element array.
+ * @return {!goog.vec.Vec4.Float32} The new 3 element array.
+ */
+goog.vec.Vec4.createFloat32FromArray = function(vec) {
+ var newVec = goog.vec.Vec4.createFloat32();
+ goog.vec.Vec4.setFromArray(newVec, vec);
+ return newVec;
+};
+
+
+/**
+ * Creates a new 4 element Float32 vector initialized with the supplied values.
+ *
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @param {number} v3 The value for element at index 3.
+ * @return {!goog.vec.Vec4.Float32} The new vector.
+ */
+goog.vec.Vec4.createFloat32FromValues = function(v0, v1, v2, v3) {
+ var vec = goog.vec.Vec4.createFloat32();
+ goog.vec.Vec4.setFromValues(vec, v0, v1, v2, v3);
+ return vec;
+};
+
+
+/**
+ * Creates a clone of the given 4 element Float32 vector.
+ *
+ * @param {goog.vec.Vec4.Float32} vec The source 3 element vector.
+ * @return {!goog.vec.Vec4.Float32} The new cloned vector.
+ */
+goog.vec.Vec4.cloneFloat32 = goog.vec.Vec4.createFloat32FromArray;
+
+
+/**
+ * Creates a new 4 element Float64 vector initialized with the value from the
+ * given array.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec The source 4 element array.
+ * @return {!goog.vec.Vec4.Float64} The new 4 element array.
+ */
+goog.vec.Vec4.createFloat64FromArray = function(vec) {
+ var newVec = goog.vec.Vec4.createFloat64();
+ goog.vec.Vec4.setFromArray(newVec, vec);
+ return newVec;
+};
+
+
+/**
+* Creates a new 4 element Float64 vector initialized with the supplied values.
+*
+* @param {number} v0 The value for element at index 0.
+* @param {number} v1 The value for element at index 1.
+* @param {number} v2 The value for element at index 2.
+* @param {number} v3 The value for element at index 3.
+* @return {!goog.vec.Vec4.Float64} The new vector.
+*/
+goog.vec.Vec4.createFloat64FromValues = function(v0, v1, v2, v3) {
+ var vec = goog.vec.Vec4.createFloat64();
+ goog.vec.Vec4.setFromValues(vec, v0, v1, v2, v3);
+ return vec;
+};
+
+
+/**
+ * Creates a clone of the given 4 element vector.
+ *
+ * @param {goog.vec.Vec4.Float64} vec The source 4 element vector.
+ * @return {!goog.vec.Vec4.Float64} The new cloned vector.
+ */
+goog.vec.Vec4.cloneFloat64 = goog.vec.Vec4.createFloat64FromArray;
+
+
+/**
+ * Creates a new 4 element vector initialized with the supplied values.
+ *
+ * @deprecated Use createFloat32FromValues.
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @param {number} v3 The value for element at index 3.
+ * @return {!goog.vec.Vec4.Type} The new vector.
+ */
+goog.vec.Vec4.createFromValues = function(v0, v1, v2, v3) {
+ var vec = goog.vec.Vec4.create();
+ goog.vec.Vec4.setFromValues(vec, v0, v1, v2, v3);
+ return vec;
+};
+
+
+/**
+ * Creates a clone of the given 4 element vector.
+ *
+ * @deprecated Use cloneFloat32.
+ * @param {goog.vec.Vec4.Vec4Like} vec The source 4 element vector.
+ * @return {!goog.vec.Vec4.Type} The new cloned vector.
+ */
+goog.vec.Vec4.clone = goog.vec.Vec4.createFromArray;
+
+
+/**
+ * Initializes the vector with the given values.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec The vector to receive the values.
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @param {number} v3 The value for element at index 3.
+ * @return {!goog.vec.Vec4.AnyType} Return vec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec4.setFromValues = function(vec, v0, v1, v2, v3) {
+ vec[0] = v0;
+ vec[1] = v1;
+ vec[2] = v2;
+ vec[3] = v3;
+ return vec;
+};
+
+
+/**
+ * Initializes the vector with the given array of values.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec The vector to receive the
+ * values.
+ * @param {goog.vec.Vec4.AnyType} values The array of values.
+ * @return {!goog.vec.Vec4.AnyType} Return vec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec4.setFromArray = function(vec, values) {
+ vec[0] = values[0];
+ vec[1] = values[1];
+ vec[2] = values[2];
+ vec[3] = values[3];
+ return vec;
+};
+
+
+/**
+ * Performs a component-wise addition of vec0 and vec1 together storing the
+ * result into resultVec.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The first addend.
+ * @param {goog.vec.Vec4.AnyType} vec1 The second addend.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to
+ * receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec4.add = function(vec0, vec1, resultVec) {
+ resultVec[0] = vec0[0] + vec1[0];
+ resultVec[1] = vec0[1] + vec1[1];
+ resultVec[2] = vec0[2] + vec1[2];
+ resultVec[3] = vec0[3] + vec1[3];
+ return resultVec;
+};
+
+
+/**
+ * Performs a component-wise subtraction of vec1 from vec0 storing the
+ * result into resultVec.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The minuend.
+ * @param {goog.vec.Vec4.AnyType} vec1 The subtrahend.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to
+ * receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec4.subtract = function(vec0, vec1, resultVec) {
+ resultVec[0] = vec0[0] - vec1[0];
+ resultVec[1] = vec0[1] - vec1[1];
+ resultVec[2] = vec0[2] - vec1[2];
+ resultVec[3] = vec0[3] - vec1[3];
+ return resultVec;
+};
+
+
+/**
+ * Negates vec0, storing the result into resultVec.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The vector to negate.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to
+ * receive the result. May be vec0.
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec4.negate = function(vec0, resultVec) {
+ resultVec[0] = -vec0[0];
+ resultVec[1] = -vec0[1];
+ resultVec[2] = -vec0[2];
+ resultVec[3] = -vec0[3];
+ return resultVec;
+};
+
+
+/**
+ * Takes the absolute value of each component of vec0 storing the result in
+ * resultVec.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to receive the result.
+ * May be vec0.
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec4.abs = function(vec0, resultVec) {
+ resultVec[0] = Math.abs(vec0[0]);
+ resultVec[1] = Math.abs(vec0[1]);
+ resultVec[2] = Math.abs(vec0[2]);
+ resultVec[3] = Math.abs(vec0[3]);
+ return resultVec;
+};
+
+
+/**
+ * Multiplies each component of vec0 with scalar storing the product into
+ * resultVec.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The source vector.
+ * @param {number} scalar The value to multiply with each component of vec0.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to
+ * receive the result. May be vec0.
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec4.scale = function(vec0, scalar, resultVec) {
+ resultVec[0] = vec0[0] * scalar;
+ resultVec[1] = vec0[1] * scalar;
+ resultVec[2] = vec0[2] * scalar;
+ resultVec[3] = vec0[3] * scalar;
+ return resultVec;
+};
+
+
+/**
+ * Returns the magnitudeSquared of the given vector.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.Vec4.magnitudeSquared = function(vec0) {
+ var x = vec0[0], y = vec0[1], z = vec0[2], w = vec0[3];
+ return x * x + y * y + z * z + w * w;
+};
+
+
+/**
+ * Returns the magnitude of the given vector.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.Vec4.magnitude = function(vec0) {
+ var x = vec0[0], y = vec0[1], z = vec0[2], w = vec0[3];
+ return Math.sqrt(x * x + y * y + z * z + w * w);
+};
+
+
+/**
+ * Normalizes the given vector storing the result into resultVec.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The vector to normalize.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to
+ * receive the result. May be vec0.
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec4.normalize = function(vec0, resultVec) {
+ var ilen = 1 / goog.vec.Vec4.magnitude(vec0);
+ resultVec[0] = vec0[0] * ilen;
+ resultVec[1] = vec0[1] * ilen;
+ resultVec[2] = vec0[2] * ilen;
+ resultVec[3] = vec0[3] * ilen;
+ return resultVec;
+};
+
+
+/**
+ * Returns the scalar product of vectors v0 and v1.
+ *
+ * @param {goog.vec.Vec4.AnyType} v0 The first vector.
+ * @param {goog.vec.Vec4.AnyType} v1 The second vector.
+ * @return {number} The scalar product.
+ */
+goog.vec.Vec4.dot = function(v0, v1) {
+ return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2] + v0[3] * v1[3];
+};
+
+
+/**
+ * Linearly interpolate from v0 to v1 according to f. The value of f should be
+ * in the range [0..1] otherwise the results are undefined.
+ *
+ * @param {goog.vec.Vec4.AnyType} v0 The first vector.
+ * @param {goog.vec.Vec4.AnyType} v1 The second vector.
+ * @param {number} f The interpolation factor.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to receive the
+ * results (may be v0 or v1).
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec4.lerp = function(v0, v1, f, resultVec) {
+ var x = v0[0], y = v0[1], z = v0[2], w = v0[3];
+ resultVec[0] = (v1[0] - x) * f + x;
+ resultVec[1] = (v1[1] - y) * f + y;
+ resultVec[2] = (v1[2] - z) * f + z;
+ resultVec[3] = (v1[3] - w) * f + w;
+ return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the larger values in resultVec.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec4.AnyType|number} limit The limit vector or scalar.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to receive the
+ * results (may be vec0 or limit).
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec4.max = function(vec0, limit, resultVec) {
+ if (goog.isNumber(limit)) {
+ resultVec[0] = Math.max(vec0[0], limit);
+ resultVec[1] = Math.max(vec0[1], limit);
+ resultVec[2] = Math.max(vec0[2], limit);
+ resultVec[3] = Math.max(vec0[3], limit);
+ } else {
+ resultVec[0] = Math.max(vec0[0], limit[0]);
+ resultVec[1] = Math.max(vec0[1], limit[1]);
+ resultVec[2] = Math.max(vec0[2], limit[2]);
+ resultVec[3] = Math.max(vec0[3], limit[3]);
+ }
+ return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the smaller values in resultVec.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec4.AnyType|number} limit The limit vector or scalar.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to receive the
+ * results (may be vec0 or limit).
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Vec4.min = function(vec0, limit, resultVec) {
+ if (goog.isNumber(limit)) {
+ resultVec[0] = Math.min(vec0[0], limit);
+ resultVec[1] = Math.min(vec0[1], limit);
+ resultVec[2] = Math.min(vec0[2], limit);
+ resultVec[3] = Math.min(vec0[3], limit);
+ } else {
+ resultVec[0] = Math.min(vec0[0], limit[0]);
+ resultVec[1] = Math.min(vec0[1], limit[1]);
+ resultVec[2] = Math.min(vec0[2], limit[2]);
+ resultVec[3] = Math.min(vec0[3], limit[3]);
+ }
+ return resultVec;
+};
+
+
+/**
+ * Returns true if the components of v0 are equal to the components of v1.
+ *
+ * @param {goog.vec.Vec4.AnyType} v0 The first vector.
+ * @param {goog.vec.Vec4.AnyType} v1 The second vector.
+ * @return {boolean} True if the vectors are equal, false otherwise.
+ */
+goog.vec.Vec4.equals = function(v0, v1) {
+ return v0.length == v1.length &&
+ v0[0] == v1[0] && v0[1] == v1[1] && v0[2] == v1[2] && v0[3] == v1[3];
+};
+
+// 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 Implements 4x4 matrices and their related functions which are
+ * compatible with WebGL. The API is structured to avoid unnecessary memory
+ * allocations. The last parameter will typically be the output vector and
+ * an object can be both an input and output parameter to all methods except
+ * where noted. Matrix operations follow the mathematical form when multiplying
+ * vectors as follows: resultVec = matrix * vec.
+ *
+ * The matrices are stored in column-major order.
+ *
+ */
+goog.provide('goog.vec.Mat4');
+
+goog.require('goog.vec');
+goog.require('goog.vec.Vec3');
+goog.require('goog.vec.Vec4');
+
+
+/** @typedef {goog.vec.Float32} */ goog.vec.Mat4.Float32;
+/** @typedef {goog.vec.Float64} */ goog.vec.Mat4.Float64;
+/** @typedef {goog.vec.Number} */ goog.vec.Mat4.Number;
+/** @typedef {goog.vec.AnyType} */ goog.vec.Mat4.AnyType;
+
+// The following two types are deprecated - use the above types instead.
+/** @typedef {!Float32Array} */ goog.vec.Mat4.Type;
+/** @typedef {goog.vec.ArrayType} */ goog.vec.Mat4.Mat4Like;
+
+
+/**
+ * Creates the array representation of a 4x4 matrix of Float32.
+ * The use of the array directly instead of a class reduces overhead.
+ * The returned matrix is cleared to all zeros.
+ *
+ * @return {!goog.vec.Mat4.Float32} The new matrix.
+ */
+goog.vec.Mat4.createFloat32 = function() {
+ return new Float32Array(16);
+};
+
+
+/**
+ * Creates the array representation of a 4x4 matrix of Float64.
+ * The returned matrix is cleared to all zeros.
+ *
+ * @return {!goog.vec.Mat4.Float64} The new matrix.
+ */
+goog.vec.Mat4.createFloat64 = function() {
+ return new Float64Array(16);
+};
+
+
+/**
+ * Creates the array representation of a 4x4 matrix of Number.
+ * The returned matrix is cleared to all zeros.
+ *
+ * @return {!goog.vec.Mat4.Number} The new matrix.
+ */
+goog.vec.Mat4.createNumber = function() {
+ var a = new Array(16);
+ goog.vec.Mat4.setFromValues(a,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0);
+ return a;
+};
+
+
+/**
+ * Creates the array representation of a 4x4 matrix of Float32.
+ * The returned matrix is cleared to all zeros.
+ *
+ * @deprecated Use createFloat32.
+ * @return {!goog.vec.Mat4.Type} The new matrix.
+ */
+goog.vec.Mat4.create = function() {
+ return goog.vec.Mat4.createFloat32();
+};
+
+
+/**
+ * Creates a 4x4 identity matrix of Float32.
+ *
+ * @return {!goog.vec.Mat4.Float32} The new 16 element array.
+ */
+goog.vec.Mat4.createFloat32Identity = function() {
+ var mat = goog.vec.Mat4.createFloat32();
+ mat[0] = mat[5] = mat[10] = mat[15] = 1;
+ return mat;
+};
+
+
+/**
+ * Creates a 4x4 identity matrix of Float64.
+ *
+ * @return {!goog.vec.Mat4.Float64} The new 16 element array.
+ */
+goog.vec.Mat4.createFloat64Identity = function() {
+ var mat = goog.vec.Mat4.createFloat64();
+ mat[0] = mat[5] = mat[10] = mat[15] = 1;
+ return mat;
+};
+
+
+/**
+ * Creates a 4x4 identity matrix of Number.
+ * The returned matrix is cleared to all zeros.
+ *
+ * @return {!goog.vec.Mat4.Number} The new 16 element array.
+ */
+goog.vec.Mat4.createNumberIdentity = function() {
+ var a = new Array(16);
+ goog.vec.Mat4.setFromValues(a,
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1);
+ return a;
+};
+
+
+/**
+ * Creates the array representation of a 4x4 matrix of Float32.
+ * The returned matrix is cleared to all zeros.
+ *
+ * @deprecated Use createFloat32Identity.
+ * @return {!goog.vec.Mat4.Type} The new 16 element array.
+ */
+goog.vec.Mat4.createIdentity = function() {
+ return goog.vec.Mat4.createFloat32Identity();
+};
+
+
+/**
+ * Creates a 4x4 matrix of Float32 initialized from the given array.
+ *
+ * @param {goog.vec.Mat4.AnyType} matrix The array containing the
+ * matrix values in column major order.
+ * @return {!goog.vec.Mat4.Float32} The new, 16 element array.
+ */
+goog.vec.Mat4.createFloat32FromArray = function(matrix) {
+ var newMatrix = goog.vec.Mat4.createFloat32();
+ goog.vec.Mat4.setFromArray(newMatrix, matrix);
+ return newMatrix;
+};
+
+
+/**
+ * Creates a 4x4 matrix of Float32 initialized from the given values.
+ *
+ * @param {number} v00 The values at (0, 0).
+ * @param {number} v10 The values at (1, 0).
+ * @param {number} v20 The values at (2, 0).
+ * @param {number} v30 The values at (3, 0).
+ * @param {number} v01 The values at (0, 1).
+ * @param {number} v11 The values at (1, 1).
+ * @param {number} v21 The values at (2, 1).
+ * @param {number} v31 The values at (3, 1).
+ * @param {number} v02 The values at (0, 2).
+ * @param {number} v12 The values at (1, 2).
+ * @param {number} v22 The values at (2, 2).
+ * @param {number} v32 The values at (3, 2).
+ * @param {number} v03 The values at (0, 3).
+ * @param {number} v13 The values at (1, 3).
+ * @param {number} v23 The values at (2, 3).
+ * @param {number} v33 The values at (3, 3).
+ * @return {!goog.vec.Mat4.Float32} The new, 16 element array.
+ */
+goog.vec.Mat4.createFloat32FromValues = function(
+ v00, v10, v20, v30,
+ v01, v11, v21, v31,
+ v02, v12, v22, v32,
+ v03, v13, v23, v33) {
+ var newMatrix = goog.vec.Mat4.createFloat32();
+ goog.vec.Mat4.setFromValues(
+ newMatrix, v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32,
+ v03, v13, v23, v33);
+ return newMatrix;
+};
+
+
+/**
+ * Creates a clone of a 4x4 matrix of Float32.
+ *
+ * @param {goog.vec.Mat4.Float32} matrix The source 4x4 matrix.
+ * @return {!goog.vec.Mat4.Float32} The new 4x4 element matrix.
+ */
+goog.vec.Mat4.cloneFloat32 = goog.vec.Mat4.createFloat32FromArray;
+
+
+/**
+ * Creates a 4x4 matrix of Float64 initialized from the given array.
+ *
+ * @param {goog.vec.Mat4.AnyType} matrix The array containing the
+ * matrix values in column major order.
+ * @return {!goog.vec.Mat4.Float64} The new, nine element array.
+ */
+goog.vec.Mat4.createFloat64FromArray = function(matrix) {
+ var newMatrix = goog.vec.Mat4.createFloat64();
+ goog.vec.Mat4.setFromArray(newMatrix, matrix);
+ return newMatrix;
+};
+
+
+/**
+ * Creates a 4x4 matrix of Float64 initialized from the given values.
+ *
+ * @param {number} v00 The values at (0, 0).
+ * @param {number} v10 The values at (1, 0).
+ * @param {number} v20 The values at (2, 0).
+ * @param {number} v30 The values at (3, 0).
+ * @param {number} v01 The values at (0, 1).
+ * @param {number} v11 The values at (1, 1).
+ * @param {number} v21 The values at (2, 1).
+ * @param {number} v31 The values at (3, 1).
+ * @param {number} v02 The values at (0, 2).
+ * @param {number} v12 The values at (1, 2).
+ * @param {number} v22 The values at (2, 2).
+ * @param {number} v32 The values at (3, 2).
+ * @param {number} v03 The values at (0, 3).
+ * @param {number} v13 The values at (1, 3).
+ * @param {number} v23 The values at (2, 3).
+ * @param {number} v33 The values at (3, 3).
+ * @return {!goog.vec.Mat4.Float64} The new, 16 element array.
+ */
+goog.vec.Mat4.createFloat64FromValues = function(
+ v00, v10, v20, v30,
+ v01, v11, v21, v31,
+ v02, v12, v22, v32,
+ v03, v13, v23, v33) {
+ var newMatrix = goog.vec.Mat4.createFloat64();
+ goog.vec.Mat4.setFromValues(
+ newMatrix, v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32,
+ v03, v13, v23, v33);
+ return newMatrix;
+};
+
+
+/**
+ * Creates a clone of a 4x4 matrix of Float64.
+ *
+ * @param {goog.vec.Mat4.Float64} matrix The source 4x4 matrix.
+ * @return {!goog.vec.Mat4.Float64} The new 4x4 element matrix.
+ */
+goog.vec.Mat4.cloneFloat64 = goog.vec.Mat4.createFloat64FromArray;
+
+
+/**
+ * Creates a 4x4 matrix of Float32 initialized from the given array.
+ *
+ * @deprecated Use createFloat32FromArray.
+ * @param {goog.vec.Mat4.Mat4Like} matrix The array containing the
+ * matrix values in column major order.
+ * @return {!goog.vec.Mat4.Type} The new, nine element array.
+ */
+goog.vec.Mat4.createFromArray = function(matrix) {
+ var newMatrix = goog.vec.Mat4.createFloat32();
+ goog.vec.Mat4.setFromArray(newMatrix, matrix);
+ return newMatrix;
+};
+
+
+/**
+ * Creates a 4x4 matrix of Float32 initialized from the given values.
+ *
+ * @deprecated Use createFloat32FromValues.
+ * @param {number} v00 The values at (0, 0).
+ * @param {number} v10 The values at (1, 0).
+ * @param {number} v20 The values at (2, 0).
+ * @param {number} v30 The values at (3, 0).
+ * @param {number} v01 The values at (0, 1).
+ * @param {number} v11 The values at (1, 1).
+ * @param {number} v21 The values at (2, 1).
+ * @param {number} v31 The values at (3, 1).
+ * @param {number} v02 The values at (0, 2).
+ * @param {number} v12 The values at (1, 2).
+ * @param {number} v22 The values at (2, 2).
+ * @param {number} v32 The values at (3, 2).
+ * @param {number} v03 The values at (0, 3).
+ * @param {number} v13 The values at (1, 3).
+ * @param {number} v23 The values at (2, 3).
+ * @param {number} v33 The values at (3, 3).
+ * @return {!goog.vec.Mat4.Type} The new, 16 element array.
+ */
+goog.vec.Mat4.createFromValues = function(
+ v00, v10, v20, v30,
+ v01, v11, v21, v31,
+ v02, v12, v22, v32,
+ v03, v13, v23, v33) {
+ return goog.vec.Mat4.createFloat32FromValues(
+ v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32,
+ v03, v13, v23, v33);
+};
+
+
+/**
+ * Creates a clone of a 4x4 matrix of Float32.
+ *
+ * @deprecated Use cloneFloat32.
+ * @param {goog.vec.Mat4.Mat4Like} matrix The source 4x4 matrix.
+ * @return {!goog.vec.Mat4.Type} The new 4x4 element matrix.
+ */
+goog.vec.Mat4.clone = goog.vec.Mat4.createFromArray;
+
+
+/**
+ * Retrieves the element at the requested row and column.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix containing the
+ * value to retrieve.
+ * @param {number} row The row index.
+ * @param {number} column The column index.
+ * @return {number} The element value at the requested row, column indices.
+ */
+goog.vec.Mat4.getElement = function(mat, row, column) {
+ return mat[row + column * 4];
+};
+
+
+/**
+ * Sets the element at the requested row and column.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to set the value on.
+ * @param {number} row The row index.
+ * @param {number} column The column index.
+ * @param {number} value The value to set at the requested row, column.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.setElement = function(mat, row, column, value) {
+ mat[row + column * 4] = value;
+ return mat;
+};
+
+
+/**
+ * Initializes the matrix from the set of values. Note the values supplied are
+ * in column major order.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the
+ * values.
+ * @param {number} v00 The values at (0, 0).
+ * @param {number} v10 The values at (1, 0).
+ * @param {number} v20 The values at (2, 0).
+ * @param {number} v30 The values at (3, 0).
+ * @param {number} v01 The values at (0, 1).
+ * @param {number} v11 The values at (1, 1).
+ * @param {number} v21 The values at (2, 1).
+ * @param {number} v31 The values at (3, 1).
+ * @param {number} v02 The values at (0, 2).
+ * @param {number} v12 The values at (1, 2).
+ * @param {number} v22 The values at (2, 2).
+ * @param {number} v32 The values at (3, 2).
+ * @param {number} v03 The values at (0, 3).
+ * @param {number} v13 The values at (1, 3).
+ * @param {number} v23 The values at (2, 3).
+ * @param {number} v33 The values at (3, 3).
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.setFromValues = function(
+ mat, v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32,
+ v03, v13, v23, v33) {
+ mat[0] = v00;
+ mat[1] = v10;
+ mat[2] = v20;
+ mat[3] = v30;
+ mat[4] = v01;
+ mat[5] = v11;
+ mat[6] = v21;
+ mat[7] = v31;
+ mat[8] = v02;
+ mat[9] = v12;
+ mat[10] = v22;
+ mat[11] = v32;
+ mat[12] = v03;
+ mat[13] = v13;
+ mat[14] = v23;
+ mat[15] = v33;
+ return mat;
+};
+
+
+/**
+ * Sets the matrix from the array of values stored in column major order.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
+ * @param {goog.vec.Mat4.AnyType} values The column major ordered
+ * array of values to store in the matrix.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.setFromArray = function(mat, values) {
+ mat[0] = values[0];
+ mat[1] = values[1];
+ mat[2] = values[2];
+ mat[3] = values[3];
+ mat[4] = values[4];
+ mat[5] = values[5];
+ mat[6] = values[6];
+ mat[7] = values[7];
+ mat[8] = values[8];
+ mat[9] = values[9];
+ mat[10] = values[10];
+ mat[11] = values[11];
+ mat[12] = values[12];
+ mat[13] = values[13];
+ mat[14] = values[14];
+ mat[15] = values[15];
+ return mat;
+};
+
+
+/**
+ * Sets the matrix from the array of values stored in row major order.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
+ * @param {goog.vec.Mat4.AnyType} values The row major ordered array of
+ * values to store in the matrix.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.setFromRowMajorArray = function(mat, values) {
+ mat[0] = values[0];
+ mat[1] = values[4];
+ mat[2] = values[8];
+ mat[3] = values[12];
+
+ mat[4] = values[1];
+ mat[5] = values[5];
+ mat[6] = values[9];
+ mat[7] = values[13];
+
+ mat[8] = values[2];
+ mat[9] = values[6];
+ mat[10] = values[10];
+ mat[11] = values[14];
+
+ mat[12] = values[3];
+ mat[13] = values[7];
+ mat[14] = values[11];
+ mat[15] = values[15];
+
+ return mat;
+};
+
+
+/**
+ * Sets the diagonal values of the matrix from the given values.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
+ * @param {number} v00 The values for (0, 0).
+ * @param {number} v11 The values for (1, 1).
+ * @param {number} v22 The values for (2, 2).
+ * @param {number} v33 The values for (3, 3).
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.setDiagonalValues = function(mat, v00, v11, v22, v33) {
+ mat[0] = v00;
+ mat[5] = v11;
+ mat[10] = v22;
+ mat[15] = v33;
+ return mat;
+};
+
+
+/**
+ * Sets the diagonal values of the matrix from the given vector.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
+ * @param {goog.vec.Vec4.AnyType} vec The vector containing the values.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.setDiagonal = function(mat, vec) {
+ mat[0] = vec[0];
+ mat[5] = vec[1];
+ mat[10] = vec[2];
+ mat[15] = vec[3];
+ return mat;
+};
+
+
+/**
+ * Gets the diagonal values of the matrix into the given vector.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix containing the values.
+ * @param {goog.vec.Vec4.AnyType} vec The vector to receive the values.
+ * @param {number=} opt_diagonal Which diagonal to get. A value of 0 selects the
+ * main diagonal, a positive number selects a super diagonal and a negative
+ * number selects a sub diagonal.
+ * @return {goog.vec.Vec4.AnyType} return vec so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.getDiagonal = function(mat, vec, opt_diagonal) {
+ if (!opt_diagonal) {
+ // This is the most common case, so we avoid the for loop.
+ vec[0] = mat[0];
+ vec[1] = mat[5];
+ vec[2] = mat[10];
+ vec[3] = mat[15];
+ } else {
+ var offset = opt_diagonal > 0 ? 4 * opt_diagonal : -opt_diagonal;
+ for (var i = 0; i < 4 - Math.abs(opt_diagonal); i++) {
+ vec[i] = mat[offset + 5 * i];
+ }
+ }
+ return vec;
+};
+
+
+/**
+ * Sets the specified column with the supplied values.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to recieve the values.
+ * @param {number} column The column index to set the values on.
+ * @param {number} v0 The value for row 0.
+ * @param {number} v1 The value for row 1.
+ * @param {number} v2 The value for row 2.
+ * @param {number} v3 The value for row 3.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.setColumnValues = function(mat, column, v0, v1, v2, v3) {
+ var i = column * 4;
+ mat[i] = v0;
+ mat[i + 1] = v1;
+ mat[i + 2] = v2;
+ mat[i + 3] = v3;
+ return mat;
+};
+
+
+/**
+ * Sets the specified column with the value from the supplied vector.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
+ * @param {number} column The column index to set the values on.
+ * @param {goog.vec.Vec4.AnyType} vec The vector of elements for the column.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.setColumn = function(mat, column, vec) {
+ var i = column * 4;
+ mat[i] = vec[0];
+ mat[i + 1] = vec[1];
+ mat[i + 2] = vec[2];
+ mat[i + 3] = vec[3];
+ return mat;
+};
+
+
+/**
+ * Retrieves the specified column from the matrix into the given vector.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix supplying the values.
+ * @param {number} column The column to get the values from.
+ * @param {goog.vec.Vec4.AnyType} vec The vector of elements to
+ * receive the column.
+ * @return {goog.vec.Vec4.AnyType} return vec so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.getColumn = function(mat, column, vec) {
+ var i = column * 4;
+ vec[0] = mat[i];
+ vec[1] = mat[i + 1];
+ vec[2] = mat[i + 2];
+ vec[3] = mat[i + 3];
+ return vec;
+};
+
+
+/**
+ * Sets the columns of the matrix from the given vectors.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
+ * @param {goog.vec.Vec4.AnyType} vec0 The values for column 0.
+ * @param {goog.vec.Vec4.AnyType} vec1 The values for column 1.
+ * @param {goog.vec.Vec4.AnyType} vec2 The values for column 2.
+ * @param {goog.vec.Vec4.AnyType} vec3 The values for column 3.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.setColumns = function(mat, vec0, vec1, vec2, vec3) {
+ goog.vec.Mat4.setColumn(mat, 0, vec0);
+ goog.vec.Mat4.setColumn(mat, 1, vec1);
+ goog.vec.Mat4.setColumn(mat, 2, vec2);
+ goog.vec.Mat4.setColumn(mat, 3, vec3);
+ return mat;
+};
+
+
+/**
+ * Retrieves the column values from the given matrix into the given vectors.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix supplying the columns.
+ * @param {goog.vec.Vec4.AnyType} vec0 The vector to receive column 0.
+ * @param {goog.vec.Vec4.AnyType} vec1 The vector to receive column 1.
+ * @param {goog.vec.Vec4.AnyType} vec2 The vector to receive column 2.
+ * @param {goog.vec.Vec4.AnyType} vec3 The vector to receive column 3.
+ */
+goog.vec.Mat4.getColumns = function(mat, vec0, vec1, vec2, vec3) {
+ goog.vec.Mat4.getColumn(mat, 0, vec0);
+ goog.vec.Mat4.getColumn(mat, 1, vec1);
+ goog.vec.Mat4.getColumn(mat, 2, vec2);
+ goog.vec.Mat4.getColumn(mat, 3, vec3);
+};
+
+
+/**
+ * Sets the row values from the supplied values.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
+ * @param {number} row The index of the row to receive the values.
+ * @param {number} v0 The value for column 0.
+ * @param {number} v1 The value for column 1.
+ * @param {number} v2 The value for column 2.
+ * @param {number} v3 The value for column 3.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.setRowValues = function(mat, row, v0, v1, v2, v3) {
+ mat[row] = v0;
+ mat[row + 4] = v1;
+ mat[row + 8] = v2;
+ mat[row + 12] = v3;
+ return mat;
+};
+
+
+/**
+ * Sets the row values from the supplied vector.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the row values.
+ * @param {number} row The index of the row.
+ * @param {goog.vec.Vec4.AnyType} vec The vector containing the values.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.setRow = function(mat, row, vec) {
+ mat[row] = vec[0];
+ mat[row + 4] = vec[1];
+ mat[row + 8] = vec[2];
+ mat[row + 12] = vec[3];
+ return mat;
+};
+
+
+/**
+ * Retrieves the row values into the given vector.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix supplying the values.
+ * @param {number} row The index of the row supplying the values.
+ * @param {goog.vec.Vec4.AnyType} vec The vector to receive the row.
+ * @return {goog.vec.Vec4.AnyType} return vec so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.getRow = function(mat, row, vec) {
+ vec[0] = mat[row];
+ vec[1] = mat[row + 4];
+ vec[2] = mat[row + 8];
+ vec[3] = mat[row + 12];
+ return vec;
+};
+
+
+/**
+ * Sets the rows of the matrix from the supplied vectors.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
+ * @param {goog.vec.Vec4.AnyType} vec0 The values for row 0.
+ * @param {goog.vec.Vec4.AnyType} vec1 The values for row 1.
+ * @param {goog.vec.Vec4.AnyType} vec2 The values for row 2.
+ * @param {goog.vec.Vec4.AnyType} vec3 The values for row 3.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.setRows = function(mat, vec0, vec1, vec2, vec3) {
+ goog.vec.Mat4.setRow(mat, 0, vec0);
+ goog.vec.Mat4.setRow(mat, 1, vec1);
+ goog.vec.Mat4.setRow(mat, 2, vec2);
+ goog.vec.Mat4.setRow(mat, 3, vec3);
+ return mat;
+};
+
+
+/**
+ * Retrieves the rows of the matrix into the supplied vectors.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to supply the values.
+ * @param {goog.vec.Vec4.AnyType} vec0 The vector to receive row 0.
+ * @param {goog.vec.Vec4.AnyType} vec1 The vector to receive row 1.
+ * @param {goog.vec.Vec4.AnyType} vec2 The vector to receive row 2.
+ * @param {goog.vec.Vec4.AnyType} vec3 The vector to receive row 3.
+ */
+goog.vec.Mat4.getRows = function(mat, vec0, vec1, vec2, vec3) {
+ goog.vec.Mat4.getRow(mat, 0, vec0);
+ goog.vec.Mat4.getRow(mat, 1, vec1);
+ goog.vec.Mat4.getRow(mat, 2, vec2);
+ goog.vec.Mat4.getRow(mat, 3, vec3);
+};
+
+
+/**
+ * Makes the given 4x4 matrix the zero matrix.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @return {!goog.vec.Mat4.AnyType} return mat so operations can be chained.
+ */
+goog.vec.Mat4.makeZero = function(mat) {
+ mat[0] = 0;
+ mat[1] = 0;
+ mat[2] = 0;
+ mat[3] = 0;
+ mat[4] = 0;
+ mat[5] = 0;
+ mat[6] = 0;
+ mat[7] = 0;
+ mat[8] = 0;
+ mat[9] = 0;
+ mat[10] = 0;
+ mat[11] = 0;
+ mat[12] = 0;
+ mat[13] = 0;
+ mat[14] = 0;
+ mat[15] = 0;
+ return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix the identity matrix.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @return {goog.vec.Mat4.AnyType} return mat so operations can be chained.
+ */
+goog.vec.Mat4.makeIdentity = function(mat) {
+ mat[0] = 1;
+ mat[1] = 0;
+ mat[2] = 0;
+ mat[3] = 0;
+ mat[4] = 0;
+ mat[5] = 1;
+ mat[6] = 0;
+ mat[7] = 0;
+ mat[8] = 0;
+ mat[9] = 0;
+ mat[10] = 1;
+ mat[11] = 0;
+ mat[12] = 0;
+ mat[13] = 0;
+ mat[14] = 0;
+ mat[15] = 1;
+ return mat;
+};
+
+
+/**
+ * Performs a per-component addition of the matrix mat0 and mat1, storing
+ * the result into resultMat.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat0 The first addend.
+ * @param {goog.vec.Mat4.AnyType} mat1 The second addend.
+ * @param {goog.vec.Mat4.AnyType} resultMat The matrix to
+ * receive the results (may be either mat0 or mat1).
+ * @return {goog.vec.Mat4.AnyType} return resultMat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.addMat = function(mat0, mat1, resultMat) {
+ resultMat[0] = mat0[0] + mat1[0];
+ resultMat[1] = mat0[1] + mat1[1];
+ resultMat[2] = mat0[2] + mat1[2];
+ resultMat[3] = mat0[3] + mat1[3];
+ resultMat[4] = mat0[4] + mat1[4];
+ resultMat[5] = mat0[5] + mat1[5];
+ resultMat[6] = mat0[6] + mat1[6];
+ resultMat[7] = mat0[7] + mat1[7];
+ resultMat[8] = mat0[8] + mat1[8];
+ resultMat[9] = mat0[9] + mat1[9];
+ resultMat[10] = mat0[10] + mat1[10];
+ resultMat[11] = mat0[11] + mat1[11];
+ resultMat[12] = mat0[12] + mat1[12];
+ resultMat[13] = mat0[13] + mat1[13];
+ resultMat[14] = mat0[14] + mat1[14];
+ resultMat[15] = mat0[15] + mat1[15];
+ return resultMat;
+};
+
+
+/**
+ * Performs a per-component subtraction of the matrix mat0 and mat1,
+ * storing the result into resultMat.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat0 The minuend.
+ * @param {goog.vec.Mat4.AnyType} mat1 The subtrahend.
+ * @param {goog.vec.Mat4.AnyType} resultMat The matrix to receive
+ * the results (may be either mat0 or mat1).
+ * @return {goog.vec.Mat4.AnyType} return resultMat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.subMat = function(mat0, mat1, resultMat) {
+ resultMat[0] = mat0[0] - mat1[0];
+ resultMat[1] = mat0[1] - mat1[1];
+ resultMat[2] = mat0[2] - mat1[2];
+ resultMat[3] = mat0[3] - mat1[3];
+ resultMat[4] = mat0[4] - mat1[4];
+ resultMat[5] = mat0[5] - mat1[5];
+ resultMat[6] = mat0[6] - mat1[6];
+ resultMat[7] = mat0[7] - mat1[7];
+ resultMat[8] = mat0[8] - mat1[8];
+ resultMat[9] = mat0[9] - mat1[9];
+ resultMat[10] = mat0[10] - mat1[10];
+ resultMat[11] = mat0[11] - mat1[11];
+ resultMat[12] = mat0[12] - mat1[12];
+ resultMat[13] = mat0[13] - mat1[13];
+ resultMat[14] = mat0[14] - mat1[14];
+ resultMat[15] = mat0[15] - mat1[15];
+ return resultMat;
+};
+
+
+/**
+ * Multiplies matrix mat with the given scalar, storing the result
+ * into resultMat.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} scalar The scalar value to multiply to each element of mat.
+ * @param {goog.vec.Mat4.AnyType} resultMat The matrix to receive
+ * the results (may be mat).
+ * @return {goog.vec.Mat4.AnyType} return resultMat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.multScalar = function(mat, scalar, resultMat) {
+ resultMat[0] = mat[0] * scalar;
+ resultMat[1] = mat[1] * scalar;
+ resultMat[2] = mat[2] * scalar;
+ resultMat[3] = mat[3] * scalar;
+ resultMat[4] = mat[4] * scalar;
+ resultMat[5] = mat[5] * scalar;
+ resultMat[6] = mat[6] * scalar;
+ resultMat[7] = mat[7] * scalar;
+ resultMat[8] = mat[8] * scalar;
+ resultMat[9] = mat[9] * scalar;
+ resultMat[10] = mat[10] * scalar;
+ resultMat[11] = mat[11] * scalar;
+ resultMat[12] = mat[12] * scalar;
+ resultMat[13] = mat[13] * scalar;
+ resultMat[14] = mat[14] * scalar;
+ resultMat[15] = mat[15] * scalar;
+ return resultMat;
+};
+
+
+/**
+ * Multiplies the two matrices mat0 and mat1 using matrix multiplication,
+ * storing the result into resultMat.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat0 The first (left hand) matrix.
+ * @param {goog.vec.Mat4.AnyType} mat1 The second (right hand) matrix.
+ * @param {goog.vec.Mat4.AnyType} resultMat The matrix to receive
+ * the results (may be either mat0 or mat1).
+ * @return {goog.vec.Mat4.AnyType} return resultMat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.multMat = function(mat0, mat1, resultMat) {
+ var a00 = mat0[0], a10 = mat0[1], a20 = mat0[2], a30 = mat0[3];
+ var a01 = mat0[4], a11 = mat0[5], a21 = mat0[6], a31 = mat0[7];
+ var a02 = mat0[8], a12 = mat0[9], a22 = mat0[10], a32 = mat0[11];
+ var a03 = mat0[12], a13 = mat0[13], a23 = mat0[14], a33 = mat0[15];
+
+ var b00 = mat1[0], b10 = mat1[1], b20 = mat1[2], b30 = mat1[3];
+ var b01 = mat1[4], b11 = mat1[5], b21 = mat1[6], b31 = mat1[7];
+ var b02 = mat1[8], b12 = mat1[9], b22 = mat1[10], b32 = mat1[11];
+ var b03 = mat1[12], b13 = mat1[13], b23 = mat1[14], b33 = mat1[15];
+
+ resultMat[0] = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30;
+ resultMat[1] = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30;
+ resultMat[2] = a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30;
+ resultMat[3] = a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30;
+
+ resultMat[4] = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31;
+ resultMat[5] = a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31;
+ resultMat[6] = a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31;
+ resultMat[7] = a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31;
+
+ resultMat[8] = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32;
+ resultMat[9] = a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32;
+ resultMat[10] = a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32;
+ resultMat[11] = a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32;
+
+ resultMat[12] = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33;
+ resultMat[13] = a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33;
+ resultMat[14] = a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33;
+ resultMat[15] = a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33;
+ return resultMat;
+};
+
+
+/**
+ * Transposes the given matrix mat storing the result into resultMat.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to transpose.
+ * @param {goog.vec.Mat4.AnyType} resultMat The matrix to receive
+ * the results (may be mat).
+ * @return {goog.vec.Mat4.AnyType} return resultMat so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.transpose = function(mat, resultMat) {
+ if (resultMat == mat) {
+ var a10 = mat[1], a20 = mat[2], a30 = mat[3];
+ var a21 = mat[6], a31 = mat[7];
+ var a32 = mat[11];
+ resultMat[1] = mat[4];
+ resultMat[2] = mat[8];
+ resultMat[3] = mat[12];
+ resultMat[4] = a10;
+ resultMat[6] = mat[9];
+ resultMat[7] = mat[13];
+ resultMat[8] = a20;
+ resultMat[9] = a21;
+ resultMat[11] = mat[14];
+ resultMat[12] = a30;
+ resultMat[13] = a31;
+ resultMat[14] = a32;
+ } else {
+ resultMat[0] = mat[0];
+ resultMat[1] = mat[4];
+ resultMat[2] = mat[8];
+ resultMat[3] = mat[12];
+
+ resultMat[4] = mat[1];
+ resultMat[5] = mat[5];
+ resultMat[6] = mat[9];
+ resultMat[7] = mat[13];
+
+ resultMat[8] = mat[2];
+ resultMat[9] = mat[6];
+ resultMat[10] = mat[10];
+ resultMat[11] = mat[14];
+
+ resultMat[12] = mat[3];
+ resultMat[13] = mat[7];
+ resultMat[14] = mat[11];
+ resultMat[15] = mat[15];
+ }
+ return resultMat;
+};
+
+
+/**
+ * Computes the determinant of the matrix.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to compute the matrix for.
+ * @return {number} The determinant of the matrix.
+ */
+goog.vec.Mat4.determinant = function(mat) {
+ var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+ var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+ var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+ var m03 = mat[12], m13 = mat[13], m23 = mat[14], m33 = mat[15];
+
+ var a0 = m00 * m11 - m10 * m01;
+ var a1 = m00 * m21 - m20 * m01;
+ var a2 = m00 * m31 - m30 * m01;
+ var a3 = m10 * m21 - m20 * m11;
+ var a4 = m10 * m31 - m30 * m11;
+ var a5 = m20 * m31 - m30 * m21;
+ var b0 = m02 * m13 - m12 * m03;
+ var b1 = m02 * m23 - m22 * m03;
+ var b2 = m02 * m33 - m32 * m03;
+ var b3 = m12 * m23 - m22 * m13;
+ var b4 = m12 * m33 - m32 * m13;
+ var b5 = m22 * m33 - m32 * m23;
+
+ return a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0;
+};
+
+
+/**
+ * Computes the inverse of mat storing the result into resultMat. If the
+ * inverse is defined, this function returns true, false otherwise.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to invert.
+ * @param {goog.vec.Mat4.AnyType} resultMat The matrix to receive
+ * the result (may be mat).
+ * @return {boolean} True if the inverse is defined. If false is returned,
+ * resultMat is not modified.
+ */
+goog.vec.Mat4.invert = function(mat, resultMat) {
+ var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+ var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+ var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+ var m03 = mat[12], m13 = mat[13], m23 = mat[14], m33 = mat[15];
+
+ var a0 = m00 * m11 - m10 * m01;
+ var a1 = m00 * m21 - m20 * m01;
+ var a2 = m00 * m31 - m30 * m01;
+ var a3 = m10 * m21 - m20 * m11;
+ var a4 = m10 * m31 - m30 * m11;
+ var a5 = m20 * m31 - m30 * m21;
+ var b0 = m02 * m13 - m12 * m03;
+ var b1 = m02 * m23 - m22 * m03;
+ var b2 = m02 * m33 - m32 * m03;
+ var b3 = m12 * m23 - m22 * m13;
+ var b4 = m12 * m33 - m32 * m13;
+ var b5 = m22 * m33 - m32 * m23;
+
+ var det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0;
+ if (det == 0) {
+ return false;
+ }
+
+ var idet = 1.0 / det;
+ resultMat[0] = (m11 * b5 - m21 * b4 + m31 * b3) * idet;
+ resultMat[1] = (-m10 * b5 + m20 * b4 - m30 * b3) * idet;
+ resultMat[2] = (m13 * a5 - m23 * a4 + m33 * a3) * idet;
+ resultMat[3] = (-m12 * a5 + m22 * a4 - m32 * a3) * idet;
+ resultMat[4] = (-m01 * b5 + m21 * b2 - m31 * b1) * idet;
+ resultMat[5] = (m00 * b5 - m20 * b2 + m30 * b1) * idet;
+ resultMat[6] = (-m03 * a5 + m23 * a2 - m33 * a1) * idet;
+ resultMat[7] = (m02 * a5 - m22 * a2 + m32 * a1) * idet;
+ resultMat[8] = (m01 * b4 - m11 * b2 + m31 * b0) * idet;
+ resultMat[9] = (-m00 * b4 + m10 * b2 - m30 * b0) * idet;
+ resultMat[10] = (m03 * a4 - m13 * a2 + m33 * a0) * idet;
+ resultMat[11] = (-m02 * a4 + m12 * a2 - m32 * a0) * idet;
+ resultMat[12] = (-m01 * b3 + m11 * b1 - m21 * b0) * idet;
+ resultMat[13] = (m00 * b3 - m10 * b1 + m20 * b0) * idet;
+ resultMat[14] = (-m03 * a3 + m13 * a1 - m23 * a0) * idet;
+ resultMat[15] = (m02 * a3 - m12 * a1 + m22 * a0) * idet;
+ return true;
+};
+
+
+/**
+ * Returns true if the components of mat0 are equal to the components of mat1.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat0 The first matrix.
+ * @param {goog.vec.Mat4.AnyType} mat1 The second matrix.
+ * @return {boolean} True if the the two matrices are equivalent.
+ */
+goog.vec.Mat4.equals = function(mat0, mat1) {
+ return mat0.length == mat1.length &&
+ mat0[0] == mat1[0] &&
+ mat0[1] == mat1[1] &&
+ mat0[2] == mat1[2] &&
+ mat0[3] == mat1[3] &&
+ mat0[4] == mat1[4] &&
+ mat0[5] == mat1[5] &&
+ mat0[6] == mat1[6] &&
+ mat0[7] == mat1[7] &&
+ mat0[8] == mat1[8] &&
+ mat0[9] == mat1[9] &&
+ mat0[10] == mat1[10] &&
+ mat0[11] == mat1[11] &&
+ mat0[12] == mat1[12] &&
+ mat0[13] == mat1[13] &&
+ mat0[14] == mat1[14] &&
+ mat0[15] == mat1[15];
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec. The input vector is multiplied against the
+ * upper 3x4 matrix omitting the projective component.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix supplying the transformation.
+ * @param {goog.vec.Vec3.AnyType} vec The 3 element vector to transform.
+ * @param {goog.vec.Vec3.AnyType} resultVec The 3 element vector to
+ * receive the results (may be vec).
+ * @return {goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.multVec3 = function(mat, vec, resultVec) {
+ var x = vec[0], y = vec[1], z = vec[2];
+ resultVec[0] = x * mat[0] + y * mat[4] + z * mat[8] + mat[12];
+ resultVec[1] = x * mat[1] + y * mat[5] + z * mat[9] + mat[13];
+ resultVec[2] = x * mat[2] + y * mat[6] + z * mat[10] + mat[14];
+ return resultVec;
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec. The input vector is multiplied against the
+ * upper 3x3 matrix omitting the projective component and translation
+ * components.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix supplying the transformation.
+ * @param {goog.vec.Vec3.AnyType} vec The 3 element vector to transform.
+ * @param {goog.vec.Vec3.AnyType} resultVec The 3 element vector to
+ * receive the results (may be vec).
+ * @return {goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.multVec3NoTranslate = function(mat, vec, resultVec) {
+ var x = vec[0], y = vec[1], z = vec[2];
+ resultVec[0] = x * mat[0] + y * mat[4] + z * mat[8];
+ resultVec[1] = x * mat[1] + y * mat[5] + z * mat[9];
+ resultVec[2] = x * mat[2] + y * mat[6] + z * mat[10];
+ return resultVec;
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec. The input vector is multiplied against the
+ * full 4x4 matrix with the homogeneous divide applied to reduce the 4 element
+ * vector to a 3 element vector.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix supplying the transformation.
+ * @param {goog.vec.Vec3.AnyType} vec The 3 element vector to transform.
+ * @param {goog.vec.Vec3.AnyType} resultVec The 3 element vector
+ * to receive the results (may be vec).
+ * @return {goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.multVec3Projective = function(mat, vec, resultVec) {
+ var x = vec[0], y = vec[1], z = vec[2];
+ var invw = 1 / (x * mat[3] + y * mat[7] + z * mat[11] + mat[15]);
+ resultVec[0] = (x * mat[0] + y * mat[4] + z * mat[8] + mat[12]) * invw;
+ resultVec[1] = (x * mat[1] + y * mat[5] + z * mat[9] + mat[13]) * invw;
+ resultVec[2] = (x * mat[2] + y * mat[6] + z * mat[10] + mat[14]) * invw;
+ return resultVec;
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix supplying the transformation.
+ * @param {goog.vec.Vec4.AnyType} vec The vector to transform.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to
+ * receive the results (may be vec).
+ * @return {goog.vec.Vec4.AnyType} return resultVec so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.multVec4 = function(mat, vec, resultVec) {
+ var x = vec[0], y = vec[1], z = vec[2], w = vec[3];
+ resultVec[0] = x * mat[0] + y * mat[4] + z * mat[8] + w * mat[12];
+ resultVec[1] = x * mat[1] + y * mat[5] + z * mat[9] + w * mat[13];
+ resultVec[2] = x * mat[2] + y * mat[6] + z * mat[10] + w * mat[14];
+ resultVec[3] = x * mat[3] + y * mat[7] + z * mat[11] + w * mat[15];
+ return resultVec;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a translation matrix with x, y and z
+ * translation factors.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} x The translation along the x axis.
+ * @param {number} y The translation along the y axis.
+ * @param {number} z The translation along the z axis.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.makeTranslate = function(mat, x, y, z) {
+ goog.vec.Mat4.makeIdentity(mat);
+ return goog.vec.Mat4.setColumnValues(mat, 3, x, y, z, 1);
+};
+
+
+/**
+ * Makes the given 4x4 matrix as a scale matrix with x, y and z scale factors.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} x The scale along the x axis.
+ * @param {number} y The scale along the y axis.
+ * @param {number} z The scale along the z axis.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.makeScale = function(mat, x, y, z) {
+ goog.vec.Mat4.makeIdentity(mat);
+ return goog.vec.Mat4.setDiagonalValues(mat, x, y, z, 1);
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the axis defined by the vector (ax, ay, az).
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @param {number} ax The x component of the rotation axis.
+ * @param {number} ay The y component of the rotation axis.
+ * @param {number} az The z component of the rotation axis.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.makeRotate = function(mat, angle, ax, ay, az) {
+ var c = Math.cos(angle);
+ var d = 1 - c;
+ var s = Math.sin(angle);
+
+ return goog.vec.Mat4.setFromValues(mat,
+ ax * ax * d + c,
+ ax * ay * d + az * s,
+ ax * az * d - ay * s,
+ 0,
+
+ ax * ay * d - az * s,
+ ay * ay * d + c,
+ ay * az * d + ax * s,
+ 0,
+
+ ax * az * d + ay * s,
+ ay * az * d - ax * s,
+ az * az * d + c,
+ 0,
+
+ 0, 0, 0, 1);
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the X axis.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.makeRotateX = function(mat, angle) {
+ var c = Math.cos(angle);
+ var s = Math.sin(angle);
+ return goog.vec.Mat4.setFromValues(
+ mat, 1, 0, 0, 0, 0, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1);
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the Y axis.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.makeRotateY = function(mat, angle) {
+ var c = Math.cos(angle);
+ var s = Math.sin(angle);
+ return goog.vec.Mat4.setFromValues(
+ mat, c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, 0, 0, 0, 1);
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the Z axis.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.makeRotateZ = function(mat, angle) {
+ var c = Math.cos(angle);
+ var s = Math.sin(angle);
+ return goog.vec.Mat4.setFromValues(
+ mat, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
+};
+
+
+/**
+ * Makes the given 4x4 matrix a perspective projection matrix.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} left The coordinate of the left clipping plane.
+ * @param {number} right The coordinate of the right clipping plane.
+ * @param {number} bottom The coordinate of the bottom clipping plane.
+ * @param {number} top The coordinate of the top clipping plane.
+ * @param {number} near The distance to the near clipping plane.
+ * @param {number} far The distance to the far clipping plane.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.makeFrustum = function(mat, left, right, bottom, top, near, far) {
+ var x = (2 * near) / (right - left);
+ var y = (2 * near) / (top - bottom);
+ var a = (right + left) / (right - left);
+ var b = (top + bottom) / (top - bottom);
+ var c = -(far + near) / (far - near);
+ var d = -(2 * far * near) / (far - near);
+
+ return goog.vec.Mat4.setFromValues(mat,
+ x, 0, 0, 0,
+ 0, y, 0, 0,
+ a, b, c, -1,
+ 0, 0, d, 0
+ );
+};
+
+
+/**
+ * Makse the given 4x4 matrix perspective projection matrix given a
+ * field of view and aspect ratio.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} fovy The field of view along the y (vertical) axis in
+ * radians.
+ * @param {number} aspect The x (width) to y (height) aspect ratio.
+ * @param {number} near The distance to the near clipping plane.
+ * @param {number} far The distance to the far clipping plane.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.makePerspective = function(mat, fovy, aspect, near, far) {
+ var angle = fovy / 2;
+ var dz = far - near;
+ var sinAngle = Math.sin(angle);
+ if (dz == 0 || sinAngle == 0 || aspect == 0) {
+ return mat;
+ }
+
+ var cot = Math.cos(angle) / sinAngle;
+ return goog.vec.Mat4.setFromValues(mat,
+ cot / aspect, 0, 0, 0,
+ 0, cot, 0, 0,
+ 0, 0, -(far + near) / dz, -1,
+ 0, 0, -(2 * near * far) / dz, 0
+ );
+};
+
+
+/**
+ * Makes the given 4x4 matrix an orthographic projection matrix.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} left The coordinate of the left clipping plane.
+ * @param {number} right The coordinate of the right clipping plane.
+ * @param {number} bottom The coordinate of the bottom clipping plane.
+ * @param {number} top The coordinate of the top clipping plane.
+ * @param {number} near The distance to the near clipping plane.
+ * @param {number} far The distance to the far clipping plane.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.makeOrtho = function(mat, left, right, bottom, top, near, far) {
+ var x = 2 / (right - left);
+ var y = 2 / (top - bottom);
+ var z = -2 / (far - near);
+ var a = -(right + left) / (right - left);
+ var b = -(top + bottom) / (top - bottom);
+ var c = -(far + near) / (far - near);
+
+ return goog.vec.Mat4.setFromValues(mat,
+ x, 0, 0, 0,
+ 0, y, 0, 0,
+ 0, 0, z, 0,
+ a, b, c, 1
+ );
+};
+
+
+/**
+ * Makes the given 4x4 matrix a modelview matrix of a camera so that
+ * the camera is 'looking at' the given center point.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {goog.vec.Vec3.AnyType} eyePt The position of the eye point
+ * (camera origin).
+ * @param {goog.vec.Vec3.AnyType} centerPt The point to aim the camera at.
+ * @param {goog.vec.Vec3.AnyType} worldUpVec The vector that identifies
+ * the up direction for the camera.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.makeLookAt = function(mat, eyePt, centerPt, worldUpVec) {
+ // Compute the direction vector from the eye point to the center point and
+ // normalize.
+ var fwdVec = goog.vec.Mat4.tmpVec4_[0];
+ goog.vec.Vec3.subtract(centerPt, eyePt, fwdVec);
+ goog.vec.Vec3.normalize(fwdVec, fwdVec);
+ fwdVec[3] = 0;
+
+ // Compute the side vector from the forward vector and the input up vector.
+ var sideVec = goog.vec.Mat4.tmpVec4_[1];
+ goog.vec.Vec3.cross(fwdVec, worldUpVec, sideVec);
+ goog.vec.Vec3.normalize(sideVec, sideVec);
+ sideVec[3] = 0;
+
+ // Now the up vector to form the orthonormal basis.
+ var upVec = goog.vec.Mat4.tmpVec4_[2];
+ goog.vec.Vec3.cross(sideVec, fwdVec, upVec);
+ goog.vec.Vec3.normalize(upVec, upVec);
+ upVec[3] = 0;
+
+ // Update the view matrix with the new orthonormal basis and position the
+ // camera at the given eye point.
+ goog.vec.Vec3.negate(fwdVec, fwdVec);
+ goog.vec.Mat4.setRow(mat, 0, sideVec);
+ goog.vec.Mat4.setRow(mat, 1, upVec);
+ goog.vec.Mat4.setRow(mat, 2, fwdVec);
+ goog.vec.Mat4.setRowValues(mat, 3, 0, 0, 0, 1);
+ goog.vec.Mat4.translate(
+ mat, -eyePt[0], -eyePt[1], -eyePt[2]);
+
+ return mat;
+};
+
+
+/**
+ * Decomposes a matrix into the lookAt vectors eyePt, fwdVec and worldUpVec.
+ * The matrix represents the modelview matrix of a camera. It is the inverse
+ * of lookAt except for the output of the fwdVec instead of centerPt.
+ * The centerPt itself cannot be recovered from a modelview matrix.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {goog.vec.Vec3.AnyType} eyePt The position of the eye point
+ * (camera origin).
+ * @param {goog.vec.Vec3.AnyType} fwdVec The vector describing where
+ * the camera points to.
+ * @param {goog.vec.Vec3.AnyType} worldUpVec The vector that
+ * identifies the up direction for the camera.
+ * @return {boolean} True if the method succeeds, false otherwise.
+ * The method can only fail if the inverse of viewMatrix is not defined.
+ */
+goog.vec.Mat4.toLookAt = function(mat, eyePt, fwdVec, worldUpVec) {
+ // Get eye of the camera.
+ var matInverse = goog.vec.Mat4.tmpMat4_[0];
+ if (!goog.vec.Mat4.invert(mat, matInverse)) {
+ // The input matrix does not have a valid inverse.
+ return false;
+ }
+
+ if (eyePt) {
+ eyePt[0] = matInverse[12];
+ eyePt[1] = matInverse[13];
+ eyePt[2] = matInverse[14];
+ }
+
+ // Get forward vector from the definition of lookAt.
+ if (fwdVec || worldUpVec) {
+ if (!fwdVec) {
+ fwdVec = goog.vec.Mat4.tmpVec3_[0];
+ }
+ fwdVec[0] = -mat[2];
+ fwdVec[1] = -mat[6];
+ fwdVec[2] = -mat[10];
+ // Normalize forward vector.
+ goog.vec.Vec3.normalize(fwdVec, fwdVec);
+ }
+
+ if (worldUpVec) {
+ // Get side vector from the definition of gluLookAt.
+ var side = goog.vec.Mat4.tmpVec3_[1];
+ side[0] = mat[0];
+ side[1] = mat[4];
+ side[2] = mat[8];
+ // Compute up vector as a up = side x forward.
+ goog.vec.Vec3.cross(side, fwdVec, worldUpVec);
+ // Normalize up vector.
+ goog.vec.Vec3.normalize(worldUpVec, worldUpVec);
+ }
+ return true;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix given Euler angles using
+ * the ZXZ convention.
+ * Given the euler angles [theta1, theta2, theta3], the rotation is defined as
+ * rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
+ * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
+ * rotation_x(theta) means rotation around the X axis of theta radians,
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} theta1 The angle of rotation around the Z axis in radians.
+ * @param {number} theta2 The angle of rotation around the X axis in radians.
+ * @param {number} theta3 The angle of rotation around the Z axis in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.makeEulerZXZ = function(mat, theta1, theta2, theta3) {
+ var c1 = Math.cos(theta1);
+ var s1 = Math.sin(theta1);
+
+ var c2 = Math.cos(theta2);
+ var s2 = Math.sin(theta2);
+
+ var c3 = Math.cos(theta3);
+ var s3 = Math.sin(theta3);
+
+ mat[0] = c1 * c3 - c2 * s1 * s3;
+ mat[1] = c2 * c1 * s3 + c3 * s1;
+ mat[2] = s3 * s2;
+ mat[3] = 0;
+
+ mat[4] = -c1 * s3 - c3 * c2 * s1;
+ mat[5] = c1 * c2 * c3 - s1 * s3;
+ mat[6] = c3 * s2;
+ mat[7] = 0;
+
+ mat[8] = s2 * s1;
+ mat[9] = -c1 * s2;
+ mat[10] = c2;
+ mat[11] = 0;
+
+ mat[12] = 0;
+ mat[13] = 0;
+ mat[14] = 0;
+ mat[15] = 1;
+
+ return mat;
+};
+
+
+/**
+ * Decomposes a rotation matrix into Euler angles using the ZXZ convention so
+ * that rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
+ * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
+ * rotation_x(theta) means rotation around the X axis of theta radians.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {goog.vec.Vec3.AnyType} euler The ZXZ Euler angles in
+ * radians as [theta1, theta2, theta3].
+ * @param {boolean=} opt_theta2IsNegative Whether theta2 is in [-pi, 0] instead
+ * of the default [0, pi].
+ * @return {goog.vec.Vec4.AnyType} return euler so that operations can be
+ * chained together.
+ */
+goog.vec.Mat4.toEulerZXZ = function(mat, euler, opt_theta2IsNegative) {
+ // There is an ambiguity in the sign of sinTheta2 because of the sqrt.
+ var sinTheta2 = Math.sqrt(mat[2] * mat[2] + mat[6] * mat[6]);
+
+ // By default we explicitely constrain theta2 to be in [0, pi],
+ // so sinTheta2 is always positive. We can change the behavior and specify
+ // theta2 to be negative in [-pi, 0] with opt_Theta2IsNegative.
+ var signTheta2 = opt_theta2IsNegative ? -1 : 1;
+
+ if (sinTheta2 > goog.vec.EPSILON) {
+ euler[2] = Math.atan2(mat[2] * signTheta2, mat[6] * signTheta2);
+ euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[10]);
+ euler[0] = Math.atan2(mat[8] * signTheta2, -mat[9] * signTheta2);
+ } else {
+ // There is also an arbitrary choice for theta1 = 0 or theta2 = 0 here.
+ // We assume theta1 = 0 as some applications do not allow the camera to roll
+ // (i.e. have theta1 != 0).
+ euler[0] = 0;
+ euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[10]);
+ euler[2] = Math.atan2(mat[1], mat[0]);
+ }
+
+ // Atan2 outputs angles in [-pi, pi] so we bring them back to [0, 2 * pi].
+ euler[0] = (euler[0] + Math.PI * 2) % (Math.PI * 2);
+ euler[2] = (euler[2] + Math.PI * 2) % (Math.PI * 2);
+ // For theta2 we want the angle to be in [0, pi] or [-pi, 0] depending on
+ // signTheta2.
+ euler[1] = ((euler[1] * signTheta2 + Math.PI * 2) % (Math.PI * 2)) *
+ signTheta2;
+
+ return euler;
+};
+
+
+/**
+ * Translates the given matrix by x,y,z. Equvialent to:
+ * goog.vec.Mat4.multMat(
+ * mat,
+ * goog.vec.Mat4.makeTranslate(goog.vec.Mat4.create(), x, y, z),
+ * mat);
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} x The translation along the x axis.
+ * @param {number} y The translation along the y axis.
+ * @param {number} z The translation along the z axis.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.translate = function(mat, x, y, z) {
+ return goog.vec.Mat4.setColumnValues(
+ mat, 3,
+ mat[0] * x + mat[4] * y + mat[8] * z + mat[12],
+ mat[1] * x + mat[5] * y + mat[9] * z + mat[13],
+ mat[2] * x + mat[6] * y + mat[10] * z + mat[14],
+ mat[3] * x + mat[7] * y + mat[11] * z + mat[15]);
+};
+
+
+/**
+ * Scales the given matrix by x,y,z. Equivalent to:
+ * goog.vec.Mat4.multMat(
+ * mat,
+ * goog.vec.Mat4.makeScale(goog.vec.Mat4.create(), x, y, z),
+ * mat);
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} x The x scale factor.
+ * @param {number} y The y scale factor.
+ * @param {number} z The z scale factor.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.scale = function(mat, x, y, z) {
+ return goog.vec.Mat4.setFromValues(
+ mat,
+ mat[0] * x, mat[1] * x, mat[2] * x, mat[3] * x,
+ mat[4] * y, mat[5] * y, mat[6] * y, mat[7] * y,
+ mat[8] * z, mat[9] * z, mat[10] * z, mat[11] * z,
+ mat[12], mat[13], mat[14], mat[15]);
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x,y,z axis. Equivalent to:
+ * goog.vec.Mat4.multMat(
+ * mat,
+ * goog.vec.Mat4.makeRotate(goog.vec.Mat4.create(), angle, x, y, z),
+ * mat);
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @param {number} x The x component of the rotation axis.
+ * @param {number} y The y component of the rotation axis.
+ * @param {number} z The z component of the rotation axis.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.rotate = function(mat, angle, x, y, z) {
+ var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+ var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+ var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+ var m03 = mat[12], m13 = mat[13], m23 = mat[14], m33 = mat[15];
+
+ var cosAngle = Math.cos(angle);
+ var sinAngle = Math.sin(angle);
+ var diffCosAngle = 1 - cosAngle;
+ var r00 = x * x * diffCosAngle + cosAngle;
+ var r10 = x * y * diffCosAngle + z * sinAngle;
+ var r20 = x * z * diffCosAngle - y * sinAngle;
+
+ var r01 = x * y * diffCosAngle - z * sinAngle;
+ var r11 = y * y * diffCosAngle + cosAngle;
+ var r21 = y * z * diffCosAngle + x * sinAngle;
+
+ var r02 = x * z * diffCosAngle + y * sinAngle;
+ var r12 = y * z * diffCosAngle - x * sinAngle;
+ var r22 = z * z * diffCosAngle + cosAngle;
+
+ return goog.vec.Mat4.setFromValues(
+ mat,
+ m00 * r00 + m01 * r10 + m02 * r20,
+ m10 * r00 + m11 * r10 + m12 * r20,
+ m20 * r00 + m21 * r10 + m22 * r20,
+ m30 * r00 + m31 * r10 + m32 * r20,
+
+ m00 * r01 + m01 * r11 + m02 * r21,
+ m10 * r01 + m11 * r11 + m12 * r21,
+ m20 * r01 + m21 * r11 + m22 * r21,
+ m30 * r01 + m31 * r11 + m32 * r21,
+
+ m00 * r02 + m01 * r12 + m02 * r22,
+ m10 * r02 + m11 * r12 + m12 * r22,
+ m20 * r02 + m21 * r12 + m22 * r22,
+ m30 * r02 + m31 * r12 + m32 * r22,
+
+ m03, m13, m23, m33);
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x axis. Equivalent to:
+ * goog.vec.Mat4.multMat(
+ * mat,
+ * goog.vec.Mat4.makeRotateX(goog.vec.Mat4.create(), angle),
+ * mat);
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.rotateX = function(mat, angle) {
+ var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+ var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+
+ var c = Math.cos(angle);
+ var s = Math.sin(angle);
+
+ mat[4] = m01 * c + m02 * s;
+ mat[5] = m11 * c + m12 * s;
+ mat[6] = m21 * c + m22 * s;
+ mat[7] = m31 * c + m32 * s;
+ mat[8] = m01 * -s + m02 * c;
+ mat[9] = m11 * -s + m12 * c;
+ mat[10] = m21 * -s + m22 * c;
+ mat[11] = m31 * -s + m32 * c;
+
+ return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the y axis. Equivalent to:
+ * goog.vec.Mat4.multMat(
+ * mat,
+ * goog.vec.Mat4.makeRotateY(goog.vec.Mat4.create(), angle),
+ * mat);
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.rotateY = function(mat, angle) {
+ var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+ var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+
+ var c = Math.cos(angle);
+ var s = Math.sin(angle);
+
+ mat[0] = m00 * c + m02 * -s;
+ mat[1] = m10 * c + m12 * -s;
+ mat[2] = m20 * c + m22 * -s;
+ mat[3] = m30 * c + m32 * -s;
+ mat[8] = m00 * s + m02 * c;
+ mat[9] = m10 * s + m12 * c;
+ mat[10] = m20 * s + m22 * c;
+ mat[11] = m30 * s + m32 * c;
+
+ return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the z axis. Equivalent to:
+ * goog.vec.Mat4.multMat(
+ * mat,
+ * goog.vec.Mat4.makeRotateZ(goog.vec.Mat4.create(), angle),
+ * mat);
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.rotateZ = function(mat, angle) {
+ var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+ var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+
+ var c = Math.cos(angle);
+ var s = Math.sin(angle);
+
+ mat[0] = m00 * c + m01 * s;
+ mat[1] = m10 * c + m11 * s;
+ mat[2] = m20 * c + m21 * s;
+ mat[3] = m30 * c + m31 * s;
+ mat[4] = m00 * -s + m01 * c;
+ mat[5] = m10 * -s + m11 * c;
+ mat[6] = m20 * -s + m21 * c;
+ mat[7] = m30 * -s + m31 * c;
+
+ return mat;
+};
+
+
+/**
+ * Retrieves the translation component of the transformation matrix.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The transformation matrix.
+ * @param {goog.vec.Vec3.AnyType} translation The vector for storing the
+ * result.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ * chained.
+ */
+goog.vec.Mat4.getTranslation = function(mat, translation) {
+ translation[0] = mat[12];
+ translation[1] = mat[13];
+ translation[2] = mat[14];
+ return translation;
+};
+
+
+/**
+ * @type {!Array<!goog.vec.Vec3.Type>}
+ * @private
+ */
+goog.vec.Mat4.tmpVec3_ = [
+ goog.vec.Vec3.createFloat64(),
+ goog.vec.Vec3.createFloat64()
+];
+
+
+/**
+ * @type {!Array<!goog.vec.Vec4.Type>}
+ * @private
+ */
+goog.vec.Mat4.tmpVec4_ = [
+ goog.vec.Vec4.createFloat64(),
+ goog.vec.Vec4.createFloat64(),
+ goog.vec.Vec4.createFloat64()
+];
+
+
+/**
+ * @type {!Array<!goog.vec.Mat4.Type>}
+ * @private
+ */
+goog.vec.Mat4.tmpMat4_ = [
+ goog.vec.Mat4.createFloat64()
+];
+
+goog.provide('ol.TransformFunction');
+
+
+/**
+ * A transform function accepts an array of input coordinate values, an optional
+ * output array, and an optional dimension (default should be 2). The function
+ * transforms the input coordinate values, populates the output array, and
+ * returns the output array.
+ *
+ * @typedef {function(Array.<number>, Array.<number>=, number=): Array.<number>}
+ * @api stable
+ */
+ol.TransformFunction;
+
+goog.provide('ol.Extent');
+goog.provide('ol.extent');
+goog.provide('ol.extent.Corner');
+goog.provide('ol.extent.Relationship');
+
+goog.require('goog.asserts');
+goog.require('goog.vec.Mat4');
+goog.require('ol.Coordinate');
+goog.require('ol.Size');
+goog.require('ol.TransformFunction');
+
+
+/**
+ * An array of numbers representing an extent: `[minx, miny, maxx, maxy]`.
+ * @typedef {Array.<number>}
+ * @api stable
+ */
+ol.Extent;
+
+
+/**
+ * Extent corner.
+ * @enum {string}
+ */
+ol.extent.Corner = {
+ BOTTOM_LEFT: 'bottom-left',
+ BOTTOM_RIGHT: 'bottom-right',
+ TOP_LEFT: 'top-left',
+ TOP_RIGHT: 'top-right'
+};
+
+
+/**
+ * Relationship to an extent.
+ * @enum {number}
+ */
+ol.extent.Relationship = {
+ UNKNOWN: 0,
+ INTERSECTING: 1,
+ ABOVE: 2,
+ RIGHT: 4,
+ BELOW: 8,
+ LEFT: 16
+};
+
+
+/**
+ * Build an extent that includes all given coordinates.
+ *
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @return {ol.Extent} Bounding extent.
+ * @api stable
+ */
+ol.extent.boundingExtent = function(coordinates) {
+ var extent = ol.extent.createEmpty();
+ for (var i = 0, ii = coordinates.length; i < ii; ++i) {
+ ol.extent.extendCoordinate(extent, coordinates[i]);
+ }
+ return extent;
+};
+
+
+/**
+ * @param {Array.<number>} xs Xs.
+ * @param {Array.<number>} ys Ys.
+ * @param {ol.Extent=} opt_extent Destination extent.
+ * @private
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.boundingExtentXYs_ = function(xs, ys, opt_extent) {
+ 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);
+ var maxY = Math.max.apply(null, ys);
+ return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent);
+};
+
+
+/**
+ * Return extent increased by the provided value.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} value The amount by which the extent should be buffered.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
+ * @api stable
+ */
+ol.extent.buffer = function(extent, value, opt_extent) {
+ if (opt_extent) {
+ opt_extent[0] = extent[0] - value;
+ opt_extent[1] = extent[1] - value;
+ opt_extent[2] = extent[2] + value;
+ opt_extent[3] = extent[3] + value;
+ return opt_extent;
+ } else {
+ return [
+ extent[0] - value,
+ extent[1] - value,
+ extent[2] + value,
+ extent[3] + value
+ ];
+ }
+};
+
+
+/**
+ * Creates a clone of an extent.
+ *
+ * @param {ol.Extent} extent Extent to clone.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} The clone.
+ */
+ol.extent.clone = function(extent, opt_extent) {
+ if (opt_extent) {
+ opt_extent[0] = extent[0];
+ opt_extent[1] = extent[1];
+ opt_extent[2] = extent[2];
+ opt_extent[3] = extent[3];
+ return opt_extent;
+ } else {
+ return extent.slice();
+ }
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @return {number} Closest squared distance.
+ */
+ol.extent.closestSquaredDistanceXY = function(extent, x, y) {
+ var dx, dy;
+ if (x < extent[0]) {
+ dx = extent[0] - x;
+ } else if (extent[2] < x) {
+ dx = x - extent[2];
+ } else {
+ dx = 0;
+ }
+ if (y < extent[1]) {
+ dy = extent[1] - y;
+ } else if (extent[3] < y) {
+ dy = y - extent[3];
+ } else {
+ dy = 0;
+ }
+ return dx * dx + dy * dy;
+};
+
+
+/**
+ * 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} The coordinate is contained in the extent.
+ * @api stable
+ */
+ol.extent.containsCoordinate = function(extent, coordinate) {
+ return ol.extent.containsXY(extent, coordinate[0], coordinate[1]);
+};
+
+
+/**
+ * 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} The second extent is contained by or on the edge of the
+ * first.
+ * @api stable
+ */
+ol.extent.containsExtent = function(extent1, extent2) {
+ return extent1[0] <= extent2[0] && extent2[2] <= extent1[2] &&
+ extent1[1] <= extent2[1] && extent2[3] <= extent1[3];
+};
+
+
+/**
+ * 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} The x, y values are contained in the extent.
+ * @api stable
+ */
+ol.extent.containsXY = function(extent, x, y) {
+ return extent[0] <= x && x <= extent[2] && extent[1] <= y && y <= extent[3];
+};
+
+
+/**
+ * Get the relationship between a coordinate and extent.
+ * @param {ol.Extent} extent The extent.
+ * @param {ol.Coordinate} coordinate The coordinate.
+ * @return {number} The relationship (bitwise compare with
+ * ol.extent.Relationship).
+ */
+ol.extent.coordinateRelationship = function(extent, coordinate) {
+ var minX = extent[0];
+ var minY = extent[1];
+ var maxX = extent[2];
+ var maxY = extent[3];
+ var x = coordinate[0];
+ var y = coordinate[1];
+ var relationship = ol.extent.Relationship.UNKNOWN;
+ if (x < minX) {
+ relationship = relationship | ol.extent.Relationship.LEFT;
+ } else if (x > maxX) {
+ relationship = relationship | ol.extent.Relationship.RIGHT;
+ }
+ if (y < minY) {
+ relationship = relationship | ol.extent.Relationship.BELOW;
+ } else if (y > maxY) {
+ relationship = relationship | ol.extent.Relationship.ABOVE;
+ }
+ if (relationship === ol.extent.Relationship.UNKNOWN) {
+ relationship = ol.extent.Relationship.INTERSECTING;
+ }
+ return relationship;
+};
+
+
+/**
+ * Create an empty extent.
+ * @return {ol.Extent} Empty extent.
+ * @api stable
+ */
+ol.extent.createEmpty = function() {
+ return [Infinity, Infinity, -Infinity, -Infinity];
+};
+
+
+/**
+ * Create a new extent or update the provided extent.
+ * @param {number} minX Minimum X.
+ * @param {number} minY Minimum Y.
+ * @param {number} maxX Maximum X.
+ * @param {number} maxY Maximum Y.
+ * @param {ol.Extent=} opt_extent Destination extent.
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.createOrUpdate = function(minX, minY, maxX, maxY, opt_extent) {
+ if (opt_extent) {
+ opt_extent[0] = minX;
+ opt_extent[1] = minY;
+ opt_extent[2] = maxX;
+ opt_extent[3] = maxY;
+ return opt_extent;
+ } else {
+ return [minX, minY, maxX, maxY];
+ }
+};
+
+
+/**
+ * Create a new empty extent or make the provided one empty.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.createOrUpdateEmpty = function(opt_extent) {
+ return ol.extent.createOrUpdate(
+ Infinity, Infinity, -Infinity, -Infinity, opt_extent);
+};
+
+
+/**
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.createOrUpdateFromCoordinate = function(coordinate, opt_extent) {
+ var x = coordinate[0];
+ var y = coordinate[1];
+ return ol.extent.createOrUpdate(x, y, x, y, opt_extent);
+};
+
+
+/**
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.createOrUpdateFromCoordinates = function(coordinates, opt_extent) {
+ var extent = ol.extent.createOrUpdateEmpty(opt_extent);
+ return ol.extent.extendCoordinates(extent, coordinates);
+};
+
+
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.createOrUpdateFromFlatCoordinates =
+ function(flatCoordinates, offset, end, stride, opt_extent) {
+ var extent = ol.extent.createOrUpdateEmpty(opt_extent);
+ return ol.extent.extendFlatCoordinates(
+ extent, flatCoordinates, offset, end, stride);
+};
+
+
+/**
+ * @param {Array.<Array.<ol.Coordinate>>} rings Rings.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.createOrUpdateFromRings = function(rings, opt_extent) {
+ var extent = ol.extent.createOrUpdateEmpty(opt_extent);
+ return ol.extent.extendRings(extent, rings);
+};
+
+
+/**
+ * Empty an extent in place.
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.empty = function(extent) {
+ extent[0] = extent[1] = Infinity;
+ extent[2] = extent[3] = -Infinity;
+ return extent;
+};
+
+
+/**
+ * Determine if two extents are equivalent.
+ * @param {ol.Extent} extent1 Extent 1.
+ * @param {ol.Extent} extent2 Extent 2.
+ * @return {boolean} The two extents are equivalent.
+ * @api stable
+ */
+ol.extent.equals = function(extent1, extent2) {
+ return extent1[0] == extent2[0] && extent1[2] == extent2[2] &&
+ extent1[1] == extent2[1] && extent1[3] == extent2[3];
+};
+
+
+/**
+ * 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) {
+ if (extent2[0] < extent1[0]) {
+ extent1[0] = extent2[0];
+ }
+ if (extent2[2] > extent1[2]) {
+ extent1[2] = extent2[2];
+ }
+ if (extent2[1] < extent1[1]) {
+ extent1[1] = extent2[1];
+ }
+ if (extent2[3] > extent1[3]) {
+ extent1[3] = extent2[3];
+ }
+ return extent1;
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ */
+ol.extent.extendCoordinate = function(extent, coordinate) {
+ if (coordinate[0] < extent[0]) {
+ extent[0] = coordinate[0];
+ }
+ if (coordinate[0] > extent[2]) {
+ extent[2] = coordinate[0];
+ }
+ if (coordinate[1] < extent[1]) {
+ extent[1] = coordinate[1];
+ }
+ if (coordinate[1] > extent[3]) {
+ extent[3] = coordinate[1];
+ }
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.extendCoordinates = function(extent, coordinates) {
+ var i, ii;
+ for (i = 0, ii = coordinates.length; i < ii; ++i) {
+ ol.extent.extendCoordinate(extent, coordinates[i]);
+ }
+ return extent;
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.extendFlatCoordinates =
+ function(extent, flatCoordinates, offset, end, stride) {
+ for (; offset < end; offset += stride) {
+ ol.extent.extendXY(
+ extent, flatCoordinates[offset], flatCoordinates[offset + 1]);
+ }
+ return extent;
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {Array.<Array.<ol.Coordinate>>} rings Rings.
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.extendRings = function(extent, rings) {
+ var i, ii;
+ for (i = 0, ii = rings.length; i < ii; ++i) {
+ ol.extent.extendCoordinates(extent, rings[i]);
+ }
+ return extent;
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {number} x X.
+ * @param {number} y Y.
+ */
+ol.extent.extendXY = function(extent, x, y) {
+ extent[0] = Math.min(extent[0], x);
+ extent[1] = Math.min(extent[1], y);
+ extent[2] = Math.max(extent[2], x);
+ extent[3] = Math.max(extent[3], y);
+};
+
+
+/**
+ * This function calls `callback` for each corner of the extent. If the
+ * callback returns a truthy value the function returns that value
+ * immediately. Otherwise the function returns `false`.
+ * @param {ol.Extent} extent Extent.
+ * @param {function(this:T, ol.Coordinate): S} callback Callback.
+ * @param {T=} opt_this Value to use as `this` when executing `callback`.
+ * @return {S|boolean} Value.
+ * @template S, T
+ */
+ol.extent.forEachCorner = function(extent, callback, opt_this) {
+ var val;
+ val = callback.call(opt_this, ol.extent.getBottomLeft(extent));
+ if (val) {
+ return val;
+ }
+ val = callback.call(opt_this, ol.extent.getBottomRight(extent));
+ if (val) {
+ return val;
+ }
+ val = callback.call(opt_this, ol.extent.getTopRight(extent));
+ if (val) {
+ return val;
+ }
+ val = callback.call(opt_this, ol.extent.getTopLeft(extent));
+ if (val) {
+ return val;
+ }
+ return false;
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @return {number} Area.
+ */
+ol.extent.getArea = function(extent) {
+ var area = 0;
+ if (!ol.extent.isEmpty(extent)) {
+ area = ol.extent.getWidth(extent) * ol.extent.getHeight(extent);
+ }
+ return area;
+};
+
+
+/**
+ * Get the bottom left coordinate of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.Coordinate} Bottom left coordinate.
+ * @api stable
+ */
+ol.extent.getBottomLeft = function(extent) {
+ return [extent[0], extent[1]];
+};
+
+
+/**
+ * Get the bottom right coordinate of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.Coordinate} Bottom right coordinate.
+ * @api stable
+ */
+ol.extent.getBottomRight = function(extent) {
+ return [extent[2], extent[1]];
+};
+
+
+/**
+ * Get the center coordinate of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.Coordinate} Center.
+ * @api stable
+ */
+ol.extent.getCenter = function(extent) {
+ return [(extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2];
+};
+
+
+/**
+ * Get a corner coordinate of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.extent.Corner} corner Corner.
+ * @return {ol.Coordinate} Corner coordinate.
+ */
+ol.extent.getCorner = function(extent, corner) {
+ var coordinate;
+ if (corner === ol.extent.Corner.BOTTOM_LEFT) {
+ coordinate = ol.extent.getBottomLeft(extent);
+ } else if (corner === ol.extent.Corner.BOTTOM_RIGHT) {
+ coordinate = ol.extent.getBottomRight(extent);
+ } else if (corner === ol.extent.Corner.TOP_LEFT) {
+ coordinate = ol.extent.getTopLeft(extent);
+ } else if (corner === ol.extent.Corner.TOP_RIGHT) {
+ coordinate = ol.extent.getTopRight(extent);
+ } else {
+ goog.asserts.fail('Invalid corner: %s', corner);
+ }
+ goog.asserts.assert(coordinate, 'coordinate should be defined');
+ return coordinate;
+};
+
+
+/**
+ * @param {ol.Extent} extent1 Extent 1.
+ * @param {ol.Extent} extent2 Extent 2.
+ * @return {number} Enlarged area.
+ */
+ol.extent.getEnlargedArea = function(extent1, extent2) {
+ var minX = Math.min(extent1[0], extent2[0]);
+ var minY = Math.min(extent1[1], extent2[1]);
+ var maxX = Math.max(extent1[2], extent2[2]);
+ var maxY = Math.max(extent1[3], extent2[3]);
+ return (maxX - minX) * (maxY - minY);
+};
+
+
+/**
+ * @param {ol.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {number} rotation Rotation.
+ * @param {ol.Size} size Size.
+ * @param {ol.Extent=} opt_extent Destination extent.
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.getForViewAndSize =
+ function(center, resolution, rotation, size, opt_extent) {
+ var dx = resolution * size[0] / 2;
+ var dy = resolution * size[1] / 2;
+ var cosRotation = Math.cos(rotation);
+ var sinRotation = Math.sin(rotation);
+ /** @type {Array.<number>} */
+ var xs = [-dx, -dx, dx, dx];
+ /** @type {Array.<number>} */
+ var ys = [-dy, dy, -dy, dy];
+ var i, x, y;
+ for (i = 0; i < 4; ++i) {
+ x = xs[i];
+ y = ys[i];
+ xs[i] = center[0] + x * cosRotation - y * sinRotation;
+ ys[i] = center[1] + x * sinRotation + y * cosRotation;
+ }
+ return ol.extent.boundingExtentXYs_(xs, ys, opt_extent);
+};
+
+
+/**
+ * Get the height of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {number} Height.
+ * @api stable
+ */
+ol.extent.getHeight = function(extent) {
+ return extent[3] - extent[1];
+};
+
+
+/**
+ * @param {ol.Extent} extent1 Extent 1.
+ * @param {ol.Extent} extent2 Extent 2.
+ * @return {number} Intersection area.
+ */
+ol.extent.getIntersectionArea = function(extent1, extent2) {
+ var intersection = ol.extent.getIntersection(extent1, extent2);
+ return ol.extent.getArea(intersection);
+};
+
+
+/**
+ * Get the intersection of two extents.
+ * @param {ol.Extent} extent1 Extent 1.
+ * @param {ol.Extent} extent2 Extent 2.
+ * @param {ol.Extent=} opt_extent Optional extent to populate with intersection.
+ * @return {ol.Extent} Intersecting extent.
+ * @api stable
+ */
+ol.extent.getIntersection = function(extent1, extent2, opt_extent) {
+ var intersection = opt_extent ? opt_extent : ol.extent.createEmpty();
+ if (ol.extent.intersects(extent1, extent2)) {
+ if (extent1[0] > extent2[0]) {
+ intersection[0] = extent1[0];
+ } else {
+ intersection[0] = extent2[0];
+ }
+ if (extent1[1] > extent2[1]) {
+ intersection[1] = extent1[1];
+ } else {
+ intersection[1] = extent2[1];
+ }
+ if (extent1[2] < extent2[2]) {
+ intersection[2] = extent1[2];
+ } else {
+ intersection[2] = extent2[2];
+ }
+ if (extent1[3] < extent2[3]) {
+ intersection[3] = extent1[3];
+ } else {
+ intersection[3] = extent2[3];
+ }
+ }
+ return intersection;
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @return {number} Margin.
+ */
+ol.extent.getMargin = function(extent) {
+ return ol.extent.getWidth(extent) + ol.extent.getHeight(extent);
+};
+
+
+/**
+ * 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) {
+ return [extent[2] - extent[0], extent[3] - extent[1]];
+};
+
+
+/**
+ * Get the top left coordinate of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.Coordinate} Top left coordinate.
+ * @api stable
+ */
+ol.extent.getTopLeft = function(extent) {
+ return [extent[0], extent[3]];
+};
+
+
+/**
+ * Get the top right coordinate of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.Coordinate} Top right coordinate.
+ * @api stable
+ */
+ol.extent.getTopRight = function(extent) {
+ return [extent[2], extent[3]];
+};
+
+
+/**
+ * Get the width of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {number} Width.
+ * @api stable
+ */
+ol.extent.getWidth = function(extent) {
+ return extent[2] - extent[0];
+};
+
+
+/**
+ * Determine if one extent intersects another.
+ * @param {ol.Extent} extent1 Extent 1.
+ * @param {ol.Extent} extent2 Extent.
+ * @return {boolean} The two extents intersect.
+ * @api stable
+ */
+ol.extent.intersects = function(extent1, extent2) {
+ return extent1[0] <= extent2[2] &&
+ extent1[2] >= extent2[0] &&
+ extent1[1] <= extent2[3] &&
+ extent1[3] >= extent2[1];
+};
+
+
+/**
+ * Determine if an extent is empty.
+ * @param {ol.Extent} extent Extent.
+ * @return {boolean} Is empty.
+ * @api stable
+ */
+ol.extent.isEmpty = function(extent) {
+ return extent[2] < extent[0] || extent[3] < extent[1];
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @return {boolean} Is infinite.
+ */
+ol.extent.isInfinite = function(extent) {
+ return extent[0] == -Infinity || extent[1] == -Infinity ||
+ extent[2] == Infinity || extent[3] == Infinity;
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {ol.Coordinate} Coordinate.
+ */
+ol.extent.normalize = function(extent, coordinate) {
+ return [
+ (coordinate[0] - extent[0]) / (extent[2] - extent[0]),
+ (coordinate[1] - extent[1]) / (extent[3] - extent[1])
+ ];
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
+ */
+ol.extent.returnOrUpdate = function(extent, opt_extent) {
+ if (opt_extent) {
+ opt_extent[0] = extent[0];
+ opt_extent[1] = extent[1];
+ opt_extent[2] = extent[2];
+ opt_extent[3] = extent[3];
+ return opt_extent;
+ } else {
+ return extent;
+ }
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {number} value Value.
+ */
+ol.extent.scaleFromCenter = function(extent, value) {
+ var deltaX = ((extent[2] - extent[0]) / 2) * (value - 1);
+ var deltaY = ((extent[3] - extent[1]) / 2) * (value - 1);
+ extent[0] -= deltaX;
+ extent[2] += deltaX;
+ extent[1] -= deltaY;
+ extent[3] += deltaY;
+};
+
+
+/**
+ * Determine if the segment between two coordinates intersects (crosses,
+ * touches, or is contained by) the provided extent.
+ * @param {ol.Extent} extent The extent.
+ * @param {ol.Coordinate} start Segment start coordinate.
+ * @param {ol.Coordinate} end Segment end coordinate.
+ * @return {boolean} The segment intersects the extent.
+ */
+ol.extent.intersectsSegment = function(extent, start, end) {
+ var intersects = false;
+ var startRel = ol.extent.coordinateRelationship(extent, start);
+ var endRel = ol.extent.coordinateRelationship(extent, end);
+ if (startRel === ol.extent.Relationship.INTERSECTING ||
+ endRel === ol.extent.Relationship.INTERSECTING) {
+ intersects = true;
+ } else {
+ var minX = extent[0];
+ var minY = extent[1];
+ var maxX = extent[2];
+ var maxY = extent[3];
+ var startX = start[0];
+ var startY = start[1];
+ var endX = end[0];
+ var endY = end[1];
+ var slope = (endY - startY) / (endX - startX);
+ var x, y;
+ if (!!(endRel & ol.extent.Relationship.ABOVE) &&
+ !(startRel & ol.extent.Relationship.ABOVE)) {
+ // potentially intersects top
+ x = endX - ((endY - maxY) / slope);
+ intersects = x >= minX && x <= maxX;
+ }
+ 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;
+ }
+ 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;
+ }
+ if (!intersects && !!(endRel & ol.extent.Relationship.LEFT) &&
+ !(startRel & ol.extent.Relationship.LEFT)) {
+ // potentially intersects left
+ y = endY - ((endX - minX) * slope);
+ intersects = y >= minY && y <= maxY;
+ }
+
+ }
+ return intersects;
+};
+
+
+/**
+ * @param {ol.Extent} extent1 Extent 1.
+ * @param {ol.Extent} extent2 Extent 2.
+ * @return {boolean} Touches.
+ */
+ol.extent.touches = function(extent1, extent2) {
+ var intersects = ol.extent.intersects(extent1, extent2);
+ return intersects &&
+ (extent1[0] == extent2[2] || extent1[2] == extent2[0] ||
+ extent1[1] == extent2[3] || extent1[3] == extent2[1]);
+};
+
+
+/**
+ * Apply a transform function to the extent.
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.TransformFunction} transformFn Transform function. Called with
+ * [minX, minY, maxX, maxY] extent coordinates.
+ * @param {ol.Extent=} opt_extent Destination extent.
+ * @return {ol.Extent} Extent.
+ * @api stable
+ */
+ol.extent.applyTransform = function(extent, transformFn, opt_extent) {
+ var coordinates = [
+ extent[0], extent[1],
+ extent[0], extent[3],
+ extent[2], extent[1],
+ extent[2], extent[3]
+ ];
+ transformFn(coordinates, coordinates, 2);
+ var xs = [coordinates[0], coordinates[2], coordinates[4], coordinates[6]];
+ var ys = [coordinates[1], coordinates[3], coordinates[5], coordinates[7]];
+ return ol.extent.boundingExtentXYs_(xs, ys, opt_extent);
+};
+
+
+/**
+ * Apply a 2d transform to an extent.
+ * @param {ol.Extent} extent Input extent.
+ * @param {goog.vec.Mat4.Number} transform The transform matrix.
+ * @param {ol.Extent=} opt_extent Optional extent for return values.
+ * @return {ol.Extent} The transformed extent.
+ */
+ol.extent.transform2D = function(extent, transform, opt_extent) {
+ var dest = opt_extent ? opt_extent : [];
+ 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 xi = [0, 2, 0, 2];
+ var yi = [1, 1, 3, 3];
+ var xs = [];
+ var ys = [];
+ var i, x, y;
+ for (i = 0; i < 4; ++i) {
+ x = extent[xi[i]];
+ y = extent[yi[i]];
+ xs[i] = m00 * x + m01 * y + m03;
+ ys[i] = m10 * x + m11 * y + m13;
+ }
+ dest[0] = Math.min.apply(null, xs);
+ dest[1] = Math.min.apply(null, ys);
+ dest[2] = Math.max.apply(null, xs);
+ dest[3] = Math.max.apply(null, ys);
+ 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.
+
+/**
+ * @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;
+ }
+};
+
+
+/**
+ * Wraps a function to allow it to be called, at most, once. All
+ * additional calls are no-ops.
+ *
+ * This is particularly useful for initialization functions
+ * that should be called, at most, once.
+ *
+ * @param {function():*} f Function to call.
+ * @return {function():undefined} Wrapped function.
+ */
+goog.functions.once = function(f) {
+ // Keep a reference to the function that we null out when we're done with
+ // it -- that way, the function can be GC'd when we're done with it.
+ var inner = f;
+ return function() {
+ if (inner) {
+ var tmp = inner;
+ inner = null;
+ tmp();
+ }
+ };
+};
+
+
+/**
+ * Wraps a function to allow it to be called, at most, once for each sequence of
+ * calls fired repeatedly so long as they are fired less than a specified
+ * interval apart (in milliseconds). Whether it receives one signal or multiple,
+ * it will always wait until a full interval has elapsed since the last signal
+ * before performing the action.
+ *
+ * This is particularly useful for bulking up repeated user actions (e.g. only
+ * refreshing a view once a user finishes typing rather than updating with every
+ * keystroke). For more stateful debouncing with support for pausing, resuming,
+ * and canceling debounced actions, use {@code goog.async.Debouncer}.
+ *
+ * @param {function(this:SCOPE):*} f Function to call.
+ * @param {number} interval Interval over which to debounce. The function will
+ * only be called after the full interval has elapsed since the last call.
+ * @param {SCOPE=} opt_scope Object in whose scope to call the function.
+ * @return {function():undefined} Wrapped function.
+ * @template SCOPE
+ */
+goog.functions.debounce = function(f, interval, opt_scope) {
+ if (opt_scope) {
+ f = goog.bind(f, opt_scope);
+ }
+ var timeout = null;
+ return function() {
+ goog.global.clearTimeout(timeout);
+ timeout = goog.global.setTimeout(f, interval);
+ };
+};
+
+
+/**
+ * Wraps a function to allow it to be called, at most, once per interval
+ * (specified in milliseconds). If it is called multiple times while it is
+ * waiting, it will only perform the action once at the end of the interval.
+ *
+ * This is particularly useful for limiting repeated user requests (e.g.
+ * preventing a user from spamming a server with frequent view refreshes). For
+ * more stateful throttling with support for pausing, resuming, and canceling
+ * throttled actions, use {@code goog.async.Throttle}.
+ *
+ * @param {function(this:SCOPE):*} f Function to call.
+ * @param {number} interval Interval over which to throttle. The function can
+ * only be called once per interval.
+ * @param {SCOPE=} opt_scope Object in whose scope to call the function.
+ * @return {function():undefined} Wrapped function.
+ * @template SCOPE
+ */
+goog.functions.throttle = function(f, interval, opt_scope) {
+ if (opt_scope) {
+ f = goog.bind(f, opt_scope);
+ }
+ var timeout = null;
+ var shouldFire = false;
+ var fire = function() {
+ timeout = goog.global.setTimeout(handleTimeout, interval);
+ f();
+ };
+ var handleTimeout = function() {
+ timeout = null;
+ if (shouldFire) {
+ shouldFire = false;
+ fire();
+ }
+ };
+
+ return function() {
+ if (!timeout) {
+ fire();
+ } else {
+ shouldFire = true;
+ }
+ };
+};
+
+/**
+ * @license
+ * Latitude/longitude spherical geodesy formulae taken from
+ * http://www.movable-type.co.uk/scripts/latlong.html
+ * Licensed under CC-BY-3.0.
+ */
+
+goog.provide('ol.Sphere');
+
+goog.require('ol.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 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 += ol.math.toRadians(x2 - x1) *
+ (2 + Math.sin(ol.math.toRadians(y1)) +
+ Math.sin(ol.math.toRadians(y2)));
+ x1 = x2;
+ y1 = y2;
+ }
+ return area * this.radius * this.radius / 2.0;
+};
+
+
+/**
+ * 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 = ol.math.toRadians(c1[1]);
+ var lat2 = ol.math.toRadians(c2[1]);
+ var deltaLatBy2 = (lat2 - lat1) / 2;
+ var deltaLonBy2 = ol.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 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 = ol.math.toRadians(c1[1]);
+ var lon1 = ol.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 [ol.math.toDegrees(lon), ol.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.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');
+
+
+/**
+ * A projection as {@link ol.proj.Projection}, SRS identifier string or
+ * undefined.
+ * @typedef {ol.proj.Projection|string|undefined} ol.proj.ProjectionLike
+ * @api stable
+ */
+ol.proj.ProjectionLike;
+
+
+/**
+ * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, `'tile-pixels'` or
+ * `'us-ft'`.
+ * @enum {string}
+ * @api stable
+ */
+ol.proj.Units = {
+ DEGREES: 'degrees',
+ FEET: 'ft',
+ METERS: 'm',
+ PIXELS: 'pixels',
+ TILE_PIXELS: 'tile-pixels',
+ USFEET: 'us-ft'
+};
+
+
+/**
+ * Meters per unit lookup table.
+ * @const
+ * @type {Object.<ol.proj.Units, number>}
+ * @api stable
+ */
+ol.proj.METERS_PER_UNIT = {};
+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;
+
+
+
+/**
+ * @classdesc
+ * Projection definition class. One of these is created for each projection
+ * supported in the application and stored in the {@link ol.proj} namespace.
+ * You can use these in applications, but this is not required, as API params
+ * and options use {@link ol.proj.ProjectionLike} which means the simple string
+ * code will suffice.
+ *
+ * You can use {@link ol.proj.get} to retrieve the object for a particular
+ * projection.
+ *
+ * The library includes definitions for `EPSG:4326` and `EPSG:3857`, together
+ * with the following aliases:
+ * * `EPSG:4326`: CRS:84, urn:ogc:def:crs:EPSG:6.6:4326,
+ * urn:ogc:def:crs:OGC:1.3:CRS84, urn:ogc:def:crs:OGC:2:84,
+ * http://www.opengis.net/gml/srs/epsg.xml#4326,
+ * urn:x-ogc:def:crs:EPSG:4326
+ * * `EPSG:3857`: EPSG:102100, EPSG:102113, EPSG:900913,
+ * urn:ogc:def:crs:EPSG:6.18:3:3857,
+ * http://www.opengis.net/gml/srs/epsg.xml#3857
+ *
+ * If you use proj4js, aliases can be added using `proj4.defs()`; see
+ * [documentation](https://github.com/proj4js/proj4js).
+ *
+ * @constructor
+ * @param {olx.ProjectionOptions} options Projection options.
+ * @struct
+ * @api stable
+ */
+ol.proj.Projection = function(options) {
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.code_ = options.code;
+
+ /**
+ * @private
+ * @type {ol.proj.Units}
+ */
+ this.units_ = /** @type {ol.proj.Units} */ (options.units);
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.extent_ = options.extent !== undefined ? options.extent : null;
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.worldExtent_ = options.worldExtent !== undefined ?
+ options.worldExtent : null;
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.axisOrientation_ = options.axisOrientation !== undefined ?
+ options.axisOrientation : 'enu';
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.global_ = options.global !== undefined ? options.global : false;
+
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.canWrapX_ = !!(this.global_ && this.extent_);
+
+ /**
+ * @private
+ * @type {function(number, ol.Coordinate):number}
+ */
+ this.getPointResolutionFunc_ = options.getPointResolution !== undefined ?
+ options.getPointResolution : this.getPointResolution_;
+
+ /**
+ * @private
+ * @type {ol.tilegrid.TileGrid}
+ */
+ this.defaultTileGrid_ = null;
+
+ var projections = ol.proj.projections_;
+ var code = options.code;
+ goog.asserts.assert(code !== undefined,
+ 'Option "code" is required for constructing instance');
+ if (ol.ENABLE_PROJ4JS && typeof proj4 == 'function' &&
+ projections[code] === undefined) {
+ var def = proj4.defs(code);
+ if (def !== undefined) {
+ if (def.axis !== undefined && options.axisOrientation === undefined) {
+ this.axisOrientation_ = def.axis;
+ }
+ if (options.units === undefined) {
+ var units = def.units;
+ if (def.to_meter !== undefined) {
+ if (units === undefined ||
+ ol.proj.METERS_PER_UNIT[units] === undefined) {
+ units = def.to_meter.toString();
+ ol.proj.METERS_PER_UNIT[units] = def.to_meter;
+ }
+ }
+ this.units_ = units;
+ }
+ var currentCode, currentDef, currentProj, proj4Transform;
+ for (currentCode in projections) {
+ currentDef = proj4.defs(currentCode);
+ if (currentDef !== undefined) {
+ 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_;
+};
+
+
+/**
+ * Get the code for this projection, e.g. 'EPSG:4326'.
+ * @return {string} Code.
+ * @api stable
+ */
+ol.proj.Projection.prototype.getCode = function() {
+ return this.code_;
+};
+
+
+/**
+ * Get the validity extent for this projection.
+ * @return {ol.Extent} Extent.
+ * @api stable
+ */
+ol.proj.Projection.prototype.getExtent = function() {
+ return this.extent_;
+};
+
+
+/**
+ * Get the units of this projection.
+ * @return {ol.proj.Units} Units.
+ * @api stable
+ */
+ol.proj.Projection.prototype.getUnits = function() {
+ return this.units_;
+};
+
+
+/**
+ * Get the amount of meters per unit of this projection. If the projection is
+ * not configured with a units identifier, the return is `undefined`.
+ * @return {number|undefined} Meters.
+ * @api stable
+ */
+ol.proj.Projection.prototype.getMetersPerUnit = function() {
+ return ol.proj.METERS_PER_UNIT[this.units_];
+};
+
+
+/**
+ * Get the world extent for this projection.
+ * @return {ol.Extent} Extent.
+ * @api
+ */
+ol.proj.Projection.prototype.getWorldExtent = function() {
+ return this.worldExtent_;
+};
+
+
+/**
+ * Get the axis orientation of this projection.
+ * Example values are:
+ * enu - the default easting, northing, elevation.
+ * neu - northing, easting, up - useful for "lat/long" geographic coordinates,
+ * or south orientated transverse mercator.
+ * wnu - westing, northing, up - some planetary coordinate systems have
+ * "west positive" coordinate systems
+ * @return {string} Axis orientation.
+ */
+ol.proj.Projection.prototype.getAxisOrientation = function() {
+ return this.axisOrientation_;
+};
+
+
+/**
+ * Is this projection a global projection which spans the whole world?
+ * @return {boolean} Whether the projection is global.
+ * @api stable
+ */
+ol.proj.Projection.prototype.isGlobal = function() {
+ return this.global_;
+};
+
+
+/**
+* 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 && this.extent_);
+};
+
+
+/**
+ * @return {ol.tilegrid.TileGrid} The default tile grid.
+ */
+ol.proj.Projection.prototype.getDefaultTileGrid = function() {
+ return this.defaultTileGrid_;
+};
+
+
+/**
+ * @param {ol.tilegrid.TileGrid} tileGrid The default tile grid.
+ */
+ol.proj.Projection.prototype.setDefaultTileGrid = function(tileGrid) {
+ this.defaultTileGrid_ = tileGrid;
+};
+
+
+/**
+ * Set the validity extent for this projection.
+ * @param {ol.Extent} extent Extent.
+ * @api stable
+ */
+ol.proj.Projection.prototype.setExtent = function(extent) {
+ this.extent_ = extent;
+ this.canWrapX_ = !!(this.global_ && extent);
+};
+
+
+/**
+ * Set the world extent for this projection.
+ * @param {ol.Extent} worldExtent World extent
+ * [minlon, minlat, maxlon, maxlat].
+ * @api
+ */
+ol.proj.Projection.prototype.setWorldExtent = function(worldExtent) {
+ this.worldExtent_ = worldExtent;
+};
+
+
+/**
+* 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;
+ } else {
+ // Estimate point resolution 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.
+ var toEPSG4326 = ol.proj.getTransformFromProjections(
+ this, ol.proj.get('EPSG:4326'));
+ var vertices = [
+ point[0] - resolution / 2, point[1],
+ point[0] + resolution / 2, point[1],
+ point[0], point[1] - resolution / 2,
+ point[0], point[1] + resolution / 2
+ ];
+ vertices = toEPSG4326(vertices, vertices, 2);
+ var width = ol.sphere.NORMAL.haversineDistance(
+ vertices.slice(0, 2), vertices.slice(2, 4));
+ var height = ol.sphere.NORMAL.haversineDistance(
+ vertices.slice(4, 6), vertices.slice(6, 8));
+ var pointResolution = (width + height) / 2;
+ var metersPerUnit = this.getMetersPerUnit();
+ if (metersPerUnit !== undefined) {
+ pointResolution /= metersPerUnit;
+ }
+ return pointResolution;
+ }
+};
+
+
+/**
+ * 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>}
+ */
+ol.proj.projections_ = {};
+
+
+/**
+ * @private
+ * @type {Object.<string, Object.<string, ol.TransformFunction>>}
+ */
+ol.proj.transforms_ = {};
+
+
+/**
+ * Registers transformation functions that don't alter coordinates. Those allow
+ * to transform between projections with equal meaning.
+ *
+ * @param {Array.<ol.proj.Projection>} projections Projections.
+ * @api
+ */
+ol.proj.addEquivalentProjections = function(projections) {
+ ol.proj.addProjections(projections);
+ projections.forEach(function(source) {
+ projections.forEach(function(destination) {
+ if (source !== destination) {
+ ol.proj.addTransform(source, destination, ol.proj.cloneTransform);
+ }
+ });
+ });
+};
+
+
+/**
+ * Registers transformation functions to convert coordinates in any projection
+ * in projection1 to any projection in projection2.
+ *
+ * @param {Array.<ol.proj.Projection>} projections1 Projections with equal
+ * meaning.
+ * @param {Array.<ol.proj.Projection>} projections2 Projections with equal
+ * meaning.
+ * @param {ol.TransformFunction} forwardTransform Transformation from any
+ * projection in projection1 to any projection in projection2.
+ * @param {ol.TransformFunction} inverseTransform Transform from any projection
+ * in projection2 to any projection in projection1..
+ */
+ol.proj.addEquivalentTransforms =
+ function(projections1, projections2, forwardTransform, inverseTransform) {
+ projections1.forEach(function(projection1) {
+ projections2.forEach(function(projection2) {
+ ol.proj.addTransform(projection1, projection2, forwardTransform);
+ ol.proj.addTransform(projection2, projection1, inverseTransform);
+ });
+ });
+};
+
+
+/**
+ * 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
+ */
+ol.proj.addProjection = function(projection) {
+ ol.proj.projections_[projection.getCode()] = projection;
+ ol.proj.addTransform(projection, projection, ol.proj.cloneTransform);
+};
+
+
+/**
+ * @param {Array.<ol.proj.Projection>} projections Projections.
+ */
+ol.proj.addProjections = function(projections) {
+ var addedProjections = [];
+ projections.forEach(function(projection) {
+ addedProjections.push(ol.proj.addProjection(projection));
+ });
+};
+
+
+/**
+ * FIXME empty description for jsdoc
+ */
+ol.proj.clearAllProjections = function() {
+ ol.proj.projections_ = {};
+ ol.proj.transforms_ = {};
+};
+
+
+/**
+ * @param {ol.proj.Projection|string|undefined} projection Projection.
+ * @param {string} defaultCode Default code.
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.proj.createProjection = function(projection, defaultCode) {
+ if (!projection) {
+ return ol.proj.get(defaultCode);
+ } else if (goog.isString(projection)) {
+ return ol.proj.get(projection);
+ } else {
+ goog.asserts.assertInstanceof(projection, ol.proj.Projection,
+ 'projection should be an ol.proj.Projection');
+ return projection;
+ }
+};
+
+
+/**
+ * Registers a conversion function to convert coordinates from the source
+ * projection to the destination projection.
+ *
+ * @param {ol.proj.Projection} source Source.
+ * @param {ol.proj.Projection} destination Destination.
+ * @param {ol.TransformFunction} transformFn Transform.
+ */
+ol.proj.addTransform = function(source, destination, transformFn) {
+ var sourceCode = source.getCode();
+ var destinationCode = destination.getCode();
+ var transforms = ol.proj.transforms_;
+ if (!(sourceCode in transforms)) {
+ transforms[sourceCode] = {};
+ }
+ transforms[sourceCode][destinationCode] = transformFn;
+};
+
+
+/**
+ * Registers coordinate transform functions to convert coordinates between the
+ * source projection and the destination projection.
+ * The forward and inverse functions convert coordinate pairs; this function
+ * converts these into the functions used internally which also handle
+ * extents and coordinate arrays.
+ *
+ * @param {ol.proj.ProjectionLike} source Source projection.
+ * @param {ol.proj.ProjectionLike} destination Destination projection.
+ * @param {function(ol.Coordinate): ol.Coordinate} forward The forward transform
+ * function (that is, from the source projection to the destination
+ * projection) that takes a {@link ol.Coordinate} as argument and returns
+ * the transformed {@link ol.Coordinate}.
+ * @param {function(ol.Coordinate): ol.Coordinate} inverse The inverse transform
+ * function (that is, from the destination projection to the source
+ * projection) that takes a {@link ol.Coordinate} as argument and returns
+ * the transformed {@link ol.Coordinate}.
+ * @api stable
+ */
+ol.proj.addCoordinateTransforms =
+ function(source, destination, forward, inverse) {
+ var sourceProj = ol.proj.get(source);
+ var destProj = ol.proj.get(destination);
+ ol.proj.addTransform(sourceProj, destProj,
+ ol.proj.createTransformFromCoordinateTransform(forward));
+ ol.proj.addTransform(destProj, sourceProj,
+ ol.proj.createTransformFromCoordinateTransform(inverse));
+};
+
+
+/**
+ * Creates a {@link ol.TransformFunction} from a simple 2D coordinate transform
+ * function.
+ * @param {function(ol.Coordinate): ol.Coordinate} transform Coordinate
+ * transform.
+ * @return {ol.TransformFunction} Transform function.
+ */
+ol.proj.createTransformFromCoordinateTransform = function(transform) {
+ return (
+ /**
+ * @param {Array.<number>} input Input.
+ * @param {Array.<number>=} opt_output Output.
+ * @param {number=} opt_dimension Dimension.
+ * @return {Array.<number>} Output.
+ */
+ function(input, opt_output, opt_dimension) {
+ var length = input.length;
+ var dimension = opt_dimension !== undefined ? opt_dimension : 2;
+ var output = opt_output !== undefined ? opt_output : new Array(length);
+ var point, i, j;
+ for (i = 0; i < length; i += dimension) {
+ point = transform([input[i], input[i + 1]]);
+ output[i] = point[0];
+ output[i + 1] = point[1];
+ for (j = dimension - 1; j >= 2; --j) {
+ output[i + j] = input[i + j];
+ }
+ }
+ return output;
+ });
+};
+
+
+/**
+ * Unregisters the conversion function to convert coordinates from the source
+ * projection to the destination projection. This method is used to clean up
+ * cached transforms during testing.
+ *
+ * @param {ol.proj.Projection} source Source projection.
+ * @param {ol.proj.Projection} destination Destination projection.
+ * @return {ol.TransformFunction} transformFn The unregistered transform.
+ */
+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,
+ '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];
+ if (goog.object.isEmpty(transforms[sourceCode])) {
+ delete transforms[sourceCode];
+ }
+ return transform;
+};
+
+
+/**
+ * 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',
+ opt_projection !== undefined ? 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,
+ opt_projection !== undefined ? opt_projection : 'EPSG:3857', 'EPSG:4326');
+};
+
+
+/**
+ * Fetches a Projection object for the code specified.
+ *
+ * @param {ol.proj.ProjectionLike} projectionLike Either a code string which is
+ * a combination of authority and identifier such as "EPSG:4326", or an
+ * existing projection object, or undefined.
+ * @return {ol.proj.Projection} Projection object, or null if not in list.
+ * @api stable
+ */
+ol.proj.get = function(projectionLike) {
+ var projection;
+ if (projectionLike instanceof ol.proj.Projection) {
+ projection = projectionLike;
+ } else if (goog.isString(projectionLike)) {
+ var code = projectionLike;
+ projection = ol.proj.projections_[code];
+ if (ol.ENABLE_PROJ4JS && projection === undefined &&
+ typeof proj4 == 'function' && proj4.defs(code) !== undefined) {
+ projection = new ol.proj.Projection({code: code});
+ ol.proj.addProjection(projection);
+ }
+ } else {
+ projection = null;
+ }
+ return projection;
+};
+
+
+/**
+ * Checks if two projections are the same, that is every coordinate in one
+ * projection does represent the same geographic point as the same coordinate in
+ * the other projection.
+ *
+ * @param {ol.proj.Projection} projection1 Projection 1.
+ * @param {ol.proj.Projection} projection2 Projection 2.
+ * @return {boolean} Equivalent.
+ */
+ol.proj.equivalent = function(projection1, projection2) {
+ if (projection1 === projection2) {
+ return true;
+ }
+ var equalUnits = projection1.getUnits() === projection2.getUnits();
+ if (projection1.getCode() === projection2.getCode()) {
+ return equalUnits;
+ } else {
+ var transformFn = ol.proj.getTransformFromProjections(
+ projection1, projection2);
+ return transformFn === ol.proj.cloneTransform && equalUnits;
+ }
+};
+
+
+/**
+ * Given the projection-like objects, searches for a transformation
+ * function to convert a coordinates array from the source projection to the
+ * destination projection.
+ *
+ * @param {ol.proj.ProjectionLike} source Source.
+ * @param {ol.proj.ProjectionLike} destination Destination.
+ * @return {ol.TransformFunction} Transform function.
+ * @api stable
+ */
+ol.proj.getTransform = function(source, destination) {
+ var sourceProjection = ol.proj.get(source);
+ var destinationProjection = ol.proj.get(destination);
+ return ol.proj.getTransformFromProjections(
+ sourceProjection, destinationProjection);
+};
+
+
+/**
+ * Searches in the list of transform functions for the function for converting
+ * coordinates from the source projection to the destination projection.
+ *
+ * @param {ol.proj.Projection} sourceProjection Source Projection object.
+ * @param {ol.proj.Projection} destinationProjection Destination Projection
+ * object.
+ * @return {ol.TransformFunction} Transform function.
+ */
+ol.proj.getTransformFromProjections =
+ function(sourceProjection, destinationProjection) {
+ var transforms = ol.proj.transforms_;
+ var sourceCode = sourceProjection.getCode();
+ var destinationCode = destinationProjection.getCode();
+ var transform;
+ if (sourceCode in transforms && destinationCode in transforms[sourceCode]) {
+ transform = transforms[sourceCode][destinationCode];
+ }
+ if (transform === undefined) {
+ goog.asserts.assert(transform !== undefined, 'transform should be defined');
+ transform = ol.proj.identityTransform;
+ }
+ return transform;
+};
+
+
+/**
+ * @param {Array.<number>} input Input coordinate array.
+ * @param {Array.<number>=} opt_output Output array of coordinate values.
+ * @param {number=} opt_dimension Dimension.
+ * @return {Array.<number>} Input coordinate array (same array as input).
+ */
+ol.proj.identityTransform = function(input, opt_output, opt_dimension) {
+ if (opt_output !== undefined && input !== opt_output) {
+ // TODO: consider making this a warning instead
+ goog.asserts.fail('This should not be used internally.');
+ for (var i = 0, ii = input.length; i < ii; ++i) {
+ opt_output[i] = input[i];
+ }
+ input = opt_output;
+ }
+ return input;
+};
+
+
+/**
+ * @param {Array.<number>} input Input coordinate array.
+ * @param {Array.<number>=} opt_output Output array of coordinate values.
+ * @param {number=} opt_dimension Dimension.
+ * @return {Array.<number>} Output coordinate array (new array, same coordinate
+ * values).
+ */
+ol.proj.cloneTransform = function(input, opt_output, opt_dimension) {
+ var output;
+ if (opt_output !== undefined) {
+ for (var i = 0, ii = input.length; i < ii; ++i) {
+ opt_output[i] = input[i];
+ }
+ output = opt_output;
+ } else {
+ output = input.slice();
+ }
+ return output;
+};
+
+
+/**
+ * Transforms a coordinate from source projection to destination projection.
+ * This returns a new coordinate (and does not modify the original).
+ *
+ * See {@link ol.proj.transformExtent} for extent transformation.
+ * See the transform method of {@link ol.geom.Geometry} and its subclasses for
+ * geometry transforms.
+ *
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {ol.proj.ProjectionLike} source Source projection-like.
+ * @param {ol.proj.ProjectionLike} destination Destination projection-like.
+ * @return {ol.Coordinate} Coordinate.
+ * @api stable
+ */
+ol.proj.transform = function(coordinate, source, destination) {
+ var transformFn = ol.proj.getTransform(source, destination);
+ return transformFn(coordinate, undefined, coordinate.length);
+};
+
+
+/**
+ * Transforms an extent from source projection to destination projection. This
+ * returns a new extent (and does not modify the original).
+ *
+ * @param {ol.Extent} extent The extent to transform.
+ * @param {ol.proj.ProjectionLike} source Source projection-like.
+ * @param {ol.proj.ProjectionLike} destination Destination projection-like.
+ * @return {ol.Extent} The transformed extent.
+ * @api stable
+ */
+ol.proj.transformExtent = function(extent, source, destination) {
+ var transformFn = ol.proj.getTransform(source, destination);
+ return ol.extent.applyTransform(extent, transformFn);
+};
+
+
+/**
+ * Transforms the given point to the destination projection.
+ *
+ * @param {ol.Coordinate} point Point.
+ * @param {ol.proj.Projection} sourceProjection Source projection.
+ * @param {ol.proj.Projection} destinationProjection Destination projection.
+ * @return {ol.Coordinate} Point.
+ */
+ol.proj.transformWithProjections =
+ function(point, sourceProjection, destinationProjection) {
+ var transformFn = ol.proj.getTransformFromProjections(
+ sourceProjection, destinationProjection);
+ return transformFn(point);
+};
+
+goog.provide('ol.geom.Geometry');
+goog.provide('ol.geom.GeometryLayout');
+goog.provide('ol.geom.GeometryType');
+
+goog.require('goog.asserts');
+goog.require('goog.functions');
+goog.require('ol.Object');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.proj.Units');
+
+
+/**
+ * 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.
+ *
+ * To get notified of changes to the geometry, register a listener for the
+ * generic `change` event on your geometry instance.
+ *
+ * @constructor
+ * @extends {ol.Object}
+ * @api stable
+ */
+ol.geom.Geometry = function() {
+
+ goog.base(this);
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.extent_ = ol.extent.createEmpty();
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.extentRevision_ = -1;
+
+ /**
+ * @protected
+ * @type {Object.<string, ol.geom.Geometry>}
+ */
+ this.simplifiedGeometryCache = {};
+
+ /**
+ * @protected
+ * @type {number}
+ */
+ this.simplifiedGeometryMaxMinSquaredTolerance = 0;
+
+ /**
+ * @protected
+ * @type {number}
+ */
+ this.simplifiedGeometryRevision = 0;
+
+};
+goog.inherits(ol.geom.Geometry, ol.Object);
+
+
+/**
+ * Make a complete copy of the geometry.
+ * @function
+ * @return {!ol.geom.Geometry} Clone.
+ */
+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;
+
+
+/**
+ * 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.geom.Geometry.prototype.getClosestPoint = function(point, opt_closestPoint) {
+ var closestPoint = opt_closestPoint ? opt_closestPoint : [NaN, NaN];
+ this.closestPointXY(point[0], point[1], closestPoint, Infinity);
+ return closestPoint;
+};
+
+
+/**
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {boolean} Contains coordinate.
+ */
+ol.geom.Geometry.prototype.containsCoordinate = function(coordinate) {
+ return this.containsXY(coordinate[0], coordinate[1]);
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @protected
+ * @return {ol.Extent} extent Extent.
+ */
+ol.geom.Geometry.prototype.computeExtent = goog.abstractMethod;
+
+
+/**
+ * @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.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} extent Extent.
+ * @api stable
+ */
+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);
+};
+
+
+/**
+ * Create a simplified version of this geometry. For linestrings, this uses
+ * the the {@link
+ * https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
+ * Douglas Peucker} algorithm. For polygons, a quantization-based
+ * simplification is used to preserve topology.
+ * @function
+ * @param {number} tolerance The tolerance distance for simplification.
+ * @return {ol.geom.Geometry} A new, simplified version of the original
+ * geometry.
+ * @api
+ */
+ol.geom.Geometry.prototype.simplify = function(tolerance) {
+ return this.getSimplifiedGeometry(tolerance * tolerance);
+};
+
+
+/**
+ * Create a simplified version of this geometry using the Douglas Peucker
+ * algorithm.
+ * @see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_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.
+ */
+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.
+ */
+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
+ */
+ol.geom.Geometry.prototype.intersectsExtent = goog.abstractMethod;
+
+
+/**
+ * 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.geom.Geometry.prototype.translate = goog.abstractMethod;
+
+
+/**
+ * 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) {
+ goog.asserts.assert(
+ ol.proj.get(source).getUnits() !== ol.proj.Units.TILE_PIXELS &&
+ ol.proj.get(destination).getUnits() !== ol.proj.Units.TILE_PIXELS,
+ 'cannot transform geometries with TILE_PIXELS units');
+ this.applyTransform(ol.proj.getTransform(source, destination));
+ return this;
+};
+
+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.
+ */
+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 = 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 (opt_dest && dest.length != i) {
+ dest.length = i;
+ }
+ return dest;
+};
+
+
+/**
+ * @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 = 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 (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');
+
+
+
+/**
+ * @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;
+
+ /**
+ * @protected
+ * @type {number}
+ */
+ this.stride = 2;
+
+ /**
+ * @protected
+ * @type {Array.<number>}
+ */
+ this.flatCoordinates = null;
+
+};
+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.
+ * @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
+ */
+ol.geom.SimpleGeometry.prototype.computeExtent = function(extent) {
+ return ol.extent.createOrUpdateFromFlatCoordinates(
+ this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
+ extent);
+};
+
+
+/**
+ * @return {Array} Coordinates.
+ */
+ol.geom.SimpleGeometry.prototype.getCoordinates = goog.abstractMethod;
+
+
+/**
+ * Return the first coordinate of the geometry.
+ * @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 the last coordinate of the geometry.
+ * @return {ol.Coordinate} Last point.
+ * @api stable
+ */
+ol.geom.SimpleGeometry.prototype.getLastCoordinate = function() {
+ return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride);
+};
+
+
+/**
+ * Return the {@link ol.geom.GeometryLayout layout} of the geometry.
+ * @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 {Array} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ */
+ol.geom.SimpleGeometry.prototype.setCoordinates = goog.abstractMethod;
+
+
+/**
+ * @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 (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 (this.flatCoordinates) {
+ transformFn(this.flatCoordinates, this.flatCoordinates, this.stride);
+ this.changed();
+ }
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.geom.SimpleGeometry.prototype.translate = function(deltaX, deltaY) {
+ var flatCoordinates = this.getFlatCoordinates();
+ if (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 (!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.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, 'maxDelta should be larger than 0');
+ var tmpPoint = 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 = 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 = 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,
+ '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 {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,
+ 'length of coordinate array should match 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 = 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 = 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 = opt_coordinates !== undefined ? 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 = opt_coordinatess !== undefined ? 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 {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 = opt_coordinatesss !== undefined ? 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.
+
+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 = opt_simplifiedFlatCoordinates !== undefined ?
+ 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;
+ }
+ }
+ if (maxSquaredDistance > squaredTolerance) {
+ markers[(index - offset) / stride] = 1;
+ if (first + stride < index) {
+ stack.push(first, index);
+ }
+ if (index + stride < last) {
+ stack.push(index, last);
+ }
+ }
+ }
+ 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.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');
+
+
+
+/**
+ * @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, opt_layout);
+
+};
+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;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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);
+};
+
+
+/**
+ * Return the area of the linear ring on projected plane.
+ * @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 the coordinates of the linear ring.
+ * @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);
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.geom.LinearRing.prototype.getType = function() {
+ return ol.geom.GeometryType.LINEAR_RING;
+};
+
+
+/**
+ * Set the coordinates of the linear ring.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
+ */
+ol.geom.LinearRing.prototype.setCoordinates =
+ function(coordinates, opt_layout) {
+ if (!coordinates) {
+ this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
+ } else {
+ this.setLayout(opt_layout, coordinates, 1);
+ if (!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.LinearRing.prototype.setFlatCoordinates =
+ function(layout, flatCoordinates) {
+ this.setFlatCoordinatesInternal(layout, flatCoordinates);
+ this.changed();
+};
+
+goog.provide('ol.geom.Point');
+
+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');
+
+
+
+/**
+ * @classdesc
+ * Point geometry.
+ *
+ * @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, opt_layout);
+};
+goog.inherits(ol.geom.Point, ol.geom.SimpleGeometry);
+
+
+/**
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.Point} Clone.
+ * @api stable
+ */
+ol.geom.Point.prototype.clone = function() {
+ var point = new ol.geom.Point(null);
+ point.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+ return point;
+};
+
+
+/**
+ * @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;
+ }
+};
+
+
+/**
+ * Return the coordinate of the point.
+ * @return {ol.Coordinate} Coordinates.
+ * @api stable
+ */
+ol.geom.Point.prototype.getCoordinates = function() {
+ return !this.flatCoordinates ? [] : this.flatCoordinates.slice();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.Point.prototype.computeExtent = function(extent) {
+ return ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates, extent);
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.geom.Point.prototype.getType = function() {
+ return ol.geom.GeometryType.POINT;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.geom.Point.prototype.intersectsExtent = function(extent) {
+ return ol.extent.containsXY(extent,
+ this.flatCoordinates[0], this.flatCoordinates[1]);
+};
+
+
+/**
+ * Set the coordinate of the point.
+ * @param {ol.Coordinate} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
+ */
+ol.geom.Point.prototype.setCoordinates = function(coordinates, opt_layout) {
+ if (!coordinates) {
+ this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
+ } else {
+ this.setLayout(opt_layout, coordinates, 0);
+ if (!this.flatCoordinates) {
+ this.flatCoordinates = [];
+ }
+ this.flatCoordinates.length = ol.geom.flat.deflate.coordinate(
+ this.flatCoordinates, 0, coordinates, this.stride);
+ this.changed();
+ }
+};
+
+
+/**
+ * @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('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.
+ */
+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;
+};
+
+
+/**
+ * @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).
+ */
+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;
+};
+
+
+/**
+ * @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).
+ */
+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;
+};
+
+
+/**
+ * @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).
+ */
+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;
+ }
+ 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');
+
+goog.require('goog.asserts');
+goog.require('ol.array');
+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.
+ */
+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(ol.array.numberSafeCompareFunction);
+ 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 (opt_dest) {
+ opt_dest.push(pointX, y);
+ return opt_dest;
+ } else {
+ return [pointX, y];
+ }
+};
+
+
+/**
+ * @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.
+ */
+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');
+
+
+/**
+ * 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
+ */
+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');
+
+goog.require('goog.asserts');
+goog.require('ol.extent');
+goog.require('ol.geom.flat.contains');
+goog.require('ol.geom.flat.segments');
+
+
+/**
+ * @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.
+ */
+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);
+ });
+};
+
+
+/**
+ * @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.
+ */
+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;
+};
+
+
+/**
+ * @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.
+ */
+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;
+ }
+ return false;
+};
+
+
+/**
+ * @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.
+ */
+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;
+};
+
+
+/**
+ * @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.
+ */
+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;
+ }
+ offset = ends[ends.length - 1];
+ }
+ return false;
+};
+
+goog.provide('ol.geom.flat.reverse');
+
+
+/**
+ * @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');
+goog.require('ol.geom.flat.reverse');
+
+
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {boolean} Is clockwise.
+ */
+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;
+};
+
+
+/**
+ * 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.
+ *
+ * @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.
+ */
+ol.geom.flat.orient.linearRingsAreOriented =
+ function(flatCoordinates, offset, ends, stride, opt_right) {
+ var right = opt_right !== undefined ? 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;
+};
+
+
+/**
+ * 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.
+ *
+ * @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.
+ */
+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;
+};
+
+
+/**
+ * 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.
+ */
+ol.geom.flat.orient.orientLinearRings =
+ function(flatCoordinates, offset, ends, stride, opt_right) {
+ var right = opt_right !== undefined ? 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;
+};
+
+
+/**
+ * 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.
+ */
+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');
+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);
+
+ /**
+ * @type {Array.<number>}
+ * @private
+ */
+ this.ends_ = [];
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.flatInteriorPointRevision_ = -1;
+
+ /**
+ * @private
+ * @type {ol.Coordinate}
+ */
+ this.flatInteriorPoint_ = null;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.maxDelta_ = -1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.maxDeltaRevision_ = -1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.orientedRevision_ = -1;
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.orientedFlatCoordinates_ = null;
+
+ this.setCoordinates(coordinates, opt_layout);
+
+};
+goog.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry);
+
+
+/**
+ * Append the passed linear ring to this polygon.
+ * @param {ol.geom.LinearRing} linearRing Linear ring.
+ * @api stable
+ */
+ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) {
+ goog.asserts.assert(linearRing.getLayout() == this.layout,
+ 'layout of linearRing should match layout');
+ if (!this.flatCoordinates) {
+ this.flatCoordinates = linearRing.getFlatCoordinates().slice();
+ } else {
+ goog.array.extend(this.flatCoordinates, linearRing.getFlatCoordinates());
+ }
+ this.ends_.push(this.flatCoordinates.length);
+ this.changed();
+};
+
+
+/**
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.Polygon} Clone.
+ * @api stable
+ */
+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;
+};
+
+
+/**
+ * @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();
+ }
+ return ol.geom.flat.closest.getsClosestPoint(
+ this.flatCoordinates, 0, this.ends_, this.stride,
+ this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.Polygon.prototype.containsXY = function(x, y) {
+ return ol.geom.flat.contains.linearRingsContainsXY(
+ this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y);
+};
+
+
+/**
+ * Return the area of the polygon on projected plane.
+ * @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);
+};
+
+
+/**
+ * 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
+ */
+ol.geom.Polygon.prototype.getCoordinates = function(opt_right) {
+ var flatCoordinates;
+ if (opt_right !== undefined) {
+ 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);
+};
+
+
+/**
+ * @return {Array.<number>} Ends.
+ */
+ol.geom.Polygon.prototype.getEnds = function() {
+ return this.ends_;
+};
+
+
+/**
+ * @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_;
+};
+
+
+/**
+ * Return an interior point of the polygon.
+ * @return {ol.geom.Point} Interior point.
+ * @api stable
+ */
+ol.geom.Polygon.prototype.getInteriorPoint = function() {
+ return new ol.geom.Point(this.getFlatInteriorPoint());
+};
+
+
+/**
+ * Return the number of rings of the polygon, this includes the exterior
+ * ring and any interior rings.
+ *
+ * @return {number} Number of rings.
+ * @api
+ */
+ol.geom.Polygon.prototype.getLinearRingCount = function() {
+ return this.ends_.length;
+};
+
+
+/**
+ * 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
+ */
+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;
+};
+
+
+/**
+ * Return the linear rings of the polygon.
+ * @return {Array.<ol.geom.LinearRing>} Linear rings.
+ * @api stable
+ */
+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;
+};
+
+
+/**
+ * @return {Array.<number>} Oriented flat coordinates.
+ */
+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_;
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.geom.Polygon.prototype.getType = function() {
+ return ol.geom.GeometryType.POLYGON;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.geom.Polygon.prototype.intersectsExtent = function(extent) {
+ return ol.geom.flat.intersectsextent.linearRings(
+ this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent);
+};
+
+
+/**
+ * Set the coordinates of the polygon.
+ * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
+ */
+ol.geom.Polygon.prototype.setCoordinates = function(coordinates, opt_layout) {
+ if (!coordinates) {
+ this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
+ } else {
+ this.setLayout(opt_layout, coordinates, 2);
+ if (!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();
+ }
+};
+
+
+/**
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Array.<number>} ends Ends.
+ */
+ol.geom.Polygon.prototype.setFlatCoordinates =
+ function(layout, flatCoordinates, ends) {
+ if (!flatCoordinates) {
+ goog.asserts.assert(ends && ends.length === 0,
+ 'ends must 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();
+};
+
+
+/**
+ * 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
+ */
+ol.geom.Polygon.circular = function(sphere, center, radius, opt_n) {
+ var n = 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;
+};
+
+
+/**
+ * Create a polygon from an extent. The layout used is `XY`.
+ * @param {ol.Extent} extent The extent.
+ * @return {ol.geom.Polygon} The polygon.
+ * @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;
+};
+
+
+/**
+ * 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
+ */
+ol.geom.Polygon.fromCircle = function(circle, opt_sides, opt_angle) {
+ var sides = 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;
+};
+
+
+/**
+ * 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 = opt_angle ? opt_angle : 0;
+ var angle, 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');
+
+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');
+
+
+/**
+ * @enum {string}
+ */
+ol.ViewProperty = {
+ CENTER: 'center',
+ RESOLUTION: 'resolution',
+ ROTATION: 'rotation'
+};
+
+
+/**
+ * @enum {number}
+ */
+ol.ViewHint = {
+ ANIMATING: 0,
+ INTERACTING: 1
+};
+
+
+
+/**
+ * @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
+ */
+ol.View = function(opt_options) {
+ goog.base(this);
+ var options = opt_options || {};
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.hints_ = [0, 0];
+
+ /**
+ * @type {Object.<string, *>}
+ */
+ var properties = {};
+ properties[ol.ViewProperty.CENTER] = options.center !== undefined ?
+ options.center : null;
+
+ /**
+ * @private
+ * @const
+ * @type {ol.proj.Projection}
+ */
+ this.projection_ = ol.proj.createProjection(options.projection, 'EPSG:3857');
+
+ var resolutionConstraintInfo = ol.View.createResolutionConstraint_(
+ options);
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.maxResolution_ = resolutionConstraintInfo.maxResolution;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.minResolution_ = resolutionConstraintInfo.minResolution;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.minZoom_ = resolutionConstraintInfo.minZoom;
+
+ 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 (options.resolution !== undefined) {
+ properties[ol.ViewProperty.RESOLUTION] = options.resolution;
+ } else if (options.zoom !== undefined) {
+ properties[ol.ViewProperty.RESOLUTION] = this.constrainResolution(
+ this.maxResolution_, options.zoom - this.minZoom_);
+ }
+ properties[ol.ViewProperty.ROTATION] =
+ options.rotation !== undefined ? options.rotation : 0;
+ this.setProperties(properties);
+};
+goog.inherits(ol.View, ol.Object);
+
+
+/**
+ * @param {number} rotation Target rotation.
+ * @param {ol.Coordinate} anchor Rotation anchor.
+ * @return {ol.Coordinate|undefined} Center for rotation and anchor.
+ */
+ol.View.prototype.calculateCenterRotate = function(rotation, anchor) {
+ var center;
+ var currentCenter = this.getCenter();
+ if (currentCenter !== undefined) {
+ center = [currentCenter[0] - anchor[0], currentCenter[1] - anchor[1]];
+ ol.coordinate.rotate(center, rotation - this.getRotation());
+ ol.coordinate.add(center, anchor);
+ }
+ return center;
+};
+
+
+/**
+ * @param {number} resolution Target resolution.
+ * @param {ol.Coordinate} anchor Zoom anchor.
+ * @return {ol.Coordinate|undefined} Center for resolution and anchor.
+ */
+ol.View.prototype.calculateCenterZoom = function(resolution, anchor) {
+ var center;
+ var currentCenter = this.getCenter();
+ var currentResolution = this.getResolution();
+ if (currentCenter !== undefined && currentResolution !== undefined) {
+ 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;
+};
+
+
+/**
+ * Get the constrained center of this view.
+ * @param {ol.Coordinate|undefined} center Center.
+ * @return {ol.Coordinate|undefined} Constrained center.
+ * @api
+ */
+ol.View.prototype.constrainCenter = function(center) {
+ return this.constraints_.center(center);
+};
+
+
+/**
+ * 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
+ */
+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);
+};
+
+
+/**
+ * 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
+ */
+ol.View.prototype.constrainRotation = function(rotation, opt_delta) {
+ var delta = opt_delta || 0;
+ return this.constraints_.rotation(rotation, delta);
+};
+
+
+/**
+ * Get the view center.
+ * @return {ol.Coordinate|undefined} The center of the view.
+ * @observable
+ * @api stable
+ */
+ol.View.prototype.getCenter = function() {
+ return /** @type {ol.Coordinate|undefined} */ (
+ this.get(ol.ViewProperty.CENTER));
+};
+
+
+/**
+ * @return {Array.<number>} Hint.
+ */
+ol.View.prototype.getHints = function() {
+ return this.hints_.slice();
+};
+
+
+/**
+ * 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
+ */
+ol.View.prototype.calculateExtent = function(size) {
+ var center = this.getCenter();
+ goog.asserts.assert(center, 'The view center is not defined');
+ var resolution = this.getResolution();
+ goog.asserts.assert(resolution !== undefined,
+ 'The view resolution is not defined');
+ var rotation = this.getRotation();
+ goog.asserts.assert(rotation !== undefined,
+ 'The view rotation is not defined');
+
+ return ol.extent.getForViewAndSize(center, resolution, rotation, size);
+};
+
+
+/**
+ * Get the view projection.
+ * @return {ol.proj.Projection} The projection of the view.
+ * @api stable
+ */
+ol.View.prototype.getProjection = function() {
+ return this.projection_;
+};
+
+
+/**
+ * Get the view resolution.
+ * @return {number|undefined} The resolution of the view.
+ * @observable
+ * @api stable
+ */
+ol.View.prototype.getResolution = function() {
+ return /** @type {number|undefined} */ (
+ this.get(ol.ViewProperty.RESOLUTION));
+};
+
+
+/**
+ * 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.
+ */
+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);
+};
+
+
+/**
+ * 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.
+ */
+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;
+ });
+};
+
+
+/**
+ * Get the view rotation.
+ * @return {number} The rotation of the view in radians.
+ * @observable
+ * @api stable
+ */
+ol.View.prototype.getRotation = function() {
+ return /** @type {number} */ (this.get(ol.ViewProperty.ROTATION));
+};
+
+
+/**
+ * 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.
+ */
+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;
+ });
+};
+
+
+/**
+ * @return {olx.ViewState} View state.
+ */
+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: projection !== undefined ? projection : null,
+ resolution: resolution,
+ rotation: rotation
+ });
+};
+
+
+/**
+ * Get the current zoom level. Return undefined if the current
+ * resolution is undefined or not a "constrained resolution".
+ * @return {number|undefined} Zoom.
+ * @api stable
+ */
+ol.View.prototype.getZoom = function() {
+ var offset;
+ var resolution = this.getResolution();
+
+ if (resolution !== undefined) {
+ var res, z = 0;
+ do {
+ res = this.constrainResolution(this.maxResolution_, z);
+ if (res == resolution) {
+ offset = z;
+ break;
+ }
+ ++z;
+ } while (res > this.minResolution_);
+ }
+
+ return offset !== undefined ? this.minZoom_ + offset : offset;
+};
+
+
+/**
+ * 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
+ */
+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 = opt_options || {};
+
+ var padding = options.padding !== undefined ? options.padding : [0, 0, 0, 0];
+ var constrainResolution = options.constrainResolution !== undefined ?
+ options.constrainResolution : true;
+ var nearest = options.nearest !== undefined ? options.nearest : false;
+ var minResolution;
+ if (options.minResolution !== undefined) {
+ minResolution = options.minResolution;
+ } else if (options.maxZoom !== undefined) {
+ 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(rotation !== undefined, '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]);
+};
+
+
+/**
+ * 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
+ */
+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]);
+};
+
+
+/**
+ * @return {boolean} Is defined.
+ */
+ol.View.prototype.isDef = function() {
+ return !!this.getCenter() && this.getResolution() !== undefined;
+};
+
+
+/**
+ * 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
+ */
+ol.View.prototype.rotate = function(rotation, opt_anchor) {
+ if (opt_anchor !== undefined) {
+ var center = this.calculateCenterRotate(rotation, opt_anchor);
+ this.setCenter(center);
+ }
+ this.setRotation(rotation);
+};
+
+
+/**
+ * Set the center of the current view.
+ * @param {ol.Coordinate|undefined} center The center of the view.
+ * @observable
+ * @api stable
+ */
+ol.View.prototype.setCenter = function(center) {
+ this.set(ol.ViewProperty.CENTER, center);
+};
+
+
+/**
+ * @param {ol.ViewHint} hint Hint.
+ * @param {number} delta Delta.
+ * @return {number} New value.
+ */
+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];
+};
+
+
+/**
+ * Set the resolution for this view.
+ * @param {number|undefined} resolution The resolution of the view.
+ * @observable
+ * @api stable
+ */
+ol.View.prototype.setResolution = function(resolution) {
+ this.set(ol.ViewProperty.RESOLUTION, resolution);
+};
+
+
+/**
+ * Set the rotation for this view.
+ * @param {number} rotation The rotation of the view in radians.
+ * @observable
+ * @api stable
+ */
+ol.View.prototype.setRotation = function(rotation) {
+ this.set(ol.ViewProperty.ROTATION, rotation);
+};
+
+
+/**
+ * Zoom to a specific zoom level.
+ * @param {number} zoom Zoom level.
+ * @api stable
+ */
+ol.View.prototype.setZoom = function(zoom) {
+ var resolution = this.constrainResolution(
+ this.maxResolution_, zoom - this.minZoom_, 0);
+ this.setResolution(resolution);
+};
+
+
+/**
+ * @param {olx.ViewOptions} options View options.
+ * @private
+ * @return {ol.CenterConstraintType}
+ */
+ol.View.createCenterConstraint_ = function(options) {
+ if (options.extent !== undefined) {
+ return ol.CenterConstraint.createExtent(options.extent);
+ } else {
+ return ol.CenterConstraint.none;
+ }
+};
+
+
+/**
+ * @private
+ * @param {olx.ViewOptions} options View options.
+ * @return {{constraint: ol.ResolutionConstraintType, maxResolution: number,
+ * minResolution: number}}
+ */
+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 = options.minZoom !== undefined ?
+ options.minZoom : ol.DEFAULT_MIN_ZOOM;
+
+ var maxZoom = options.maxZoom !== undefined ?
+ options.maxZoom : defaultMaxZoom;
+
+ var zoomFactor = options.zoomFactor !== undefined ?
+ options.zoomFactor : defaultZoomFactor;
+
+ if (options.resolutions !== undefined) {
+ 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 = !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 (maxResolution !== undefined) {
+ minZoom = 0;
+ } else {
+ maxResolution = defaultMaxResolution / Math.pow(zoomFactor, minZoom);
+ }
+
+ // user provided minResolution takes precedence
+ minResolution = options.minResolution;
+ if (minResolution === undefined) {
+ if (options.maxZoom !== undefined) {
+ if (options.maxResolution !== undefined) {
+ 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};
+};
+
+
+/**
+ * @private
+ * @param {olx.ViewOptions} options View options.
+ * @return {ol.RotationConstraintType} Rotation constraint.
+ */
+ol.View.createRotationConstraint_ = function(options) {
+ var enableRotation = options.enableRotation !== undefined ?
+ options.enableRotation : true;
+ if (enableRotation) {
+ var constrainRotation = options.constrainRotation;
+ if (constrainRotation === undefined || 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;
+ }
+};
+
+goog.provide('ol.easing');
+
+
+/**
+ * Start slow and speed up.
+ * @param {number} t Input between 0 and 1.
+ * @return {number} Output between 0 and 1.
+ * @api
+ */
+ol.easing.easeIn = function(t) {
+ return Math.pow(t, 3);
+};
+
+
+/**
+ * Start fast and slow down.
+ * @param {number} t Input between 0 and 1.
+ * @return {number} Output between 0 and 1.
+ * @api
+ */
+ol.easing.easeOut = function(t) {
+ return 1 - ol.easing.easeIn(1 - t);
+};
+
+
+/**
+ * Start slow, speed up, and then slow down again.
+ * @param {number} t Input between 0 and 1.
+ * @return {number} Output between 0 and 1.
+ * @api
+ */
+ol.easing.inAndOut = function(t) {
+ return 3 * t * t - 2 * t * t * t;
+};
+
+
+/**
+ * Maintain a constant speed over time.
+ * @param {number} t Input between 0 and 1.
+ * @return {number} Output between 0 and 1.
+ * @api
+ */
+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');
+goog.require('ol.PreRenderFunction');
+goog.require('ol.ViewHint');
+goog.require('ol.coordinate');
+goog.require('ol.easing');
+
+
+/**
+ * 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
+ */
+ol.animation.bounce = function(options) {
+ var resolution = options.resolution;
+ var start = options.start ? options.start : Date.now();
+ var duration = options.duration !== undefined ? options.duration : 1000;
+ var easing = 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;
+ }
+ });
+};
+
+
+/**
+ * Generate an animated transition while updating the view center.
+ * @param {olx.animation.PanOptions} options Pan options.
+ * @return {ol.PreRenderFunction} Pre-render function.
+ * @api
+ */
+ol.animation.pan = function(options) {
+ var source = options.source;
+ var start = options.start ? options.start : Date.now();
+ var sourceX = source[0];
+ var sourceY = source[1];
+ var duration = options.duration !== undefined ? options.duration : 1000;
+ var easing = 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;
+ }
+ });
+};
+
+
+/**
+ * Generate an animated transition while updating the view rotation.
+ * @param {olx.animation.RotateOptions} options Rotate options.
+ * @return {ol.PreRenderFunction} Pre-render function.
+ * @api
+ */
+ol.animation.rotate = function(options) {
+ var sourceRotation = options.rotation ? options.rotation : 0;
+ var start = options.start ? options.start : Date.now();
+ var duration = options.duration !== undefined ? options.duration : 1000;
+ var easing = options.easing ?
+ options.easing : ol.easing.inAndOut;
+ var anchor = 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 (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;
+ }
+ });
+};
+
+
+/**
+ * Generate an animated transition while updating the view resolution.
+ * @param {olx.animation.ZoomOptions} options Zoom options.
+ * @return {ol.PreRenderFunction} Pre-render function.
+ * @api
+ */
+ol.animation.zoom = function(options) {
+ var sourceResolution = options.resolution;
+ var start = options.start ? options.start : Date.now();
+ var duration = options.duration !== undefined ? options.duration : 1000;
+ var easing = 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.asserts');
+goog.require('ol.extent');
+
+
+/**
+ * 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
+ */
+ol.TileCoord;
+
+
+/**
+ * @enum {number}
+ */
+ol.QuadKeyCharCode = {
+ ZERO: '0'.charCodeAt(0),
+ ONE: '1'.charCodeAt(0),
+ TWO: '2'.charCodeAt(0),
+ THREE: '3'.charCodeAt(0)
+};
+
+
+/**
+ * @param {string} str String that follows pattern “z/x/y” where x, y and z are
+ * numbers.
+ * @return {ol.TileCoord} Tile coord.
+ */
+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);
+ return v.map(function(e) {
+ return parseInt(e, 10);
+ });
+};
+
+
+/**
+ * @param {number} z Z.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @param {ol.TileCoord=} opt_tileCoord Tile coordinate.
+ * @return {ol.TileCoord} Tile coordinate.
+ */
+ol.tilecoord.createOrUpdate = function(z, x, y, opt_tileCoord) {
+ if (opt_tileCoord !== undefined) {
+ opt_tileCoord[0] = z;
+ opt_tileCoord[1] = x;
+ opt_tileCoord[2] = y;
+ return opt_tileCoord;
+ } else {
+ return [z, x, y];
+ }
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * @param {ol.TileCoord} tileCoord Tile coord.
+ * @return {number} Hash.
+ */
+ol.tilecoord.hash = function(tileCoord) {
+ return (tileCoord[1] << tileCoord[0]) + tileCoord[2];
+};
+
+
+/**
+ * @param {ol.TileCoord} tileCoord Tile coord.
+ * @return {string} Quad key.
+ */
+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 (!extent) {
+ tileRange = tileGrid.getFullTileRange(z);
+ } else {
+ tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
+ }
+ if (!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.minX = minX;
+
+ /**
+ * @type {number}
+ */
+ this.maxX = maxX;
+
+ /**
+ * @type {number}
+ */
+ this.minY = minY;
+
+ /**
+ * @type {number}
+ */
+ this.maxY = maxY;
+
+};
+
+
+/**
+ * @param {...ol.TileCoord} var_args Tile coordinates.
+ * @return {!ol.TileRange} Bounding tile box.
+ */
+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;
+};
+
+
+/**
+ * @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.
+ */
+ol.TileRange.createOrUpdate = function(minX, maxX, minY, maxY, tileRange) {
+ if (tileRange !== undefined) {
+ tileRange.minX = minX;
+ tileRange.maxX = maxX;
+ tileRange.minY = minY;
+ tileRange.maxY = maxY;
+ return tileRange;
+ } else {
+ return new ol.TileRange(minX, maxX, minY, maxY);
+ }
+};
+
+
+/**
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @return {boolean} Contains tile coordinate.
+ */
+ol.TileRange.prototype.contains = function(tileCoord) {
+ return this.containsXY(tileCoord[1], tileCoord[2]);
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * @param {ol.TileRange} tileRange Tile range.
+ * @return {boolean} Equals.
+ */
+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;
+ }
+};
+
+
+/**
+ * @return {number} Height.
+ */
+ol.TileRange.prototype.getHeight = function() {
+ return this.maxY - this.minY + 1;
+};
+
+
+/**
+ * @return {ol.Size} Size.
+ */
+ol.TileRange.prototype.getSize = function() {
+ return [this.getWidth(), this.getHeight()];
+};
+
+
+/**
+ * @return {number} Width.
+ */
+ol.TileRange.prototype.getWidth = function() {
+ return this.maxX - this.minX + 1;
+};
+
+
+/**
+ * @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('ol.Attribution');
+
+goog.require('goog.math');
+goog.require('ol.TileRange');
+
+
+
+/**
+ * @classdesc
+ * An attribution for a layer source.
+ *
+ * Example:
+ *
+ * source: new ol.source.OSM({
+ * attributions: [
+ * new ol.Attribution({
+ * html: 'All maps &copy; ' +
+ * '<a href="http://www.opencyclemap.org/">OpenCycleMap</a>'
+ * }),
+ * ol.source.OSM.ATTRIBUTION
+ * ],
+ * ..
+ *
+ * @constructor
+ * @param {olx.AttributionOptions} options Attribution options.
+ * @struct
+ * @api stable
+ */
+ol.Attribution = function(options) {
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.html_ = options.html;
+
+ /**
+ * @private
+ * @type {Object.<string, Array.<ol.TileRange>>}
+ */
+ this.tileRanges_ = options.tileRanges ? options.tileRanges : null;
+
+};
+
+
+/**
+ * Get the attribution markup.
+ * @return {string} The attribution HTML.
+ * @api stable
+ */
+ol.Attribution.prototype.getHTML = function() {
+ return this.html_;
+};
+
+
+/**
+ * @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.
+ */
+ol.Attribution.prototype.intersectsAnyTileRange =
+ function(tileRanges, tileGrid, projection) {
+ if (!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');
+
+
+/**
+ * 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
+ */
+ol.CanvasFunctionType;
+
+/**
+ * An implementation of Google Maps' MVCArray.
+ * @see https://developers.google.com/maps/documentation/javascript/reference
+ */
+
+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 = {
+ /**
+ * 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'
+};
+
+
+
+/**
+ * @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.
+ */
+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);
+
+
+/**
+ * @enum {string}
+ */
+ol.CollectionProperty = {
+ LENGTH: 'length'
+};
+
+
+
+/**
+ * @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.
+ *
+ * @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);
+
+ /**
+ * @private
+ * @type {!Array.<T>}
+ */
+ this.array_ = opt_array ? opt_array : [];
+
+ this.updateLength_();
+
+};
+goog.inherits(ol.Collection, ol.Object);
+
+
+/**
+ * Remove all elements from the collection.
+ * @api stable
+ */
+ol.Collection.prototype.clear = function() {
+ while (this.getLength() > 0) {
+ this.pop();
+ }
+};
+
+
+/**
+ * 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
+ */
+ol.Collection.prototype.extend = function(arr) {
+ var i, ii;
+ for (i = 0, ii = arr.length; i < ii; ++i) {
+ this.push(arr[i]);
+ }
+ return this;
+};
+
+
+/**
+ * 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
+ */
+ol.Collection.prototype.forEach = function(f, opt_this) {
+ this.array_.forEach(f, opt_this);
+};
+
+
+/**
+ * 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
+ */
+ol.Collection.prototype.getArray = function() {
+ return this.array_;
+};
+
+
+/**
+ * Get the element at the provided index.
+ * @param {number} index Index.
+ * @return {T} Element.
+ * @api stable
+ */
+ol.Collection.prototype.item = function(index) {
+ return this.array_[index];
+};
+
+
+/**
+ * Get the length of this collection.
+ * @return {number} The length of the array.
+ * @observable
+ * @api stable
+ */
+ol.Collection.prototype.getLength = function() {
+ return /** @type {number} */ (this.get(ol.CollectionProperty.LENGTH));
+};
+
+
+/**
+ * Insert an element at the provided index.
+ * @param {number} index Index.
+ * @param {T} elem Element.
+ * @api stable
+ */
+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));
+};
+
+
+/**
+ * Remove the last element of the collection and return it.
+ * Return `undefined` if the collection is empty.
+ * @return {T|undefined} Element.
+ * @api stable
+ */
+ol.Collection.prototype.pop = function() {
+ return this.removeAt(this.getLength() - 1);
+};
+
+
+/**
+ * Insert the provided element at the end of the collection.
+ * @param {T} elem Element.
+ * @return {number} Length.
+ * @api stable
+ */
+ol.Collection.prototype.push = function(elem) {
+ var n = this.array_.length;
+ this.insertAt(n, elem);
+ return n;
+};
+
+
+/**
+ * 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
+ */
+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;
+};
+
+
+/**
+ * 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
+ */
+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;
+};
+
+
+/**
+ * Set the element at the provided index.
+ * @param {number} index Index.
+ * @param {T} elem Element.
+ * @api stable
+ */
+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);
+ }
+};
+
+
+/**
+ * @private
+ */
+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.
+
+/**
+ * @fileoverview Names of standard colors with their associated hex values.
+ */
+
+goog.provide('goog.color.names');
+
+
+/**
+ * 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.
+ *
+ * @type {!Object<string, string>}
+ */
+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'
+};
+
+// 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.
+ */
+
+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');
+
+
+/**
+ * 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.color.Rgb;
+
+
+/**
+ * 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.color.Hsv;
+
+
+/**
+ * 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.color.Hsl;
+
+
+/**
+ * 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.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');
+};
+
+
+/**
+ * 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.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()]);
+};
+
+
+/**
+ * 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.color.parseRgb = function(str) {
+ var rgb = goog.color.isValidRgbColor_(str);
+ if (!rgb.length) {
+ throw Error(str + ' is not a valid RGB color');
+ }
+ return rgb;
+};
+
+
+/**
+ * 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.color.hexToRgbStyle = function(hexColor) {
+ return goog.color.rgbStyle_(goog.color.hexToRgb(hexColor));
+};
+
+
+/**
+ * Regular expression for extracting the digits in a hex color triplet.
+ * @type {RegExp}
+ * @private
+ */
+goog.color.hexTripletRe_ = /#(.)(.)(.)/;
+
+
+/**
+ * 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.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();
+};
+
+
+/**
+ * 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.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];
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.color.rgbArrayToHex = function(rgb) {
+ return goog.color.rgbToHex(rgb[0], rgb[1], rgb[2]);
+};
+
+
+/**
+ * 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.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];
+};
+
+
+/**
+ * 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.color.rgbArrayToHsl = function(rgb) {
+ return goog.color.rgbToHsl(rgb[0], rgb[1], rgb[2]);
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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 (s == 0) {
+ r = g = b = l * 255;
+ } else {
+ 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 [Math.round(r), Math.round(g), Math.round(b)];
+};
+
+
+/**
+ * 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]);
+};
+
+
+/**
+ * Helper for isValidHexColor_.
+ * @type {RegExp}
+ * @private
+ */
+goog.color.validHexColorRe_ = /^#(?:[0-9a-f]{3}){1,2}$/i;
+
+
+/**
+ * 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.color.isValidHexColor_ = function(str) {
+ return goog.color.validHexColorRe_.test(str);
+};
+
+
+/**
+ * Helper for isNormalizedHexColor_.
+ * @type {RegExp}
+ * @private
+ */
+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);
+};
+
+
+/**
+ * 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
+ */
+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 [];
+};
+
+
+/**
+ * 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.color.prependZeroIfNecessaryHelper = function(hex) {
+ return hex.length == 1 ? '0' + hex : hex;
+};
+
+
+/**
+ * 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.color.prependHashIfNecessaryHelper = function(str) {
+ return str.charAt(0) == '#' ? str : '#' + str;
+};
+
+
+/**
+ * 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.color.rgbStyle_ = function(rgb) {
+ return 'rgb(' + rgb.join(',') + ')';
+};
+
+
+/**
+ * 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;
+ }
+ }
+
+ 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);
+ } else {
+ hue = 4 + ((red - green) / delta);
+ }
+ hue *= 60;
+ if (hue < 0) {
+ hue += 360;
+ }
+ if (hue > 360) {
+ hue -= 360;
+ }
+ }
+
+ return [hue, saturation, value];
+};
+
+
+/**
+ * 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.color.rgbArrayToHsv = function(rgb) {
+ return goog.color.rgbToHsv(rgb[0], rgb[1], rgb[2]);
+};
+
+
+/**
+ * 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.color.hsvArrayToRgb = function(hsv) {
+ return goog.color.hsvToRgb(hsv[0], hsv[1], hsv[2]);
+};
+
+
+/**
+ * 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]);
+};
+
+
+/**
+ * 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.color.hslToHex = function(h, s, l) {
+ return goog.color.rgbArrayToHex(goog.color.hslToRgb(h, s, l));
+};
+
+
+/**
+ * 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.color.hslArrayToHex = function(hsl) {
+ return goog.color.rgbArrayToHex(goog.color.hslToRgb(hsl[0], hsl[1], hsl[2]));
+};
+
+
+/**
+ * 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.color.hexToHsv = function(hex) {
+ return goog.color.rgbArrayToHsv(goog.color.hexToRgb(hex));
+};
+
+
+/**
+ * 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.color.hsvToHex = function(h, s, v) {
+ return goog.color.rgbArrayToHex(goog.color.hsvToRgb(h, s, v));
+};
+
+
+/**
+ * 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.color.hsvArrayToHex = function(hsv) {
+ return goog.color.hsvToHex(hsv[0], hsv[1], hsv[2]);
+};
+
+
+/**
+ * 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]);
+ }
+
+ 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);
+};
+
+
+/**
+ * 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.color.blend = function(rgb1, rgb2, factor) {
+ factor = goog.math.clamp(factor, 0, 1);
+
+ 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])
+ ];
+};
+
+
+/**
+ * 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.color.darken = function(rgb, factor) {
+ var black = [0, 0, 0];
+ return goog.color.blend(black, rgb, factor);
+};
+
+
+/**
+ * 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.color.lighten = function(rgb, factor) {
+ var white = [255, 255, 255];
+ return goog.color.blend(white, rgb, factor);
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.color.yiqBrightness_ = function(rgb) {
+ return Math.round((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000);
+};
+
+
+/**
+ * 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.color.yiqBrightnessDiff_ = function(rgb1, rgb2) {
+ return Math.abs(goog.color.yiqBrightness_(rgb1) -
+ goog.color.yiqBrightness_(rgb2));
+};
+
+
+/**
+ * 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.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.vec.Mat4');
+goog.require('ol');
+goog.require('ol.math');
+
+
+/**
+ * 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
+ */
+ol.Color;
+
+
+/**
+ * This RegExp matches # followed by 3 or 6 hex digits.
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+ol.color.hexColorRe_ = /^#(?:[0-9a-f]{3}){1,2}$/i;
+
+
+/**
+ * @see goog.color.rgbColorRe_
+ * @const
+ * @type {RegExp}
+ * @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;
+
+
+/**
+ * @see goog.color.alpha.rgbaColorRe_
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+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;
+
+
+/**
+ * @param {ol.Color} dst Destination.
+ * @param {ol.Color} src Source.
+ * @param {ol.Color=} opt_color Color.
+ * @return {ol.Color} Color.
+ */
+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 = 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;
+ }
+ }
+ goog.asserts.assert(ol.color.isValid(out),
+ 'Output color of blend should be a valid color');
+ return out;
+};
+
+
+/**
+ * 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
+ */
+ol.color.asArray = function(color) {
+ if (goog.isArray(color)) {
+ return color;
+ } else {
+ goog.asserts.assert(goog.isString(color), 'Color should be a string');
+ return ol.color.fromString(color);
+ }
+};
+
+
+/**
+ * Return the color as an rgba string.
+ * @param {ol.Color|string} color Color.
+ * @return {string} Rgba string.
+ * @api
+ */
+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);
+ }
+};
+
+
+/**
+ * @param {ol.Color} color1 Color1.
+ * @param {ol.Color} color2 Color2.
+ * @return {boolean} Equals.
+ */
+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]);
+};
+
+
+/**
+ * @param {string} s String.
+ * @return {ol.Color} Color.
+ */
+ol.color.fromString = (
+ /**
+ * @return {function(string): ol.Color}
+ */
+ function() {
+
+ // 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;
+
+ /**
+ * @type {Object.<string, ol.Color>}
+ */
+ var cache = {};
+
+ /**
+ * @type {number}
+ */
+ var cacheSize = 0;
+
+ 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;
+ });
+
+ })();
+
+
+/**
+ * @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)) {
+ s = 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;
+ }
+ 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 {
+ goog.asserts.fail(s + ' is not a valid color');
+ }
+
+};
+
+
+/**
+ * @param {ol.Color} color Color.
+ * @return {boolean} Is valid.
+ */
+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;
+};
+
+
+/**
+ * @param {ol.Color} color Color.
+ * @param {ol.Color=} opt_color Color.
+ * @return {ol.Color} Clamped color.
+ */
+ol.color.normalize = function(color, opt_color) {
+ var result = opt_color || [];
+ result[0] = ol.math.clamp((color[0] + 0.5) | 0, 0, 255);
+ result[1] = ol.math.clamp((color[1] + 0.5) | 0, 0, 255);
+ result[2] = ol.math.clamp((color[2] + 0.5) | 0, 0, 255);
+ result[3] = ol.math.clamp(color[3], 0, 1);
+ return result;
+};
+
+
+/**
+ * @param {ol.Color} color Color.
+ * @return {string} String.
+ */
+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 + ')';
+};
+
+
+/**
+ * @param {!ol.Color} color Color.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @param {!ol.Color=} opt_color Color.
+ * @return {ol.Color} Transformed color.
+ */
+ol.color.transform = function(color, transform, opt_color) {
+ var result = 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);
+};
+
+
+/**
+ * @param {ol.Color|string} color1 Color2.
+ * @param {ol.Color|string} color2 Color2.
+ * @return {boolean} Equals.
+ */
+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);
+};
+
+// 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('goog.dom.BrowserFeature');
+
+goog.require('goog.userAgent');
+
+
+/**
+ * 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),
+
+ /**
+ * 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'),
+
+ /**
+ * 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')),
+
+ /**
+ * 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,
+
+ /**
+ * 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,
+
+ /**
+ * 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.
+
+/**
+ * @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.provide('goog.dom.TagName');
+
+
+/**
+ * Enum of all html tag names specified by the W3C HTML4.01 and HTML5
+ * specifications.
+ * @enum {string}
+ */
+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',
+ TEMPLATE: 'TEMPLATE',
+ 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.
+
+/**
+ * @fileoverview Utilities for HTML element tag names.
+ */
+goog.provide('goog.dom.tags');
+
+goog.require('goog.object');
+
+
+/**
+ * The void elements specified by
+ * http://www.w3.org/TR/html-markup/syntax.html#void-elements.
+ * @const @private {!Object<string, boolean>}
+ */
+goog.dom.tags.VOID_TAGS_ = goog.object.createSet(
+ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input',
+ 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr');
+
+
+/**
+ * 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.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');
+
+
+
+/**
+ * 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.string.TypedString = function() {};
+
+
+/**
+ * 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.string.TypedString.prototype.implementsGoogStringTypedString;
+
+
+/**
+ * Retrieves this wrapped string's value.
+ * @return {!string} The wrapped string's value.
+ */
+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');
+
+
+
+/**
+ * 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.
+ *
+ * @see goog.string.Const#from
+ * @constructor
+ * @final
+ * @struct
+ * @implements {goog.string.TypedString}
+ */
+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_;
+};
+
+
+/**
+ * @override
+ * @const
+ */
+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 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.string.Const.prototype.toString = function() {
+ return 'Const{' +
+ this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ +
+ '}';
+};
+
+
+/**
+ * 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.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';
+ }
+};
+
+
+/**
+ * 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(xtof): 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.
+ */
+goog.string.Const.from = function(s) {
+ return goog.string.Const.create__googStringSecurityPrivate_(s);
+};
+
+
+/**
+ * Type marker for the Const type, used to implement additional run-time
+ * type checking.
+ * @const {!Object}
+ * @private
+ */
+goog.string.Const.TYPE_MARKER_ = {};
+
+
+/**
+ * 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.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.
+
+/**
+ * @fileoverview The SafeStyle type and its builders.
+ *
+ * TODO(xtof): Link to document stating type contract.
+ */
+
+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');
+
+
+
+/**
+ * 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 #getTypedStringValue()}) 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 &lt;style&gt; tag (where it can't
+ * be HTML escaped). For example, if the SafeStyle containing
+ * "{@code font: 'foo &lt;style/&gt;&lt;script&gt;evil&lt;/script&gt;'}" were
+ * interpolated within a &lt;style&gt; 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.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_;
+};
+
+
+/**
+ * @override
+ * @const
+ */
+goog.html.SafeStyle.prototype.implementsGoogStringTypedString = true;
+
+
+/**
+ * Type marker for the SafeStyle type, used to implement additional
+ * run-time type checking.
+ * @const {!Object}
+ * @private
+ */
+goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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_;
+};
+
+
+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}.
+ */
+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';
+ }
+};
+
+
+/**
+ * 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.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse =
+ function(style) {
+ return new goog.html.SafeStyle().initSecurityPrivateDoNotAccessOrElse_(style);
+};
+
+
+/**
+ * Called from createSafeStyleSecurityPrivateDoNotAccessOrElse(). This
+ * method exists only so that the compiler can dead code eliminate static
+ * fields (like EMPTY) when they're not accessed.
+ * @param {string} style
+ * @return {!goog.html.SafeStyle}
+ * @private
+ */
+goog.html.SafeStyle.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
+ style) {
+ this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = style;
+ return this;
+};
+
+
+/**
+ * A SafeStyle instance corresponding to the empty string.
+ * @const {!goog.html.SafeStyle}
+ */
+goog.html.SafeStyle.EMPTY =
+ goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse('');
+
+
+/**
+ * The innocuous string generated by goog.html.SafeUrl.create when passed
+ * an unsafe value.
+ * @const {string}
+ */
+goog.html.SafeStyle.INNOCUOUS_STRING = 'zClosurez';
+
+
+/**
+ * Mapping of property names to their values.
+ * @typedef {!Object<string, goog.string.Const|string>}
+ */
+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], where " and ' must be properly balanced.
+ * 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;
+ } else if (!goog.html.SafeStyle.hasBalancedQuotes_(value)) {
+ goog.asserts.fail('String value requires balanced quotes, got: ' + value);
+ value = goog.html.SafeStyle.INNOCUOUS_STRING;
+ }
+ style += name + ':' + value + ';';
+ }
+ if (!style) {
+ return goog.html.SafeStyle.EMPTY;
+ }
+ goog.html.SafeStyle.checkStyle_(style);
+ return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
+ style);
+};
+
+
+/**
+ * Checks that quotes (" and ') are properly balanced inside a string. Assumes
+ * that neither escape (\) nor any other character that could result in
+ * breaking out of a string parsing context are allowed;
+ * see http://www.w3.org/TR/css3-syntax/#string-token-diagram.
+ * @param {string} value Untrusted CSS property value.
+ * @return {boolean} True if property value is safe with respect to quote
+ * balancedness.
+ * @private
+ */
+goog.html.SafeStyle.hasBalancedQuotes_ = function(value) {
+ var outsideSingle = true;
+ var outsideDouble = true;
+ for (var i = 0; i < value.length; i++) {
+ var c = value.charAt(i);
+ if (c == "'" && outsideDouble) {
+ outsideSingle = !outsideSingle;
+ } else if (c == '"' && outsideSingle) {
+ outsideDouble = !outsideDouble;
+ }
+ }
+ return outsideSingle && outsideDouble;
+};
+
+
+// Keep in sync with the error string in create().
+/**
+ * Regular expression for safe values.
+ *
+ * Quotes (" and ') are allowed, but a check must be done elsewhere to ensure
+ * they're balanced.
+ *
+ * ',' allows multiple values to be assigned to the same property
+ * (e.g. background-attachment or font-family) and hence could allow
+ * multiple values to get injected, but that should pose no risk of XSS.
+ * @const {!RegExp}
+ * @private
+ */
+goog.html.SafeStyle.VALUE_RE_ = /^[-,."'%_!# a-zA-Z0-9]+$/;
+
+
+/**
+ * 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.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);
+ }
+ };
+
+ goog.array.forEach(arguments, addArgument);
+ if (!style) {
+ return goog.html.SafeStyle.EMPTY;
+ }
+ 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.
+
+/**
+ * @fileoverview The SafeStyleSheet type and its builders.
+ *
+ * TODO(xtof): Link to document stating type contract.
+ */
+
+goog.provide('goog.html.SafeStyleSheet');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.string');
+goog.require('goog.string.Const');
+goog.require('goog.string.TypedString');
+
+
+
+/**
+ * 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 &lt;} is dangerous, even when inside CSS strings, and so should
+ * always be forbidden or CSS-escaped in user controlled input. For example, if
+ * {@code &lt;/style&gt;&lt;script&gt;evil&lt;/script&gt;"} 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 &amp;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_ = '';
+
+ /**
+ * 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_;
+};
+
+
+/**
+ * @override
+ * @const
+ */
+goog.html.SafeStyleSheet.prototype.implementsGoogStringTypedString = true;
+
+
+/**
+ * Type marker for the SafeStyleSheet type, used to implement additional
+ * run-time type checking.
+ * @const {!Object}
+ * @private
+ */
+goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
+
+
+/**
+ * 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.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);
+ }
+ };
+
+ goog.array.forEach(arguments, addArgument);
+ return goog.html.SafeStyleSheet
+ .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(result);
+};
+
+
+/**
+ * Creates a SafeStyleSheet object from a compile-time constant string.
+ *
+ * {@code styleSheet} must not have any &lt; 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.html.SafeStyleSheet.fromConstant = function(styleSheet) {
+ var styleSheetString = goog.string.Const.unwrap(styleSheet);
+ if (styleSheetString.length === 0) {
+ return goog.html.SafeStyleSheet.EMPTY;
+ }
+ // > 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);
+};
+
+
+/**
+ * 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
+ */
+goog.html.SafeStyleSheet.prototype.getTypedStringValue = function() {
+ return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
+};
+
+
+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_ + '}';
+ };
+}
+
+
+/**
+ * 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';
+ }
+};
+
+
+/**
+ * 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) {
+ return new goog.html.SafeStyleSheet().initSecurityPrivateDoNotAccessOrElse_(
+ styleSheet);
+};
+
+
+/**
+ * Called from createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(). This
+ * method exists only so that the compiler can dead code eliminate static
+ * fields (like EMPTY) when they're not accessed.
+ * @param {string} styleSheet
+ * @return {!goog.html.SafeStyleSheet}
+ * @private
+ */
+goog.html.SafeStyleSheet.prototype.initSecurityPrivateDoNotAccessOrElse_ =
+ function(styleSheet) {
+ this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = styleSheet;
+ return this;
+};
+
+
+/**
+ * A SafeStyleSheet instance corresponding to the empty string.
+ * @const {!goog.html.SafeStyleSheet}
+ */
+goog.html.SafeStyleSheet.EMPTY =
+ goog.html.SafeStyleSheet.
+ createSafeStyleSheetSecurityPrivateDoNotAccessOrElse('');
+
+// Copyright 2015 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 for URL and its createObjectUrl and revokeObjectUrl
+ * methods that are part of the HTML5 File API.
+ */
+
+goog.provide('goog.fs.url');
+
+
+/**
+ * Creates a blob URL for a blob object.
+ * Throws an error if the browser does not support Object Urls.
+ *
+ * @param {!Blob} blob The object for which to create the URL.
+ * @return {string} The URL for the object.
+ */
+goog.fs.url.createObjectUrl = function(blob) {
+ return goog.fs.url.getUrlObject_().createObjectURL(blob);
+};
+
+
+/**
+ * Revokes a URL created by {@link goog.fs.url.createObjectUrl}.
+ * Throws an error if the browser does not support Object Urls.
+ *
+ * @param {string} url The URL to revoke.
+ */
+goog.fs.url.revokeObjectUrl = function(url) {
+ goog.fs.url.getUrlObject_().revokeObjectURL(url);
+};
+
+
+/**
+ * @typedef {{createObjectURL: (function(!Blob): string),
+ * revokeObjectURL: function(string): void}}
+ */
+goog.fs.url.UrlObject_;
+
+
+/**
+ * Get the object that has the createObjectURL and revokeObjectURL functions for
+ * this browser.
+ *
+ * @return {goog.fs.url.UrlObject_} The object for this browser.
+ * @private
+ */
+goog.fs.url.getUrlObject_ = function() {
+ var urlObject = goog.fs.url.findUrlObject_();
+ if (urlObject != null) {
+ return urlObject;
+ } else {
+ throw Error('This browser doesn\'t seem to support blob URLs');
+ }
+};
+
+
+/**
+ * Finds the object that has the createObjectURL and revokeObjectURL functions
+ * for this browser.
+ *
+ * @return {?goog.fs.url.UrlObject_} The object for this browser or null if the
+ * browser does not support Object Urls.
+ * @suppress {unnecessaryCasts} Depending on how the code is compiled, casting
+ * goog.global to UrlObject_ may result in unnecessary cast warning.
+ * However, the cast cannot be removed because with different set of
+ * compiler flags, the cast is indeed necessary. As such, silencing it.
+ * @private
+ */
+goog.fs.url.findUrlObject_ = function() {
+ // This is what the spec says to do
+ // http://dev.w3.org/2006/webapi/FileAPI/#dfn-createObjectURL
+ if (goog.isDef(goog.global.URL) &&
+ goog.isDef(goog.global.URL.createObjectURL)) {
+ return /** @type {goog.fs.url.UrlObject_} */ (goog.global.URL);
+ // This is what Chrome does (as of 10.0.648.6 dev)
+ } else if (goog.isDef(goog.global.webkitURL) &&
+ goog.isDef(goog.global.webkitURL.createObjectURL)) {
+ return /** @type {goog.fs.url.UrlObject_} */ (goog.global.webkitURL);
+ // This is what the spec used to say to do
+ } else if (goog.isDef(goog.global.createObjectURL)) {
+ return /** @type {goog.fs.url.UrlObject_} */ (goog.global);
+ } else {
+ return null;
+ }
+};
+
+
+/**
+ * Checks whether this browser supports Object Urls. If not, calls to
+ * createObjectUrl and revokeObjectUrl will result in an error.
+ *
+ * @return {boolean} True if this browser supports Object Urls.
+ */
+goog.fs.url.browserSupportsObjectUrls = function() {
+ return goog.fs.url.findUrlObject_() != null;
+};
+
+// 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 Utility functions for supporting Bidi issues.
+ */
+
+
+/**
+ * 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');
+
+
+/**
+ * @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.define('goog.i18n.bidi.FORCE_RTL', false);
+
+
+/**
+ * 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.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) == '_')
+ );
+
+
+/**
+ * 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'
+};
+
+
+/**
+ * Directionality enum.
+ * @enum {number}
+ */
+goog.i18n.bidi.Dir = {
+ /**
+ * Left-to-right.
+ */
+ LTR: 1,
+
+ /**
+ * Right-to-left.
+ */
+ RTL: -1,
+
+ /**
+ * Neither left-to-right nor right-to-left.
+ */
+ NEUTRAL: 0
+};
+
+
+/**
+ * 'right' string constant.
+ * @type {string}
+ */
+goog.i18n.bidi.RIGHT = 'right';
+
+
+/**
+ * 'left' string constant.
+ * @type {string}
+ */
+goog.i18n.bidi.LEFT = 'left';
+
+
+/**
+ * 'left' if locale is RTL, 'right' if not.
+ * @type {string}
+ */
+goog.i18n.bidi.I18N_RIGHT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.LEFT :
+ goog.i18n.bidi.RIGHT;
+
+
+/**
+ * 'right' if locale is RTL, 'left' if not.
+ * @type {string}
+ */
+goog.i18n.bidi.I18N_LEFT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.RIGHT :
+ goog.i18n.bidi.LEFT;
+
+
+/**
+ * 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.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;
+ }
+};
+
+
+/**
+ * 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.i18n.bidi.ltrChars_ =
+ 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
+ '\u200E\u2C00-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF';
+
+
+/**
+ * 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-\u06EF\u06FA-\u07FF\u200F\uFB1D-\uFDFF\uFE70-\uFEFC';
+
+
+/**
+ * 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.i18n.bidi.htmlSkipReg_ = /<[^>]*>|&[^;]+;/g;
+
+
+/**
+ * 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.i18n.bidi.stripHtmlIfNeeded_ = function(str, opt_isStripNeeded) {
+ return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') :
+ str;
+};
+
+
+/**
+ * Regular expression to check for RTL characters.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']');
+
+
+/**
+ * Regular expression to check for LTR characters.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.ltrCharReg_ = new RegExp('[' + goog.i18n.bidi.ltrChars_ + ']');
+
+
+/**
+ * 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.i18n.bidi.hasAnyRtl = function(str, opt_isHtml) {
+ return goog.i18n.bidi.rtlCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
+ str, opt_isHtml));
+};
+
+
+/**
+ * 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.i18n.bidi.hasRtlChar = goog.i18n.bidi.hasAnyRtl;
+
+
+/**
+ * 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.i18n.bidi.hasAnyLtr = function(str, opt_isHtml) {
+ return goog.i18n.bidi.ltrCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
+ str, opt_isHtml));
+};
+
+
+/**
+ * Regular expression pattern to check if the first character in the string
+ * is LTR.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.ltrRe_ = new RegExp('^[' + goog.i18n.bidi.ltrChars_ + ']');
+
+
+/**
+ * Regular expression pattern to check if the first character in the string
+ * is RTL.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.rtlRe_ = new RegExp('^[' + goog.i18n.bidi.rtlChars_ + ']');
+
+
+/**
+ * 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.i18n.bidi.isRtlChar = function(str) {
+ return goog.i18n.bidi.rtlRe_.test(str);
+};
+
+
+/**
+ * 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.i18n.bidi.isLtrChar = function(str) {
+ return goog.i18n.bidi.ltrRe_.test(str);
+};
+
+
+/**
+ * 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.i18n.bidi.isNeutralChar = function(str) {
+ return !goog.i18n.bidi.isLtrChar(str) && !goog.i18n.bidi.isRtlChar(str);
+};
+
+
+/**
+ * Regular expressions to check if a piece of text is of LTR directionality
+ * on first character with strong directionality.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.ltrDirCheckRe_ = new RegExp(
+ '^[^' + goog.i18n.bidi.rtlChars_ + ']*[' + goog.i18n.bidi.ltrChars_ + ']');
+
+
+/**
+ * Regular expressions to check if a piece of text is of RTL directionality
+ * on first character with strong directionality.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.rtlDirCheckRe_ = new RegExp(
+ '^[^' + goog.i18n.bidi.ltrChars_ + ']*[' + goog.i18n.bidi.rtlChars_ + ']');
+
+
+/**
+ * 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.i18n.bidi.startsWithRtl = function(str, opt_isHtml) {
+ return goog.i18n.bidi.rtlDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
+ str, opt_isHtml));
+};
+
+
+/**
+ * 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.i18n.bidi.isRtlText = goog.i18n.bidi.startsWithRtl;
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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;
+
+
+/**
+ * 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.i18n.bidi.isRequiredLtrRe_ = /^http:\/\/.*/;
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * Regular expressions to check if the last strongly-directional character in a
+ * piece of text is LTR.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.ltrExitDirCheckRe_ = new RegExp(
+ '[' + goog.i18n.bidi.ltrChars_ + '][^' + goog.i18n.bidi.rtlChars_ + ']*$');
+
+
+/**
+ * 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_ + ']*$');
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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;
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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;
+
+
+/**
+ * A regular expression for matching right-to-left language codes.
+ * See {@link #isRtlLanguage} for the design.
+ * @type {RegExp}
+ * @private
+ */
+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');
+
+
+/**
+ * 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.i18n.bidi.isRtlLanguage = function(lang) {
+ return goog.i18n.bidi.rtlLocalesRe_.test(lang);
+};
+
+
+/**
+ * Regular expression for bracket guard replacement in html.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.bracketGuardHtmlRe_ =
+ /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(&lt;.*?(&gt;)+)/g;
+
+
+/**
+ * Regular expression for bracket guard replacement in text.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.bracketGuardTextRe_ =
+ /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g;
+
+
+/**
+ * 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.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>');
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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.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>';
+};
+
+
+/**
+ * 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.i18n.bidi.enforceRtlInText = function(text) {
+ return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF;
+};
+
+
+/**
+ * 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.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>';
+};
+
+
+/**
+ * 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.i18n.bidi.enforceLtrInText = function(text) {
+ return goog.i18n.bidi.Format.LRE + text + goog.i18n.bidi.Format.PDF;
+};
+
+
+/**
+ * Regular expression to find dimensions such as "padding: .3 0.4ex 5px 6;"
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.dimensionsRe_ =
+ /:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g;
+
+
+/**
+ * Regular expression for left.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.leftRe_ = /left/gi;
+
+
+/**
+ * Regular expression for right.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.rightRe_ = /right/gi;
+
+
+/**
+ * Placeholder regular expression for swapping.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.tempRe_ = /%%%%/g;
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * Regular expression for hebrew double quote substitution, finding quote
+ * directly after hebrew characters.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g;
+
+
+/**
+ * Regular expression for hebrew single quote substitution, finding quote
+ * directly after hebrew characters.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g;
+
+
+/**
+ * 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.i18n.bidi.normalizeHebrewQuote = function(str) {
+ return str.
+ replace(goog.i18n.bidi.doubleQuoteSubstituteRe_, '$1\u05f4').
+ replace(goog.i18n.bidi.singleQuoteSubstituteRe_, '$1\u05f3');
+};
+
+
+/**
+ * Regular expression to split a string into "words" for directionality
+ * estimation based on relative word counts.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.wordSeparatorRe_ = /\s+/;
+
+
+/**
+ * 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.
+ *
+ * Native Arabic digits (\u0660 - \u0669) are not included because although they
+ * do flow left-to-right inside a number, this is the case even if the overall
+ * directionality is RTL, and a mathematical expression using these digits is
+ * supposed to flow right-to-left overall, including unary plus and minus
+ * appearing to the right of a number, and this does depend on the overall
+ * directionality being RTL. The digits used in Farsi (\u06F0 - \u06F9), on the
+ * other hand, are included, since Farsi math (including unary plus and minus)
+ * does flow left-to-right.
+ *
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.hasNumeralsRe_ = /[\d\u06f0-\u06f9]/;
+
+
+/**
+ * This constant controls threshold of RTL directionality.
+ * @type {number}
+ * @private
+ */
+goog.i18n.bidi.rtlDetectionThreshold_ = 0.40;
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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.i18n.bidi.detectRtlDirectionality = function(str, opt_isHtml) {
+ return goog.i18n.bidi.estimateDirection(str, opt_isHtml) ==
+ goog.i18n.bidi.Dir.RTL;
+};
+
+
+/**
+ * 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.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';
+ }
+ }
+};
+
+
+/**
+ * Sets element dir based on estimated directionality of the given text.
+ * @param {!Element} element
+ * @param {string} text
+ */
+goog.i18n.bidi.setElementDirByTextDirectionality = function(element, text) {
+ switch (goog.i18n.bidi.estimateDirection(text)) {
+ case (goog.i18n.bidi.Dir.LTR):
+ element.dir = 'ltr';
+ break;
+ case (goog.i18n.bidi.Dir.RTL):
+ element.dir = 'rtl';
+ break;
+ default:
+ // Default for no direction, inherit from document.
+ element.removeAttribute('dir');
+ }
+};
+
+
+
+/**
+ * Strings that have an (optional) known direction.
+ *
+ * Implementations of this interface are string-like objects that carry an
+ * attached direction, if known.
+ * @interface
+ */
+goog.i18n.bidi.DirectionalString = function() {};
+
+
+/**
+ * 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.i18n.bidi.DirectionalString.prototype.
+ implementsGoogI18nBidiDirectionalString;
+
+
+/**
+ * Retrieves this object's known direction (if any).
+ * @return {?goog.i18n.bidi.Dir} The known direction. Null if unknown.
+ */
+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.
+
+/**
+ * @fileoverview The SafeUrl type and its builders.
+ *
+ * TODO(xtof): Link to document stating type contract.
+ */
+
+goog.provide('goog.html.SafeUrl');
+
+goog.require('goog.asserts');
+goog.require('goog.fs.url');
+goog.require('goog.i18n.bidi.Dir');
+goog.require('goog.i18n.bidi.DirectionalString');
+goog.require('goog.string.Const');
+goog.require('goog.string.TypedString');
+
+
+
+/**
+ * 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.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_ = '';
+
+ /**
+ * 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_;
+};
+
+
+/**
+ * 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.html.SafeUrl.INNOCUOUS_STRING = 'about:invalid#zClosurez';
+
+
+/**
+ * @override
+ * @const
+ */
+goog.html.SafeUrl.prototype.implementsGoogStringTypedString = true;
+
+
+/**
+ * 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.html.SafeUrl.prototype.getTypedStringValue = function() {
+ return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
+};
+
+
+/**
+ * @override
+ * @const
+ */
+goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString = true;
+
+
+/**
+ * Returns this URLs directionality, which is always {@code LTR}.
+ * @override
+ */
+goog.html.SafeUrl.prototype.getDirection = function() {
+ return goog.i18n.bidi.Dir.LTR;
+};
+
+
+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_ +
+ '}';
+ };
+}
+
+
+/**
+ * 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).
+ *
+ * @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.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';
+
+ }
+};
+
+
+/**
+ * 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.html.SafeUrl.fromConstant = function(url) {
+ return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
+ goog.string.Const.unwrap(url));
+};
+
+
+/**
+ * A pattern that matches Blob or data types that can have SafeUrls created
+ * from URL.createObjectURL(blob) or via a data: URI. Only matches image and
+ * video types, currently.
+ * @const
+ * @private
+ */
+goog.html.SAFE_MIME_TYPE_PATTERN_ =
+ /^(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm))$/i;
+
+
+/**
+ * Creates a SafeUrl wrapping a blob URL for the given {@code blob}.
+ *
+ * The blob URL is created with {@code URL.createObjectURL}. If the MIME type
+ * for {@code blob} is not of a known safe image or video MIME type, then the
+ * SafeUrl will wrap {@link #INNOCUOUS_STRING}.
+ *
+ * @see http://www.w3.org/TR/FileAPI/#url
+ * @param {!Blob} blob
+ * @return {!goog.html.SafeUrl} The blob URL, or an innocuous string wrapped
+ * as a SafeUrl.
+ */
+goog.html.SafeUrl.fromBlob = function(blob) {
+ var url = goog.html.SAFE_MIME_TYPE_PATTERN_.test(blob.type) ?
+ goog.fs.url.createObjectUrl(blob) : goog.html.SafeUrl.INNOCUOUS_STRING;
+ return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
+};
+
+
+/**
+ * Matches a base-64 data URL, with the first match group being the MIME type.
+ * @const
+ * @private
+ */
+goog.html.DATA_URL_PATTERN_ = /^data:([^;,]*);base64,[a-z0-9+\/]+=*$/i;
+
+
+/**
+ * Creates a SafeUrl wrapping a data: URL, after validating it matches a
+ * known-safe image or video MIME type.
+ *
+ * @param {string} dataUrl A valid base64 data URL with one of the whitelisted
+ * image or video MIME types.
+ * @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
+ * wrapped as a SafeUrl if it does not pass.
+ */
+goog.html.SafeUrl.fromDataUrl = function(dataUrl) {
+ // There's a slight risk here that a browser sniffs the content type if it
+ // doesn't know the MIME type and executes HTML within the data: URL. For this
+ // to cause XSS it would also have to execute the HTML in the same origin
+ // of the page with the link. It seems unlikely that both of these will
+ // happen, particularly in not really old IEs.
+ var match = dataUrl.match(goog.html.DATA_URL_PATTERN_);
+ var valid = match && goog.html.SAFE_MIME_TYPE_PATTERN_.test(match[1]);
+ return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
+ valid ? dataUrl : goog.html.SafeUrl.INNOCUOUS_STRING);
+};
+
+
+/**
+ * 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 or ftp).
+ * (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. "h&#116;tp" for "http".
+ * It also disallows HTML entities in the first path part of a relative path,
+ * e.g. "foo&lt;bar/baz". Our existing escaping functions should not produce
+ * that. More importantly, it disallows masking of a colon,
+ * e.g. "javascript&#58;...".
+ *
+ * @private
+ * @const {!RegExp}
+ */
+goog.html.SAFE_URL_PATTERN_ =
+ /^(?:(?:https?|mailto|ftp):|[^&:/?#]*(?:[/?#]|$))/i;
+
+
+/**
+ * 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, mailto or ftp 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.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;
+ }
+ return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
+};
+
+
+/**
+ * Type marker for the SafeUrl type, used to implement additional run-time
+ * type checking.
+ * @const {!Object}
+ * @private
+ */
+goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
+
+
+/**
+ * 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.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.
+
+/**
+ * @fileoverview The TrustedResourceUrl type and its builders.
+ *
+ * TODO(xtof): Link to document stating type contract.
+ */
+
+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');
+
+
+
+/**
+ * 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.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_;
+};
+
+
+/**
+ * @override
+ * @const
+ */
+goog.html.TrustedResourceUrl.prototype.implementsGoogStringTypedString = true;
+
+
+/**
+ * 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_;
+};
+
+
+/**
+ * @override
+ * @const
+ */
+goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString =
+ true;
+
+
+/**
+ * Returns this URLs directionality, which is always {@code LTR}.
+ * @override
+ */
+goog.html.TrustedResourceUrl.prototype.getDirection = function() {
+ return goog.i18n.bidi.Dir.LTR;
+};
+
+
+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_ + '}';
+ };
+}
+
+
+/**
+ * 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.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';
+
+ }
+};
+
+
+/**
+ * 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.html.TrustedResourceUrl.fromConstant = function(url) {
+ return goog.html.TrustedResourceUrl
+ .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(
+ goog.string.Const.unwrap(url));
+};
+
+
+/**
+ * Type marker for the TrustedResourceUrl type, used to implement additional
+ * run-time type checking.
+ * @const {!Object}
+ * @private
+ */
+goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
+
+
+/**
+ * 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.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.
+
+
+/**
+ * @fileoverview The SafeHtml type and its builders.
+ *
+ * TODO(xtof): Link to document stating type contract.
+ */
+
+goog.provide('goog.html.SafeHtml');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.TagName');
+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');
+
+
+
+/**
+ * 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.
+ *
+ * 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.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;
+};
+
+
+/**
+ * @override
+ * @const
+ */
+goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true;
+
+
+/** @override */
+goog.html.SafeHtml.prototype.getDirection = function() {
+ return this.dir_;
+};
+
+
+/**
+ * @override
+ * @const
+ */
+goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true;
+
+
+/**
+ * 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.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_ +
+ '}';
+ };
+}
+
+
+/**
+ * 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.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';
+ }
+};
+
+
+/**
+ * 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.html.SafeHtml.TextOrHtml_;
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * Returns HTML-escaped text as a SafeHtml object, with newlines changed to
+ * &lt;br&gt;.
+ * @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());
+};
+
+
+/**
+ * Returns HTML-escaped text as a SafeHtml object, with newlines changed to
+ * &lt;br&gt; 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.
+ */
+goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces = function(
+ textOrHtml) {
+ if (textOrHtml instanceof goog.html.SafeHtml) {
+ return textOrHtml;
+ }
+ 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.
+ */
+goog.html.SafeHtml.from = goog.html.SafeHtml.htmlEscape;
+
+
+/**
+ * @const
+ * @private
+ */
+goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/;
+
+
+/**
+ * Set of attributes containing URL as defined at
+ * http://www.w3.org/TR/html5/index.html#attributes-1.
+ * @private @const {!Object<string,boolean>}
+ */
+goog.html.SafeHtml.URL_ATTRIBUTES_ = goog.object.createSet('action', 'cite',
+ 'data', 'formaction', 'href', 'manifest', 'poster', 'src');
+
+
+/**
+ * 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>}
+ */
+goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_ = goog.object.createSet(
+ goog.dom.TagName.EMBED, goog.dom.TagName.IFRAME, goog.dom.TagName.LINK,
+ goog.dom.TagName.OBJECT, goog.dom.TagName.SCRIPT, goog.dom.TagName.STYLE,
+ goog.dom.TagName.TEMPLATE);
+
+
+/**
+ * @typedef {string|number|goog.string.TypedString|
+ * goog.html.SafeStyle.PropertyMap}
+ * @private
+ */
+goog.html.SafeHtml.AttributeValue_;
+
+
+/**
+ * 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, goog.string.Const or string is required. If a string
+ * is passed, it will be sanitized with SafeUrl.sanitize().
+ * - 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.toUpperCase() 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);
+};
+
+
+/**
+ * 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.
+ */
+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);
+};
+
+
+/**
+ * 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);
+
+ 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 {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
+ */
+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 if (goog.isString(value)) {
+ value = goog.html.SafeUrl.sanitize(value).getTypedStringValue();
+ } else {
+ throw Error('Attribute "' + name + '" on tag "' + tagName +
+ '" requires goog.html.SafeUrl, goog.string.Const, or string,' +
+ ' 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)) + '"';
+};
+
+
+/**
+ * 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
+ */
+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);
+};
+
+
+/**
+ * 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.
+ */
+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;
+};
+
+
+/**
+ * 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}
+ */
+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;
+ }
+ }
+ };
+
+ goog.array.forEach(arguments, addArgument);
+ return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
+ content, dir);
+};
+
+
+/**
+ * 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}
+ */
+goog.html.SafeHtml.concatWithDir = function(dir, var_args) {
+ var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1));
+ html.dir_ = dir;
+ return html;
+};
+
+
+/**
+ * Type marker for the SafeHtml type, used to implement additional run-time
+ * type checking.
+ * @const {!Object}
+ * @private
+ */
+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) {
+ return new goog.html.SafeHtml().initSecurityPrivateDoNotAccessOrElse_(
+ html, dir);
+};
+
+
+/**
+ * Called from createSafeHtmlSecurityPrivateDoNotAccessOrElse(). This
+ * method exists only so that the compiler can dead code eliminate static
+ * fields (like EMPTY) when they're not accessed.
+ * @param {string} html
+ * @param {?goog.i18n.bidi.Dir} dir
+ * @return {!goog.html.SafeHtml}
+ * @private
+ */
+goog.html.SafeHtml.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
+ html, dir) {
+ this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html;
+ this.dir_ = dir;
+ return this;
+};
+
+
+/**
+ * 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.isDefAndNotNull(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
+ */
+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;
+};
+
+
+/**
+ * A SafeHtml instance corresponding to the HTML doctype: "<!DOCTYPE html>".
+ * @const {!goog.html.SafeHtml}
+ */
+goog.html.SafeHtml.DOCTYPE_HTML =
+ goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
+ '<!DOCTYPE html>', goog.i18n.bidi.Dir.NEUTRAL);
+
+
+/**
+ * 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.
+//
+// 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 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.
+ */
+
+goog.provide('goog.dom.safe');
+goog.provide('goog.dom.safe.InsertAdjacentHtmlPosition');
+
+goog.require('goog.asserts');
+goog.require('goog.html.SafeHtml');
+goog.require('goog.html.SafeUrl');
+goog.require('goog.html.TrustedResourceUrl');
+goog.require('goog.string');
+goog.require('goog.string.Const');
+
+
+/** @enum {string} */
+goog.dom.safe.InsertAdjacentHtmlPosition = {
+ AFTERBEGIN: 'afterbegin',
+ AFTEREND: 'afterend',
+ BEFOREBEGIN: 'beforebegin',
+ BEFOREEND: 'beforeend'
+};
+
+
+/**
+ * Inserts known-safe HTML into a Node, at the specified position.
+ * @param {!Node} node The node on which to call insertAdjacentHTML.
+ * @param {!goog.dom.safe.InsertAdjacentHtmlPosition} position Position where
+ * to insert the HTML.
+ * @param {!goog.html.SafeHtml} html The known-safe HTML to insert.
+ */
+goog.dom.safe.insertAdjacentHtml = function(node, position, html) {
+ node.insertAdjacentHTML(position, goog.html.SafeHtml.unwrap(html));
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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.
+ */
+goog.dom.safe.setOuterHtml = function(elem, html) {
+ elem.outerHTML = goog.html.SafeHtml.unwrap(html);
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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
+ */
+goog.dom.safe.setAnchorHref = function(anchor, url) {
+ /** @type {!goog.html.SafeUrl} */
+ var safeUrl;
+ if (url instanceof goog.html.SafeUrl) {
+ safeUrl = url;
+ } else {
+ safeUrl = goog.html.SafeUrl.sanitize(url);
+ }
+ anchor.href = goog.html.SafeUrl.unwrap(safeUrl);
+};
+
+
+/**
+ * Safely assigns a URL to an embed element's src property.
+ *
+ * Example usage:
+ * goog.dom.safe.setEmbedSrc(embedEl, url);
+ * which is a safe alternative to
+ * embedEl.src = url;
+ * The latter can result in loading untrusted code unless it is ensured that
+ * the URL refers to a trustworthy resource.
+ *
+ * @param {!HTMLEmbedElement} embed The embed element whose src property
+ * is to be assigned to.
+ * @param {!goog.html.TrustedResourceUrl} url The URL to assign.
+ */
+goog.dom.safe.setEmbedSrc = function(embed, url) {
+ embed.src = goog.html.TrustedResourceUrl.unwrap(url);
+};
+
+
+/**
+ * Safely assigns a URL to a frame element's src property.
+ *
+ * Example usage:
+ * goog.dom.safe.setFrameSrc(frameEl, url);
+ * which is a safe alternative to
+ * frameEl.src = url;
+ * The latter can result in loading untrusted code unless it is ensured that
+ * the URL refers to a trustworthy resource.
+ *
+ * @param {!HTMLFrameElement} frame The frame element whose src property
+ * is to be assigned to.
+ * @param {!goog.html.TrustedResourceUrl} url The URL to assign.
+ */
+goog.dom.safe.setFrameSrc = function(frame, url) {
+ frame.src = goog.html.TrustedResourceUrl.unwrap(url);
+};
+
+
+/**
+ * Safely assigns a URL to an iframe element's src property.
+ *
+ * Example usage:
+ * goog.dom.safe.setIframeSrc(iframeEl, url);
+ * which is a safe alternative to
+ * iframeEl.src = url;
+ * The latter can result in loading untrusted code unless it is ensured that
+ * the URL refers to a trustworthy resource.
+ *
+ * @param {!HTMLIFrameElement} iframe The iframe element whose src property
+ * is to be assigned to.
+ * @param {!goog.html.TrustedResourceUrl} url The URL to assign.
+ */
+goog.dom.safe.setIframeSrc = function(iframe, url) {
+ iframe.src = goog.html.TrustedResourceUrl.unwrap(url);
+};
+
+
+/**
+ * Safely sets a link element's href and rel properties. Whether or not
+ * the URL assigned to href has to be a goog.html.TrustedResourceUrl
+ * depends on the value of the rel property. If rel contains "stylesheet"
+ * then a TrustedResourceUrl is required.
+ *
+ * Example usage:
+ * goog.dom.safe.setLinkHrefAndRel(linkEl, url, 'stylesheet');
+ * which is a safe alternative to
+ * linkEl.rel = 'stylesheet';
+ * linkEl.href = url;
+ * The latter can result in loading untrusted code unless it is ensured that
+ * the URL refers to a trustworthy resource.
+ *
+ * @param {!HTMLLinkElement} link The link element whose href property
+ * is to be assigned to.
+ * @param {string|!goog.html.SafeUrl|!goog.html.TrustedResourceUrl} url The URL
+ * to assign to the href property. Must be a TrustedResourceUrl if the
+ * value assigned to rel contains "stylesheet". A string value is
+ * sanitized with goog.html.SafeUrl.sanitize.
+ * @param {string} rel The value to assign to the rel property.
+ * @throws {Error} if rel contains "stylesheet" and url is not a
+ * TrustedResourceUrl
+ * @see goog.html.SafeUrl#sanitize
+ */
+goog.dom.safe.setLinkHrefAndRel = function(link, url, rel) {
+ link.rel = rel;
+ if (goog.string.caseInsensitiveContains(rel, 'stylesheet')) {
+ goog.asserts.assert(
+ url instanceof goog.html.TrustedResourceUrl,
+ 'URL must be TrustedResourceUrl because "rel" contains "stylesheet"');
+ link.href = goog.html.TrustedResourceUrl.unwrap(url);
+ } else if (url instanceof goog.html.TrustedResourceUrl) {
+ link.href = goog.html.TrustedResourceUrl.unwrap(url);
+ } else if (url instanceof goog.html.SafeUrl) {
+ link.href = goog.html.SafeUrl.unwrap(url);
+ } else { // string
+ // SafeUrl.sanitize must return legitimate SafeUrl when passed a string.
+ link.href = goog.html.SafeUrl.sanitize(url).getTypedStringValue();
+ }
+};
+
+
+/**
+ * Safely assigns a URL to an object element's data property.
+ *
+ * Example usage:
+ * goog.dom.safe.setObjectData(objectEl, url);
+ * which is a safe alternative to
+ * objectEl.data = url;
+ * The latter can result in loading untrusted code unless setit is ensured that
+ * the URL refers to a trustworthy resource.
+ *
+ * @param {!HTMLObjectElement} object The object element whose data property
+ * is to be assigned to.
+ * @param {!goog.html.TrustedResourceUrl} url The URL to assign.
+ */
+goog.dom.safe.setObjectData = function(object, url) {
+ object.data = goog.html.TrustedResourceUrl.unwrap(url);
+};
+
+
+/**
+ * Safely assigns a URL to an iframe element's src property.
+ *
+ * Example usage:
+ * goog.dom.safe.setScriptSrc(scriptEl, url);
+ * which is a safe alternative to
+ * scriptEl.src = url;
+ * The latter can result in loading untrusted code unless it is ensured that
+ * the URL refers to a trustworthy resource.
+ *
+ * @param {!HTMLScriptElement} script The script element whose src property
+ * is to be assigned to.
+ * @param {!goog.html.TrustedResourceUrl} url The URL to assign.
+ */
+goog.dom.safe.setScriptSrc = function(script, url) {
+ script.src = goog.html.TrustedResourceUrl.unwrap(url);
+};
+
+
+/**
+ * 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
+ */
+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);
+};
+
+
+/**
+ * Safely opens a URL in a new window (via window.open).
+ *
+ * If url is of type goog.html.SafeUrl, its value is unwrapped and passed in to
+ * window.open. If url is of type string however, it is first sanitized
+ * using goog.html.SafeUrl.sanitize.
+ *
+ * Note that this function does not prevent leakages via the referer that is
+ * sent by window.open. It is advised to only use this to open 1st party URLs.
+ *
+ * Example usage:
+ * goog.dom.safe.openInWindow(url);
+ * which is a safe alternative to
+ * window.open(url);
+ * The latter can result in XSS vulnerabilities if redirectUrl is a
+ * user-/attacker-controlled value.
+ *
+ * @param {string|!goog.html.SafeUrl} url The URL to open.
+ * @param {Window=} opt_openerWin Window of which to call the .open() method.
+ * Defaults to the global window.
+ * @param {!goog.string.Const=} opt_name Name of the window to open in. Can be
+ * _top, etc as allowed by window.open().
+ * @param {string=} opt_specs Comma-separated list of specifications, same as
+ * in window.open().
+ * @param {boolean=} opt_replace Whether to replace the current entry in browser
+ * history, same as in window.open().
+ * @return {Window} Window the url was opened in.
+ */
+goog.dom.safe.openInWindow = function(
+ url, opt_openerWin, opt_name, opt_specs, opt_replace) {
+ /** @type {!goog.html.SafeUrl} */
+ var safeUrl;
+ if (url instanceof goog.html.SafeUrl) {
+ safeUrl = url;
+ } else {
+ safeUrl = goog.html.SafeUrl.sanitize(url);
+ }
+ var win = opt_openerWin || window;
+ return win.open(goog.html.SafeUrl.unwrap(safeUrl),
+ // If opt_name is undefined, simply passing that in to open() causes IE to
+ // reuse the current window instead of opening a new one. Thus we pass ''
+ // in instead, which according to spec opens a new window. See
+ // https://html.spec.whatwg.org/multipage/browsers.html#dom-open .
+ opt_name ? goog.string.Const.unwrap(opt_name) : '',
+ opt_specs, opt_replace);
+};
+
+// 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.
+ */
+
+
+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
+ */
+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;
+};
+
+
+/**
+ * Returns a new copy of the coordinate.
+ * @return {!goog.math.Coordinate} A clone of this coordinate.
+ */
+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 + ')';
+ };
+}
+
+
+/**
+ * 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.
+ */
+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;
+};
+
+
+/**
+ * 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}.
+ */
+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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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}.
+ */
+goog.math.Coordinate.squaredDistance = function(a, b) {
+ var dx = a.x - b.x;
+ var dy = a.y - b.y;
+ return dx * dx + dy * dy;
+};
+
+
+/**
+ * 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}.
+ */
+goog.math.Coordinate.difference = function(a, b) {
+ return new goog.math.Coordinate(a.x - b.x, a.y - b.y);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * Rounds the x and y fields to the nearest integer values.
+ * @return {!goog.math.Coordinate} This coordinate with rounded fields.
+ */
+goog.math.Coordinate.prototype.round = function() {
+ this.x = Math.round(this.x);
+ this.y = Math.round(this.y);
+ return this;
+};
+
+
+/**
+ * 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.
+ */
+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;
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * 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.
+ */
+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');
+
+
+
+/**
+ * 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
+ */
+goog.math.Size = function(width, height) {
+ /**
+ * Width
+ * @type {number}
+ */
+ this.width = width;
+
+ /**
+ * Height
+ * @type {number}
+ */
+ this.height = height;
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * @return {!goog.math.Size} A new copy of the Size.
+ */
+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 + ')';
+ };
+}
+
+
+/**
+ * @return {number} The longer of the two dimensions in the size.
+ */
+goog.math.Size.prototype.getLongest = function() {
+ return Math.max(this.width, this.height);
+};
+
+
+/**
+ * @return {number} The shorter of the two dimensions in the size.
+ */
+goog.math.Size.prototype.getShortest = function() {
+ return Math.min(this.width, this.height);
+};
+
+
+/**
+ * @return {number} The area of the size (width * height).
+ */
+goog.math.Size.prototype.area = function() {
+ return this.width * this.height;
+};
+
+
+/**
+ * @return {number} The perimeter of the size (width + height) * 2.
+ */
+goog.math.Size.prototype.perimeter = function() {
+ return (this.width + this.height) * 2;
+};
+
+
+/**
+ * @return {number} The ratio of the size's width to its height.
+ */
+goog.math.Size.prototype.aspectRatio = function() {
+ return this.width / this.height;
+};
+
+
+/**
+ * @return {boolean} True if the size has zero area, false if both dimensions
+ * are non-zero numbers.
+ */
+goog.math.Size.prototype.isEmpty = function() {
+ return !this.area();
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * @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.
+ */
+goog.math.Size.prototype.fitsInside = function(target) {
+ return this.width <= target.width && this.height <= target.height;
+};
+
+
+/**
+ * Clamps the width and height parameters downward to integer values.
+ * @return {!goog.math.Size} This size with floored components.
+ */
+goog.math.Size.prototype.floor = function() {
+ this.width = Math.floor(this.width);
+ this.height = Math.floor(this.height);
+ return this;
+};
+
+
+/**
+ * Rounds the width and height parameters to integer values.
+ * @return {!goog.math.Size} This size with rounded components.
+ */
+goog.math.Size.prototype.round = function() {
+ this.width = Math.round(this.width);
+ this.height = Math.round(this.height);
+ return this;
+};
+
+
+/**
+ * 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.
+ */
+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;
+};
+
+
+/**
+ * Uniformly scales the size to perfectly cover the dimensions of a given size.
+ * If the size is already larger than the target, it will be scaled down to the
+ * minimum size at which it still covers the entire target. 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.
+ */
+goog.math.Size.prototype.scaleToCover = function(target) {
+ var s = this.aspectRatio() <= target.aspectRatio() ?
+ target.width / this.width :
+ target.height / this.height;
+
+ return this.scale(s);
+};
+
+
+/**
+ * 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.
+ */
+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.
+//
+// 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)
+ */
+
+
+// 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');
+
+
+/**
+ * @define {boolean} Whether we know at compile time that the browser is in
+ * quirks mode.
+ */
+goog.define('goog.dom.ASSUME_QUIRKS_MODE', false);
+
+
+/**
+ * @define {boolean} Whether we know at compile time that the browser is in
+ * standards compliance mode.
+ */
+goog.define('goog.dom.ASSUME_STANDARDS_MODE', false);
+
+
+/**
+ * Whether we know the compatibility mode at compile time.
+ * @type {boolean}
+ * @private
+ */
+goog.dom.COMPAT_MODE_KNOWN_ =
+ goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE;
+
+
+/**
+ * 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.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()));
+};
+
+
+/**
+ * Cached default DOM helper.
+ * @type {goog.dom.DomHelper}
+ * @private
+ */
+goog.dom.defaultDomHelper_;
+
+
+/**
+ * Gets the document object being used by the dom library.
+ * @return {!Document} Document object.
+ */
+goog.dom.getDocument = function() {
+ return document;
+};
+
+
+/**
+ * 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.dom.getElement = function(element) {
+ return goog.dom.getElementHelper_(document, element);
+};
+
+
+/**
+ * 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.dom.getElementHelper_ = function(doc, element) {
+ return goog.isString(element) ?
+ doc.getElementById(element) :
+ element;
+};
+
+
+/**
+ * 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.dom.getRequiredElement = function(id) {
+ return goog.dom.getRequiredElementHelper_(document, id);
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.dom.$ = goog.dom.getElement;
+
+
+/**
+ * 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.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) {
+ return goog.dom.getElementsByTagNameAndClass_(document, opt_tag, opt_class,
+ opt_el);
+};
+
+
+/**
+ * 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.dom.getElementsByClass = function(className, opt_el) {
+ var parent = opt_el || document;
+ if (goog.dom.canUseQuerySelector_(parent)) {
+ return parent.querySelectorAll('.' + className);
+ }
+ return goog.dom.getElementsByTagNameAndClass_(
+ document, '*', className, opt_el);
+};
+
+
+/**
+ * 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.dom.getElementByClass = function(className, opt_el) {
+ var parent = opt_el || document;
+ var retVal = null;
+ if (parent.getElementsByClassName) {
+ retVal = parent.getElementsByClassName(className)[0];
+ } else if (goog.dom.canUseQuerySelector_(parent)) {
+ retVal = parent.querySelector('.' + className);
+ } else {
+ retVal = goog.dom.getElementsByTagNameAndClass_(
+ document, '*', className, opt_el)[0];
+ }
+ 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.
+ */
+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);
+};
+
+
+/**
+ * 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.dom.canUseQuerySelector_ = function(parent) {
+ return !!(parent.querySelectorAll && parent.querySelector);
+};
+
+
+/**
+ * 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.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);
+ }
+
+ // 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);
+
+ if (tagName) {
+ var arrayLike = {};
+ var len = 0;
+
+ // 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 {
+ return els;
+ }
+ }
+
+ var els = parent.getElementsByTagName(tagName || '*');
+
+ 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;
+ }
+};
+
+
+/**
+ * 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.dom.$$ = goog.dom.getElementsByTagNameAndClass;
+
+
+/**
+ * Sets multiple properties on a node.
+ * @param {Element} element DOM node to set properties on.
+ * @param {Object} properties Hash of property:value pairs.
+ */
+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 (goog.dom.DIRECT_ATTRIBUTE_MAP_.hasOwnProperty(key)) {
+ 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;
+ }
+ });
+};
+
+
+/**
+ * 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.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'
+};
+
+
+/**
+ * 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.dom.getViewportSize = function(opt_window) {
+ // TODO(arv): This should not take an argument
+ return goog.dom.getViewportSize_(opt_window || window);
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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.
+ */
+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.
+
+ var body = doc.body;
+ var docEl = /** @type {!HTMLElement} */ (doc.documentElement);
+ if (!(docEl && body)) {
+ return 0;
+ }
+
+ // 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;
+ }
+
+ // 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 height;
+};
+
+
+/**
+ * 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();
+};
+
+
+/**
+ * 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
+ */
+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);
+};
+
+
+/**
+ * Gets the document scroll element.
+ * @return {!Element} Scrolling element.
+ */
+goog.dom.getDocumentScrollElement = function() {
+ return goog.dom.getDocumentScrollElement_(document);
+};
+
+
+/**
+ * Helper for {@code getDocumentScrollElement}.
+ * @param {!Document} doc The document to get the scroll element for.
+ * @return {!Element} Scrolling element.
+ * @private
+ */
+goog.dom.getDocumentScrollElement_ = function(doc) {
+ // Old 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).
+ // Uses http://dev.w3.org/csswg/cssom-view/#dom-document-scrollingelement to
+ // avoid trying to guess about browser behavior from the UA string.
+ if (doc.scrollingElement) {
+ return doc.scrollingElement;
+ }
+ if (!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc)) {
+ return doc.documentElement;
+ }
+ return doc.body || doc.documentElement;
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * Helper for {@code getWindow}.
+ *
+ * @param {!Document} doc Document object to get window for.
+ * @return {!Window} The window associated with the given document.
+ * @private
+ */
+goog.dom.getWindow_ = function(doc) {
+ return doc.parentWindow || doc.defaultView;
+};
+
+
+/**
+ * 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,
+ * 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];
+
+ // Internet Explorer is dumb:
+ // name: https://msdn.microsoft.com/en-us/library/ms534184(v=vs.85).aspx
+ // type: https://msdn.microsoft.com/en-us/library/ms534700(v=vs.85).aspx
+ // 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('');
+ }
+
+ 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);
+ }
+ }
+
+ if (args.length > 2) {
+ goog.dom.append_(doc, element, args, 2);
+ }
+
+ return element;
+};
+
+
+/**
+ * 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.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);
+ }
+ }
+};
+
+
+/**
+ * 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.dom.$dom = goog.dom.createDom;
+
+
+/**
+ * Creates a new element.
+ * @param {string} name Tag name.
+ * @return {!Element} The new element.
+ */
+goog.dom.createElement = function(name) {
+ return document.createElement(name);
+};
+
+
+/**
+ * Creates a new text node.
+ * @param {number|string} content Content.
+ * @return {!Text} The new text node.
+ */
+goog.dom.createTextNode = function(content) {
+ return document.createTextNode(String(content));
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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.
+ */
+goog.dom.safeHtmlToNode = function(html) {
+ return goog.dom.safeHtmlToNode_(document, html);
+};
+
+
+/**
+ * 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(goog.dom.TagName.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);
+};
+
+
+/**
+ * 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('&lt;img src=x onerror=alert(0)&gt;')}
+ * triggers an alert in all browsers, even if the returned document fragment
+ * is thrown away immediately.
+ *
+ * NOTE: This method doesn't work if your htmlString contains elements that
+ * can't be contained in a <div>. For example, <tr>.
+ *
+ * @param {string} htmlString The HTML string to convert.
+ * @return {!Node} The resulting document fragment.
+ */
+goog.dom.htmlToDocumentFragment = function(htmlString) {
+ return goog.dom.htmlToDocumentFragment_(document, htmlString);
+};
+
+
+// TODO(jakubvrana): Merge with {@code safeHtmlToNode_}.
+/**
+ * Helper for {@code htmlToDocumentFragment}.
+ *
+ * @param {!Document} doc The document.
+ * @param {string} htmlString The HTML string to convert.
+ * @return {!Node} The resulting document fragment.
+ * @private
+ */
+goog.dom.htmlToDocumentFragment_ = function(doc, htmlString) {
+ var tempDiv = doc.createElement(goog.dom.TagName.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);
+};
+
+
+/**
+ * Helper for {@code htmlToDocumentFragment_}.
+ * @param {!Document} doc The document.
+ * @param {!Node} tempDiv The input node.
+ * @return {!Node} The resulting node.
+ * @private
+ */
+goog.dom.childrenToNode_ = function(doc, tempDiv) {
+ if (tempDiv.childNodes.length == 1) {
+ return tempDiv.removeChild(tempDiv.firstChild);
+ } else {
+ var fragment = doc.createDocumentFragment();
+ while (tempDiv.firstChild) {
+ fragment.appendChild(tempDiv.firstChild);
+ }
+ return fragment;
+ }
+};
+
+
+/**
+ * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
+ * mode, false otherwise.
+ * @return {boolean} True if in CSS1-compatible mode.
+ */
+goog.dom.isCss1CompatMode = function() {
+ return goog.dom.isCss1CompatMode_(document);
+};
+
+
+/**
+ * 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
+ */
+goog.dom.isCss1CompatMode_ = function(doc) {
+ if (goog.dom.COMPAT_MODE_KNOWN_) {
+ return goog.dom.ASSUME_STANDARDS_MODE;
+ }
+
+ 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.
+ *
+ * In practice all non-IE browsers allow you to add children to any node, but
+ * the behavior is inconsistent:
+ *
+ * <pre>
+ * var a = document.createElement(goog.dom.TagName.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.
+ */
+goog.dom.canHaveChildren = function(node) {
+ if (node.nodeType != goog.dom.NodeType.ELEMENT) {
+ return false;
+ }
+ switch (/** @type {!Element} */ (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;
+};
+
+
+/**
+ * Appends a child to a node.
+ * @param {Node} parent Parent.
+ * @param {Node} child Child.
+ */
+goog.dom.appendChild = function(parent, child) {
+ parent.appendChild(child);
+};
+
+
+/**
+ * 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.dom.append = function(parent, var_args) {
+ goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1);
+};
+
+
+/**
+ * Removes all the child nodes on a DOM node.
+ * @param {Node} node Node to remove children from.
+ */
+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);
+ }
+};
+
+
+/**
+ * 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);
+ }
+};
+
+
+/**
+ * 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);
+ }
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * Removes a node from its parent.
+ * @param {Node} node The node to remove.
+ * @return {Node} The node removed if removed; else, null.
+ */
+goog.dom.removeNode = function(node) {
+ return node && node.parentNode ? node.parentNode.removeChild(node) : null;
+};
+
+
+/**
+ * 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);
+ }
+};
+
+
+/**
+ * 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));
+ }
+ }
+};
+
+
+/**
+ * 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.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;
+ });
+};
+
+
+/**
+ * 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.dom.getFirstElementChild = function(node) {
+ if (goog.isDef(node.firstElementChild)) {
+ return /** @type {!Element} */(node).firstElementChild;
+ }
+ return goog.dom.getNextElementNode_(node.firstChild, true);
+};
+
+
+/**
+ * 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.getLastElementChild = function(node) {
+ if (goog.isDef(node.lastElementChild)) {
+ return /** @type {!Element} */(node).lastElementChild;
+ }
+ return goog.dom.getNextElementNode_(node.lastChild, false);
+};
+
+
+/**
+ * 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.dom.getNextElementSibling = function(node) {
+ if (goog.isDef(node.nextElementSibling)) {
+ return /** @type {!Element} */(node).nextElementSibling;
+ }
+ return goog.dom.getNextElementNode_(node.nextSibling, true);
+};
+
+
+/**
+ * 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.dom.getPreviousElementSibling = function(node) {
+ if (goog.isDef(node.previousElementSibling)) {
+ return /** @type {!Element} */(node).previousElementSibling;
+ }
+ return goog.dom.getNextElementNode_(node.previousSibling, false);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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.dom.getNextNode = function(node) {
+ if (!node) {
+ return null;
+ }
+
+ if (node.firstChild) {
+ return node.firstChild;
+ }
+
+ while (node && !node.nextSibling) {
+ node = node.parentNode;
+ }
+
+ return node ? node.nextSibling : null;
+};
+
+
+/**
+ * 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;
+ }
+
+ if (!node.previousSibling) {
+ return node.parentNode;
+ }
+
+ node = node.previousSibling;
+ while (node && node.lastChild) {
+ node = node.lastChild;
+ }
+
+ 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.
+ */
+goog.dom.isNodeLike = function(obj) {
+ return goog.isObject(obj) && obj.nodeType > 0;
+};
+
+
+/**
+ * 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.dom.isElement = function(obj) {
+ return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT;
+};
+
+
+/**
+ * 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.dom.isWindow = function(obj) {
+ return goog.isObject(obj) && obj['window'] == obj;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+ }
+ return descendant == parent;
+};
+
+
+/**
+ * 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 {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;
+ }
+
+ // 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 (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);
+ }
+
+ if (!isElement1 && goog.dom.contains(parent1, node2)) {
+ return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2);
+ }
+
+
+ if (!isElement2 && goog.dom.contains(parent2, node1)) {
+ return goog.dom.compareParentsDescendantNodeIe_(node2, node1);
+ }
+
+ return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) -
+ (isElement2 ? node2.sourceIndex : parent2.sourceIndex);
+ }
+ }
+
+ // 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);
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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.compareSiblingOrder_ = function(node1, node2) {
+ var s = node2;
+ while ((s = s.previousSibling)) {
+ if (s == node1) {
+ // We just found node1 before node2.
+ return -1;
+ }
+ }
+
+ // Since we didn't find it, node1 must be after node2.
+ return 1;
+};
+
+
+/**
+ * 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.findCommonAncestor = function(var_args) {
+ var i, count = arguments.length;
+ if (!count) {
+ return null;
+ } else if (count == 1) {
+ return arguments[0];
+ }
+
+ 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;
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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.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)));
+ }
+};
+
+
+/**
+ * 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.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(goog.dom.TagName.DIV);
+ div.appendChild(element.cloneNode(true));
+ return div.innerHTML;
+ }
+};
+
+
+/**
+ * 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.findNode = function(root, p) {
+ var rv = [];
+ var found = goog.dom.findNodes_(root, p, rv, true);
+ return found ? rv[0] : undefined;
+};
+
+
+/**
+ * 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.findNodes = function(root, p) {
+ var rv = [];
+ goog.dom.findNodes_(root, p, rv, false);
+ return rv;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * Map of tags whose content to ignore when calculating text length.
+ * @private {!Object<string, number>}
+ * @const
+ */
+goog.dom.TAGS_TO_IGNORE_ = {
+ 'SCRIPT': 1,
+ 'STYLE': 1,
+ 'HEAD': 1,
+ 'IFRAME': 1,
+ 'OBJECT': 1
+};
+
+
+/**
+ * Map of tags which have predefined values with regard to whitespace.
+ * @private {!Object<string, string>}
+ * @const
+ */
+goog.dom.PREDEFINED_TAG_VALUES_ = {'IMG': ' ', 'BR': '\n'};
+
+
+/**
+ * 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.dom.isFocusableTabIndex = function(element) {
+ return goog.dom.hasSpecifiedTabIndex_(element) &&
+ goog.dom.isTabIndexFocusable_(element);
+};
+
+
+/**
+ * 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.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!
+ }
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * 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.
+ */
+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 &shy; 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 &shy; entities. goog.format.insertWordBreaks inserts them in Opera.
+ textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, '');
+ // Strip &#8203; 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 &nbsp; 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;
+};
+
+
+/**
+ * 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.dom.getRawTextContent = function(node) {
+ var buf = [];
+ goog.dom.getTextContent_(node, buf, false);
+
+ return buf.join('');
+};
+
+
+/**
+ * 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.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;
+ }
+ }
+};
+
+
+/**
+ * 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.dom.getNodeTextLength = function(node) {
+ return goog.dom.getTextContent(node).length;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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));
+};
+
+
+/**
+ * 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.dom.getAncestorByClass = function(element, className, opt_maxSearchSteps) {
+ return goog.dom.getAncestorByTagNameAndClass(element, null, className,
+ opt_maxSearchSteps);
+};
+
+
+/**
+ * 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.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)) {
+ goog.asserts.assert(element.name != 'parentNode');
+ if (matcher(element)) {
+ return element;
+ }
+ element = element.parentNode;
+ steps++;
+ }
+ // Reached the root of the DOM without a match
+ return null;
+};
+
+
+/**
+ * Determines the active element in the given document.
+ * @param {Document} doc The document to look in.
+ * @return {Element} The active element.
+ */
+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;
+};
+
+
+/**
+ * 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.
+ */
+goog.dom.getPixelRatio = function() {
+ var win = goog.dom.getWindow();
+ if (goog.isDef(win.devicePixelRatio)) {
+ 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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+
+/**
+ * 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.dom.DomHelper = function(opt_document) {
+ /**
+ * Reference to the document object to use
+ * @type {!Document}
+ * @private
+ */
+ this.document_ = opt_document || goog.global.document || document;
+};
+
+
+/**
+ * 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;
+
+
+/**
+ * Sets the document object.
+ * @param {!Document} document Document object.
+ */
+goog.dom.DomHelper.prototype.setDocument = function(document) {
+ this.document_ = document;
+};
+
+
+/**
+ * Gets the document object being used by the dom library.
+ * @return {!Document} Document object.
+ */
+goog.dom.DomHelper.prototype.getDocument = function() {
+ return this.document_;
+};
+
+
+/**
+ * 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.dom.DomHelper.prototype.getElement = function(element) {
+ return goog.dom.getElementHelper_(this.document_, element);
+};
+
+
+/**
+ * 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.dom.DomHelper.prototype.getRequiredElement = function(id) {
+ return goog.dom.getRequiredElementHelper_(this.document_, id);
+};
+
+
+/**
+ * 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.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement;
+
+
+/**
+ * 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.dom.DomHelper.prototype.getElementsByTagNameAndClass = function(opt_tag,
+ opt_class,
+ opt_el) {
+ return goog.dom.getElementsByTagNameAndClass_(this.document_, opt_tag,
+ opt_class, opt_el);
+};
+
+
+/**
+ * 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.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) {
+ var doc = opt_el || this.document_;
+ return goog.dom.getElementsByClass(className, doc);
+};
+
+
+/**
+ * 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.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) {
+ var doc = opt_el || this.document_;
+ return goog.dom.getElementByClass(className, doc);
+};
+
+
+/**
+ * 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.dom.DomHelper.prototype.getRequiredElementByClass = function(className,
+ opt_root) {
+ var root = opt_root || this.document_;
+ return goog.dom.getRequiredElementByClass(className, root);
+};
+
+
+/**
+ * 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.dom.DomHelper.prototype.$$ =
+ goog.dom.DomHelper.prototype.getElementsByTagNameAndClass;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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.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());
+};
+
+
+/**
+ * Calculates the height of the document.
+ *
+ * @return {number} The height of the document.
+ */
+goog.dom.DomHelper.prototype.getDocumentHeight = function() {
+ return goog.dom.getDocumentHeight_(this.getWindow());
+};
+
+
+/**
+ * Typedef for use with goog.dom.createDom and goog.dom.append.
+ * @typedef {Object|string|Array|NodeList}
+ */
+goog.dom.Appendable;
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom;
+
+
+/**
+ * Creates a new element.
+ * @param {string} name Tag name.
+ * @return {!Element} The new element.
+ */
+goog.dom.DomHelper.prototype.createElement = function(name) {
+ return this.document_.createElement(name);
+};
+
+
+/**
+ * Creates a new text node.
+ * @param {number|string} content Content.
+ * @return {!Text} The new text node.
+ */
+goog.dom.DomHelper.prototype.createTextNode = function(content) {
+ return this.document_.createTextNode(String(content));
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
+ * mode, false otherwise.
+ * @return {boolean} True if in CSS1-compatible mode.
+ */
+goog.dom.DomHelper.prototype.isCss1CompatMode = function() {
+ return goog.dom.isCss1CompatMode_(this.document_);
+};
+
+
+/**
+ * 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_);
+};
+
+
+/**
+ * Gets the document scroll element.
+ * @return {!Element} Scrolling element.
+ */
+goog.dom.DomHelper.prototype.getDocumentScrollElement = function() {
+ return goog.dom.getDocumentScrollElement_(this.document_);
+};
+
+
+/**
+ * Gets the document scroll distance as a coordinate object.
+ * @return {!goog.math.Coordinate} Object with properties 'x' and 'y'.
+ */
+goog.dom.DomHelper.prototype.getDocumentScroll = function() {
+ return goog.dom.getDocumentScroll_(this.document_);
+};
+
+
+/**
+ * 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_);
+};
+
+
+/**
+ * Appends a child to a node.
+ * @param {Node} parent Parent.
+ * @param {Node} child Child.
+ */
+goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild;
+
+
+/**
+ * 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.dom.DomHelper.prototype.append = goog.dom.append;
+
+
+/**
+ * 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.dom.DomHelper.prototype.canHaveChildren = goog.dom.canHaveChildren;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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.
+ */
+goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter;
+
+
+/**
+ * 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;
+
+
+/**
+ * Removes a node from its parent.
+ * @param {Node} node The node to remove.
+ * @return {Node} The node removed if removed; else, null.
+ */
+goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode;
+
+
+/**
+ * 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.DomHelper.prototype.replaceNode = goog.dom.replaceNode;
+
+
+/**
+ * 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.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement;
+
+
+/**
+ * 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.dom.DomHelper.prototype.getChildren = goog.dom.getChildren;
+
+
+/**
+ * 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.dom.DomHelper.prototype.getFirstElementChild =
+ goog.dom.getFirstElementChild;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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.dom.DomHelper.prototype.getNextElementSibling =
+ goog.dom.getNextElementSibling;
+
+
+/**
+ * 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.dom.DomHelper.prototype.getPreviousElementSibling =
+ goog.dom.getPreviousElementSibling;
+
+
+/**
+ * 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.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode;
+
+
+/**
+ * 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.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode;
+
+
+/**
+ * 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.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike;
+
+
+/**
+ * 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.dom.DomHelper.prototype.isElement = goog.dom.isElement;
+
+
+/**
+ * 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.dom.DomHelper.prototype.isWindow = goog.dom.isWindow;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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.dom.DomHelper.prototype.contains = goog.dom.contains;
+
+
+/**
+ * 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 {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.DomHelper.prototype.compareNodeOrder = goog.dom.compareNodeOrder;
+
+
+/**
+ * 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.
+ */
+goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument;
+
+
+/**
+ * Cross browser function for getting the document element of an iframe.
+ * @param {Element} iframe Iframe element.
+ * @return {!Document} The frame content document.
+ */
+goog.dom.DomHelper.prototype.getFrameContentDocument =
+ goog.dom.getFrameContentDocument;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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.DomHelper.prototype.setTextContent = goog.dom.setTextContent;
+
+
+/**
+ * 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.DomHelper.prototype.getOuterHtml = goog.dom.getOuterHtml;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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.dom.DomHelper.prototype.findNodes = goog.dom.findNodes;
+
+
+/**
+ * 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.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex;
+
+
+/**
+ * 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.DomHelper.prototype.setFocusableTabIndex =
+ goog.dom.setFocusableTabIndex;
+
+
+/**
+ * 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.DomHelper.prototype.isFocusable = goog.dom.isFocusable;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength;
+
+
+/**
+ * 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.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset;
+
+
+/**
+ * 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.dom.DomHelper.prototype.getNodeAtOffset = goog.dom.getNodeAtOffset;
+
+
+/**
+ * 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.dom.DomHelper.prototype.isNodeList = goog.dom.isNodeList;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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.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.
+
+/**
+ * @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.
+ *
+ * Note: these utilities are meant to operate on HTMLElements
+ * and may have unexpected behavior on elements with differing interfaces
+ * (such as SVGElements).
+ */
+
+
+goog.provide('goog.dom.classlist');
+
+goog.require('goog.array');
+
+
+/**
+ * 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);
+
+
+/**
+ * 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;
+ }
+
+ 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) || [];
+};
+
+
+/**
+ * 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.dom.classlist.set = function(element, className) {
+ element.className = className;
+};
+
+
+/**
+ * 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.dom.classlist.contains = function(element, className) {
+ if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) {
+ return element.classList.contains(className);
+ }
+ return goog.array.contains(goog.dom.classlist.get(element), className);
+};
+
+
+/**
+ * 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.dom.classlist.add = function(element, className) {
+ if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) {
+ element.classList.add(className);
+ return;
+ }
+
+ 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;
+ }
+};
+
+
+/**
+ * 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.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 = {};
+
+ // 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;
+ }
+};
+
+
+/**
+ * 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.dom.classlist.remove = function(element, className) {
+ if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) {
+ element.classList.remove(className);
+ return;
+ }
+
+ 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(' ');
+ }
+};
+
+
+/**
+ * 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.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(' ');
+};
+
+
+/**
+ * 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);
+ }
+};
+
+
+/**
+ * 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.dom.classlist.enableAll = function(element, classesToEnable, enabled) {
+ var f = enabled ? goog.dom.classlist.addAll :
+ goog.dom.classlist.removeAll;
+ f(element, classesToEnable);
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.dom.classlist.toggle = function(element, className) {
+ var add = !goog.dom.classlist.contains(element, className);
+ goog.dom.classlist.enable(element, className, add);
+ return add;
+};
+
+
+/**
+ * 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.dom.classlist.addRemove = function(element, classToRemove, classToAdd) {
+ goog.dom.classlist.remove(element, classToRemove);
+ goog.dom.classlist.add(element, classToAdd);
+};
+
+// 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.
+ */
+
+goog.provide('goog.dom.vendor');
+
+goog.require('goog.string');
+goog.require('goog.userAgent');
+
+
+/**
+ * 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.
+ */
+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';
+ }
+
+ return null;
+};
+
+
+/**
+ * Returns the vendor prefix used in CSS properties.
+ *
+ * @return {?string} The vendor prefix or null if there is none.
+ */
+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 null;
+};
+
+
+/**
+ * @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.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;
+};
+
+
+/**
+ * @param {string} eventType An event type.
+ * @return {string} A lower-cased vendor prefixed event type.
+ */
+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.
+
+/**
+ * @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.
+ *
+ * This class assumes 'screen coordinates': larger Y coordinates are further
+ * from the top of the screen.
+ *
+ * @param {number} top Top.
+ * @param {number} right Right.
+ * @param {number} bottom Bottom.
+ * @param {number} left Left.
+ * @struct
+ * @constructor
+ */
+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;
+};
+
+
+/**
+ * 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.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++) {
+ box.expandToIncludeCoordinate(arguments[i]);
+ }
+ return box;
+};
+
+
+/**
+ * @return {number} width The width of this Box.
+ */
+goog.math.Box.prototype.getWidth = function() {
+ return this.right - this.left;
+};
+
+
+/**
+ * @return {number} height The height of this Box.
+ */
+goog.math.Box.prototype.getHeight = function() {
+ return this.bottom - this.top;
+};
+
+
+/**
+ * Creates a copy of the box with the same dimensions.
+ * @return {!goog.math.Box} A clone of this Box.
+ */
+goog.math.Box.prototype.clone = function() {
+ return new goog.math.Box(this.top, this.right, this.bottom, this.left);
+};
+
+
+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.
+ */
+goog.math.Box.prototype.contains = function(other) {
+ return goog.math.Box.contains(this, other);
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * Expand this box to include the coordinate.
+ * @param {!goog.math.Coordinate} coord The coordinate to be included
+ * inside the box.
+ */
+goog.math.Box.prototype.expandToIncludeCoordinate = function(coord) {
+ this.top = Math.min(this.top, coord.y);
+ this.right = Math.max(this.right, coord.x);
+ this.bottom = Math.max(this.bottom, coord.y);
+ this.left = Math.min(this.left, coord.x);
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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}.
+ */
+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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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}.
+ */
+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);
+};
+
+
+/**
+ * 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.
+ */
+goog.math.Box.intersects = function(a, b) {
+ return (a.left <= b.right && b.left <= a.right &&
+ a.top <= b.bottom && b.top <= a.bottom);
+};
+
+
+/**
+ * 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.
+ */
+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);
+};
+
+
+/**
+ * Rounds the fields to the next larger integer values.
+ *
+ * @return {!goog.math.Box} This box with ceil'd fields.
+ */
+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;
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * Rounds the fields to nearest integer values.
+ *
+ * @return {!goog.math.Box} This box with rounded fields.
+ */
+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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.
+ */
+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.
+
+/**
+ * @fileoverview A utility class for representing rectangles.
+ */
+
+goog.provide('goog.math.Rect');
+
+goog.require('goog.math.Box');
+goog.require('goog.math.Coordinate');
+goog.require('goog.math.Size');
+
+
+
+/**
+ * Class for representing rectangular regions.
+ * @param {number} x Left.
+ * @param {number} y Top.
+ * @param {number} w Width.
+ * @param {number} h Height.
+ * @struct
+ * @constructor
+ */
+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;
+};
+
+
+/**
+ * @return {!goog.math.Rect} A new copy of this Rectangle.
+ */
+goog.math.Rect.prototype.clone = function() {
+ return new goog.math.Rect(this.left, this.top, this.width, this.height);
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * Creates a new Rect object with the position and size given.
+ * @param {!goog.math.Coordinate} position The top-left coordinate of the Rect
+ * @param {!goog.math.Size} size The size of the Rect
+ * @return {!goog.math.Rect} A new Rect initialized with the given position and
+ * size.
+ */
+goog.math.Rect.createFromPositionAndSize = function(position, size) {
+ return new goog.math.Rect(position.x, position.y, size.width, size.height);
+};
+
+
+/**
+ * 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.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)';
+ };
+}
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * Returns whether a rectangle intersects this rectangle.
+ * @param {goog.math.Rect} rect A rectangle.
+ * @return {boolean} Whether rect intersects this rectangle.
+ */
+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()];
+ }
+
+ 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;
+};
+
+
+/**
+ * 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.math.Rect.prototype.difference = function(rect) {
+ return goog.math.Rect.difference(this, rect);
+};
+
+
+/**
+ * Expand this rectangle to also include the area of the given rectangle.
+ * @param {goog.math.Rect} rect The other rectangle.
+ */
+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;
+};
+
+
+/**
+ * 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.math.Rect.boundingRect = function(a, b) {
+ if (!a || !b) {
+ return null;
+ }
+
+ 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;
+ }
+};
+
+
+/**
+ * @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.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;
+};
+
+
+/**
+ * @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.math.Rect.prototype.distance = function(point) {
+ return Math.sqrt(this.squaredDistance(point));
+};
+
+
+/**
+ * @return {!goog.math.Size} The size of this rectangle.
+ */
+goog.math.Rect.prototype.getSize = function() {
+ return new goog.math.Size(this.width, this.height);
+};
+
+
+/**
+ * @return {!goog.math.Coordinate} A new coordinate for the top-left corner of
+ * the rectangle.
+ */
+goog.math.Rect.prototype.getTopLeft = function() {
+ return new goog.math.Coordinate(this.left, this.top);
+};
+
+
+/**
+ * @return {!goog.math.Coordinate} A new coordinate for the center of the
+ * rectangle.
+ */
+goog.math.Rect.prototype.getCenter = function() {
+ return new goog.math.Coordinate(
+ this.left + this.width / 2, this.top + this.height / 2);
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * Rounds the fields to the next larger integer values.
+ * @return {!goog.math.Rect} This rectangle with ceil'd fields.
+ */
+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;
+};
+
+
+/**
+ * Rounds the fields to the next smaller integer values.
+ * @return {!goog.math.Rect} This rectangle with floored fields.
+ */
+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;
+};
+
+
+/**
+ * Rounds the fields to nearest integer values.
+ * @return {!goog.math.Rect} This rectangle with rounded fields.
+ */
+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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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.
+
+/**
+ * @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.provide('goog.style');
+
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.TagName');
+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.forwardDeclare('goog.events.BrowserEvent');
+goog.forwardDeclare('goog.events.Event');
+
+
+/**
+ * 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.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);
+ }
+ }
+};
+
+
+/**
+ * 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.setStyle_ = function(element, value, style) {
+ var propertyName = goog.style.getVendorJsStyleName_(element, style);
+
+ if (propertyName) {
+ element.style[propertyName] = value;
+ }
+};
+
+
+/**
+ * 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.styleNameCache_ = {};
+
+
+/**
+ * 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.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 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.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;
+};
+
+
+/**
+ * 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.
+ */
+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)] ||
+ '';
+};
+
+
+/**
+ * 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.
+ */
+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 '';
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getCascadedStyle = function(element, style) {
+ // TODO(nicksantos): This should be documented to return null. #fixTypes
+ return element.currentStyle ? element.currentStyle[style] : null;
+};
+
+
+/**
+ * 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
+ */
+goog.style.getStyle_ = function(element, style) {
+ return goog.style.getComputedStyle(element, style) ||
+ goog.style.getCascadedStyle(element, style) ||
+ (element.style && element.style[style]);
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * 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');
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getBackgroundColor = function(element) {
+ return goog.style.getStyle_(element, 'backgroundColor');
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getComputedOverflowX = function(element) {
+ return goog.style.getStyle_(element, 'overflowX');
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getComputedOverflowY = function(element) {
+ return goog.style.getStyle_(element, 'overflowY');
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getComputedZIndex = function(element) {
+ return goog.style.getStyle_(element, 'zIndex');
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getComputedTextAlign = function(element) {
+ return goog.style.getStyle_(element, 'textAlign');
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getComputedCursor = function(element) {
+ return goog.style.getStyle_(element, 'cursor');
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getComputedTransform = function(element) {
+ var property = goog.style.getVendorStyleName_(element, 'transform');
+ return goog.style.getStyle_(element, property) ||
+ goog.style.getStyle_(element, 'transform');
+};
+
+
+/**
+ * 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.
+ */
+goog.style.setPosition = function(el, arg1, opt_arg2) {
+ var x, y;
+
+ 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);
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getPosition = function(element) {
+ return new goog.math.Coordinate(
+ /** @type {!HTMLElement} */ (element).offsetLeft,
+ /** @type {!HTMLElement} */ (element).offsetTop);
+};
+
+
+/**
+ * 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();
+ }
+
+ // 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;
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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};
+ }
+
+ // 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.
+
+ // 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 rect;
+};
+
+
+/**
+ * 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.
+ */
+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) {
+ // Skip shadowDOM roots.
+ if (parent.nodeType == goog.dom.NodeType.DOCUMENT_FRAGMENT &&
+ parent.host) {
+ parent = parent.host;
+ }
+ 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;
+};
+
+
+/**
+ * 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.
+ */
+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);
+ }
+ }
+
+ // 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;
+};
+
+
+/**
+ * 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=} opt_container The container to scroll. If not set, then the
+ * document scroll element will be used.
+ * @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).
+ */
+goog.style.getContainerOffsetToScrollInto =
+ function(element, opt_container, opt_center) {
+ var container = opt_container || goog.dom.getDocumentScrollElement();
+ // 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);
+ if (container == goog.dom.getDocumentScrollElement()) {
+ // The element position is calculated based on the page offset, and the
+ // document scroll element holds the scroll position within the page. We can
+ // use the scroll position to calculate the relative position from the
+ // element.
+ var relX = elementPos.x - container.scrollLeft;
+ var relY = elementPos.y - container.scrollTop;
+ if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(10)) {
+ // In older versions of IE getPageOffset(element) does not include the
+ // container border so it has to be added to accomodate.
+ relX += containerBorder.left;
+ relY += containerBorder.top;
+ }
+ } else {
+ // 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 -
+ /** @type {HTMLElement} */ (element).offsetWidth;
+ var spaceY = container.clientHeight -
+ /** @type {HTMLElement} */ (element).offsetHeight;
+
+ 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));
+ }
+ return new goog.math.Coordinate(scrollLeft, scrollTop);
+};
+
+
+/**
+ * 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=} opt_container The container to scroll. If not set, then the
+ * document scroll element will be used.
+ * @param {boolean=} opt_center Whether to center the element in the container.
+ * Defaults to false.
+ */
+goog.style.scrollIntoContainerView =
+ function(element, opt_container, opt_center) {
+ var container = opt_container || goog.dom.getDocumentScrollElement();
+ var offset =
+ goog.style.getContainerOffsetToScrollInto(element, container, opt_center);
+ container.scrollLeft = offset.x;
+ container.scrollTop = offset.y;
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getClientLeftTop = function(el) {
+ return new goog.math.Coordinate(el.clientLeft, el.clientTop);
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getPageOffset = function(el) {
+ var doc = goog.dom.getOwnerDocument(el);
+ // 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.
+
+ // 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;
+};
+
+
+/**
+ * Returns the left coordinate of an element relative to the HTML document
+ * @param {Element} el Elements.
+ * @return {number} The left coordinate.
+ */
+goog.style.getPageOffsetLeft = function(el) {
+ return goog.style.getPageOffset(el).x;
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * 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.
+ */
+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 &&
+ currentWin != currentWin.parent &&
+ (currentEl = currentWin.frameElement) &&
+ (currentWin = currentWin.parent));
+
+ return position;
+};
+
+
+/**
+ * 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());
+
+ // Adjust Body's margin.
+ pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body));
+
+ 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;
+ }
+};
+
+
+/**
+ * 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.
+ */
+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
+ */
+goog.style.getClientPositionForElement_ = function(el) {
+ var box = goog.style.getBoundingClientRect_(el);
+ return new goog.math.Coordinate(box.left, box.top);
+};
+
+
+/**
+ * Returns the position of the event or the element's border box relative to
+ * the client viewport. If an event is passed, and if this event is a "touch"
+ * event, then the position of the first changedTouches will be returned.
+ * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event.
+ * @return {!goog.math.Coordinate} The position.
+ */
+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 targetEvent = el.changedTouches ? el.changedTouches[0] : el;
+ return new goog.math.Coordinate(
+ targetEvent.clientX,
+ targetEvent.clientY);
+ }
+};
+
+
+/**
+ * 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.
+ */
+goog.style.setPageOffset = function(el, x, opt_y) {
+ // Get current pageoffset
+ var cur = goog.style.getPageOffset(el);
+
+ if (x instanceof goog.math.Coordinate) {
+ opt_y = x.y;
+ x = x.x;
+ }
+
+ // NOTE(arv): We cannot allow strings for x and y. We could but that would
+ // require us to manually transform between different units
+
+ // Work out deltas
+ var dx = x - cur.x;
+ var dy = opt_y - cur.y;
+
+ // Set position to current left/top + delta
+ goog.style.setPosition(el, /** @type {!HTMLElement} */ (el).offsetLeft + dx,
+ /** @type {!HTMLElement} */ (el).offsetTop + dy);
+};
+
+
+/**
+ * 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.style.setWidth(element, /** @type {string|number} */ (w));
+ goog.style.setHeight(element, h);
+};
+
+
+/**
+ * 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';
+ }
+
+ return value;
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getSize = function(element) {
+ return goog.style.evaluateWithTemporaryDisplay_(
+ goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element));
+};
+
+
+/**
+ * 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
+ */
+goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) {
+ if (goog.style.getStyle_(element, 'display') != 'none') {
+ return fn(element);
+ }
+
+ 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);
+
+ style.display = originalDisplay;
+ style.position = originalPosition;
+ style.visibility = originalVisibility;
+
+ return retVal;
+};
+
+
+/**
+ * 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 = /** @type {!HTMLElement} */ (element).offsetWidth;
+ var offsetHeight = /** @type {!HTMLElement} */ (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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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.style.toSelectorCase = function(selector) {
+ return goog.string.toSelectorCase(selector);
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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.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 + ')';
+ }
+ }
+};
+
+
+/**
+ * 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.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';
+ }
+};
+
+
+/**
+ * Clears the background image of an element in a browser independent manner.
+ * @param {Element} el The element to clear background image for.
+ */
+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';
+ }
+};
+
+
+/**
+ * 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.style.showElement = function(el, display) {
+ goog.style.setElementShown(el, display);
+};
+
+
+/**
+ * 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.style.setElementShown = function(el, isShown) {
+ el.style.display = isShown ? '' : 'none';
+};
+
+
+/**
+ * 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.
+ *
+ * @param {Element} el The element to test.
+ * @return {boolean} Whether the element has been shown.
+ * @see #setElementShown
+ */
+goog.style.isElementShown = function(el) {
+ return el.style.display != 'none';
+};
+
+
+/**
+ * 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;
+
+ // 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(goog.dom.TagName.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(goog.dom.TagName.BODY)[0];
+ head = dh.createDom(goog.dom.TagName.HEAD);
+ body.parentNode.insertBefore(head, body);
+ }
+ styleSheet = dh.createDom(goog.dom.TagName.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;
+};
+
+
+/**
+ * Removes the styles added by {@link #installStyles}.
+ * @param {Element|StyleSheet} styleSheet The value returned by
+ * {@link #installStyles}.
+ */
+goog.style.uninstallStyles = function(styleSheet) {
+ var node = styleSheet.ownerNode || styleSheet.owningElement ||
+ /** @type {Element} */ (styleSheet);
+ goog.dom.removeNode(node);
+};
+
+
+/**
+ * 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.
+ */
+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 {
+ element.innerHTML = stylesString;
+ }
+};
+
+
+/**
+ * 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.
+ */
+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';
+ }
+};
+
+
+/**
+ * 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
+ */
+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 {
+ // Opera, Webkit, and Safari seem to do OK with the standard inline-block
+ // style.
+ style.display = 'inline-block';
+ }
+};
+
+
+/**
+ * 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.
+ */
+goog.style.isRightToLeft = function(el) {
+ return 'rtl' == goog.style.getStyle_(el, 'direction');
+};
+
+
+/**
+ * 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
+ */
+goog.style.unselectableStyle_ =
+ goog.userAgent.GECKO ? 'MozUserSelect' :
+ goog.userAgent.WEBKIT ? 'WebkitUserSelect' :
+ null;
+
+
+/**
+ * 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';
+ }
+ return false;
+};
+
+
+/**
+ * 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.
+ */
+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' : '';
+ // MathML elements do not have a style property. Verify before setting.
+ if (el.style) {
+ el.style[name] = value;
+ }
+ if (descendants) {
+ for (var i = 0, descendant; descendant = descendants[i]; i++) {
+ if (descendant.style) {
+ 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);
+ }
+ }
+ }
+};
+
+
+/**
+ * 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.
+ */
+goog.style.getBorderBoxSize = function(element) {
+ return new goog.math.Size(
+ /** @type {!HTMLElement} */ (element).offsetWidth,
+ /** @type {!HTMLElement} */ (element).offsetHeight);
+};
+
+
+/**
+ * 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.
+ */
+goog.style.setBorderBoxSize = 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) {
+ 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');
+ }
+};
+
+
+/**
+ * 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.
+ */
+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);
+ }
+};
+
+
+/**
+ * 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.
+ */
+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');
+ }
+};
+
+
+/**
+ * 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
+ */
+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;
+ }
+
+ // 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';
+};
+
+
+/**
+ * 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
+ */
+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;
+ }
+};
+
+
+/**
+ * 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
+ */
+goog.style.getIePixelDistance_ = function(element, propName) {
+ var value = goog.style.getCascadedStyle(element, propName);
+ return value ?
+ goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : 0;
+};
+
+
+/**
+ * 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 = goog.style.getComputedStyle(element, stylePrefix + 'Left');
+ var right = goog.style.getComputedStyle(element, stylePrefix + 'Right');
+ var top = goog.style.getComputedStyle(element, stylePrefix + 'Top');
+ var bottom = goog.style.getComputedStyle(element, stylePrefix + 'Bottom');
+
+ // 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));
+ }
+};
+
+
+/**
+ * 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');
+};
+
+
+/**
+ * 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.style.getMarginBox = function(element) {
+ return goog.style.getBox_(element, 'margin');
+};
+
+
+/**
+ * A map used to map the border width keywords to a pixel width.
+ * @type {Object}
+ * @private
+ */
+goog.style.ieBorderWidthKeywords_ = {
+ 'thin': 2,
+ 'medium': 4,
+ 'thick': 6
+};
+
+
+/**
+ * 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.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');
+};
+
+
+/**
+ * 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.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 = goog.style.getComputedStyle(element, 'borderLeftWidth');
+ var right = goog.style.getComputedStyle(element, 'borderRightWidth');
+ var top = goog.style.getComputedStyle(element, 'borderTopWidth');
+ var bottom = goog.style.getComputedStyle(element, 'borderBottomWidth');
+
+ return new goog.math.Box(parseFloat(top),
+ parseFloat(right),
+ parseFloat(bottom),
+ parseFloat(left));
+ }
+};
+
+
+/**
+ * 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.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, '"\'');
+};
+
+
+/**
+ * Regular expression used for getLengthUnits.
+ * @type {RegExp}
+ * @private
+ */
+goog.style.lengthUnitRegex_ = /[^\d]+$/;
+
+
+/**
+ * Returns the units used for a CSS length measurement.
+ * @param {string} value A CSS length quantity.
+ * @return {?string} The units of measurement.
+ */
+goog.style.getLengthUnits = function(value) {
+ var units = value.match(goog.style.lengthUnitRegex_);
+ return units && units[0] || null;
+};
+
+
+/**
+ * Map of absolute CSS length units
+ * @type {Object}
+ * @private
+ */
+goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = {
+ 'cm' : 1,
+ 'in' : 1,
+ 'mm' : 1,
+ 'pc' : 1,
+ 'pt' : 1
+};
+
+
+/**
+ * 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
+};
+
+
+/**
+ * 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.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');
+ }
+ }
+
+ // 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(
+ goog.dom.TagName.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;
+};
+
+
+/**
+ * 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.style.parseStyleAttribute = function(value) {
+ var result = {};
+ goog.array.forEach(value.split(/\s*;\s*/), function(pair) {
+ var keyValue = pair.match(/\s*([\w-]+)\s*\:(.+)/);
+ if (keyValue) {
+ var styleName = keyValue[1];
+ var styleValue = goog.string.trim(keyValue[2]);
+ result[goog.string.toCamelCase(styleName.toLowerCase())] = styleValue;
+ }
+ });
+ 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.
+ */
+goog.style.toStyleAttribute = function(obj) {
+ var buffer = [];
+ goog.object.forEach(obj, function(value, key) {
+ buffer.push(goog.string.toSelectorCase(key), ':', value, ';');
+ });
+ return buffer.join('');
+};
+
+
+/**
+ * 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.style.setFloat = function(el, value) {
+ el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value;
+};
+
+
+/**
+ * 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.style.getFloat = function(el) {
+ return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || '';
+};
+
+
+/**
+ * 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.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(goog.dom.TagName.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(goog.dom.TagName.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;
+};
+
+
+/**
+ * Regular expression to extract x and y translation components from a CSS
+ * transform Matrix representation.
+ *
+ * @type {!RegExp}
+ * @const
+ * @private
+ */
+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 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.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');
+
+
+/**
+ * @enum {string}
+ */
+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'
+
+};
+
+
+
+/**
+ * @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.
+ */
+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 = opt_frameState !== undefined ? opt_frameState : null;
+
+};
+goog.inherits(ol.MapEvent, goog.events.Event);
+
+goog.provide('ol.control.Control');
+
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('ol');
+goog.require('ol.MapEventType');
+goog.require('ol.Object');
+
+
+
+/**
+ * @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.
+ *
+ * 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
+ */
+ol.control.Control = function(options) {
+
+ goog.base(this);
+
+ /**
+ * @protected
+ * @type {Element}
+ */
+ this.element = 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 = options.render ? options.render : ol.nullFunction;
+
+ if (options.target) {
+ this.setTarget(options.target);
+ }
+
+};
+goog.inherits(ol.control.Control, ol.Object);
+
+
+/**
+ * @inheritDoc
+ */
+ol.control.Control.prototype.disposeInternal = function() {
+ goog.dom.removeNode(this.element);
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * Get the map associated with this control.
+ * @return {ol.Map} Map.
+ * @api stable
+ */
+ol.control.Control.prototype.getMap = function() {
+ return this.map_;
+};
+
+
+/**
+ * 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
+ */
+ol.control.Control.prototype.setMap = function(map) {
+ if (this.map_) {
+ goog.dom.removeNode(this.element);
+ }
+ if (this.listenerKeys.length > 0) {
+ this.listenerKeys.forEach(goog.events.unlistenByKey);
+ this.listenerKeys.length = 0;
+ }
+ this.map_ = map;
+ if (this.map_) {
+ var target = this.target_ ?
+ this.target_ : map.getOverlayContainerStopEvent();
+ target.appendChild(this.element);
+ if (this.render !== ol.nullFunction) {
+ this.listenerKeys.push(goog.events.listen(map,
+ ol.MapEventType.POSTRENDER, this.render, false, this));
+ }
+ map.render();
+ }
+};
+
+
+/**
+ * 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
+ */
+ol.control.Control.prototype.setTarget = function(target) {
+ this.target_ = goog.dom.getElement(target);
+};
+
+goog.provide('ol.css');
+
+
+/**
+ * The CSS class for hidden feature.
+ *
+ * @const
+ * @type {string}
+ */
+ol.css.CLASS_HIDDEN = 'ol-hidden';
+
+
+/**
+ * The CSS class that we'll give the DOM elements to have them unselectable.
+ *
+ * @const
+ * @type {string}
+ */
+ol.css.CLASS_UNSELECTABLE = 'ol-unselectable';
+
+
+/**
+ * The CSS class for unsupported feature.
+ *
+ * @const
+ * @type {string}
+ */
+ol.css.CLASS_UNSUPPORTED = 'ol-unsupported';
+
+
+/**
+ * The CSS class for controls.
+ *
+ * @const
+ * @type {string}
+ */
+ol.css.CLASS_CONTROL = 'ol-control';
+
+goog.provide('ol.structs.LRUCache');
+
+goog.require('goog.asserts');
+goog.require('goog.object');
+
+
+
+/**
+ * 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
+ */
+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;
+
+};
+
+
+/**
+ * FIXME empty description for jsdoc
+ */
+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(!this.oldest_,
+ 'oldest must be null (count = 0)');
+ goog.asserts.assert(!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(this.oldest_,
+ 'we have an oldest entry');
+ goog.asserts.assert(!this.oldest_.older,
+ 'no entry is older than oldest');
+ goog.asserts.assert(this.newest_,
+ 'we have a newest entry');
+ goog.asserts.assert(!this.newest_.newer,
+ 'no entry is newer than newest');
+ var i, entry;
+ var older = null;
+ i = 0;
+ for (entry = this.oldest_; 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_; 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');
+ }
+};
+
+
+/**
+ * FIXME empty description for jsdoc
+ */
+ol.structs.LRUCache.prototype.clear = function() {
+ this.count_ = 0;
+ this.entries_ = {};
+ this.oldest_ = null;
+ this.newest_ = null;
+};
+
+
+/**
+ * @param {string} key Key.
+ * @return {boolean} Contains key.
+ */
+ol.structs.LRUCache.prototype.containsKey = function(key) {
+ return this.entries_.hasOwnProperty(key);
+};
+
+
+/**
+ * @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
+ */
+ol.structs.LRUCache.prototype.forEach = function(f, opt_this) {
+ var entry = this.oldest_;
+ while (entry) {
+ f.call(opt_this, entry.value_, entry.key_, this);
+ entry = entry.newer;
+ }
+};
+
+
+/**
+ * @param {string} key Key.
+ * @return {T} Value.
+ */
+ol.structs.LRUCache.prototype.get = function(key) {
+ var entry = this.entries_[key];
+ goog.asserts.assert(entry !== undefined, '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;
+ }
+ entry.newer = null;
+ entry.older = this.newest_;
+ this.newest_.newer = entry;
+ this.newest_ = entry;
+ return entry.value_;
+};
+
+
+/**
+ * @return {number} Count.
+ */
+ol.structs.LRUCache.prototype.getCount = function() {
+ return this.count_;
+};
+
+
+/**
+ * @return {Array.<string>} Keys.
+ */
+ol.structs.LRUCache.prototype.getKeys = function() {
+ var keys = new Array(this.count_);
+ var i = 0;
+ var entry;
+ for (entry = this.newest_; entry; entry = entry.older) {
+ keys[i++] = entry.key_;
+ }
+ goog.asserts.assert(i == this.count_, 'iterated correct number of times');
+ return keys;
+};
+
+
+/**
+ * @return {Array.<T>} Values.
+ */
+ol.structs.LRUCache.prototype.getValues = function() {
+ var values = new Array(this.count_);
+ var i = 0;
+ var entry;
+ for (entry = this.newest_; entry; entry = entry.older) {
+ values[i++] = entry.value_;
+ }
+ goog.asserts.assert(i == this.count_, 'iterated correct number of times');
+ return values;
+};
+
+
+/**
+ * @return {T} Last value.
+ */
+ol.structs.LRUCache.prototype.peekLast = function() {
+ goog.asserts.assert(this.oldest_, 'oldest must not be null');
+ return this.oldest_.value_;
+};
+
+
+/**
+ * @return {string} Last key.
+ */
+ol.structs.LRUCache.prototype.peekLastKey = function() {
+ goog.asserts.assert(this.oldest_, 'oldest must not be null');
+ return this.oldest_.key_;
+};
+
+
+/**
+ * @return {T} value Value.
+ */
+ol.structs.LRUCache.prototype.pop = function() {
+ goog.asserts.assert(this.oldest_, 'oldest must not be null');
+ goog.asserts.assert(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 (entry.newer) {
+ entry.newer.older = null;
+ }
+ this.oldest_ = entry.newer;
+ if (!this.oldest_) {
+ this.newest_ = null;
+ }
+ --this.count_;
+ return entry.value_;
+};
+
+
+/**
+ * @param {string} key Key.
+ * @param {T} value Value.
+ */
+ol.structs.LRUCache.prototype.replace = function(key, value) {
+ this.get(key); // update `newest_`
+ this.entries_[key].value_ = value;
+};
+
+
+/**
+ * @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 (!this.newest_) {
+ this.oldest_ = entry;
+ } else {
+ this.newest_.newer = entry;
+ }
+ this.newest_ = entry;
+ this.entries_[key] = entry;
+ ++this.count_;
+};
+
+
+/**
+ * @typedef {{key_: string,
+ * newer: ol.structs.LRUCacheEntry,
+ * older: ol.structs.LRUCacheEntry,
+ * value_: *}}
+ */
+ol.structs.LRUCacheEntry;
+
+goog.provide('ol.TileCache');
+
+goog.require('ol');
+goog.require('ol.TileRange');
+goog.require('ol.structs.LRUCache');
+goog.require('ol.tilecoord');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.structs.LRUCache.<ol.Tile>}
+ * @param {number=} opt_highWaterMark High water mark.
+ * @struct
+ */
+ol.TileCache = function(opt_highWaterMark) {
+
+ goog.base(this);
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.highWaterMark_ = opt_highWaterMark !== undefined ?
+ opt_highWaterMark : ol.DEFAULT_TILE_CACHE_HIGH_WATER_MARK;
+
+};
+goog.inherits(ol.TileCache, ol.structs.LRUCache);
+
+
+/**
+ * @return {boolean} Can expire cache.
+ */
+ol.TileCache.prototype.canExpireCache = function() {
+ return this.getCount() > this.highWaterMark_;
+};
+
+
+/**
+ * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
+ */
+ol.TileCache.prototype.expireCache = function(usedTiles) {
+ var tile, zKey;
+ while (this.canExpireCache()) {
+ tile = this.peekLast();
+ zKey = tile.tileCoord[0].toString();
+ if (zKey in usedTiles && usedTiles[zKey].contains(tile.tileCoord)) {
+ break;
+ } else {
+ this.pop().dispose();
+ }
+ }
+};
+
+
+/**
+ * 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();
+ } else {
+ this.get(key);
+ }
+ }
+};
+
+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;
+
+ /**
+ * An "interim" tile for this tile. The interim tile may be used while this
+ * one is loading, for "smooth" transitions when changing params/dimensions
+ * on the source.
+ * @type {ol.Tile}
+ */
+ this.interimTile = null;
+
+ /**
+ * A key assigned to the tile. This is used by the tile source to determine
+ * if this tile can effectively be used, or if a new tile should be created
+ * and this one be used as an interim tile for this new tile.
+ * @type {string}
+ */
+ this.key = '';
+
+};
+goog.inherits(ol.Tile, goog.events.EventTarget);
+
+
+/**
+ * @protected
+ */
+ol.Tile.prototype.changed = function() {
+ this.dispatchEvent(goog.events.EventType.CHANGE);
+};
+
+
+/**
+ * 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;
+
+
+/**
+ * @return {string} Key.
+ */
+ol.Tile.prototype.getKey = function() {
+ return goog.getUid(this).toString();
+};
+
+
+/**
+ * Get the tile coordinate for this tile.
+ * @return {ol.TileCoord}
+ * @api
+ */
+ol.Tile.prototype.getTileCoord = function() {
+ return this.tileCoord;
+};
+
+
+/**
+ * @return {ol.TileState} State.
+ */
+ol.Tile.prototype.getState = function() {
+ return this.state;
+};
+
+
+/**
+ * FIXME empty description for jsdoc
+ */
+ol.Tile.prototype.load = goog.abstractMethod;
+
+goog.provide('ol.source.Source');
+goog.provide('ol.source.State');
+
+goog.require('ol');
+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;
+
+
+
+/**
+ * @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 {ol.Object}
+ * @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_ = options.attributions !== undefined ?
+ options.attributions : null;
+
+ /**
+ * @private
+ * @type {string|olx.LogoOptions|undefined}
+ */
+ this.logo_ = options.logo;
+
+ /**
+ * @private
+ * @type {ol.source.State}
+ */
+ this.state_ = options.state !== undefined ?
+ options.state : ol.source.State.READY;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.wrapX_ = options.wrapX !== undefined ? options.wrapX : false;
+
+};
+goog.inherits(ol.source.Source, ol.Object);
+
+
+/**
+ * @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|ol.render.Feature)): T} callback Feature
+ * callback.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+ol.source.Source.prototype.forEachFeatureAtCoordinate = ol.nullFunction;
+
+
+/**
+ * Get the attributions of the source.
+ * @return {Array.<ol.Attribution>} Attributions.
+ * @api stable
+ */
+ol.source.Source.prototype.getAttributions = function() {
+ return this.attributions_;
+};
+
+
+/**
+ * Get the logo of the source.
+ * @return {string|olx.LogoOptions|undefined} Logo.
+ * @api stable
+ */
+ol.source.Source.prototype.getLogo = function() {
+ return this.logo_;
+};
+
+
+/**
+ * Get the projection of the source.
+ * @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;
+
+
+/**
+ * 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 {boolean|undefined} Wrap X.
+ */
+ol.source.Source.prototype.getWrapX = function() {
+ return this.wrapX_;
+};
+
+
+/**
+ * Set the attributions of the source.
+ * @param {Array.<ol.Attribution>} attributions Attributions.
+ * @api
+ */
+ol.source.Source.prototype.setAttributions = function(attributions) {
+ this.attributions_ = attributions;
+ this.changed();
+};
+
+
+/**
+ * Set the logo of the source.
+ * @param {string|olx.LogoOptions|undefined} logo Logo.
+ */
+ol.source.Source.prototype.setLogo = function(logo) {
+ this.logo_ = logo;
+};
+
+
+/**
+ * Set the state of the source.
+ * @param {ol.source.State} state State.
+ * @protected
+ */
+ol.source.Source.prototype.setState = function(state) {
+ this.state_ = state;
+ this.changed();
+};
+
+
+/**
+ * Set the projection of the source.
+ * @param {ol.proj.Projection} projection Projection.
+ */
+ol.source.Source.prototype.setProjection = function(projection) {
+ this.projection_ = projection;
+};
+
+goog.provide('ol.tilegrid.TileGrid');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+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.math');
+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');
+
+
+
+/**
+ * @classdesc
+ * Base class for setting the grid pattern for sources accessing tiled-image
+ * servers.
+ *
+ * @constructor
+ * @param {olx.tilegrid.TileGridOptions} options Tile grid options.
+ * @struct
+ * @api stable
+ */
+ol.tilegrid.TileGrid = function(options) {
+
+ /**
+ * @protected
+ * @type {number}
+ */
+ this.minZoom = options.minZoom !== undefined ? 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');
+
+ /**
+ * @protected
+ * @type {number}
+ */
+ this.maxZoom = this.resolutions_.length - 1;
+
+ /**
+ * @private
+ * @type {ol.Coordinate}
+ */
+ this.origin_ = options.origin !== undefined ? options.origin : null;
+
+ /**
+ * @private
+ * @type {Array.<ol.Coordinate>}
+ */
+ this.origins_ = null;
+ if (options.origins !== undefined) {
+ this.origins_ = options.origins;
+ goog.asserts.assert(this.origins_.length == this.resolutions_.length,
+ 'number of origins and resolutions must be equal');
+ }
+
+ var extent = options.extent;
+
+ if (extent !== undefined &&
+ !this.origin_ && !this.origins_) {
+ this.origin_ = ol.extent.getTopLeft(extent);
+ }
+
+ goog.asserts.assert(
+ (!this.origin_ && this.origins_) ||
+ (this.origin_ && !this.origins_),
+ 'either origin or origins must be configured, never both');
+
+ /**
+ * @private
+ * @type {Array.<number|ol.Size>}
+ */
+ this.tileSizes_ = null;
+ if (options.tileSizes !== undefined) {
+ this.tileSizes_ = options.tileSizes;
+ goog.asserts.assert(this.tileSizes_.length == this.resolutions_.length,
+ 'number of tileSizes and resolutions must be equal');
+ }
+
+ /**
+ * @private
+ * @type {number|ol.Size}
+ */
+ this.tileSize_ = options.tileSize !== undefined ?
+ options.tileSize :
+ !this.tileSizes_ ? ol.DEFAULT_TILE_SIZE : null;
+ goog.asserts.assert(
+ (!this.tileSize_ && this.tileSizes_) ||
+ (this.tileSize_ && !this.tileSizes_),
+ 'either tileSize or tileSizes must be configured, never both');
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.extent_ = extent !== undefined ? extent : null;
+
+
+ /**
+ * @private
+ * @type {Array.<ol.TileRange>}
+ */
+ this.fullTileRanges_ = null;
+
+ if (options.sizes !== undefined) {
+ goog.asserts.assert(options.sizes.length == this.resolutions_.length,
+ 'number of sizes and resolutions must be equal');
+ this.fullTileRanges_ = options.sizes.map(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 && extent !== undefined) {
+ goog.asserts.assert(tileRange.containsTileRange(
+ this.getTileRangeForExtentAndZ(extent, z)),
+ 'extent tile range must not exceed tilegrid width and height');
+ }
+ return tileRange;
+ }, this);
+ } else if (extent) {
+ this.calculateTileRanges_(extent);
+ }
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.tmpSize_ = [0, 0];
+
+};
+
+
+/**
+ * @private
+ * @type {ol.TileCoord}
+ */
+ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0];
+
+
+/**
+ * @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
+ */
+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;
+};
+
+
+/**
+ * Get the extent for this tile grid, if it was configured.
+ * @return {ol.Extent} Extent.
+ */
+ol.tilegrid.TileGrid.prototype.getExtent = function() {
+ return this.extent_;
+};
+
+
+/**
+ * Get the maximum zoom level for the grid.
+ * @return {number} Max zoom.
+ * @api
+ */
+ol.tilegrid.TileGrid.prototype.getMaxZoom = function() {
+ return this.maxZoom;
+};
+
+
+/**
+ * Get the minimum zoom level for the grid.
+ * @return {number} Min zoom.
+ * @api
+ */
+ol.tilegrid.TileGrid.prototype.getMinZoom = function() {
+ return this.minZoom;
+};
+
+
+/**
+ * Get the origin for the grid at the given zoom level.
+ * @param {number} z Z.
+ * @return {ol.Coordinate} Origin.
+ * @api stable
+ */
+ol.tilegrid.TileGrid.prototype.getOrigin = function(z) {
+ if (this.origin_) {
+ return this.origin_;
+ } else {
+ goog.asserts.assert(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];
+ }
+};
+
+
+/**
+ * Get the resolution for the given zoom level.
+ * @param {number} z Z.
+ * @return {number} Resolution.
+ * @api stable
+ */
+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];
+};
+
+
+/**
+ * Get the list of resolutions for the tile grid.
+ * @return {Array.<number>} Resolutions.
+ * @api stable
+ */
+ol.tilegrid.TileGrid.prototype.getResolutions = function() {
+ return this.resolutions_;
+};
+
+
+/**
+ * @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.
+ */
+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;
+ }
+};
+
+
+/**
+ * @param {number} z Z.
+ * @param {ol.TileRange} tileRange Tile range.
+ * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
+ * @return {ol.Extent} Extent.
+ */
+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);
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @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 = 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
+ ];
+};
+
+
+/**
+ * Get the extent of a tile coordinate.
+ *
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.Extent=} opt_extent Temporary extent object.
+ * @return {ol.Extent} Extent.
+ * @api
+ */
+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);
+};
+
+
+/**
+ * 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
+ */
+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 {
+ tileCoordX = Math.floor(tileCoordX);
+ tileCoordY = Math.floor(tileCoordY);
+ }
+
+ 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 (this.tileSize_) {
+ return this.tileSize_;
+ } else {
+ goog.asserts.assert(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];
+ }
+};
+
+
+/**
+ * @param {number} z Zoom level.
+ * @return {ol.TileRange} Extent tile range for the specified zoom level.
+ */
+ol.tilegrid.TileGrid.prototype.getFullTileRange = function(z) {
+ if (!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];
+ }
+};
+
+
+/**
+ * @param {number} resolution Resolution.
+ * @return {number} Z.
+ */
+ol.tilegrid.TileGrid.prototype.getZForResolution = function(resolution) {
+ var z = ol.array.linearFindNearest(this.resolutions_, resolution, 0);
+ return ol.math.clamp(z, this.minZoom, this.maxZoom);
+};
+
+
+/**
+ * @param {!ol.Extent} extent Extent for this tile grid.
+ * @private
+ */
+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);
+ }
+ this.fullTileRanges_ = fullTileRanges;
+};
+
+
+/**
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {ol.tilegrid.TileGrid} Default tile grid for the passed projection.
+ */
+ol.tilegrid.getForProjection = function(projection) {
+ var tileGrid = projection.getDefaultTileGrid();
+ if (!tileGrid) {
+ tileGrid = ol.tilegrid.createForProjection(projection);
+ projection.setDefaultTileGrid(tileGrid);
+ }
+ return tileGrid;
+};
+
+
+/**
+ * @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.
+ */
+ol.tilegrid.createForExtent =
+ function(extent, opt_maxZoom, opt_tileSize, opt_corner) {
+ var corner = opt_corner !== undefined ?
+ 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
+ });
+};
+
+
+/**
+ * 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
+ */
+ol.tilegrid.createXYZ = function(opt_options) {
+ var options = /** @type {olx.tilegrid.TileGridOptions} */ ({});
+ goog.object.extend(options, opt_options !== undefined ?
+ opt_options : /** @type {olx.tilegrid.XYZOptions} */ ({}));
+ if (options.extent === undefined) {
+ 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);
+};
+
+
+/**
+ * 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.
+ */
+ol.tilegrid.resolutionsFromExtent =
+ function(extent, opt_maxZoom, opt_tileSize) {
+ var maxZoom = opt_maxZoom !== undefined ?
+ opt_maxZoom : ol.DEFAULT_MAX_ZOOM;
+
+ var height = ol.extent.getHeight(extent);
+ var width = ol.extent.getWidth(extent);
+
+ var tileSize = ol.size.toSize(opt_tileSize !== undefined ?
+ 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;
+};
+
+
+/**
+ * @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.
+ */
+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);
+};
+
+
+/**
+ * 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 (!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.TileEvent');
+goog.provide('ol.source.TileOptions');
+
+goog.require('goog.asserts');
+goog.require('goog.events.Event');
+goog.require('ol');
+goog.require('ol.Attribution');
+goog.require('ol.Extent');
+goog.require('ol.TileCache');
+goog.require('ol.TileRange');
+goog.require('ol.TileState');
+goog.require('ol.proj');
+goog.require('ol.size');
+goog.require('ol.source.Source');
+goog.require('ol.tilecoord');
+goog.require('ol.tilegrid.TileGrid');
+
+
+/**
+ * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
+ * cacheSize: (number|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)}}
+ */
+ol.source.TileOptions;
+
+
+
+/**
+ * @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
+ */
+ol.source.Tile = function(options) {
+
+ goog.base(this, {
+ attributions: options.attributions,
+ extent: options.extent,
+ logo: options.logo,
+ projection: options.projection,
+ state: options.state,
+ wrapX: options.wrapX
+ });
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.opaque_ = options.opaque !== undefined ? options.opaque : false;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.tilePixelRatio_ = options.tilePixelRatio !== undefined ?
+ options.tilePixelRatio : 1;
+
+ /**
+ * @protected
+ * @type {ol.tilegrid.TileGrid}
+ */
+ this.tileGrid = options.tileGrid !== undefined ? options.tileGrid : null;
+
+ /**
+ * @protected
+ * @type {ol.TileCache}
+ */
+ this.tileCache = new ol.TileCache(options.cacheSize);
+
+ /**
+ * @protected
+ * @type {ol.Size}
+ */
+ this.tmpSize = [0, 0];
+
+};
+goog.inherits(ol.source.Tile, ol.source.Source);
+
+
+/**
+ * @return {boolean} Can expire cache.
+ */
+ol.source.Tile.prototype.canExpireCache = function() {
+ return this.tileCache.canExpireCache();
+};
+
+
+/**
+ * @param {ol.proj.Projection} projection Projection.
+ * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
+ */
+ol.source.Tile.prototype.expireCache = function(projection, usedTiles) {
+ var tileCache = this.getTileCacheForProjection(projection);
+ if (tileCache) {
+ tileCache.expireCache(usedTiles);
+ }
+};
+
+
+/**
+ * @param {ol.proj.Projection} projection Projection.
+ * @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.
+ */
+ol.source.Tile.prototype.forEachLoadedTile =
+ function(projection, z, tileRange, callback) {
+ var tileCache = this.getTileCacheForProjection(projection);
+ if (!tileCache) {
+ return false;
+ }
+
+ 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 (tileCache.containsKey(tileCoordKey)) {
+ tile = /** @type {!ol.Tile} */ (tileCache.get(tileCoordKey));
+ loaded = tile.getState() === ol.TileState.LOADED;
+ if (loaded) {
+ loaded = (callback(tile) !== false);
+ }
+ }
+ if (!loaded) {
+ covered = false;
+ }
+ }
+ }
+ return covered;
+};
+
+
+/**
+ * @return {number} Gutter.
+ */
+ol.source.Tile.prototype.getGutter = function() {
+ return 0;
+};
+
+
+/**
+ * Return the "parameters" key, a string composed of the source's
+ * parameters/dimensions.
+ * @return {string} The parameters key.
+ * @protected
+ */
+ol.source.Tile.prototype.getKeyParams = function() {
+ return '';
+};
+
+
+/**
+ * @param {number} z Z.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @return {string} Key.
+ * @protected
+ */
+ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY;
+
+
+/**
+ * @return {boolean} Opaque.
+ */
+ol.source.Tile.prototype.getOpaque = function() {
+ return this.opaque_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.Tile.prototype.getResolutions = function() {
+ return this.tileGrid.getResolutions();
+};
+
+
+/**
+ * @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} projection Projection.
+ * @return {!ol.Tile} Tile.
+ */
+ol.source.Tile.prototype.getTile = goog.abstractMethod;
+
+
+/**
+ * Return the tile grid of the tile source.
+ * @return {ol.tilegrid.TileGrid} Tile grid.
+ * @api stable
+ */
+ol.source.Tile.prototype.getTileGrid = function() {
+ return this.tileGrid;
+};
+
+
+/**
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {ol.tilegrid.TileGrid} Tile grid.
+ */
+ol.source.Tile.prototype.getTileGridForProjection = function(projection) {
+ if (!this.tileGrid) {
+ return ol.tilegrid.getForProjection(projection);
+ } else {
+ return this.tileGrid;
+ }
+};
+
+
+/**
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {ol.TileCache} Tile cache.
+ * @protected
+ */
+ol.source.Tile.prototype.getTileCacheForProjection = function(projection) {
+ var thisProj = this.getProjection();
+ if (thisProj && !ol.proj.equivalent(thisProj, projection)) {
+ return null;
+ } else {
+ return this.tileCache;
+ }
+};
+
+
+/**
+ * @return {number} Tile pixel ratio.
+ */
+ol.source.Tile.prototype.getTilePixelRatio = function() {
+ return this.tilePixelRatio_;
+};
+
+
+/**
+ * @param {number} z Z.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {ol.Size} Tile size.
+ */
+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);
+};
+
+
+/**
+ * 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`.
+ */
+ol.source.Tile.prototype.getTileCoordForTileUrlFunction =
+ function(tileCoord, opt_projection) {
+ var projection = opt_projection !== undefined ?
+ opt_projection : this.getProjection();
+ var tileGrid = this.getTileGridForProjection(projection);
+ goog.asserts.assert(tileGrid, 'tile grid needed');
+ if (this.getWrapX() && projection.isGlobal()) {
+ tileCoord = ol.tilecoord.wrapX(tileCoord, tileGrid, projection);
+ }
+ return ol.tilecoord.withinExtentAndZ(tileCoord, tileGrid) ? tileCoord : null;
+};
+
+
+/**
+ * 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.
+ * @param {ol.proj.Projection} projection Projection.
+ */
+ol.source.Tile.prototype.useTile = ol.nullFunction;
+
+
+
+/**
+ * @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.
+ */
+ol.source.TileEvent = function(type, tile) {
+
+ goog.base(this, type);
+
+ /**
+ * The tile related to the event.
+ * @type {ol.Tile}
+ * @api
+ */
+ this.tile = tile;
+
+};
+goog.inherits(ol.source.TileEvent, goog.events.Event);
+
+
+/**
+ * @enum {string}
+ */
+ol.source.TileEventType = {
+
+ /**
+ * Triggered when a tile starts loading.
+ * @event ol.source.TileEvent#tileloadstart
+ * @api stable
+ */
+ TILELOADSTART: 'tileloadstart',
+
+ /**
+ * 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'
+
+};
+
+// FIXME handle date line wrap
+
+goog.provide('ol.control.Attribution');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+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');
+goog.require('ol.Attribution');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.source.Tile');
+
+
+
+/**
+ * @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
+ */
+ol.control.Attribution = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * @private
+ * @type {Element}
+ */
+ this.ulElement_ = document.createElement('UL');
+
+ /**
+ * @private
+ * @type {Element}
+ */
+ this.logoLi_ = document.createElement('LI');
+
+ this.ulElement_.appendChild(this.logoLi_);
+ goog.style.setElementShown(this.logoLi_, false);
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.collapsible_ = options.collapsible !== undefined ?
+ options.collapsible : true;
+
+ if (!this.collapsible_) {
+ this.collapsed_ = false;
+ }
+
+ var className = options.className ? options.className : 'ol-attribution';
+
+ var tipLabel = options.tipLabel ? options.tipLabel : 'Attributions';
+
+ var collapseLabel = options.collapseLabel ? options.collapseLabel : '\u00BB';
+
+ /**
+ * @private
+ * @type {Node}
+ */
+ this.collapseLabel_ = goog.isString(collapseLabel) ?
+ goog.dom.createDom('SPAN', {}, collapseLabel) :
+ collapseLabel;
+
+ var label = options.label ? options.label : 'i';
+
+ /**
+ * @private
+ * @type {Node}
+ */
+ this.label_ = goog.isString(label) ?
+ goog.dom.createDom('SPAN', {}, label) :
+ label;
+
+ var activeLabel = (this.collapsible_ && !this.collapsed_) ?
+ this.collapseLabel_ : this.label_;
+ var button = goog.dom.createDom('BUTTON', {
+ 'type': 'button',
+ 'title': tipLabel
+ }, activeLabel);
+
+ goog.events.listen(button, goog.events.EventType.CLICK,
+ this.handleClick_, false, this);
+
+ 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('DIV',
+ cssClasses, this.ulElement_, button);
+
+ var render = 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.
+ */
+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(projection, 'projection of viewState required');
+ for (i = 0, ii = layerStatesArray.length; i < ii; i++) {
+ source = layerStatesArray[i].layer.getSource();
+ if (!source) {
+ continue;
+ }
+ sourceKey = goog.getUid(source).toString();
+ sourceAttributions = source.getAttributions();
+ if (!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 (tileRanges) {
+ goog.asserts.assertInstanceof(source, ol.source.Tile,
+ 'source should be an ol.source.Tile');
+ var tileGrid = source.getTileGridForProjection(projection);
+ goog.asserts.assert(tileGrid, 'tileGrid required for projection');
+ 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 [attributions, hiddenAttributions];
+};
+
+
+/**
+ * Update the attribution element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.Attribution}
+ * @api
+ */
+ol.control.Attribution.render = function(mapEvent) {
+ this.updateElement_(mapEvent.frameState);
+};
+
+
+/**
+ * @private
+ * @param {?olx.FrameState} frameState Frame state.
+ */
+ol.control.Attribution.prototype.updateElement_ = function(frameState) {
+
+ if (!frameState) {
+ if (this.renderedVisible_) {
+ goog.style.setElementShown(this.element, false);
+ this.renderedVisible_ = false;
+ }
+ 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 = document.createElement('LI');
+ attributionElement.innerHTML =
+ visibleAttributions[attributionKey].getHTML();
+ this.ulElement_.appendChild(attributionElement);
+ this.attributionElements_[attributionKey] = attributionElement;
+ this.attributionElementRenderedVisible_[attributionKey] = true;
+ }
+ for (attributionKey in hiddenAttributions) {
+ attributionElement = document.createElement('LI');
+ attributionElement.innerHTML =
+ hiddenAttributions[attributionKey].getHTML();
+ goog.style.setElementShown(attributionElement, false);
+ this.ulElement_.appendChild(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);
+
+};
+
+
+/**
+ * @param {?olx.FrameState} frameState Frame state.
+ * @private
+ */
+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];
+ }
+ }
+
+ 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('A', {
+ 'href': logoValue
+ });
+ logoElement.appendChild(image);
+ }
+ this.logoLi_.appendChild(logoElement);
+ logoElements[logoKey] = logoElement;
+ }
+ }
+
+ goog.style.setElementShown(this.logoLi_, !goog.object.isEmpty(logos));
+
+};
+
+
+/**
+ * @param {goog.events.BrowserEvent} event The event to handle
+ * @private
+ */
+ol.control.Attribution.prototype.handleClick_ = function(event) {
+ event.preventDefault();
+ this.handleToggle_();
+};
+
+
+/**
+ * @private
+ */
+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_;
+};
+
+
+/**
+ * Return `true` if the attribution is collapsible, `false` otherwise.
+ * @return {boolean} True if the widget is collapsible.
+ * @api stable
+ */
+ol.control.Attribution.prototype.getCollapsible = function() {
+ return this.collapsible_;
+};
+
+
+/**
+ * Set whether the attribution should be collapsible.
+ * @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_();
+ }
+};
+
+
+/**
+ * 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
+ */
+ol.control.Attribution.prototype.setCollapsed = function(collapsed) {
+ if (!this.collapsible_ || this.collapsed_ === collapsed) {
+ return;
+ }
+ this.handleToggle_();
+};
+
+
+/**
+ * Return `true` when the attribution is currently collapsed or `false`
+ * otherwise.
+ * @return {boolean} True if the widget is collapsed.
+ * @api stable
+ */
+ol.control.Attribution.prototype.getCollapsed = function() {
+ return this.collapsed_;
+};
+
+goog.provide('ol.control.Rotate');
+
+goog.require('goog.dom');
+goog.require('goog.dom.classlist');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('ol');
+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.
+ *
+ * @constructor
+ * @extends {ol.control.Control}
+ * @param {olx.control.RotateOptions=} opt_options Rotate options.
+ * @api stable
+ */
+ol.control.Rotate = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ var className = options.className ?
+ options.className : 'ol-rotate';
+
+ var label = options.label ? options.label : '\u21E7';
+
+ /**
+ * @type {Element}
+ * @private
+ */
+ this.label_ = null;
+
+ if (goog.isString(label)) {
+ this.label_ = goog.dom.createDom('SPAN',
+ 'ol-compass', label);
+ } else {
+ this.label_ = label;
+ goog.dom.classlist.add(this.label_, 'ol-compass');
+ }
+
+ var tipLabel = options.tipLabel ? options.tipLabel : 'Reset rotation';
+
+ var button = goog.dom.createDom('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);
+
+ var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
+ ol.css.CLASS_CONTROL;
+ var element = goog.dom.createDom('DIV', cssClasses, button);
+
+ var render = options.render ? options.render : ol.control.Rotate.render;
+
+ this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined;
+
+ goog.base(this, {
+ element: element,
+ render: render,
+ target: options.target
+ });
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.duration_ = options.duration !== undefined ? options.duration : 250;
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.rotation_ = undefined;
+
+ if (this.autoHide_) {
+ goog.dom.classlist.add(this.element, ol.css.CLASS_HIDDEN);
+ }
+
+};
+goog.inherits(ol.control.Rotate, ol.control.Control);
+
+
+/**
+ * @param {goog.events.BrowserEvent} event The event to handle
+ * @private
+ */
+ol.control.Rotate.prototype.handleClick_ = function(event) {
+ event.preventDefault();
+ if (this.callResetNorth_ !== undefined) {
+ this.callResetNorth_();
+ } else {
+ this.resetNorth_();
+ }
+};
+
+
+/**
+ * @private
+ */
+ol.control.Rotate.prototype.resetNorth_ = function() {
+ var map = this.getMap();
+ var view = map.getView();
+ if (!view) {
+ // the map does not have a view, so we can't act
+ // upon it
+ return;
+ }
+ var currentRotation = view.getRotation();
+ if (currentRotation !== undefined) {
+ if (this.duration_ > 0) {
+ currentRotation = currentRotation % (2 * Math.PI);
+ if (currentRotation < -Math.PI) {
+ currentRotation += 2 * Math.PI;
+ }
+ if (currentRotation > Math.PI) {
+ currentRotation -= 2 * Math.PI;
+ }
+ map.beforeRender(ol.animation.rotate({
+ rotation: currentRotation,
+ duration: this.duration_,
+ easing: ol.easing.easeOut
+ }));
+ }
+ view.setRotation(0);
+ }
+};
+
+
+/**
+ * Update the rotate control element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.Rotate}
+ * @api
+ */
+ol.control.Rotate.render = function(mapEvent) {
+ var frameState = mapEvent.frameState;
+ if (!frameState) {
+ return;
+ }
+ var rotation = frameState.viewState.rotation;
+ if (rotation != this.rotation_) {
+ var transform = 'rotate(' + rotation + 'rad)';
+ 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');
+
+goog.require('goog.dom');
+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
+ */
+ol.control.Zoom = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ var className = options.className ? options.className : 'ol-zoom';
+
+ var delta = options.delta ? options.delta : 1;
+
+ var zoomInLabel = options.zoomInLabel ? options.zoomInLabel : '+';
+ var zoomOutLabel = options.zoomOutLabel ? options.zoomOutLabel : '\u2212';
+
+ var zoomInTipLabel = options.zoomInTipLabel ?
+ options.zoomInTipLabel : 'Zoom in';
+ var zoomOutTipLabel = options.zoomOutTipLabel ?
+ options.zoomOutTipLabel : 'Zoom out';
+
+ var inElement = goog.dom.createDom('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);
+
+ var outElement = goog.dom.createDom('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);
+
+ var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
+ ol.css.CLASS_CONTROL;
+ var element = goog.dom.createDom('DIV', cssClasses, inElement,
+ outElement);
+
+ goog.base(this, {
+ element: element,
+ target: options.target
+ });
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.duration_ = options.duration !== undefined ? options.duration : 250;
+
+};
+goog.inherits(ol.control.Zoom, ol.control.Control);
+
+
+/**
+ * @param {number} delta Zoom delta.
+ * @param {goog.events.BrowserEvent} event The event to handle
+ * @private
+ */
+ol.control.Zoom.prototype.handleClick_ = function(delta, event) {
+ event.preventDefault();
+ this.zoomByDelta_(delta);
+};
+
+
+/**
+ * @param {number} delta Zoom delta.
+ * @private
+ */
+ol.control.Zoom.prototype.zoomByDelta_ = function(delta) {
+ var map = this.getMap();
+ var view = map.getView();
+ if (!view) {
+ // the map does not have a view, so we can't act
+ // upon it
+ return;
+ }
+ var currentResolution = view.getResolution();
+ if (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');
+
+goog.require('ol');
+goog.require('ol.Collection');
+goog.require('ol.control.Attribution');
+goog.require('ol.control.Rotate');
+goog.require('ol.control.Zoom');
+
+
+/**
+ * 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) {
+
+ var options = opt_options ? opt_options : {};
+
+ var controls = new ol.Collection();
+
+ var zoomControl = options.zoom !== undefined ? options.zoom : true;
+ if (zoomControl) {
+ controls.push(new ol.control.Zoom(options.zoomOptions));
+ }
+
+ var rotateControl = options.rotate !== undefined ? options.rotate : true;
+ if (rotateControl) {
+ controls.push(new ol.control.Rotate(options.rotateOptions));
+ }
+
+ var attributionControl = options.attribution !== undefined ?
+ options.attribution : true;
+ if (attributionControl) {
+ controls.push(new ol.control.Attribution(options.attributionOptions));
+ }
+
+ return controls;
+
+};
+
+// 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.
+ *
+ */
+
+goog.provide('goog.dom.fullscreen');
+goog.provide('goog.dom.fullscreen.EventType');
+
+goog.require('goog.dom');
+goog.require('goog.userAgent');
+
+
+/**
+ * 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';
+ }
+ 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';
+ })()
+};
+
+
+/**
+ * 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.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));
+};
+
+
+/**
+ * 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();
+ }
+};
+
+
+/**
+ * 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 {
+ goog.dom.fullscreen.requestFullScreen(element);
+ }
+};
+
+
+/**
+ * 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();
+ }
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * Get the root element in full screen mode.
+ * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being
+ * queried. If not provided, use the current DOM.
+ * @return {?Element} The root element in full screen mode.
+ */
+goog.dom.fullscreen.getFullScreenElement = function(opt_domHelper) {
+ var doc = goog.dom.fullscreen.getDocument_(opt_domHelper);
+ var element_list = [
+ doc.webkitFullscreenElement,
+ doc.mozFullScreenElement,
+ doc.msFullscreenElement,
+ doc.fullscreenElement
+ ];
+ for (var i = 0; i < element_list.length; i++) {
+ if (element_list[i] != null) {
+ return element_list[i];
+ }
+ }
+ return null;
+};
+
+
+/**
+ * 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.dom.fullscreen.getDocument_ = function(opt_domHelper) {
+ return opt_domHelper ?
+ opt_domHelper.getDocument() :
+ goog.dom.getDomHelper().getDocument();
+};
+
+goog.provide('ol.control.FullScreen');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+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');
+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.
+ *
+ *
+ * @constructor
+ * @extends {ol.control.Control}
+ * @param {olx.control.FullScreenOptions=} opt_options Options.
+ * @api stable
+ */
+ol.control.FullScreen = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.cssClassName_ = options.className ? options.className : 'ol-full-screen';
+
+ var label = options.label ? options.label : '\u2922';
+
+ /**
+ * @private
+ * @type {Node}
+ */
+ this.labelNode_ = goog.isString(label) ?
+ document.createTextNode(label) : label;
+
+ var labelActive = options.labelActive ? options.labelActive : '\u00d7';
+
+ /**
+ * @private
+ * @type {Node}
+ */
+ this.labelActiveNode_ = goog.isString(labelActive) ?
+ document.createTextNode(labelActive) : labelActive;
+
+ var tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen';
+ var button = goog.dom.createDom('BUTTON', {
+ 'class': this.cssClassName_ + '-' + goog.dom.fullscreen.isFullScreen(),
+ 'type': 'button',
+ 'title': tipLabel
+ }, this.labelNode_);
+
+ 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);
+
+ 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('DIV', cssClasses, button);
+
+ goog.base(this, {
+ element: element,
+ target: options.target
+ });
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.keys_ = options.keys !== undefined ? options.keys : false;
+
+};
+goog.inherits(ol.control.FullScreen, ol.control.Control);
+
+
+/**
+ * @param {goog.events.BrowserEvent} event The event to handle
+ * @private
+ */
+ol.control.FullScreen.prototype.handleClick_ = function(event) {
+ event.preventDefault();
+ this.handleFullScreen_();
+};
+
+
+/**
+ * @private
+ */
+ol.control.FullScreen.prototype.handleFullScreen_ = function() {
+ if (!goog.dom.fullscreen.isSupported()) {
+ return;
+ }
+ var map = this.getMap();
+ if (!map) {
+ return;
+ }
+ if (goog.dom.fullscreen.isFullScreen()) {
+ goog.dom.fullscreen.exitFullScreen();
+ } else {
+ var target = map.getTarget();
+ goog.asserts.assert(target, 'target should be defined');
+ var element = goog.dom.getElement(target);
+ goog.asserts.assert(element, 'element should be defined');
+ if (this.keys_) {
+ goog.dom.fullscreen.requestFullScreenWithKeys(element);
+ } else {
+ goog.dom.fullscreen.requestFullScreen(element);
+ }
+ }
+};
+
+
+/**
+ * @private
+ */
+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 (map) {
+ map.updateSize();
+ }
+};
+
+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
+ */
+ol.Pixel;
+
+// FIXME should listen on appropriate pane, once it is defined
+
+goog.provide('ol.control.MousePosition');
+
+goog.require('goog.dom');
+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');
+
+
+/**
+ * @enum {string}
+ */
+ol.control.MousePositionProperty = {
+ PROJECTION: 'projection',
+ COORDINATE_FORMAT: 'coordinateFormat'
+};
+
+
+
+/**
+ * @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
+ */
+ol.control.MousePosition = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ var className = options.className ? options.className : 'ol-mouse-position';
+
+ var element = goog.dom.createDom('DIV', className);
+
+ var render = 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 (options.coordinateFormat) {
+ this.setCoordinateFormat(options.coordinateFormat);
+ }
+ if (options.projection) {
+ this.setProjection(ol.proj.get(options.projection));
+ }
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.undefinedHTML_ = 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);
+
+
+/**
+ * Update the mouseposition element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.MousePosition}
+ * @api
+ */
+ol.control.MousePosition.render = function(mapEvent) {
+ var frameState = mapEvent.frameState;
+ if (!frameState) {
+ this.mapProjection_ = null;
+ } else {
+ if (this.mapProjection_ != frameState.viewState.projection) {
+ this.mapProjection_ = frameState.viewState.projection;
+ this.transform_ = null;
+ }
+ }
+ this.updateHTML_(this.lastMouseMovePixel_);
+};
+
+
+/**
+ * @private
+ */
+ol.control.MousePosition.prototype.handleProjectionChanged_ = function() {
+ this.transform_ = null;
+};
+
+
+/**
+ * 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
+ */
+ol.control.MousePosition.prototype.getCoordinateFormat = function() {
+ return /** @type {ol.CoordinateFormatType|undefined} */ (
+ this.get(ol.control.MousePositionProperty.COORDINATE_FORMAT));
+};
+
+
+/**
+ * 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
+ */
+ol.control.MousePosition.prototype.getProjection = function() {
+ return /** @type {ol.proj.Projection|undefined} */ (
+ this.get(ol.control.MousePositionProperty.PROJECTION));
+};
+
+
+/**
+ * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @protected
+ */
+ol.control.MousePosition.prototype.handleMouseMove = function(browserEvent) {
+ var map = this.getMap();
+ this.lastMouseMovePixel_ = map.getEventPixel(browserEvent.getBrowserEvent());
+ this.updateHTML_(this.lastMouseMovePixel_);
+};
+
+
+/**
+ * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @protected
+ */
+ol.control.MousePosition.prototype.handleMouseOut = function(browserEvent) {
+ this.updateHTML_(null);
+ this.lastMouseMovePixel_ = null;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.control.MousePosition.prototype.setMap = function(map) {
+ goog.base(this, 'setMap', map);
+ if (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)
+ );
+ }
+};
+
+
+/**
+ * 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
+ */
+ol.control.MousePosition.prototype.setCoordinateFormat = function(format) {
+ this.set(ol.control.MousePositionProperty.COORDINATE_FORMAT, format);
+};
+
+
+/**
+ * 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
+ */
+ol.control.MousePosition.prototype.setProjection = function(projection) {
+ this.set(ol.control.MousePositionProperty.PROJECTION, projection);
+};
+
+
+/**
+ * @param {?ol.Pixel} pixel Pixel.
+ * @private
+ */
+ol.control.MousePosition.prototype.updateHTML_ = function(pixel) {
+ var html = this.undefinedHTML_;
+ if (pixel && this.mapProjection_) {
+ if (!this.transform_) {
+ var projection = this.getProjection();
+ if (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 (coordinate) {
+ this.transform_(coordinate, coordinate);
+ var coordinateFormat = this.getCoordinateFormat();
+ if (coordinateFormat) {
+ html = coordinateFormat(coordinate);
+ } else {
+ html = coordinate.toString();
+ }
+ }
+ }
+ if (!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.
+
+/**
+ * @fileoverview A delayed callback that pegs to the next animation frame
+ * instead of a user-configurable timeout.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ */
+
+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?
+
+
+
+/**
+ * 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
+ * @struct
+ * @extends {goog.Disposable}
+ * @final
+ */
+goog.async.AnimationDelay = function(listener, opt_window, opt_handler) {
+ goog.async.AnimationDelay.base(this, 'constructor');
+
+ /**
+ * Identifier of the active delay timeout, or event listener,
+ * or null when inactive.
+ * @private {goog.events.Key|number}
+ */
+ this.id_ = null;
+
+ /**
+ * If we're using dom listeners.
+ * @private {?boolean}
+ */
+ this.usingListeners_ = false;
+
+ /**
+ * The function that will be invoked after a delay.
+ * @private {function(number)}
+ */
+ this.listener_ = listener;
+
+ /**
+ * The object context to invoke the callback in.
+ * @private {Object|undefined}
+ */
+ this.handler_ = opt_handler;
+
+ /**
+ * @private {Window}
+ */
+ this.win_ = opt_window || window;
+
+ /**
+ * Cached callback function invoked when the delay finishes.
+ * @private {function()}
+ */
+ this.callback_ = goog.bind(this.doAction_, this);
+};
+goog.inherits(goog.async.AnimationDelay, goog.Disposable);
+
+
+/**
+ * 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;
+
+
+/**
+ * Name of event received from the requestAnimationFrame in Firefox.
+ *
+ * @type {string}
+ * @const
+ * @private
+ */
+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);
+ }
+};
+
+
+/**
+ * Stops the delay timer if it is active. No action is taken if the timer is not
+ * in use.
+ */
+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;
+};
+
+
+/**
+ * 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.async.AnimationDelay.prototype.fire = function() {
+ this.stop();
+ this.doAction_();
+};
+
+
+/**
+ * Fires delay's action only if timer is currently active. Stops the delay
+ * timer.
+ */
+goog.async.AnimationDelay.prototype.fireIfActive = function() {
+ if (this.isActive()) {
+ this.fire();
+ }
+};
+
+
+/**
+ * @return {boolean} True if the delay is currently active, false otherwise.
+ */
+goog.async.AnimationDelay.prototype.isActive = function() {
+ return this.id_ != null;
+};
+
+
+/**
+ * Invokes the callback function after the delay successfully completes.
+ * @private
+ */
+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());
+};
+
+
+/** @override */
+goog.async.AnimationDelay.prototype.disposeInternal = function() {
+ this.stop();
+ goog.async.AnimationDelay.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @return {?function(function(number)): number} The requestAnimationFrame
+ * function, or null if not available on this browser.
+ * @private
+ */
+goog.async.AnimationDelay.prototype.getRaf_ = function() {
+ var win = this.win_;
+ return win.requestAnimationFrame ||
+ win.webkitRequestAnimationFrame ||
+ win.mozRequestAnimationFrame ||
+ win.oRequestAnimationFrame ||
+ win.msRequestAnimationFrame ||
+ null;
+};
+
+
+/**
+ * @return {?function(number): number} The cancelAnimationFrame function,
+ * or null if not available on this browser.
+ * @private
+ */
+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.
+
+/**
+ * @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.
+ *
+ */
+
+goog.provide('goog.async.nextTick');
+goog.provide('goog.async.throwException');
+
+goog.require('goog.debug.entryPointRegistry');
+goog.require('goog.dom.TagName');
+goog.require('goog.functions');
+goog.require('goog.labs.userAgent.browser');
+goog.require('goog.labs.userAgent.engine');
+
+
+/**
+ * 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.async.throwException = function(exception) {
+ // Each throw needs to be in its own context.
+ goog.global.setTimeout(function() { throw exception; }, 0);
+};
+
+
+/**
+ * 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.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 in.
+ (opt_useSetImmediate ||
+ // or it isn't a browser or the environment is weird
+ !goog.global.Window || !goog.global.Window.prototype ||
+ // or something redefined setImmediate in which case we (YOLO) decide
+ // to use it (This is so that we use the mockClock setImmediate. sigh).
+ goog.global.Window.prototype.setImmediate != goog.global.setImmediate)) {
+ goog.global.setImmediate(cb);
+ return;
+ }
+
+ // 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);
+};
+
+
+/**
+ * Cache for the setImmediate implementation.
+ * @type {function(function())}
+ * @private
+ */
+goog.async.nextTick.setImmediate_;
+
+
+/**
+ * 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.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 &&
+ // Presto (The old pre-blink Opera engine) has problems with iframes
+ // and contentWindow.
+ !goog.labs.userAgent.engine.isPresto()) {
+ /** @constructor */
+ Channel = function() {
+ // Make an empty, invisible iframe.
+ var iframe = document.createElement(goog.dom.TagName.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(goog.dom.TagName.SCRIPT)) {
+ return function(cb) {
+ var script = document.createElement(goog.dom.TagName.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);
+ };
+};
+
+
+/**
+ * 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.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;
+ });
+
+// 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(xtof): Link to document stating type contract.
+ */
+
+goog.provide('goog.html.SafeScript');
+
+goog.require('goog.asserts');
+goog.require('goog.string.Const');
+goog.require('goog.string.TypedString');
+
+
+
+/**
+ * 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 &lt;} is dangerous, even when inside JavaScript strings, and so should
+ * always be forbidden or JavaScript escaped in user controlled input. For
+ * example, if {@code &lt;/script&gt;&lt;script&gt;evil&lt;/script&gt;"} 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 "&lt;" 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.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_;
+};
+
+
+/**
+ * @override
+ * @const
+ */
+goog.html.SafeScript.prototype.implementsGoogStringTypedString = true;
+
+
+/**
+ * Type marker for the SafeScript type, used to implement additional
+ * run-time type checking.
+ * @const {!Object}
+ * @private
+ */
+goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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_;
+};
+
+
+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_ + '}';
+ };
+}
+
+
+/**
+ * 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';
+ }
+};
+
+
+/**
+ * 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) {
+ return new goog.html.SafeScript().initSecurityPrivateDoNotAccessOrElse_(
+ script);
+};
+
+
+/**
+ * Called from createSafeScriptSecurityPrivateDoNotAccessOrElse(). This
+ * method exists only so that the compiler can dead code eliminate static
+ * fields (like EMPTY) when they're not accessed.
+ * @param {string} script
+ * @return {!goog.html.SafeScript}
+ * @private
+ */
+goog.html.SafeScript.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
+ script) {
+ this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = script;
+ return this;
+};
+
+
+/**
+ * A SafeScript instance corresponding to the empty string.
+ * @const {!goog.html.SafeScript}
+ */
+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.
+
+/**
+ * @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.provide('goog.html.uncheckedconversions');
+
+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');
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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.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.
+
+/**
+ * @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.provide('goog.structs');
+
+goog.require('goog.array');
+goog.require('goog.object');
+
+
+// We treat an object as a dictionary if it has getKeys or it is an object that
+// isn't arrayLike.
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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 goog.object.getKeys(col);
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * Whether the collection is empty.
+ * @param {Object} col The collection-like object.
+ * @return {boolean} True if empty.
+ */
+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 the elements from the collection.
+ * @param {Object} col The collection-like object.
+ */
+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);
+ }
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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);
+ }
+
+ 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;
+};
+
+
+/**
+ * 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.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;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * 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
+ */
+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 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)
+ */
+
+goog.provide('goog.structs.Collection');
+
+
+
+/**
+ * An interface for a collection of values.
+ * @interface
+ * @template T
+ */
+goog.structs.Collection = function() {};
+
+
+/**
+ * @param {T} value Value to add to the collection.
+ */
+goog.structs.Collection.prototype.add;
+
+
+/**
+ * @param {T} value Value to remove from the collection.
+ */
+goog.structs.Collection.prototype.remove;
+
+
+/**
+ * @param {T} value Value to find in the collection.
+ * @return {boolean} Whether the collection contains the specified value.
+ */
+goog.structs.Collection.prototype.contains;
+
+
+/**
+ * @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.
+
+/**
+ * @fileoverview Python style iteration utilities.
+ * @author arv@google.com (Erik Arvidsson)
+ */
+
+
+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');
+
+
+/**
+ * @typedef {goog.iter.Iterator|{length:number}|{__iterator__}}
+ */
+goog.iter.Iterable;
+
+
+/**
+ * Singleton Error object that is used to terminate iterations.
+ * @const {!Error}
+ */
+goog.iter.StopIteration = ('StopIteration' in goog.global) ?
+ // For script engines that support legacy iterators.
+ goog.global['StopIteration'] :
+ { message: 'StopIteration', stack: ''};
+
+
+
+/**
+ * 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.iter.Iterator = function() {};
+
+
+/**
+ * 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.iter.Iterator.prototype.next = function() {
+ throw goog.iter.StopIteration;
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * 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.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;
+ }
+
+
+ // TODO(arv): Should we fall back on goog.structs.getValues()?
+ throw Error('Not implemented');
+};
+
+
+/**
+ * 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.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;
+ }
+ }
+ }
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.iter.filterFalse = function(iterable, f, opt_obj) {
+ return goog.iter.filter(iterable, goog.functions.not(f), opt_obj);
+};
+
+
+/**
+ * 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.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');
+ }
+
+ 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;
+};
+
+
+/**
+ * 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.iter.join = function(iterable, deliminator) {
+ return goog.iter.toArray(iterable).join(deliminator);
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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 {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.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;
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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]);
+ });
+};
+
+
+/**
+ * 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.iter.nextOrValue = function(iterable, defaultValue) {
+ try {
+ return goog.iter.toIterator(iterable).next();
+ } catch (e) {
+ if (e != goog.iter.StopIteration) {
+ throw e;
+ }
+ return defaultValue;
+ }
+};
+
+
+/**
+ * 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.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();
+ }
+
+ var iter = new goog.iter.Iterator();
+ var arrays = arguments;
+
+ // The first indices are [0, 0, ...]
+ var indicies = goog.array.repeat(0, arrays.length);
+
+ iter.next = function() {
+
+ 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;
+ }
+
+ // 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;
+ };
+
+ return iter;
+};
+
+
+/**
+ * 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.iter.cycle = function(iterable) {
+ var baseIterator = goog.iter.toIterator(iterable);
+
+ // 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;
+
+ var iter = new goog.iter.Iterator();
+
+ // 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;
+ }
+ }
+
+ returnElement = cache[cacheIndex];
+ cacheIndex = (cacheIndex + 1) % cache.length;
+
+ return returnElement;
+ };
+
+ return iter;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.iter.repeat = function(value) {
+ var iter = new goog.iter.Iterator();
+
+ iter.next = goog.functions.constant(value);
+
+ return iter;
+};
+
+
+/**
+ * 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.iter.accumulate = function(iterable) {
+ var iterator = goog.iter.toIterator(iterable);
+ var total = 0;
+ var iter = new goog.iter.Iterator();
+
+ iter.next = function() {
+ total += iterator.next();
+ return total;
+ };
+
+ return iter;
+};
+
+
+/**
+ * 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.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;
+ };
+ }
+
+ return iter;
+};
+
+
+/**
+ * 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.iter.zipLongest = function(fillValue, var_args) {
+ var args = goog.array.slice(arguments, 1);
+ var iter = new goog.iter.Iterator();
+
+ 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;
+ });
+
+ if (!iteratorsHaveValues) {
+ throw goog.iter.StopIteration;
+ }
+ return arr;
+ };
+ }
+
+ return iter;
+};
+
+
+/**
+ * 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.iter.compress = function(iterable, selectors) {
+ var selectorIterator = goog.iter.toIterator(selectors);
+
+ return goog.iter.filter(iterable, function() {
+ return !!selectorIterator.next();
+ });
+};
+
+
+
+/**
+ * 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.iter.GroupByIterator_ = function(iterable, opt_keyFunc) {
+
+ /**
+ * The iterable to group, coerced to an iterator.
+ * @type {!goog.iter.Iterator}
+ */
+ this.iterator = goog.iter.toIterator(iterable);
+
+ /**
+ * 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;
+
+ /**
+ * The current key visited during iteration.
+ * @type {KEY}
+ */
+ this.currentKey;
+
+ /**
+ * The current value being added to the group.
+ * @type {VALUE}
+ */
+ this.currentValue;
+};
+goog.inherits(goog.iter.GroupByIterator_, goog.iter.Iterator);
+
+
+/** @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 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.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;
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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
+ */
+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;
+};
+
+
+/**
+ * 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.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);
+};
+
+
+/**
+ * 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.iter.enumerate = function(iterable, opt_start) {
+ return goog.iter.zip(goog.iter.count(opt_start), iterable);
+};
+
+
+/**
+ * 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
+ */
+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;
+};
+
+
+/**
+ * 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.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(opt_end) && opt_end >= start);
+ iterator = goog.iter.limit(iterator, opt_end - start /* limitSize */);
+ }
+
+ return iterator;
+};
+
+
+/**
+ * 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
+ */
+// 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;
+};
+
+
+/**
+ * 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);
+
+ return goog.iter.filter(product, function(arr) {
+ return !goog.iter.hasDuplicates_(arr);
+ });
+};
+
+
+/**
+ * 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.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(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.
+//
+// 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 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');
+
+
+
+/**
+ * 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) {
+
+ /**
+ * 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_ = [];
+
+ /**
+ * 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 key-value pairs in the map.
+ */
+goog.structs.Map.prototype.getCount = function() {
+ return this.count_;
+};
+
+
+/**
+ * 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 version = this.version_;
+ var selfObj = this;
+
+ var newIter = new goog.iter.Iterator;
+ newIter.next = function() {
+ if (version != selfObj.version_) {
+ throw Error('The map has changed since the iterator was created');
+ }
+ if (i >= selfObj.keys_.length) {
+ throw goog.iter.StopIteration;
+ }
+ var key = selfObj.keys_[i++];
+ return opt_keys ? key : selfObj.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);
+};
+
+
+/**
+ * Adds all the values in the given collection to this set.
+ * @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection
+ * containing the elements to add.
+ */
+goog.structs.Set.prototype.addAll = function(col) {
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ for (var i = 0; i < l; i++) {
+ this.add(values[i]);
+ }
+};
+
+
+/**
+ * Removes all values in the given collection from this set.
+ * @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection
+ * containing the elements to remove.
+ */
+goog.structs.Set.prototype.removeAll = function(col) {
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ for (var i = 0; i < l; i++) {
+ this.remove(values[i]);
+ }
+};
+
+
+/**
+ * Removes the given element from this set.
+ * @param {T} element The primitive or object to remove.
+ * @return {boolean} Whether the element was found and removed.
+ * @override
+ */
+goog.structs.Set.prototype.remove = function(element) {
+ return this.map_.remove(goog.structs.Set.getKey_(element));
+};
+
+
+/**
+ * Removes all elements from this set.
+ */
+goog.structs.Set.prototype.clear = function() {
+ this.map_.clear();
+};
+
+
+/**
+ * Tests whether this set is empty.
+ * @return {boolean} True if there are no elements in this set.
+ */
+goog.structs.Set.prototype.isEmpty = function() {
+ return this.map_.isEmpty();
+};
+
+
+/**
+ * Tests whether this set contains the given element.
+ * @param {T} element The primitive or object to test for.
+ * @return {boolean} True if this set contains the given element.
+ * @override
+ */
+goog.structs.Set.prototype.contains = function(element) {
+ return this.map_.containsKey(goog.structs.Set.getKey_(element));
+};
+
+
+/**
+ * Tests whether this set contains all the values in a given collection.
+ * Repeated elements in the collection are ignored, e.g. (new
+ * goog.structs.Set([1, 2])).containsAll([1, 1]) is True.
+ * @param {goog.structs.Collection<T>|Object} col A collection-like object.
+ * @return {boolean} True if the set contains all elements.
+ */
+goog.structs.Set.prototype.containsAll = function(col) {
+ return goog.structs.every(col, this.contains, this);
+};
+
+
+/**
+ * Finds all values that are present in both this set and the given collection.
+ * @param {Array<S>|Object<?,S>} col A collection.
+ * @return {!goog.structs.Set<T|S>} A new set containing all the values
+ * (primitives or objects) present in both this set and the given
+ * collection.
+ * @template S
+ */
+goog.structs.Set.prototype.intersection = function(col) {
+ var result = new goog.structs.Set();
+
+ var values = goog.structs.getValues(col);
+ for (var i = 0; i < values.length; i++) {
+ var value = values[i];
+ if (this.contains(value)) {
+ result.add(value);
+ }
+ }
+
+ return result;
+};
+
+
+/**
+ * Finds all values that are present in this set and not in the given
+ * collection.
+ * @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection.
+ * @return {!goog.structs.Set} A new set containing all the values
+ * (primitives or objects) present in this set but not in the given
+ * collection.
+ */
+goog.structs.Set.prototype.difference = function(col) {
+ var result = this.clone();
+ result.removeAll(col);
+ return result;
+};
+
+
+/**
+ * Returns an array containing all the elements in this set.
+ * @return {!Array<T>} An array containing all the elements in this set.
+ */
+goog.structs.Set.prototype.getValues = function() {
+ return this.map_.getValues();
+};
+
+
+/**
+ * Creates a shallow clone of this set.
+ * @return {!goog.structs.Set<T>} A new set containing all the same elements as
+ * this set.
+ */
+goog.structs.Set.prototype.clone = function() {
+ return new goog.structs.Set(this);
+};
+
+
+/**
+ * Tests whether the given collection consists of the same elements as this set,
+ * regardless of order, without repetition. Primitives are treated as equal if
+ * they have the same type and convert to the same string; objects are treated
+ * as equal if they are references to the same object. This operation is O(n).
+ * @param {goog.structs.Collection<T>|Object} col A collection.
+ * @return {boolean} True if the given collection consists of the same elements
+ * as this set, regardless of order, without repetition.
+ */
+goog.structs.Set.prototype.equals = function(col) {
+ return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col);
+};
+
+
+/**
+ * Tests whether the given collection contains all the elements in this set.
+ * Primitives are treated as equal if they have the same type and convert to the
+ * same string; objects are treated as equal if they are references to the same
+ * object. This operation is O(n).
+ * @param {goog.structs.Collection<T>|Object} col A collection.
+ * @return {boolean} True if this set is a subset of the given collection.
+ */
+goog.structs.Set.prototype.isSubsetOf = function(col) {
+ var colCount = goog.structs.getCount(col);
+ if (this.getCount() > colCount) {
+ return false;
+ }
+ // TODO(user) Find the minimal collection size where the conversion makes
+ // the contains() method faster.
+ if (!(col instanceof goog.structs.Set) && colCount > 5) {
+ // Convert to a goog.structs.Set so that goog.structs.contains runs in
+ // O(1) time instead of O(n) time.
+ col = new goog.structs.Set(col);
+ }
+ return goog.structs.every(this, function(value) {
+ return goog.structs.contains(col, value);
+ });
+};
+
+
+/**
+ * Returns an iterator that iterates over the elements in this set.
+ * @param {boolean=} opt_keys This argument is ignored.
+ * @return {!goog.iter.Iterator} An iterator over the elements in this set.
+ */
+goog.structs.Set.prototype.__iterator__ = function(opt_keys) {
+ return this.map_.__iterator__(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 Logging and debugging utilities.
+ *
+ * @see ../demos/debug.html
+ */
+
+goog.provide('goog.debug');
+
+goog.require('goog.array');
+goog.require('goog.html.SafeHtml');
+goog.require('goog.html.SafeUrl');
+goog.require('goog.html.uncheckedconversions');
+goog.require('goog.string.Const');
+goog.require('goog.structs.Set');
+goog.require('goog.userAgent');
+
+
+/** @define {boolean} Whether logging should be enabled. */
+goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
+
+
+/**
+ * Catches onerror events fired by windows and similar objects.
+ * @param {function(Object)} logFunc The function to call with the error
+ * information.
+ * @param {boolean=} opt_cancel Whether to stop the error from reaching the
+ * browser.
+ * @param {Object=} opt_target Object that fires onerror events.
+ */
+goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
+ var target = opt_target || goog.global;
+ var oldErrorHandler = target.onerror;
+ var retVal = !!opt_cancel;
+
+ // Chrome interprets onerror return value backwards (http://crbug.com/92062)
+ // until it was fixed in webkit revision r94061 (Webkit 535.3). This
+ // workaround still needs to be skipped in Safari after the webkit change
+ // gets pushed out in Safari.
+ // See https://bugs.webkit.org/show_bug.cgi?id=67119
+ if (goog.userAgent.WEBKIT &&
+ !goog.userAgent.isVersionOrHigher('535.3')) {
+ retVal = !retVal;
+ }
+
+ /**
+ * New onerror handler for this target. This onerror handler follows the spec
+ * according to
+ * http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors
+ * The spec was changed in August 2013 to support receiving column information
+ * and an error object for all scripts on the same origin or cross origin
+ * scripts with the proper headers. See
+ * https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
+ *
+ * @param {string} message The error message. For cross-origin errors, this
+ * will be scrubbed to just "Script error.". For new browsers that have
+ * updated to follow the latest spec, errors that come from origins that
+ * have proper cross origin headers will not be scrubbed.
+ * @param {string} url The URL of the script that caused the error. The URL
+ * will be scrubbed to "" for cross origin scripts unless the script has
+ * proper cross origin headers and the browser has updated to the latest
+ * spec.
+ * @param {number} line The line number in the script that the error
+ * occurred on.
+ * @param {number=} opt_col The optional column number that the error
+ * occurred on. Only browsers that have updated to the latest spec will
+ * include this.
+ * @param {Error=} opt_error The optional actual error object for this
+ * error that should include the stack. Only browsers that have updated
+ * to the latest spec will inlude this parameter.
+ * @return {boolean} Whether to prevent the error from reaching the browser.
+ */
+ target.onerror = function(message, url, line, opt_col, opt_error) {
+ if (oldErrorHandler) {
+ oldErrorHandler(message, url, line, opt_col, opt_error);
+ }
+ logFunc({
+ message: message,
+ fileName: url,
+ line: line,
+ col: opt_col,
+ error: opt_error
+ });
+ return retVal;
+ };
+};
+
+
+/**
+ * Creates a string representing an object and all its properties.
+ * @param {Object|null|undefined} obj Object to expose.
+ * @param {boolean=} opt_showFn Show the functions as well as the properties,
+ * default is false.
+ * @return {string} The string representation of {@code obj}.
+ */
+goog.debug.expose = function(obj, opt_showFn) {
+ if (typeof obj == 'undefined') {
+ return 'undefined';
+ }
+ if (obj == null) {
+ return 'NULL';
+ }
+ var str = [];
+
+ for (var x in obj) {
+ if (!opt_showFn && goog.isFunction(obj[x])) {
+ continue;
+ }
+ var s = x + ' = ';
+ /** @preserveTry */
+ try {
+ s += obj[x];
+ } catch (e) {
+ s += '*** ' + e + ' ***';
+ }
+ str.push(s);
+ }
+ return str.join('\n');
+};
+
+
+/**
+ * Creates a string representing a given primitive or object, and for an
+ * object, all its properties and nested objects. WARNING: If an object is
+ * given, it and all its nested objects will be modified. To detect reference
+ * cycles, this method identifies objects using goog.getUid() which mutates the
+ * object.
+ * @param {*} obj Object to expose.
+ * @param {boolean=} opt_showFn Also show properties that are functions (by
+ * default, functions are omitted).
+ * @return {string} A string representation of {@code obj}.
+ */
+goog.debug.deepExpose = function(obj, opt_showFn) {
+ var str = [];
+
+ var helper = function(obj, space, parentSeen) {
+ var nestspace = space + ' ';
+ var seen = new goog.structs.Set(parentSeen);
+
+ var indentMultiline = function(str) {
+ return str.replace(/\n/g, '\n' + space);
+ };
+
+ /** @preserveTry */
+ try {
+ if (!goog.isDef(obj)) {
+ str.push('undefined');
+ } else if (goog.isNull(obj)) {
+ str.push('NULL');
+ } else if (goog.isString(obj)) {
+ str.push('"' + indentMultiline(obj) + '"');
+ } else if (goog.isFunction(obj)) {
+ str.push(indentMultiline(String(obj)));
+ } else if (goog.isObject(obj)) {
+ if (seen.contains(obj)) {
+ str.push('*** reference loop detected ***');
+ } else {
+ seen.add(obj);
+ str.push('{');
+ for (var x in obj) {
+ if (!opt_showFn && goog.isFunction(obj[x])) {
+ continue;
+ }
+ str.push('\n');
+ str.push(nestspace);
+ str.push(x + ' = ');
+ helper(obj[x], nestspace, seen);
+ }
+ str.push('\n' + space + '}');
+ }
+ } else {
+ str.push(obj);
+ }
+ } catch (e) {
+ str.push('*** ' + e + ' ***');
+ }
+ };
+
+ helper(obj, '', new goog.structs.Set());
+ return str.join('');
+};
+
+
+/**
+ * Recursively outputs a nested array as a string.
+ * @param {Array<?>} arr The array.
+ * @return {string} String representing nested array.
+ */
+goog.debug.exposeArray = function(arr) {
+ var str = [];
+ for (var i = 0; i < arr.length; i++) {
+ if (goog.isArray(arr[i])) {
+ str.push(goog.debug.exposeArray(arr[i]));
+ } else {
+ str.push(arr[i]);
+ }
+ }
+ return '[ ' + str.join(', ') + ' ]';
+};
+
+
+/**
+ * Exposes an exception that has been caught by a try...catch and outputs the
+ * error as HTML with a stack trace.
+ * @param {Object} err Error object or string.
+ * @param {Function=} opt_fn Optional function to start stack trace from.
+ * @return {string} Details of exception, as HTML.
+ */
+goog.debug.exposeException = function(err, opt_fn) {
+ var html = goog.debug.exposeExceptionAsHtml(err, opt_fn);
+ return goog.html.SafeHtml.unwrap(html);
+};
+
+
+/**
+ * Exposes an exception that has been caught by a try...catch and outputs the
+ * error with a stack trace.
+ * @param {Object} err Error object or string.
+ * @param {Function=} opt_fn Optional function to start stack trace from.
+ * @return {!goog.html.SafeHtml} Details of exception.
+ */
+goog.debug.exposeExceptionAsHtml = function(err, opt_fn) {
+ /** @preserveTry */
+ try {
+ var e = goog.debug.normalizeErrorObject(err);
+ // Create the error message
+ var viewSourceUrl = goog.debug.createViewSourceUrl_(e.fileName);
+ var error = goog.html.SafeHtml.concat(
+ goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
+ 'Message: ' + e.message + '\nUrl: '),
+ goog.html.SafeHtml.create('a',
+ {href: viewSourceUrl, target: '_new'}, e.fileName),
+ goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
+ '\nLine: ' + e.lineNumber + '\n\nBrowser stack:\n' +
+ e.stack + '-> ' + '[end]\n\nJS stack traversal:\n' +
+ goog.debug.getStacktrace(opt_fn) + '-> '));
+ return error;
+ } catch (e2) {
+ return goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
+ 'Exception trying to expose exception! You win, we lose. ' + e2);
+ }
+};
+
+
+/**
+ * @param {?string=} opt_fileName
+ * @return {!goog.html.SafeUrl} SafeUrl with view-source scheme, pointing at
+ * fileName.
+ * @private
+ */
+goog.debug.createViewSourceUrl_ = function(opt_fileName) {
+ if (!goog.isDefAndNotNull(opt_fileName)) {
+ opt_fileName = '';
+ }
+ if (!/^https?:\/\//i.test(opt_fileName)) {
+ return goog.html.SafeUrl.fromConstant(
+ goog.string.Const.from('sanitizedviewsrc'));
+ }
+ var sanitizedFileName = goog.html.SafeUrl.sanitize(opt_fileName);
+ return goog.html.uncheckedconversions.
+ safeUrlFromStringKnownToSatisfyTypeContract(
+ goog.string.Const.from('view-source scheme plus HTTP/HTTPS URL'),
+ 'view-source:' + goog.html.SafeUrl.unwrap(sanitizedFileName));
+};
+
+
+/**
+ * Normalizes the error/exception object between browsers.
+ * @param {Object} err Raw error object.
+ * @return {!Object} Normalized error object.
+ */
+goog.debug.normalizeErrorObject = function(err) {
+ var href = goog.getObjectByName('window.location.href');
+ if (goog.isString(err)) {
+ return {
+ 'message': err,
+ 'name': 'Unknown error',
+ 'lineNumber': 'Not available',
+ 'fileName': href,
+ 'stack': 'Not available'
+ };
+ }
+
+ var lineNumber, fileName;
+ var threwError = false;
+
+ try {
+ lineNumber = err.lineNumber || err.line || 'Not available';
+ } catch (e) {
+ // Firefox 2 sometimes throws an error when accessing 'lineNumber':
+ // Message: Permission denied to get property UnnamedClass.lineNumber
+ lineNumber = 'Not available';
+ threwError = true;
+ }
+
+ try {
+ fileName = err.fileName || err.filename || err.sourceURL ||
+ // $googDebugFname may be set before a call to eval to set the filename
+ // that the eval is supposed to present.
+ goog.global['$googDebugFname'] || href;
+ } catch (e) {
+ // Firefox 2 may also throw an error when accessing 'filename'.
+ fileName = 'Not available';
+ threwError = true;
+ }
+
+ // The IE Error object contains only the name and the message.
+ // The Safari Error object uses the line and sourceURL fields.
+ if (threwError || !err.lineNumber || !err.fileName || !err.stack ||
+ !err.message || !err.name) {
+ return {
+ 'message': err.message || 'Not available',
+ 'name': err.name || 'UnknownError',
+ 'lineNumber': lineNumber,
+ 'fileName': fileName,
+ 'stack': err.stack || 'Not available'
+ };
+ }
+
+ // Standards error object
+ return err;
+};
+
+
+/**
+ * Converts an object to an Error if it's a String,
+ * adds a stacktrace if there isn't one,
+ * and optionally adds an extra message.
+ * @param {Error|string} err the original thrown object or string.
+ * @param {string=} opt_message optional additional message to add to the
+ * error.
+ * @return {!Error} If err is a string, it is used to create a new Error,
+ * which is enhanced and returned. Otherwise err itself is enhanced
+ * and returned.
+ */
+goog.debug.enhanceError = function(err, opt_message) {
+ var error;
+ if (typeof err == 'string') {
+ error = Error(err);
+ if (Error.captureStackTrace) {
+ // Trim this function off the call stack, if we can.
+ Error.captureStackTrace(error, goog.debug.enhanceError);
+ }
+ } else {
+ error = err;
+ }
+
+ if (!error.stack) {
+ error.stack = goog.debug.getStacktrace(goog.debug.enhanceError);
+ }
+ if (opt_message) {
+ // find the first unoccupied 'messageX' property
+ var x = 0;
+ while (error['message' + x]) {
+ ++x;
+ }
+ error['message' + x] = String(opt_message);
+ }
+ return error;
+};
+
+
+/**
+ * Gets the current stack trace. Simple and iterative - doesn't worry about
+ * catching circular references or getting the args.
+ * @param {number=} opt_depth Optional maximum depth to trace back to.
+ * @return {string} A string with the function names of all functions in the
+ * stack, separated by \n.
+ * @suppress {es5Strict}
+ */
+goog.debug.getStacktraceSimple = function(opt_depth) {
+ if (goog.STRICT_MODE_COMPATIBLE) {
+ var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);
+ if (stack) {
+ return stack;
+ }
+ // NOTE: browsers that have strict mode support also have native "stack"
+ // properties. Fall-through for legacy browser support.
+ }
+
+ var sb = [];
+ var fn = arguments.callee.caller;
+ var depth = 0;
+
+ while (fn && (!opt_depth || depth < opt_depth)) {
+ sb.push(goog.debug.getFunctionName(fn));
+ sb.push('()\n');
+ /** @preserveTry */
+ try {
+ fn = fn.caller;
+ } catch (e) {
+ sb.push('[exception trying to get caller]\n');
+ break;
+ }
+ depth++;
+ if (depth >= goog.debug.MAX_STACK_DEPTH) {
+ sb.push('[...long stack...]');
+ break;
+ }
+ }
+ if (opt_depth && depth >= opt_depth) {
+ sb.push('[...reached max depth limit...]');
+ } else {
+ sb.push('[end]');
+ }
+
+ return sb.join('');
+};
+
+
+/**
+ * Max length of stack to try and output
+ * @type {number}
+ */
+goog.debug.MAX_STACK_DEPTH = 50;
+
+
+/**
+ * @param {Function} fn The function to start getting the trace from.
+ * @return {?string}
+ * @private
+ */
+goog.debug.getNativeStackTrace_ = function(fn) {
+ var tempErr = new Error();
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(tempErr, fn);
+ return String(tempErr.stack);
+ } else {
+ // IE10, only adds stack traces when an exception is thrown.
+ try {
+ throw tempErr;
+ } catch (e) {
+ tempErr = e;
+ }
+ var stack = tempErr.stack;
+ if (stack) {
+ return String(stack);
+ }
+ }
+ return null;
+};
+
+
+/**
+ * Gets the current stack trace, either starting from the caller or starting
+ * from a specified function that's currently on the call stack.
+ * @param {Function=} opt_fn Optional function to start getting the trace from.
+ * If not provided, defaults to the function that called this.
+ * @return {string} Stack trace.
+ * @suppress {es5Strict}
+ */
+goog.debug.getStacktrace = function(opt_fn) {
+ var stack;
+ if (goog.STRICT_MODE_COMPATIBLE) {
+ // Try to get the stack trace from the environment if it is available.
+ var contextFn = opt_fn || goog.debug.getStacktrace;
+ stack = goog.debug.getNativeStackTrace_(contextFn);
+ }
+ if (!stack) {
+ // NOTE: browsers that have strict mode support also have native "stack"
+ // properties. This function will throw in strict mode.
+ stack = goog.debug.getStacktraceHelper_(
+ opt_fn || arguments.callee.caller, []);
+ }
+ return stack;
+};
+
+
+/**
+ * Private helper for getStacktrace().
+ * @param {Function} fn Function to start getting the trace from.
+ * @param {Array<!Function>} visited List of functions visited so far.
+ * @return {string} Stack trace starting from function fn.
+ * @suppress {es5Strict}
+ * @private
+ */
+goog.debug.getStacktraceHelper_ = function(fn, visited) {
+ var sb = [];
+
+ // Circular reference, certain functions like bind seem to cause a recursive
+ // loop so we need to catch circular references
+ if (goog.array.contains(visited, fn)) {
+ sb.push('[...circular reference...]');
+
+ // Traverse the call stack until function not found or max depth is reached
+ } else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
+ sb.push(goog.debug.getFunctionName(fn) + '(');
+ var args = fn.arguments;
+ // Args may be null for some special functions such as host objects or eval.
+ for (var i = 0; args && i < args.length; i++) {
+ if (i > 0) {
+ sb.push(', ');
+ }
+ var argDesc;
+ var arg = args[i];
+ switch (typeof arg) {
+ case 'object':
+ argDesc = arg ? 'object' : 'null';
+ break;
+
+ case 'string':
+ argDesc = arg;
+ break;
+
+ case 'number':
+ argDesc = String(arg);
+ break;
+
+ case 'boolean':
+ argDesc = arg ? 'true' : 'false';
+ break;
+
+ case 'function':
+ argDesc = goog.debug.getFunctionName(arg);
+ argDesc = argDesc ? argDesc : '[fn]';
+ break;
+
+ case 'undefined':
+ default:
+ argDesc = typeof arg;
+ break;
+ }
+
+ if (argDesc.length > 40) {
+ argDesc = argDesc.substr(0, 40) + '...';
+ }
+ sb.push(argDesc);
+ }
+ visited.push(fn);
+ sb.push(')\n');
+ /** @preserveTry */
+ try {
+ sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
+ } catch (e) {
+ sb.push('[exception trying to get caller]\n');
+ }
+
+ } else if (fn) {
+ sb.push('[...long stack...]');
+ } else {
+ sb.push('[end]');
+ }
+ return sb.join('');
+};
+
+
+/**
+ * Set a custom function name resolver.
+ * @param {function(Function): string} resolver Resolves functions to their
+ * names.
+ */
+goog.debug.setFunctionResolver = function(resolver) {
+ goog.debug.fnNameResolver_ = resolver;
+};
+
+
+/**
+ * Gets a function name
+ * @param {Function} fn Function to get name of.
+ * @return {string} Function's name.
+ */
+goog.debug.getFunctionName = function(fn) {
+ if (goog.debug.fnNameCache_[fn]) {
+ return goog.debug.fnNameCache_[fn];
+ }
+ if (goog.debug.fnNameResolver_) {
+ var name = goog.debug.fnNameResolver_(fn);
+ if (name) {
+ goog.debug.fnNameCache_[fn] = name;
+ return name;
+ }
+ }
+
+ // Heuristically determine function name based on code.
+ var functionSource = String(fn);
+ if (!goog.debug.fnNameCache_[functionSource]) {
+ var matches = /function ([^\(]+)/.exec(functionSource);
+ if (matches) {
+ var method = matches[1];
+ goog.debug.fnNameCache_[functionSource] = method;
+ } else {
+ goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
+ }
+ }
+
+ return goog.debug.fnNameCache_[functionSource];
+};
+
+
+/**
+ * Makes whitespace visible by replacing it with printable characters.
+ * This is useful in finding diffrences between the expected and the actual
+ * output strings of a testcase.
+ * @param {string} string whose whitespace needs to be made visible.
+ * @return {string} string whose whitespace is made visible.
+ */
+goog.debug.makeWhitespaceVisible = function(string) {
+ return string.replace(/ /g, '[_]')
+ .replace(/\f/g, '[f]')
+ .replace(/\n/g, '[n]\n')
+ .replace(/\r/g, '[r]')
+ .replace(/\t/g, '[t]');
+};
+
+
+/**
+ * Returns the type of a value. If a constructor is passed, and a suitable
+ * string cannot be found, 'unknown type name' will be returned.
+ *
+ * <p>Forked rather than moved from {@link goog.asserts.getType_}
+ * to avoid adding a dependency to goog.asserts.
+ * @param {*} value A constructor, object, or primitive.
+ * @return {string} The best display name for the value, or 'unknown type name'.
+ */
+goog.debug.runtimeType = function(value) {
+ if (value instanceof Function) {
+ return value.displayName || value.name || 'unknown type name';
+ } else if (value instanceof Object) {
+ return value.constructor.displayName || value.constructor.name ||
+ Object.prototype.toString.call(value);
+ } else {
+ return value === null ? 'null' : typeof value;
+ }
+};
+
+
+/**
+ * Hash map for storing function names that have already been looked up.
+ * @type {Object}
+ * @private
+ */
+goog.debug.fnNameCache_ = {};
+
+
+/**
+ * Resolves functions to their names. Resolved function names will be cached.
+ * @type {function(Function):string}
+ * @private
+ */
+goog.debug.fnNameResolver_;
+
+// 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 Definition of the LogRecord class. Please minimize
+ * dependencies this file has on other closure classes as any dependency it
+ * takes won't be able to use the logging infrastructure.
+ *
+ */
+
+goog.provide('goog.debug.LogRecord');
+
+
+
+/**
+ * LogRecord objects are used to pass logging requests between
+ * the logging framework and individual log Handlers.
+ * @constructor
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {string} msg The string message.
+ * @param {string} loggerName The name of the source logger.
+ * @param {number=} opt_time Time this log record was created if other than now.
+ * If 0, we use #goog.now.
+ * @param {number=} opt_sequenceNumber Sequence number of this log record. This
+ * should only be passed in when restoring a log record from persistence.
+ */
+goog.debug.LogRecord = function(level, msg, loggerName,
+ opt_time, opt_sequenceNumber) {
+ this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber);
+};
+
+
+/**
+ * Time the LogRecord was created.
+ * @type {number}
+ * @private
+ */
+goog.debug.LogRecord.prototype.time_;
+
+
+/**
+ * Level of the LogRecord
+ * @type {goog.debug.Logger.Level}
+ * @private
+ */
+goog.debug.LogRecord.prototype.level_;
+
+
+/**
+ * Message associated with the record
+ * @type {string}
+ * @private
+ */
+goog.debug.LogRecord.prototype.msg_;
+
+
+/**
+ * Name of the logger that created the record.
+ * @type {string}
+ * @private
+ */
+goog.debug.LogRecord.prototype.loggerName_;
+
+
+/**
+ * Sequence number for the LogRecord. Each record has a unique sequence number
+ * that is greater than all log records created before it.
+ * @type {number}
+ * @private
+ */
+goog.debug.LogRecord.prototype.sequenceNumber_ = 0;
+
+
+/**
+ * Exception associated with the record
+ * @type {Object}
+ * @private
+ */
+goog.debug.LogRecord.prototype.exception_ = null;
+
+
+/**
+ * @define {boolean} Whether to enable log sequence numbers.
+ */
+goog.define('goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS', true);
+
+
+/**
+ * A sequence counter for assigning increasing sequence numbers to LogRecord
+ * objects.
+ * @type {number}
+ * @private
+ */
+goog.debug.LogRecord.nextSequenceNumber_ = 0;
+
+
+/**
+ * Sets all fields of the log record.
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {string} msg The string message.
+ * @param {string} loggerName The name of the source logger.
+ * @param {number=} opt_time Time this log record was created if other than now.
+ * If 0, we use #goog.now.
+ * @param {number=} opt_sequenceNumber Sequence number of this log record. This
+ * should only be passed in when restoring a log record from persistence.
+ */
+goog.debug.LogRecord.prototype.reset = function(level, msg, loggerName,
+ opt_time, opt_sequenceNumber) {
+ if (goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS) {
+ this.sequenceNumber_ = typeof opt_sequenceNumber == 'number' ?
+ opt_sequenceNumber : goog.debug.LogRecord.nextSequenceNumber_++;
+ }
+
+ this.time_ = opt_time || goog.now();
+ this.level_ = level;
+ this.msg_ = msg;
+ this.loggerName_ = loggerName;
+ delete this.exception_;
+};
+
+
+/**
+ * Get the source Logger's name.
+ *
+ * @return {string} source logger name (may be null).
+ */
+goog.debug.LogRecord.prototype.getLoggerName = function() {
+ return this.loggerName_;
+};
+
+
+/**
+ * Get the exception that is part of the log record.
+ *
+ * @return {Object} the exception.
+ */
+goog.debug.LogRecord.prototype.getException = function() {
+ return this.exception_;
+};
+
+
+/**
+ * Set the exception that is part of the log record.
+ *
+ * @param {Object} exception the exception.
+ */
+goog.debug.LogRecord.prototype.setException = function(exception) {
+ this.exception_ = exception;
+};
+
+
+/**
+ * Get the source Logger's name.
+ *
+ * @param {string} loggerName source logger name (may be null).
+ */
+goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) {
+ this.loggerName_ = loggerName;
+};
+
+
+/**
+ * Get the logging message level, for example Level.SEVERE.
+ * @return {goog.debug.Logger.Level} the logging message level.
+ */
+goog.debug.LogRecord.prototype.getLevel = function() {
+ return this.level_;
+};
+
+
+/**
+ * Set the logging message level, for example Level.SEVERE.
+ * @param {goog.debug.Logger.Level} level the logging message level.
+ */
+goog.debug.LogRecord.prototype.setLevel = function(level) {
+ this.level_ = level;
+};
+
+
+/**
+ * Get the "raw" log message, before localization or formatting.
+ *
+ * @return {string} the raw message string.
+ */
+goog.debug.LogRecord.prototype.getMessage = function() {
+ return this.msg_;
+};
+
+
+/**
+ * Set the "raw" log message, before localization or formatting.
+ *
+ * @param {string} msg the raw message string.
+ */
+goog.debug.LogRecord.prototype.setMessage = function(msg) {
+ this.msg_ = msg;
+};
+
+
+/**
+ * Get event time in milliseconds since 1970.
+ *
+ * @return {number} event time in millis since 1970.
+ */
+goog.debug.LogRecord.prototype.getMillis = function() {
+ return this.time_;
+};
+
+
+/**
+ * Set event time in milliseconds since 1970.
+ *
+ * @param {number} time event time in millis since 1970.
+ */
+goog.debug.LogRecord.prototype.setMillis = function(time) {
+ this.time_ = time;
+};
+
+
+/**
+ * Get the sequence number.
+ * <p>
+ * Sequence numbers are normally assigned in the LogRecord
+ * constructor, which assigns unique sequence numbers to
+ * each new LogRecord in increasing order.
+ * @return {number} the sequence number.
+ */
+goog.debug.LogRecord.prototype.getSequenceNumber = function() {
+ return this.sequenceNumber_;
+};
+
+
+// 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 A buffer for log records. The purpose of this is to improve
+ * logging performance by re-using old objects when the buffer becomes full and
+ * to eliminate the need for each app to implement their own log buffer. The
+ * disadvantage to doing this is that log handlers cannot maintain references to
+ * log records and expect that they are not overwriten at a later point.
+ *
+ * @author agrieve@google.com (Andrew Grieve)
+ */
+
+goog.provide('goog.debug.LogBuffer');
+
+goog.require('goog.asserts');
+goog.require('goog.debug.LogRecord');
+
+
+
+/**
+ * Creates the log buffer.
+ * @constructor
+ * @final
+ */
+goog.debug.LogBuffer = function() {
+ goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(),
+ 'Cannot use goog.debug.LogBuffer without defining ' +
+ 'goog.debug.LogBuffer.CAPACITY.');
+ this.clear();
+};
+
+
+/**
+ * A static method that always returns the same instance of LogBuffer.
+ * @return {!goog.debug.LogBuffer} The LogBuffer singleton instance.
+ */
+goog.debug.LogBuffer.getInstance = function() {
+ if (!goog.debug.LogBuffer.instance_) {
+ // This function is written with the return statement after the assignment
+ // to avoid the jscompiler StripCode bug described in http://b/2608064.
+ // After that bug is fixed this can be refactored.
+ goog.debug.LogBuffer.instance_ = new goog.debug.LogBuffer();
+ }
+ return goog.debug.LogBuffer.instance_;
+};
+
+
+/**
+ * @define {number} The number of log records to buffer. 0 means disable
+ * buffering.
+ */
+goog.define('goog.debug.LogBuffer.CAPACITY', 0);
+
+
+/**
+ * The array to store the records.
+ * @type {!Array<!goog.debug.LogRecord|undefined>}
+ * @private
+ */
+goog.debug.LogBuffer.prototype.buffer_;
+
+
+/**
+ * The index of the most recently added record or -1 if there are no records.
+ * @type {number}
+ * @private
+ */
+goog.debug.LogBuffer.prototype.curIndex_;
+
+
+/**
+ * Whether the buffer is at capacity.
+ * @type {boolean}
+ * @private
+ */
+goog.debug.LogBuffer.prototype.isFull_;
+
+
+/**
+ * Adds a log record to the buffer, possibly overwriting the oldest record.
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {string} msg The string message.
+ * @param {string} loggerName The name of the source logger.
+ * @return {!goog.debug.LogRecord} The log record.
+ */
+goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) {
+ var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY;
+ this.curIndex_ = curIndex;
+ if (this.isFull_) {
+ var ret = this.buffer_[curIndex];
+ ret.reset(level, msg, loggerName);
+ return ret;
+ }
+ this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1;
+ return this.buffer_[curIndex] =
+ new goog.debug.LogRecord(level, msg, loggerName);
+};
+
+
+/**
+ * @return {boolean} Whether the log buffer is enabled.
+ */
+goog.debug.LogBuffer.isBufferingEnabled = function() {
+ return goog.debug.LogBuffer.CAPACITY > 0;
+};
+
+
+/**
+ * Removes all buffered log records.
+ */
+goog.debug.LogBuffer.prototype.clear = function() {
+ this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY);
+ this.curIndex_ = -1;
+ this.isFull_ = false;
+};
+
+
+/**
+ * Calls the given function for each buffered log record, starting with the
+ * oldest one.
+ * @param {function(!goog.debug.LogRecord)} func The function to call.
+ */
+goog.debug.LogBuffer.prototype.forEachRecord = function(func) {
+ var buffer = this.buffer_;
+ // Corner case: no records.
+ if (!buffer[0]) {
+ return;
+ }
+ var curIndex = this.curIndex_;
+ var i = this.isFull_ ? curIndex : -1;
+ do {
+ i = (i + 1) % goog.debug.LogBuffer.CAPACITY;
+ func(/** @type {!goog.debug.LogRecord} */ (buffer[i]));
+ } while (i != curIndex);
+};
+
+
+// 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 Definition of the Logger class. Please minimize dependencies
+ * this file has on other closure classes as any dependency it takes won't be
+ * able to use the logging infrastructure.
+ *
+ * @see ../demos/debug.html
+ */
+
+goog.provide('goog.debug.LogManager');
+goog.provide('goog.debug.Loggable');
+goog.provide('goog.debug.Logger');
+goog.provide('goog.debug.Logger.Level');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.debug');
+goog.require('goog.debug.LogBuffer');
+goog.require('goog.debug.LogRecord');
+
+
+/**
+ * A message value that can be handled by a Logger.
+ *
+ * Functions are treated like callbacks, but are only called when the event's
+ * log level is enabled. This is useful for logging messages that are expensive
+ * to construct.
+ *
+ * @typedef {string|function(): string}
+ */
+goog.debug.Loggable;
+
+
+
+/**
+ * The Logger is an object used for logging debug messages. Loggers are
+ * normally named, using a hierarchical dot-separated namespace. Logger names
+ * can be arbitrary strings, but they should normally be based on the package
+ * name or class name of the logged component, such as goog.net.BrowserChannel.
+ *
+ * The Logger object is loosely based on the java class
+ * java.util.logging.Logger. It supports different levels of filtering for
+ * different loggers.
+ *
+ * The logger object should never be instantiated by application code. It
+ * should always use the goog.debug.Logger.getLogger function.
+ *
+ * @constructor
+ * @param {string} name The name of the Logger.
+ * @final
+ */
+goog.debug.Logger = function(name) {
+ /**
+ * Name of the Logger. Generally a dot-separated namespace
+ * @private {string}
+ */
+ this.name_ = name;
+
+ /**
+ * Parent Logger.
+ * @private {goog.debug.Logger}
+ */
+ this.parent_ = null;
+
+ /**
+ * Level that this logger only filters above. Null indicates it should
+ * inherit from the parent.
+ * @private {goog.debug.Logger.Level}
+ */
+ this.level_ = null;
+
+ /**
+ * Map of children loggers. The keys are the leaf names of the children and
+ * the values are the child loggers.
+ * @private {Object}
+ */
+ this.children_ = null;
+
+ /**
+ * Handlers that are listening to this logger.
+ * @private {Array<Function>}
+ */
+ this.handlers_ = null;
+};
+
+
+/** @const */
+goog.debug.Logger.ROOT_LOGGER_NAME = '';
+
+
+/**
+ * @define {boolean} Toggles whether loggers other than the root logger can have
+ * log handlers attached to them and whether they can have their log level
+ * set. Logging is a bit faster when this is set to false.
+ */
+goog.define('goog.debug.Logger.ENABLE_HIERARCHY', true);
+
+
+if (!goog.debug.Logger.ENABLE_HIERARCHY) {
+ /**
+ * @type {!Array<Function>}
+ * @private
+ */
+ goog.debug.Logger.rootHandlers_ = [];
+
+
+ /**
+ * @type {goog.debug.Logger.Level}
+ * @private
+ */
+ goog.debug.Logger.rootLevel_;
+}
+
+
+
+/**
+ * The Level class defines a set of standard logging levels that
+ * can be used to control logging output. The logging Level objects
+ * are ordered and are specified by ordered integers. Enabling logging
+ * at a given level also enables logging at all higher levels.
+ * <p>
+ * Clients should normally use the predefined Level constants such
+ * as Level.SEVERE.
+ * <p>
+ * The levels in descending order are:
+ * <ul>
+ * <li>SEVERE (highest value)
+ * <li>WARNING
+ * <li>INFO
+ * <li>CONFIG
+ * <li>FINE
+ * <li>FINER
+ * <li>FINEST (lowest value)
+ * </ul>
+ * In addition there is a level OFF that can be used to turn
+ * off logging, and a level ALL that can be used to enable
+ * logging of all messages.
+ *
+ * @param {string} name The name of the level.
+ * @param {number} value The numeric value of the level.
+ * @constructor
+ * @final
+ */
+goog.debug.Logger.Level = function(name, value) {
+ /**
+ * The name of the level
+ * @type {string}
+ */
+ this.name = name;
+
+ /**
+ * The numeric value of the level
+ * @type {number}
+ */
+ this.value = value;
+};
+
+
+/**
+ * @return {string} String representation of the logger level.
+ * @override
+ */
+goog.debug.Logger.Level.prototype.toString = function() {
+ return this.name;
+};
+
+
+/**
+ * OFF is a special level that can be used to turn off logging.
+ * This level is initialized to <CODE>Infinity</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.OFF =
+ new goog.debug.Logger.Level('OFF', Infinity);
+
+
+/**
+ * SHOUT is a message level for extra debugging loudness.
+ * This level is initialized to <CODE>1200</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level('SHOUT', 1200);
+
+
+/**
+ * SEVERE is a message level indicating a serious failure.
+ * This level is initialized to <CODE>1000</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level('SEVERE', 1000);
+
+
+/**
+ * WARNING is a message level indicating a potential problem.
+ * This level is initialized to <CODE>900</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.WARNING = new goog.debug.Logger.Level('WARNING', 900);
+
+
+/**
+ * INFO is a message level for informational messages.
+ * This level is initialized to <CODE>800</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level('INFO', 800);
+
+
+/**
+ * CONFIG is a message level for static configuration messages.
+ * This level is initialized to <CODE>700</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level('CONFIG', 700);
+
+
+/**
+ * FINE is a message level providing tracing information.
+ * This level is initialized to <CODE>500</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level('FINE', 500);
+
+
+/**
+ * FINER indicates a fairly detailed tracing message.
+ * This level is initialized to <CODE>400</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.FINER = new goog.debug.Logger.Level('FINER', 400);
+
+/**
+ * FINEST indicates a highly detailed tracing message.
+ * This level is initialized to <CODE>300</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+
+goog.debug.Logger.Level.FINEST = new goog.debug.Logger.Level('FINEST', 300);
+
+
+/**
+ * ALL indicates that all messages should be logged.
+ * This level is initialized to <CODE>0</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level('ALL', 0);
+
+
+/**
+ * The predefined levels.
+ * @type {!Array<!goog.debug.Logger.Level>}
+ * @final
+ */
+goog.debug.Logger.Level.PREDEFINED_LEVELS = [
+ goog.debug.Logger.Level.OFF,
+ goog.debug.Logger.Level.SHOUT,
+ goog.debug.Logger.Level.SEVERE,
+ goog.debug.Logger.Level.WARNING,
+ goog.debug.Logger.Level.INFO,
+ goog.debug.Logger.Level.CONFIG,
+ goog.debug.Logger.Level.FINE,
+ goog.debug.Logger.Level.FINER,
+ goog.debug.Logger.Level.FINEST,
+ goog.debug.Logger.Level.ALL];
+
+
+/**
+ * A lookup map used to find the level object based on the name or value of
+ * the level object.
+ * @type {Object}
+ * @private
+ */
+goog.debug.Logger.Level.predefinedLevelsCache_ = null;
+
+
+/**
+ * Creates the predefined levels cache and populates it.
+ * @private
+ */
+goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() {
+ goog.debug.Logger.Level.predefinedLevelsCache_ = {};
+ for (var i = 0, level; level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
+ i++) {
+ goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level;
+ goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level;
+ }
+};
+
+
+/**
+ * Gets the predefined level with the given name.
+ * @param {string} name The name of the level.
+ * @return {goog.debug.Logger.Level} The level, or null if none found.
+ */
+goog.debug.Logger.Level.getPredefinedLevel = function(name) {
+ if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
+ goog.debug.Logger.Level.createPredefinedLevelsCache_();
+ }
+
+ return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null;
+};
+
+
+/**
+ * Gets the highest predefined level <= #value.
+ * @param {number} value Level value.
+ * @return {goog.debug.Logger.Level} The level, or null if none found.
+ */
+goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) {
+ if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
+ goog.debug.Logger.Level.createPredefinedLevelsCache_();
+ }
+
+ if (value in goog.debug.Logger.Level.predefinedLevelsCache_) {
+ return goog.debug.Logger.Level.predefinedLevelsCache_[value];
+ }
+
+ for (var i = 0; i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length; ++i) {
+ var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
+ if (level.value <= value) {
+ return level;
+ }
+ }
+ return null;
+};
+
+
+/**
+ * Finds or creates a logger for a named subsystem. If a logger has already been
+ * created with the given name it is returned. Otherwise a new logger is
+ * created. If a new logger is created its log level will be configured based
+ * on the LogManager configuration and it will configured to also send logging
+ * output to its parent's handlers. It will be registered in the LogManager
+ * global namespace.
+ *
+ * @param {string} name A name for the logger. This should be a dot-separated
+ * name and should normally be based on the package name or class name of the
+ * subsystem, such as goog.net.BrowserChannel.
+ * @return {!goog.debug.Logger} The named logger.
+ * @deprecated use goog.log instead. http://go/goog-debug-logger-deprecated
+ */
+goog.debug.Logger.getLogger = function(name) {
+ return goog.debug.LogManager.getLogger(name);
+};
+
+
+/**
+ * Logs a message to profiling tools, if available.
+ * {@see https://developers.google.com/web-toolkit/speedtracer/logging-api}
+ * {@see http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx}
+ * @param {string} msg The message to log.
+ */
+goog.debug.Logger.logToProfilers = function(msg) {
+ // Using goog.global, as loggers might be used in window-less contexts.
+ if (goog.global['console']) {
+ if (goog.global['console']['timeStamp']) {
+ // Logs a message to Firebug, Web Inspector, SpeedTracer, etc.
+ goog.global['console']['timeStamp'](msg);
+ } else if (goog.global['console']['markTimeline']) {
+ // TODO(user): markTimeline is deprecated. Drop this else clause entirely
+ // after Chrome M14 hits stable.
+ goog.global['console']['markTimeline'](msg);
+ }
+ }
+
+ if (goog.global['msWriteProfilerMark']) {
+ // Logs a message to the Microsoft profiler
+ goog.global['msWriteProfilerMark'](msg);
+ }
+};
+
+
+/**
+ * Gets the name of this logger.
+ * @return {string} The name of this logger.
+ */
+goog.debug.Logger.prototype.getName = function() {
+ return this.name_;
+};
+
+
+/**
+ * Adds a handler to the logger. This doesn't use the event system because
+ * we want to be able to add logging to the event system.
+ * @param {Function} handler Handler function to add.
+ */
+goog.debug.Logger.prototype.addHandler = function(handler) {
+ if (goog.debug.LOGGING_ENABLED) {
+ if (goog.debug.Logger.ENABLE_HIERARCHY) {
+ if (!this.handlers_) {
+ this.handlers_ = [];
+ }
+ this.handlers_.push(handler);
+ } else {
+ goog.asserts.assert(!this.name_,
+ 'Cannot call addHandler on a non-root logger when ' +
+ 'goog.debug.Logger.ENABLE_HIERARCHY is false.');
+ goog.debug.Logger.rootHandlers_.push(handler);
+ }
+ }
+};
+
+
+/**
+ * Removes a handler from the logger. This doesn't use the event system because
+ * we want to be able to add logging to the event system.
+ * @param {Function} handler Handler function to remove.
+ * @return {boolean} Whether the handler was removed.
+ */
+goog.debug.Logger.prototype.removeHandler = function(handler) {
+ if (goog.debug.LOGGING_ENABLED) {
+ var handlers = goog.debug.Logger.ENABLE_HIERARCHY ? this.handlers_ :
+ goog.debug.Logger.rootHandlers_;
+ return !!handlers && goog.array.remove(handlers, handler);
+ } else {
+ return false;
+ }
+};
+
+
+/**
+ * Returns the parent of this logger.
+ * @return {goog.debug.Logger} The parent logger or null if this is the root.
+ */
+goog.debug.Logger.prototype.getParent = function() {
+ return this.parent_;
+};
+
+
+/**
+ * Returns the children of this logger as a map of the child name to the logger.
+ * @return {!Object} The map where the keys are the child leaf names and the
+ * values are the Logger objects.
+ */
+goog.debug.Logger.prototype.getChildren = function() {
+ if (!this.children_) {
+ this.children_ = {};
+ }
+ return this.children_;
+};
+
+
+/**
+ * Set the log level specifying which message levels will be logged by this
+ * logger. Message levels lower than this value will be discarded.
+ * The level value Level.OFF can be used to turn off logging. If the new level
+ * is null, it means that this node should inherit its level from its nearest
+ * ancestor with a specific (non-null) level value.
+ *
+ * @param {goog.debug.Logger.Level} level The new level.
+ */
+goog.debug.Logger.prototype.setLevel = function(level) {
+ if (goog.debug.LOGGING_ENABLED) {
+ if (goog.debug.Logger.ENABLE_HIERARCHY) {
+ this.level_ = level;
+ } else {
+ goog.asserts.assert(!this.name_,
+ 'Cannot call setLevel() on a non-root logger when ' +
+ 'goog.debug.Logger.ENABLE_HIERARCHY is false.');
+ goog.debug.Logger.rootLevel_ = level;
+ }
+ }
+};
+
+
+/**
+ * Gets the log level specifying which message levels will be logged by this
+ * logger. Message levels lower than this value will be discarded.
+ * The level value Level.OFF can be used to turn off logging. If the level
+ * is null, it means that this node should inherit its level from its nearest
+ * ancestor with a specific (non-null) level value.
+ *
+ * @return {goog.debug.Logger.Level} The level.
+ */
+goog.debug.Logger.prototype.getLevel = function() {
+ return goog.debug.LOGGING_ENABLED ?
+ this.level_ : goog.debug.Logger.Level.OFF;
+};
+
+
+/**
+ * Returns the effective level of the logger based on its ancestors' levels.
+ * @return {goog.debug.Logger.Level} The level.
+ */
+goog.debug.Logger.prototype.getEffectiveLevel = function() {
+ if (!goog.debug.LOGGING_ENABLED) {
+ return goog.debug.Logger.Level.OFF;
+ }
+
+ if (!goog.debug.Logger.ENABLE_HIERARCHY) {
+ return goog.debug.Logger.rootLevel_;
+ }
+ if (this.level_) {
+ return this.level_;
+ }
+ if (this.parent_) {
+ return this.parent_.getEffectiveLevel();
+ }
+ goog.asserts.fail('Root logger has no level set.');
+ return null;
+};
+
+
+/**
+ * Checks if a message of the given level would actually be logged by this
+ * logger. This check is based on the Loggers effective level, which may be
+ * inherited from its parent.
+ * @param {goog.debug.Logger.Level} level The level to check.
+ * @return {boolean} Whether the message would be logged.
+ */
+goog.debug.Logger.prototype.isLoggable = function(level) {
+ return goog.debug.LOGGING_ENABLED &&
+ level.value >= this.getEffectiveLevel().value;
+};
+
+
+/**
+ * Logs a message. If the logger is currently enabled for the
+ * given message level then the given message is forwarded to all the
+ * registered output Handler objects.
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error|Object=} opt_exception An exception associated with the
+ * message.
+ */
+goog.debug.Logger.prototype.log = function(level, msg, opt_exception) {
+ // java caches the effective level, not sure it's necessary here
+ if (goog.debug.LOGGING_ENABLED && this.isLoggable(level)) {
+ // Message callbacks can be useful when a log message is expensive to build.
+ if (goog.isFunction(msg)) {
+ msg = msg();
+ }
+
+ this.doLogRecord_(this.getLogRecord(level, msg, opt_exception));
+ }
+};
+
+
+/**
+ * Creates a new log record and adds the exception (if present) to it.
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {string} msg The string message.
+ * @param {Error|Object=} opt_exception An exception associated with the
+ * message.
+ * @return {!goog.debug.LogRecord} A log record.
+ * @suppress {es5Strict}
+ */
+goog.debug.Logger.prototype.getLogRecord = function(
+ level, msg, opt_exception) {
+ if (goog.debug.LogBuffer.isBufferingEnabled()) {
+ var logRecord =
+ goog.debug.LogBuffer.getInstance().addRecord(level, msg, this.name_);
+ } else {
+ logRecord = new goog.debug.LogRecord(level, String(msg), this.name_);
+ }
+ if (opt_exception) {
+ logRecord.setException(opt_exception);
+ }
+ return logRecord;
+};
+
+
+/**
+ * Logs a message at the Logger.Level.SHOUT level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.shout = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.SEVERE level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.severe = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.WARNING level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.warning = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.INFO level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.info = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.INFO, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.CONFIG level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.config = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.FINE level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.fine = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.FINE, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.FINER level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.finer = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.FINER, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.FINEST level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.finest = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a LogRecord. If the logger is currently enabled for the
+ * given message level then the given message is forwarded to all the
+ * registered output Handler objects.
+ * @param {goog.debug.LogRecord} logRecord A log record to log.
+ */
+goog.debug.Logger.prototype.logRecord = function(logRecord) {
+ if (goog.debug.LOGGING_ENABLED && this.isLoggable(logRecord.getLevel())) {
+ this.doLogRecord_(logRecord);
+ }
+};
+
+
+/**
+ * Logs a LogRecord.
+ * @param {goog.debug.LogRecord} logRecord A log record to log.
+ * @private
+ */
+goog.debug.Logger.prototype.doLogRecord_ = function(logRecord) {
+ goog.debug.Logger.logToProfilers('log:' + logRecord.getMessage());
+ if (goog.debug.Logger.ENABLE_HIERARCHY) {
+ var target = this;
+ while (target) {
+ target.callPublish_(logRecord);
+ target = target.getParent();
+ }
+ } else {
+ for (var i = 0, handler; handler = goog.debug.Logger.rootHandlers_[i++]; ) {
+ handler(logRecord);
+ }
+ }
+};
+
+
+/**
+ * Calls the handlers for publish.
+ * @param {goog.debug.LogRecord} logRecord The log record to publish.
+ * @private
+ */
+goog.debug.Logger.prototype.callPublish_ = function(logRecord) {
+ if (this.handlers_) {
+ for (var i = 0, handler; handler = this.handlers_[i]; i++) {
+ handler(logRecord);
+ }
+ }
+};
+
+
+/**
+ * Sets the parent of this logger. This is used for setting up the logger tree.
+ * @param {goog.debug.Logger} parent The parent logger.
+ * @private
+ */
+goog.debug.Logger.prototype.setParent_ = function(parent) {
+ this.parent_ = parent;
+};
+
+
+/**
+ * Adds a child to this logger. This is used for setting up the logger tree.
+ * @param {string} name The leaf name of the child.
+ * @param {goog.debug.Logger} logger The child logger.
+ * @private
+ */
+goog.debug.Logger.prototype.addChild_ = function(name, logger) {
+ this.getChildren()[name] = logger;
+};
+
+
+/**
+ * There is a single global LogManager object that is used to maintain a set of
+ * shared state about Loggers and log services. This is loosely based on the
+ * java class java.util.logging.LogManager.
+ * @const
+ */
+goog.debug.LogManager = {};
+
+
+/**
+ * Map of logger names to logger objects.
+ *
+ * @type {!Object<string, !goog.debug.Logger>}
+ * @private
+ */
+goog.debug.LogManager.loggers_ = {};
+
+
+/**
+ * The root logger which is the root of the logger tree.
+ * @type {goog.debug.Logger}
+ * @private
+ */
+goog.debug.LogManager.rootLogger_ = null;
+
+
+/**
+ * Initializes the LogManager if not already initialized.
+ */
+goog.debug.LogManager.initialize = function() {
+ if (!goog.debug.LogManager.rootLogger_) {
+ goog.debug.LogManager.rootLogger_ = new goog.debug.Logger(
+ goog.debug.Logger.ROOT_LOGGER_NAME);
+ goog.debug.LogManager.loggers_[goog.debug.Logger.ROOT_LOGGER_NAME] =
+ goog.debug.LogManager.rootLogger_;
+ goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG);
+ }
+};
+
+
+/**
+ * Returns all the loggers.
+ * @return {!Object<string, !goog.debug.Logger>} Map of logger names to logger
+ * objects.
+ */
+goog.debug.LogManager.getLoggers = function() {
+ return goog.debug.LogManager.loggers_;
+};
+
+
+/**
+ * Returns the root of the logger tree namespace, the logger with the empty
+ * string as its name.
+ *
+ * @return {!goog.debug.Logger} The root logger.
+ */
+goog.debug.LogManager.getRoot = function() {
+ goog.debug.LogManager.initialize();
+ return /** @type {!goog.debug.Logger} */ (goog.debug.LogManager.rootLogger_);
+};
+
+
+/**
+ * Finds a named logger.
+ *
+ * @param {string} name A name for the logger. This should be a dot-separated
+ * name and should normally be based on the package name or class name of the
+ * subsystem, such as goog.net.BrowserChannel.
+ * @return {!goog.debug.Logger} The named logger.
+ */
+goog.debug.LogManager.getLogger = function(name) {
+ goog.debug.LogManager.initialize();
+ var ret = goog.debug.LogManager.loggers_[name];
+ return ret || goog.debug.LogManager.createLogger_(name);
+};
+
+
+/**
+ * Creates a function that can be passed to goog.debug.catchErrors. The function
+ * will log all reported errors using the given logger.
+ * @param {goog.debug.Logger=} opt_logger The logger to log the errors to.
+ * Defaults to the root logger.
+ * @return {function(Object)} The created function.
+ */
+goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) {
+ return function(info) {
+ var logger = opt_logger || goog.debug.LogManager.getRoot();
+ logger.severe('Error: ' + info.message + ' (' + info.fileName +
+ ' @ Line: ' + info.line + ')');
+ };
+};
+
+
+/**
+ * Creates the named logger. Will also create the parents of the named logger
+ * if they don't yet exist.
+ * @param {string} name The name of the logger.
+ * @return {!goog.debug.Logger} The named logger.
+ * @private
+ */
+goog.debug.LogManager.createLogger_ = function(name) {
+ // find parent logger
+ var logger = new goog.debug.Logger(name);
+ if (goog.debug.Logger.ENABLE_HIERARCHY) {
+ var lastDotIndex = name.lastIndexOf('.');
+ var parentName = name.substr(0, lastDotIndex);
+ var leafName = name.substr(lastDotIndex + 1);
+ var parentLogger = goog.debug.LogManager.getLogger(parentName);
+
+ // tell the parent about the child and the child about the parent
+ parentLogger.addChild_(leafName, logger);
+ logger.setParent_(parentLogger);
+ }
+
+ goog.debug.LogManager.loggers_[name] = logger;
+ return logger;
+};
+
+// 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 Definition the goog.debug.RelativeTimeProvider class.
+ *
+ */
+
+goog.provide('goog.debug.RelativeTimeProvider');
+
+
+
+/**
+ * A simple object to keep track of a timestamp considered the start of
+ * something. The main use is for the logger system to maintain a start time
+ * that is occasionally reset. For example, in Gmail, we reset this relative
+ * time at the start of a user action so that timings are offset from the
+ * beginning of the action. This class also provides a singleton as the default
+ * behavior for most use cases is to share the same start time.
+ *
+ * @constructor
+ * @final
+ */
+goog.debug.RelativeTimeProvider = function() {
+ /**
+ * The start time.
+ * @type {number}
+ * @private
+ */
+ this.relativeTimeStart_ = goog.now();
+};
+
+
+/**
+ * Default instance.
+ * @type {goog.debug.RelativeTimeProvider}
+ * @private
+ */
+goog.debug.RelativeTimeProvider.defaultInstance_ =
+ new goog.debug.RelativeTimeProvider();
+
+
+/**
+ * Sets the start time to the specified time.
+ * @param {number} timeStamp The start time.
+ */
+goog.debug.RelativeTimeProvider.prototype.set = function(timeStamp) {
+ this.relativeTimeStart_ = timeStamp;
+};
+
+
+/**
+ * Resets the start time to now.
+ */
+goog.debug.RelativeTimeProvider.prototype.reset = function() {
+ this.set(goog.now());
+};
+
+
+/**
+ * @return {number} The start time.
+ */
+goog.debug.RelativeTimeProvider.prototype.get = function() {
+ return this.relativeTimeStart_;
+};
+
+
+/**
+ * @return {goog.debug.RelativeTimeProvider} The default instance.
+ */
+goog.debug.RelativeTimeProvider.getDefaultInstance = function() {
+ return goog.debug.RelativeTimeProvider.defaultInstance_;
+};
+
+// 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 Definition of various formatters for logging. Please minimize
+ * dependencies this file has on other closure classes as any dependency it
+ * takes won't be able to use the logging infrastructure.
+ *
+ */
+
+goog.provide('goog.debug.Formatter');
+goog.provide('goog.debug.HtmlFormatter');
+goog.provide('goog.debug.TextFormatter');
+
+goog.require('goog.debug');
+goog.require('goog.debug.Logger');
+goog.require('goog.debug.RelativeTimeProvider');
+goog.require('goog.html.SafeHtml');
+
+
+
+/**
+ * Base class for Formatters. A Formatter is used to format a LogRecord into
+ * something that can be displayed to the user.
+ *
+ * @param {string=} opt_prefix The prefix to place before text records.
+ * @constructor
+ */
+goog.debug.Formatter = function(opt_prefix) {
+ this.prefix_ = opt_prefix || '';
+
+ /**
+ * A provider that returns the relative start time.
+ * @type {goog.debug.RelativeTimeProvider}
+ * @private
+ */
+ this.startTimeProvider_ =
+ goog.debug.RelativeTimeProvider.getDefaultInstance();
+};
+
+
+/**
+ * Whether to append newlines to the end of formatted log records.
+ * @type {boolean}
+ */
+goog.debug.Formatter.prototype.appendNewline = true;
+
+
+/**
+ * Whether to show absolute time in the DebugWindow.
+ * @type {boolean}
+ */
+goog.debug.Formatter.prototype.showAbsoluteTime = true;
+
+
+/**
+ * Whether to show relative time in the DebugWindow.
+ * @type {boolean}
+ */
+goog.debug.Formatter.prototype.showRelativeTime = true;
+
+
+/**
+ * Whether to show the logger name in the DebugWindow.
+ * @type {boolean}
+ */
+goog.debug.Formatter.prototype.showLoggerName = true;
+
+
+/**
+ * Whether to show the logger exception text.
+ * @type {boolean}
+ */
+goog.debug.Formatter.prototype.showExceptionText = false;
+
+
+/**
+ * Whether to show the severity level.
+ * @type {boolean}
+ */
+goog.debug.Formatter.prototype.showSeverityLevel = false;
+
+
+/**
+ * Formats a record.
+ * @param {goog.debug.LogRecord} logRecord the logRecord to format.
+ * @return {string} The formatted string.
+ */
+goog.debug.Formatter.prototype.formatRecord = goog.abstractMethod;
+
+
+/**
+ * Formats a record as SafeHtml.
+ * @param {goog.debug.LogRecord} logRecord the logRecord to format.
+ * @return {!goog.html.SafeHtml} The formatted string as SafeHtml.
+ */
+goog.debug.Formatter.prototype.formatRecordAsHtml = goog.abstractMethod;
+
+
+/**
+ * Sets the start time provider. By default, this is the default instance
+ * but can be changed.
+ * @param {goog.debug.RelativeTimeProvider} provider The provider to use.
+ */
+goog.debug.Formatter.prototype.setStartTimeProvider = function(provider) {
+ this.startTimeProvider_ = provider;
+};
+
+
+/**
+ * Returns the start time provider. By default, this is the default instance
+ * but can be changed.
+ * @return {goog.debug.RelativeTimeProvider} The start time provider.
+ */
+goog.debug.Formatter.prototype.getStartTimeProvider = function() {
+ return this.startTimeProvider_;
+};
+
+
+/**
+ * Resets the start relative time.
+ */
+goog.debug.Formatter.prototype.resetRelativeTimeStart = function() {
+ this.startTimeProvider_.reset();
+};
+
+
+/**
+ * Returns a string for the time/date of the LogRecord.
+ * @param {goog.debug.LogRecord} logRecord The record to get a time stamp for.
+ * @return {string} A string representation of the time/date of the LogRecord.
+ * @private
+ */
+goog.debug.Formatter.getDateTimeStamp_ = function(logRecord) {
+ var time = new Date(logRecord.getMillis());
+ return goog.debug.Formatter.getTwoDigitString_((time.getFullYear() - 2000)) +
+ goog.debug.Formatter.getTwoDigitString_((time.getMonth() + 1)) +
+ goog.debug.Formatter.getTwoDigitString_(time.getDate()) + ' ' +
+ goog.debug.Formatter.getTwoDigitString_(time.getHours()) + ':' +
+ goog.debug.Formatter.getTwoDigitString_(time.getMinutes()) + ':' +
+ goog.debug.Formatter.getTwoDigitString_(time.getSeconds()) + '.' +
+ goog.debug.Formatter.getTwoDigitString_(
+ Math.floor(time.getMilliseconds() / 10));
+};
+
+
+/**
+ * Returns the number as a two-digit string, meaning it prepends a 0 if the
+ * number if less than 10.
+ * @param {number} n The number to format.
+ * @return {string} A two-digit string representation of {@code n}.
+ * @private
+ */
+goog.debug.Formatter.getTwoDigitString_ = function(n) {
+ if (n < 10) {
+ return '0' + n;
+ }
+ return String(n);
+};
+
+
+/**
+ * Returns a string for the number of seconds relative to the start time.
+ * Prepads with spaces so that anything less than 1000 seconds takes up the
+ * same number of characters for better formatting.
+ * @param {goog.debug.LogRecord} logRecord The log to compare time to.
+ * @param {number} relativeTimeStart The start time to compare to.
+ * @return {string} The number of seconds of the LogRecord relative to the
+ * start time.
+ * @private
+ */
+goog.debug.Formatter.getRelativeTime_ = function(logRecord,
+ relativeTimeStart) {
+ var ms = logRecord.getMillis() - relativeTimeStart;
+ var sec = ms / 1000;
+ var str = sec.toFixed(3);
+
+ var spacesToPrepend = 0;
+ if (sec < 1) {
+ spacesToPrepend = 2;
+ } else {
+ while (sec < 100) {
+ spacesToPrepend++;
+ sec *= 10;
+ }
+ }
+ while (spacesToPrepend-- > 0) {
+ str = ' ' + str;
+ }
+ return str;
+};
+
+
+
+/**
+ * Formatter that returns formatted html. See formatRecord for the classes
+ * it uses for various types of formatted output.
+ *
+ * @param {string=} opt_prefix The prefix to place before text records.
+ * @constructor
+ * @extends {goog.debug.Formatter}
+ */
+goog.debug.HtmlFormatter = function(opt_prefix) {
+ goog.debug.Formatter.call(this, opt_prefix);
+};
+goog.inherits(goog.debug.HtmlFormatter, goog.debug.Formatter);
+
+
+/**
+ * Whether to show the logger exception text
+ * @type {boolean}
+ * @override
+ */
+goog.debug.HtmlFormatter.prototype.showExceptionText = true;
+
+
+/**
+ * Formats a record
+ * @param {goog.debug.LogRecord} logRecord the logRecord to format.
+ * @return {string} The formatted string as html.
+ * @override
+ */
+goog.debug.HtmlFormatter.prototype.formatRecord = function(logRecord) {
+ if (!logRecord) {
+ return '';
+ }
+ // OK not to use goog.html.SafeHtml.unwrap() here.
+ return this.formatRecordAsHtml(logRecord).getTypedStringValue();
+};
+
+
+/**
+ * Formats a record.
+ * @param {goog.debug.LogRecord} logRecord the logRecord to format.
+ * @return {!goog.html.SafeHtml} The formatted string as SafeHtml.
+ * @override
+ */
+goog.debug.HtmlFormatter.prototype.formatRecordAsHtml = function(logRecord) {
+ if (!logRecord) {
+ return goog.html.SafeHtml.EMPTY;
+ }
+
+ var className;
+ switch (logRecord.getLevel().value) {
+ case goog.debug.Logger.Level.SHOUT.value:
+ className = 'dbg-sh';
+ break;
+ case goog.debug.Logger.Level.SEVERE.value:
+ className = 'dbg-sev';
+ break;
+ case goog.debug.Logger.Level.WARNING.value:
+ className = 'dbg-w';
+ break;
+ case goog.debug.Logger.Level.INFO.value:
+ className = 'dbg-i';
+ break;
+ case goog.debug.Logger.Level.FINE.value:
+ default:
+ className = 'dbg-f';
+ break;
+ }
+
+ // HTML for user defined prefix, time, logger name, and severity.
+ var sb = [];
+ sb.push(this.prefix_, ' ');
+ if (this.showAbsoluteTime) {
+ sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
+ }
+ if (this.showRelativeTime) {
+ sb.push('[',
+ goog.debug.Formatter.getRelativeTime_(
+ logRecord, this.startTimeProvider_.get()),
+ 's] ');
+ }
+ if (this.showLoggerName) {
+ sb.push('[', logRecord.getLoggerName(), '] ');
+ }
+ if (this.showSeverityLevel) {
+ sb.push('[', logRecord.getLevel().name, '] ');
+ }
+ var fullPrefixHtml =
+ goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(sb.join(''));
+
+ // HTML for exception text and log record.
+ var exceptionHtml = goog.html.SafeHtml.EMPTY;
+ if (this.showExceptionText && logRecord.getException()) {
+ exceptionHtml = goog.html.SafeHtml.concat(
+ goog.html.SafeHtml.create('br'),
+ goog.debug.exposeExceptionAsHtml(logRecord.getException()));
+ }
+ var logRecordHtml = goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
+ logRecord.getMessage());
+ var recordAndExceptionHtml = goog.html.SafeHtml.create(
+ 'span',
+ {'class': className},
+ goog.html.SafeHtml.concat(logRecordHtml, exceptionHtml));
+
+
+ // Combine both pieces of HTML and, if needed, append a final newline.
+ var html;
+ if (this.appendNewline) {
+ html = goog.html.SafeHtml.concat(fullPrefixHtml, recordAndExceptionHtml,
+ goog.html.SafeHtml.create('br'));
+ } else {
+ html = goog.html.SafeHtml.concat(fullPrefixHtml, recordAndExceptionHtml);
+ }
+ return html;
+};
+
+
+
+/**
+ * Formatter that returns formatted plain text
+ *
+ * @param {string=} opt_prefix The prefix to place before text records.
+ * @constructor
+ * @extends {goog.debug.Formatter}
+ * @final
+ */
+goog.debug.TextFormatter = function(opt_prefix) {
+ goog.debug.Formatter.call(this, opt_prefix);
+};
+goog.inherits(goog.debug.TextFormatter, goog.debug.Formatter);
+
+
+/**
+ * Formats a record as text
+ * @param {goog.debug.LogRecord} logRecord the logRecord to format.
+ * @return {string} The formatted string.
+ * @override
+ */
+goog.debug.TextFormatter.prototype.formatRecord = function(logRecord) {
+ var sb = [];
+ sb.push(this.prefix_, ' ');
+ if (this.showAbsoluteTime) {
+ sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
+ }
+ if (this.showRelativeTime) {
+ sb.push('[', goog.debug.Formatter.getRelativeTime_(logRecord,
+ this.startTimeProvider_.get()), 's] ');
+ }
+
+ if (this.showLoggerName) {
+ sb.push('[', logRecord.getLoggerName(), '] ');
+ }
+ if (this.showSeverityLevel) {
+ sb.push('[', logRecord.getLevel().name, '] ');
+ }
+ sb.push(logRecord.getMessage());
+ if (this.showExceptionText) {
+ var exception = logRecord.getException();
+ if (exception) {
+ var exceptionText = exception instanceof Error ?
+ exception.message :
+ exception.toString();
+ sb.push('\n', exceptionText);
+ }
+ }
+ if (this.appendNewline) {
+ sb.push('\n');
+ }
+ return sb.join('');
+};
+
+
+/**
+ * Formats a record as text
+ * @param {goog.debug.LogRecord} logRecord the logRecord to format.
+ * @return {!goog.html.SafeHtml} The formatted string as SafeHtml. This is
+ * just an HTML-escaped version of the text obtained from formatRecord().
+ * @override
+ */
+goog.debug.TextFormatter.prototype.formatRecordAsHtml = function(logRecord) {
+ return goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
+ goog.debug.TextFormatter.prototype.formatRecord(logRecord));
+};
+
+// 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 Simple logger that logs to the window console if available.
+ *
+ * Has an autoInstall option which can be put into initialization code, which
+ * will start logging if "Debug=true" is in document.location.href
+ *
+ */
+
+goog.provide('goog.debug.Console');
+
+goog.require('goog.debug.LogManager');
+goog.require('goog.debug.Logger');
+goog.require('goog.debug.TextFormatter');
+
+
+
+/**
+ * Create and install a log handler that logs to window.console if available
+ * @constructor
+ */
+goog.debug.Console = function() {
+ this.publishHandler_ = goog.bind(this.addLogRecord, this);
+
+ /**
+ * Formatter for formatted output.
+ * @type {!goog.debug.TextFormatter}
+ * @private
+ */
+ this.formatter_ = new goog.debug.TextFormatter();
+ this.formatter_.showAbsoluteTime = false;
+ this.formatter_.showExceptionText = false;
+ // The console logging methods automatically append a newline.
+ this.formatter_.appendNewline = false;
+
+ this.isCapturing_ = false;
+ this.logBuffer_ = '';
+
+ /**
+ * Loggers that we shouldn't output.
+ * @type {!Object<boolean>}
+ * @private
+ */
+ this.filteredLoggers_ = {};
+};
+
+
+/**
+ * Returns the text formatter used by this console
+ * @return {!goog.debug.TextFormatter} The text formatter.
+ */
+goog.debug.Console.prototype.getFormatter = function() {
+ return this.formatter_;
+};
+
+
+/**
+ * Sets whether we are currently capturing logger output.
+ * @param {boolean} capturing Whether to capture logger output.
+ */
+goog.debug.Console.prototype.setCapturing = function(capturing) {
+ if (capturing == this.isCapturing_) {
+ return;
+ }
+
+ // attach or detach handler from the root logger
+ var rootLogger = goog.debug.LogManager.getRoot();
+ if (capturing) {
+ rootLogger.addHandler(this.publishHandler_);
+ } else {
+ rootLogger.removeHandler(this.publishHandler_);
+ this.logBuffer = '';
+ }
+ this.isCapturing_ = capturing;
+};
+
+
+/**
+ * Adds a log record.
+ * @param {goog.debug.LogRecord} logRecord The log entry.
+ */
+goog.debug.Console.prototype.addLogRecord = function(logRecord) {
+
+ // Check to see if the log record is filtered or not.
+ if (this.filteredLoggers_[logRecord.getLoggerName()]) {
+ return;
+ }
+
+ var record = this.formatter_.formatRecord(logRecord);
+ var console = goog.debug.Console.console_;
+ if (console) {
+ switch (logRecord.getLevel()) {
+ case goog.debug.Logger.Level.SHOUT:
+ goog.debug.Console.logToConsole_(console, 'info', record);
+ break;
+ case goog.debug.Logger.Level.SEVERE:
+ goog.debug.Console.logToConsole_(console, 'error', record);
+ break;
+ case goog.debug.Logger.Level.WARNING:
+ goog.debug.Console.logToConsole_(console, 'warn', record);
+ break;
+ default:
+ goog.debug.Console.logToConsole_(console, 'debug', record);
+ break;
+ }
+ } else {
+ this.logBuffer_ += record;
+ }
+};
+
+
+/**
+ * Adds a logger name to be filtered.
+ * @param {string} loggerName the logger name to add.
+ */
+goog.debug.Console.prototype.addFilter = function(loggerName) {
+ this.filteredLoggers_[loggerName] = true;
+};
+
+
+/**
+ * Removes a logger name to be filtered.
+ * @param {string} loggerName the logger name to remove.
+ */
+goog.debug.Console.prototype.removeFilter = function(loggerName) {
+ delete this.filteredLoggers_[loggerName];
+};
+
+
+/**
+ * Global console logger instance
+ * @type {goog.debug.Console}
+ */
+goog.debug.Console.instance = null;
+
+
+/**
+ * The console to which to log. This is a property so it can be mocked out in
+ * this unit test for goog.debug.Console. Using goog.global, as console might be
+ * used in window-less contexts.
+ * @type {Object}
+ * @private
+ */
+goog.debug.Console.console_ = goog.global['console'];
+
+
+/**
+ * Sets the console to which to log.
+ * @param {!Object} console The console to which to log.
+ */
+goog.debug.Console.setConsole = function(console) {
+ goog.debug.Console.console_ = console;
+};
+
+
+/**
+ * Install the console and start capturing if "Debug=true" is in the page URL
+ */
+goog.debug.Console.autoInstall = function() {
+ if (!goog.debug.Console.instance) {
+ goog.debug.Console.instance = new goog.debug.Console();
+ }
+
+ if (goog.global.location &&
+ goog.global.location.href.indexOf('Debug=true') != -1) {
+ goog.debug.Console.instance.setCapturing(true);
+ }
+};
+
+
+/**
+ * Show an alert with all of the captured debug information.
+ * Information is only captured if console is not available
+ */
+goog.debug.Console.show = function() {
+ alert(goog.debug.Console.instance.logBuffer_);
+};
+
+
+/**
+ * Logs the record to the console using the given function. If the function is
+ * not available on the console object, the log function is used instead.
+ * @param {!Object} console The console object.
+ * @param {string} fnName The name of the function to use.
+ * @param {string} record The record to log.
+ * @private
+ */
+goog.debug.Console.logToConsole_ = function(console, fnName, record) {
+ if (console[fnName]) {
+ console[fnName](record);
+ } else {
+ console.log(record);
+ }
+};
+
+// 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 Utility class that monitors viewport size changes.
+ *
+ * @author attila@google.com (Attila Bodis)
+ * @see ../demos/viewportsizemonitor.html
+ */
+
+goog.provide('goog.dom.ViewportSizeMonitor');
+
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.math.Size');
+
+
+
+/**
+ * This class can be used to monitor changes in the viewport size. Instances
+ * dispatch a {@link goog.events.EventType.RESIZE} event when the viewport size
+ * changes. Handlers can call {@link goog.dom.ViewportSizeMonitor#getSize} to
+ * get the new viewport size.
+ *
+ * Use this class if you want to execute resize/reflow logic each time the
+ * user resizes the browser window. This class is guaranteed to only dispatch
+ * {@code RESIZE} events when the pixel dimensions of the viewport change.
+ * (Internet Explorer fires resize events if any element on the page is resized,
+ * even if the viewport dimensions are unchanged, which can lead to infinite
+ * resize loops.)
+ *
+ * Example usage:
+ * <pre>
+ * var vsm = new goog.dom.ViewportSizeMonitor();
+ * goog.events.listen(vsm, goog.events.EventType.RESIZE, function(e) {
+ * alert('Viewport size changed to ' + vsm.getSize());
+ * });
+ * </pre>
+ *
+ * Manually verified on IE6, IE7, FF2, Opera 11, Safari 4 and Chrome.
+ *
+ * @param {Window=} opt_window The window to monitor; defaults to the window in
+ * which this code is executing.
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+goog.dom.ViewportSizeMonitor = function(opt_window) {
+ goog.dom.ViewportSizeMonitor.base(this, 'constructor');
+
+ /**
+ * The window to monitor. Defaults to the window in which the code is running.
+ * @private {Window}
+ */
+ this.window_ = opt_window || window;
+
+ /**
+ * Event listener key for window the window resize handler, as returned by
+ * {@link goog.events.listen}.
+ * @private {goog.events.Key}
+ */
+ this.listenerKey_ = goog.events.listen(this.window_,
+ goog.events.EventType.RESIZE, this.handleResize_, false, this);
+
+ /**
+ * The most recently recorded size of the viewport, in pixels.
+ * @private {goog.math.Size}
+ */
+ this.size_ = goog.dom.getViewportSize(this.window_);
+};
+goog.inherits(goog.dom.ViewportSizeMonitor, goog.events.EventTarget);
+
+
+/**
+ * Returns a viewport size monitor for the given window. A new one is created
+ * if it doesn't exist already. This prevents the unnecessary creation of
+ * multiple spooling monitors for a window.
+ * @param {Window=} opt_window The window to monitor; defaults to the window in
+ * which this code is executing.
+ * @return {!goog.dom.ViewportSizeMonitor} Monitor for the given window.
+ */
+goog.dom.ViewportSizeMonitor.getInstanceForWindow = function(opt_window) {
+ var currentWindow = opt_window || window;
+ var uid = goog.getUid(currentWindow);
+
+ return goog.dom.ViewportSizeMonitor.windowInstanceMap_[uid] =
+ goog.dom.ViewportSizeMonitor.windowInstanceMap_[uid] ||
+ new goog.dom.ViewportSizeMonitor(currentWindow);
+};
+
+
+/**
+ * Removes and disposes a viewport size monitor for the given window if one
+ * exists.
+ * @param {Window=} opt_window The window whose monitor should be removed;
+ * defaults to the window in which this code is executing.
+ */
+goog.dom.ViewportSizeMonitor.removeInstanceForWindow = function(opt_window) {
+ var uid = goog.getUid(opt_window || window);
+
+ goog.dispose(goog.dom.ViewportSizeMonitor.windowInstanceMap_[uid]);
+ delete goog.dom.ViewportSizeMonitor.windowInstanceMap_[uid];
+};
+
+
+/**
+ * Map of window hash code to viewport size monitor for that window, if
+ * created.
+ * @type {Object<number,goog.dom.ViewportSizeMonitor>}
+ * @private
+ */
+goog.dom.ViewportSizeMonitor.windowInstanceMap_ = {};
+
+
+/**
+ * Returns the most recently recorded size of the viewport, in pixels. May
+ * return null if no window resize event has been handled yet.
+ * @return {goog.math.Size} The viewport dimensions, in pixels.
+ */
+goog.dom.ViewportSizeMonitor.prototype.getSize = function() {
+ // Return a clone instead of the original to preserve encapsulation.
+ return this.size_ ? this.size_.clone() : null;
+};
+
+
+/** @override */
+goog.dom.ViewportSizeMonitor.prototype.disposeInternal = function() {
+ goog.dom.ViewportSizeMonitor.superClass_.disposeInternal.call(this);
+
+ if (this.listenerKey_) {
+ goog.events.unlistenByKey(this.listenerKey_);
+ this.listenerKey_ = null;
+ }
+
+ this.window_ = null;
+ this.size_ = null;
+};
+
+
+/**
+ * Handles window resize events by measuring the dimensions of the
+ * viewport and dispatching a {@link goog.events.EventType.RESIZE} event if the
+ * current dimensions are different from the previous ones.
+ * @param {goog.events.Event} event The window resize event to handle.
+ * @private
+ */
+goog.dom.ViewportSizeMonitor.prototype.handleResize_ = function(event) {
+ var size = goog.dom.getViewportSize(this.window_);
+ if (!goog.math.Size.equals(size, this.size_)) {
+ this.size_ = size;
+ this.dispatchEvent(goog.events.EventType.RESIZE);
+ }
+};
+
+// 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 Constant declarations for common key codes.
+ *
+ * @author eae@google.com (Emil A Eklund)
+ * @see ../demos/keyhandler.html
+ */
+
+goog.provide('goog.events.KeyCodes');
+
+goog.require('goog.userAgent');
+
+goog.forwardDeclare('goog.events.BrowserEvent');
+
+
+/**
+ * Key codes for common characters.
+ *
+ * This list is not localized and therefore some of the key codes are not
+ * correct for non US keyboard layouts. See comments below.
+ *
+ * @enum {number}
+ */
+goog.events.KeyCodes = {
+ WIN_KEY_FF_LINUX: 0,
+ MAC_ENTER: 3,
+ BACKSPACE: 8,
+ TAB: 9,
+ NUM_CENTER: 12, // NUMLOCK on FF/Safari Mac
+ ENTER: 13,
+ SHIFT: 16,
+ CTRL: 17,
+ ALT: 18,
+ PAUSE: 19,
+ CAPS_LOCK: 20,
+ ESC: 27,
+ SPACE: 32,
+ PAGE_UP: 33, // also NUM_NORTH_EAST
+ PAGE_DOWN: 34, // also NUM_SOUTH_EAST
+ END: 35, // also NUM_SOUTH_WEST
+ HOME: 36, // also NUM_NORTH_WEST
+ LEFT: 37, // also NUM_WEST
+ UP: 38, // also NUM_NORTH
+ RIGHT: 39, // also NUM_EAST
+ DOWN: 40, // also NUM_SOUTH
+ PLUS_SIGN: 43, // NOT numpad plus
+ PRINT_SCREEN: 44,
+ INSERT: 45, // also NUM_INSERT
+ DELETE: 46, // also NUM_DELETE
+ ZERO: 48,
+ ONE: 49,
+ TWO: 50,
+ THREE: 51,
+ FOUR: 52,
+ FIVE: 53,
+ SIX: 54,
+ SEVEN: 55,
+ EIGHT: 56,
+ NINE: 57,
+ FF_SEMICOLON: 59, // Firefox (Gecko) fires this for semicolon instead of 186
+ FF_EQUALS: 61, // Firefox (Gecko) fires this for equals instead of 187
+ FF_DASH: 173, // Firefox (Gecko) fires this for dash instead of 189
+ QUESTION_MARK: 63, // needs localization
+ AT_SIGN: 64,
+ A: 65,
+ B: 66,
+ C: 67,
+ D: 68,
+ E: 69,
+ F: 70,
+ G: 71,
+ H: 72,
+ I: 73,
+ J: 74,
+ K: 75,
+ L: 76,
+ M: 77,
+ N: 78,
+ O: 79,
+ P: 80,
+ Q: 81,
+ R: 82,
+ S: 83,
+ T: 84,
+ U: 85,
+ V: 86,
+ W: 87,
+ X: 88,
+ Y: 89,
+ Z: 90,
+ META: 91, // WIN_KEY_LEFT
+ WIN_KEY_RIGHT: 92,
+ CONTEXT_MENU: 93,
+ NUM_ZERO: 96,
+ NUM_ONE: 97,
+ NUM_TWO: 98,
+ NUM_THREE: 99,
+ NUM_FOUR: 100,
+ NUM_FIVE: 101,
+ NUM_SIX: 102,
+ NUM_SEVEN: 103,
+ NUM_EIGHT: 104,
+ NUM_NINE: 105,
+ NUM_MULTIPLY: 106,
+ NUM_PLUS: 107,
+ NUM_MINUS: 109,
+ NUM_PERIOD: 110,
+ NUM_DIVISION: 111,
+ F1: 112,
+ F2: 113,
+ F3: 114,
+ F4: 115,
+ F5: 116,
+ F6: 117,
+ F7: 118,
+ F8: 119,
+ F9: 120,
+ F10: 121,
+ F11: 122,
+ F12: 123,
+ NUMLOCK: 144,
+ SCROLL_LOCK: 145,
+
+ // OS-specific media keys like volume controls and browser controls.
+ FIRST_MEDIA_KEY: 166,
+ LAST_MEDIA_KEY: 183,
+
+ SEMICOLON: 186, // needs localization
+ DASH: 189, // needs localization
+ EQUALS: 187, // needs localization
+ COMMA: 188, // needs localization
+ PERIOD: 190, // needs localization
+ SLASH: 191, // needs localization
+ APOSTROPHE: 192, // needs localization
+ TILDE: 192, // needs localization
+ SINGLE_QUOTE: 222, // needs localization
+ OPEN_SQUARE_BRACKET: 219, // needs localization
+ BACKSLASH: 220, // needs localization
+ CLOSE_SQUARE_BRACKET: 221, // needs localization
+ WIN_KEY: 224,
+ MAC_FF_META: 224, // Firefox (Gecko) fires this for the meta key instead of 91
+ MAC_WK_CMD_LEFT: 91, // WebKit Left Command key fired, same as META
+ MAC_WK_CMD_RIGHT: 93, // WebKit Right Command key fired, different from META
+ WIN_IME: 229,
+
+ // "Reserved for future use". Some programs (e.g. the SlingPlayer 2.4 ActiveX
+ // control) fire this as a hacky way to disable screensavers.
+ VK_NONAME: 252,
+
+ // We've seen users whose machines fire this keycode at regular one
+ // second intervals. The common thread among these users is that
+ // they're all using Dell Inspiron laptops, so we suspect that this
+ // indicates a hardware/bios problem.
+ // http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx
+ PHANTOM: 255
+};
+
+
+/**
+ * Returns true if the event contains a text modifying key.
+ * @param {goog.events.BrowserEvent} e A key event.
+ * @return {boolean} Whether it's a text modifying key.
+ */
+goog.events.KeyCodes.isTextModifyingKeyEvent = function(e) {
+ if (e.altKey && !e.ctrlKey ||
+ e.metaKey ||
+ // Function keys don't generate text
+ e.keyCode >= goog.events.KeyCodes.F1 &&
+ e.keyCode <= goog.events.KeyCodes.F12) {
+ return false;
+ }
+
+ // The following keys are quite harmless, even in combination with
+ // CTRL, ALT or SHIFT.
+ switch (e.keyCode) {
+ case goog.events.KeyCodes.ALT:
+ case goog.events.KeyCodes.CAPS_LOCK:
+ case goog.events.KeyCodes.CONTEXT_MENU:
+ case goog.events.KeyCodes.CTRL:
+ case goog.events.KeyCodes.DOWN:
+ case goog.events.KeyCodes.END:
+ case goog.events.KeyCodes.ESC:
+ case goog.events.KeyCodes.HOME:
+ case goog.events.KeyCodes.INSERT:
+ case goog.events.KeyCodes.LEFT:
+ case goog.events.KeyCodes.MAC_FF_META:
+ case goog.events.KeyCodes.META:
+ case goog.events.KeyCodes.NUMLOCK:
+ case goog.events.KeyCodes.NUM_CENTER:
+ case goog.events.KeyCodes.PAGE_DOWN:
+ case goog.events.KeyCodes.PAGE_UP:
+ case goog.events.KeyCodes.PAUSE:
+ case goog.events.KeyCodes.PHANTOM:
+ case goog.events.KeyCodes.PRINT_SCREEN:
+ case goog.events.KeyCodes.RIGHT:
+ case goog.events.KeyCodes.SCROLL_LOCK:
+ case goog.events.KeyCodes.SHIFT:
+ case goog.events.KeyCodes.UP:
+ case goog.events.KeyCodes.VK_NONAME:
+ case goog.events.KeyCodes.WIN_KEY:
+ case goog.events.KeyCodes.WIN_KEY_RIGHT:
+ return false;
+ case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
+ return !goog.userAgent.GECKO;
+ default:
+ return e.keyCode < goog.events.KeyCodes.FIRST_MEDIA_KEY ||
+ e.keyCode > goog.events.KeyCodes.LAST_MEDIA_KEY;
+ }
+};
+
+
+/**
+ * Returns true if the key fires a keypress event in the current browser.
+ *
+ * Accoridng to MSDN [1] IE only fires keypress events for the following keys:
+ * - Letters: A - Z (uppercase and lowercase)
+ * - Numerals: 0 - 9
+ * - Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
+ * - System: ESC, SPACEBAR, ENTER
+ *
+ * That's not entirely correct though, for instance there's no distinction
+ * between upper and lower case letters.
+ *
+ * [1] http://msdn2.microsoft.com/en-us/library/ms536939(VS.85).aspx)
+ *
+ * Safari is similar to IE, but does not fire keypress for ESC.
+ *
+ * Additionally, IE6 does not fire keydown or keypress events for letters when
+ * the control or alt keys are held down and the shift key is not. IE7 does
+ * fire keydown in these cases, though, but not keypress.
+ *
+ * @param {number} keyCode A key code.
+ * @param {number=} opt_heldKeyCode Key code of a currently-held key.
+ * @param {boolean=} opt_shiftKey Whether the shift key is held down.
+ * @param {boolean=} opt_ctrlKey Whether the control key is held down.
+ * @param {boolean=} opt_altKey Whether the alt key is held down.
+ * @return {boolean} Whether it's a key that fires a keypress event.
+ */
+goog.events.KeyCodes.firesKeyPressEvent = function(keyCode, opt_heldKeyCode,
+ opt_shiftKey, opt_ctrlKey, opt_altKey) {
+ if (!goog.userAgent.IE && !goog.userAgent.EDGE &&
+ !(goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525'))) {
+ return true;
+ }
+
+ if (goog.userAgent.MAC && opt_altKey) {
+ return goog.events.KeyCodes.isCharacterKey(keyCode);
+ }
+
+ // Alt but not AltGr which is represented as Alt+Ctrl.
+ if (opt_altKey && !opt_ctrlKey) {
+ return false;
+ }
+
+ // Saves Ctrl or Alt + key for IE and WebKit 525+, which won't fire keypress.
+ // Non-IE browsers and WebKit prior to 525 won't get this far so no need to
+ // check the user agent.
+ if (goog.isNumber(opt_heldKeyCode)) {
+ opt_heldKeyCode = goog.events.KeyCodes.normalizeKeyCode(opt_heldKeyCode);
+ }
+ if (!opt_shiftKey &&
+ (opt_heldKeyCode == goog.events.KeyCodes.CTRL ||
+ opt_heldKeyCode == goog.events.KeyCodes.ALT ||
+ goog.userAgent.MAC &&
+ opt_heldKeyCode == goog.events.KeyCodes.META)) {
+ return false;
+ }
+
+ // Some keys with Ctrl/Shift do not issue keypress in WEBKIT.
+ if ((goog.userAgent.WEBKIT || goog.userAgent.EDGE) &&
+ opt_ctrlKey && opt_shiftKey) {
+ switch (keyCode) {
+ case goog.events.KeyCodes.BACKSLASH:
+ case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
+ case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
+ case goog.events.KeyCodes.TILDE:
+ case goog.events.KeyCodes.SEMICOLON:
+ case goog.events.KeyCodes.DASH:
+ case goog.events.KeyCodes.EQUALS:
+ case goog.events.KeyCodes.COMMA:
+ case goog.events.KeyCodes.PERIOD:
+ case goog.events.KeyCodes.SLASH:
+ case goog.events.KeyCodes.APOSTROPHE:
+ case goog.events.KeyCodes.SINGLE_QUOTE:
+ return false;
+ }
+ }
+
+ // When Ctrl+<somekey> is held in IE, it only fires a keypress once, but it
+ // continues to fire keydown events as the event repeats.
+ if (goog.userAgent.IE && opt_ctrlKey && opt_heldKeyCode == keyCode) {
+ return false;
+ }
+
+ switch (keyCode) {
+ case goog.events.KeyCodes.ENTER:
+ return true;
+ case goog.events.KeyCodes.ESC:
+ return !(goog.userAgent.WEBKIT || goog.userAgent.EDGE);
+ }
+
+ return goog.events.KeyCodes.isCharacterKey(keyCode);
+};
+
+
+/**
+ * Returns true if the key produces a character.
+ * This does not cover characters on non-US keyboards (Russian, Hebrew, etc.).
+ *
+ * @param {number} keyCode A key code.
+ * @return {boolean} Whether it's a character key.
+ */
+goog.events.KeyCodes.isCharacterKey = function(keyCode) {
+ if (keyCode >= goog.events.KeyCodes.ZERO &&
+ keyCode <= goog.events.KeyCodes.NINE) {
+ return true;
+ }
+
+ if (keyCode >= goog.events.KeyCodes.NUM_ZERO &&
+ keyCode <= goog.events.KeyCodes.NUM_MULTIPLY) {
+ return true;
+ }
+
+ if (keyCode >= goog.events.KeyCodes.A &&
+ keyCode <= goog.events.KeyCodes.Z) {
+ return true;
+ }
+
+ // Safari sends zero key code for non-latin characters.
+ if ((goog.userAgent.WEBKIT || goog.userAgent.EDGE) && keyCode == 0) {
+ return true;
+ }
+
+ switch (keyCode) {
+ case goog.events.KeyCodes.SPACE:
+ case goog.events.KeyCodes.PLUS_SIGN:
+ case goog.events.KeyCodes.QUESTION_MARK:
+ case goog.events.KeyCodes.AT_SIGN:
+ case goog.events.KeyCodes.NUM_PLUS:
+ case goog.events.KeyCodes.NUM_MINUS:
+ case goog.events.KeyCodes.NUM_PERIOD:
+ case goog.events.KeyCodes.NUM_DIVISION:
+ case goog.events.KeyCodes.SEMICOLON:
+ case goog.events.KeyCodes.FF_SEMICOLON:
+ case goog.events.KeyCodes.DASH:
+ case goog.events.KeyCodes.EQUALS:
+ case goog.events.KeyCodes.FF_EQUALS:
+ case goog.events.KeyCodes.COMMA:
+ case goog.events.KeyCodes.PERIOD:
+ case goog.events.KeyCodes.SLASH:
+ case goog.events.KeyCodes.APOSTROPHE:
+ case goog.events.KeyCodes.SINGLE_QUOTE:
+ case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
+ case goog.events.KeyCodes.BACKSLASH:
+ case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
+ return true;
+ default:
+ return false;
+ }
+};
+
+
+/**
+ * Normalizes key codes from OS/Browser-specific value to the general one.
+ * @param {number} keyCode The native key code.
+ * @return {number} The normalized key code.
+ */
+goog.events.KeyCodes.normalizeKeyCode = function(keyCode) {
+ if (goog.userAgent.GECKO) {
+ return goog.events.KeyCodes.normalizeGeckoKeyCode(keyCode);
+ } else if (goog.userAgent.MAC && goog.userAgent.WEBKIT) {
+ return goog.events.KeyCodes.normalizeMacWebKitKeyCode(keyCode);
+ } else {
+ return keyCode;
+ }
+};
+
+
+/**
+ * Normalizes key codes from their Gecko-specific value to the general one.
+ * @param {number} keyCode The native key code.
+ * @return {number} The normalized key code.
+ */
+goog.events.KeyCodes.normalizeGeckoKeyCode = function(keyCode) {
+ switch (keyCode) {
+ case goog.events.KeyCodes.FF_EQUALS:
+ return goog.events.KeyCodes.EQUALS;
+ case goog.events.KeyCodes.FF_SEMICOLON:
+ return goog.events.KeyCodes.SEMICOLON;
+ case goog.events.KeyCodes.FF_DASH:
+ return goog.events.KeyCodes.DASH;
+ case goog.events.KeyCodes.MAC_FF_META:
+ return goog.events.KeyCodes.META;
+ case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
+ return goog.events.KeyCodes.WIN_KEY;
+ default:
+ return keyCode;
+ }
+};
+
+
+/**
+ * Normalizes key codes from their Mac WebKit-specific value to the general one.
+ * @param {number} keyCode The native key code.
+ * @return {number} The normalized key code.
+ */
+goog.events.KeyCodes.normalizeMacWebKitKeyCode = function(keyCode) {
+ switch (keyCode) {
+ case goog.events.KeyCodes.MAC_WK_CMD_RIGHT: // 93
+ return goog.events.KeyCodes.META; // 91
+ default:
+ return keyCode;
+ }
+};
+
+// 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 This file contains a class for working with keyboard events
+ * that repeat consistently across browsers and platforms. It also unifies the
+ * key code so that it is the same in all browsers and platforms.
+ *
+ * Different web browsers have very different keyboard event handling. Most
+ * importantly is that only certain browsers repeat keydown events:
+ * IE, Opera, FF/Win32, and Safari 3 repeat keydown events.
+ * FF/Mac and Safari 2 do not.
+ *
+ * For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit
+ * decided that they should try to match IE's key handling behavior.
+ * Safari 3.0.4, which shipped with Leopard (WebKit 523), has the
+ * Safari 2 behavior.
+ *
+ * Firefox, Safari, Opera prevent on keypress
+ *
+ * IE prevents on keydown
+ *
+ * Firefox does not fire keypress for shift, ctrl, alt
+ * Firefox does fire keydown for shift, ctrl, alt, meta
+ * Firefox does not repeat keydown for shift, ctrl, alt, meta
+ *
+ * Firefox does not fire keypress for up and down in an input
+ *
+ * Opera fires keypress for shift, ctrl, alt, meta
+ * Opera does not repeat keypress for shift, ctrl, alt, meta
+ *
+ * Safari 2 and 3 do not fire keypress for shift, ctrl, alt
+ * Safari 2 does not fire keydown for shift, ctrl, alt
+ * Safari 3 *does* fire keydown for shift, ctrl, alt
+ *
+ * IE provides the keycode for keyup/down events and the charcode (in the
+ * keycode field) for keypress.
+ *
+ * Mozilla provides the keycode for keyup/down and the charcode for keypress
+ * unless it's a non text modifying key in which case the keycode is provided.
+ *
+ * Safari 3 provides the keycode and charcode for all events.
+ *
+ * Opera provides the keycode for keyup/down event and either the charcode or
+ * the keycode (in the keycode field) for keypress events.
+ *
+ * Firefox x11 doesn't fire keydown events if a another key is already held down
+ * until the first key is released. This can cause a key event to be fired with
+ * a keyCode for the first key and a charCode for the second key.
+ *
+ * Safari in keypress
+ *
+ * charCode keyCode which
+ * ENTER: 13 13 13
+ * F1: 63236 63236 63236
+ * F8: 63243 63243 63243
+ * ...
+ * p: 112 112 112
+ * P: 80 80 80
+ *
+ * Firefox, keypress:
+ *
+ * charCode keyCode which
+ * ENTER: 0 13 13
+ * F1: 0 112 0
+ * F8: 0 119 0
+ * ...
+ * p: 112 0 112
+ * P: 80 0 80
+ *
+ * Opera, Mac+Win32, keypress:
+ *
+ * charCode keyCode which
+ * ENTER: undefined 13 13
+ * F1: undefined 112 0
+ * F8: undefined 119 0
+ * ...
+ * p: undefined 112 112
+ * P: undefined 80 80
+ *
+ * IE7, keydown
+ *
+ * charCode keyCode which
+ * ENTER: undefined 13 undefined
+ * F1: undefined 112 undefined
+ * F8: undefined 119 undefined
+ * ...
+ * p: undefined 80 undefined
+ * P: undefined 80 undefined
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ * @author eae@google.com (Emil A Eklund)
+ * @see ../demos/keyhandler.html
+ */
+
+goog.provide('goog.events.KeyEvent');
+goog.provide('goog.events.KeyHandler');
+goog.provide('goog.events.KeyHandler.EventType');
+
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.events.KeyCodes');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * A wrapper around an element that you want to listen to keyboard events on.
+ * @param {Element|Document=} opt_element The element or document to listen on.
+ * @param {boolean=} opt_capture Whether to listen for browser events in
+ * capture phase (defaults to false).
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @final
+ */
+goog.events.KeyHandler = function(opt_element, opt_capture) {
+ goog.events.EventTarget.call(this);
+
+ if (opt_element) {
+ this.attach(opt_element, opt_capture);
+ }
+};
+goog.inherits(goog.events.KeyHandler, goog.events.EventTarget);
+
+
+/**
+ * This is the element that we will listen to the real keyboard events on.
+ * @type {Element|Document|null}
+ * @private
+ */
+goog.events.KeyHandler.prototype.element_ = null;
+
+
+/**
+ * The key for the key press listener.
+ * @type {goog.events.Key}
+ * @private
+ */
+goog.events.KeyHandler.prototype.keyPressKey_ = null;
+
+
+/**
+ * The key for the key down listener.
+ * @type {goog.events.Key}
+ * @private
+ */
+goog.events.KeyHandler.prototype.keyDownKey_ = null;
+
+
+/**
+ * The key for the key up listener.
+ * @type {goog.events.Key}
+ * @private
+ */
+goog.events.KeyHandler.prototype.keyUpKey_ = null;
+
+
+/**
+ * Used to detect keyboard repeat events.
+ * @private
+ * @type {number}
+ */
+goog.events.KeyHandler.prototype.lastKey_ = -1;
+
+
+/**
+ * Keycode recorded for key down events. As most browsers don't report the
+ * keycode in the key press event we need to record it in the key down phase.
+ * @private
+ * @type {number}
+ */
+goog.events.KeyHandler.prototype.keyCode_ = -1;
+
+
+/**
+ * Alt key recorded for key down events. FF on Mac does not report the alt key
+ * flag in the key press event, we need to record it in the key down phase.
+ * @type {boolean}
+ * @private
+ */
+goog.events.KeyHandler.prototype.altKey_ = false;
+
+
+/**
+ * Enum type for the events fired by the key handler
+ * @enum {string}
+ */
+goog.events.KeyHandler.EventType = {
+ KEY: 'key'
+};
+
+
+/**
+ * An enumeration of key codes that Safari 2 does incorrectly
+ * @type {Object}
+ * @private
+ */
+goog.events.KeyHandler.safariKey_ = {
+ '3': goog.events.KeyCodes.ENTER, // 13
+ '12': goog.events.KeyCodes.NUMLOCK, // 144
+ '63232': goog.events.KeyCodes.UP, // 38
+ '63233': goog.events.KeyCodes.DOWN, // 40
+ '63234': goog.events.KeyCodes.LEFT, // 37
+ '63235': goog.events.KeyCodes.RIGHT, // 39
+ '63236': goog.events.KeyCodes.F1, // 112
+ '63237': goog.events.KeyCodes.F2, // 113
+ '63238': goog.events.KeyCodes.F3, // 114
+ '63239': goog.events.KeyCodes.F4, // 115
+ '63240': goog.events.KeyCodes.F5, // 116
+ '63241': goog.events.KeyCodes.F6, // 117
+ '63242': goog.events.KeyCodes.F7, // 118
+ '63243': goog.events.KeyCodes.F8, // 119
+ '63244': goog.events.KeyCodes.F9, // 120
+ '63245': goog.events.KeyCodes.F10, // 121
+ '63246': goog.events.KeyCodes.F11, // 122
+ '63247': goog.events.KeyCodes.F12, // 123
+ '63248': goog.events.KeyCodes.PRINT_SCREEN, // 44
+ '63272': goog.events.KeyCodes.DELETE, // 46
+ '63273': goog.events.KeyCodes.HOME, // 36
+ '63275': goog.events.KeyCodes.END, // 35
+ '63276': goog.events.KeyCodes.PAGE_UP, // 33
+ '63277': goog.events.KeyCodes.PAGE_DOWN, // 34
+ '63289': goog.events.KeyCodes.NUMLOCK, // 144
+ '63302': goog.events.KeyCodes.INSERT // 45
+};
+
+
+/**
+ * An enumeration of key identifiers currently part of the W3C draft for DOM3
+ * and their mappings to keyCodes.
+ * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set
+ * This is currently supported in Safari and should be platform independent.
+ * @type {Object}
+ * @private
+ */
+goog.events.KeyHandler.keyIdentifier_ = {
+ 'Up': goog.events.KeyCodes.UP, // 38
+ 'Down': goog.events.KeyCodes.DOWN, // 40
+ 'Left': goog.events.KeyCodes.LEFT, // 37
+ 'Right': goog.events.KeyCodes.RIGHT, // 39
+ 'Enter': goog.events.KeyCodes.ENTER, // 13
+ 'F1': goog.events.KeyCodes.F1, // 112
+ 'F2': goog.events.KeyCodes.F2, // 113
+ 'F3': goog.events.KeyCodes.F3, // 114
+ 'F4': goog.events.KeyCodes.F4, // 115
+ 'F5': goog.events.KeyCodes.F5, // 116
+ 'F6': goog.events.KeyCodes.F6, // 117
+ 'F7': goog.events.KeyCodes.F7, // 118
+ 'F8': goog.events.KeyCodes.F8, // 119
+ 'F9': goog.events.KeyCodes.F9, // 120
+ 'F10': goog.events.KeyCodes.F10, // 121
+ 'F11': goog.events.KeyCodes.F11, // 122
+ 'F12': goog.events.KeyCodes.F12, // 123
+ 'U+007F': goog.events.KeyCodes.DELETE, // 46
+ 'Home': goog.events.KeyCodes.HOME, // 36
+ 'End': goog.events.KeyCodes.END, // 35
+ 'PageUp': goog.events.KeyCodes.PAGE_UP, // 33
+ 'PageDown': goog.events.KeyCodes.PAGE_DOWN, // 34
+ 'Insert': goog.events.KeyCodes.INSERT // 45
+};
+
+
+/**
+ * If true, the KeyEvent fires on keydown. Otherwise, it fires on keypress.
+ *
+ * @type {boolean}
+ * @private
+ */
+goog.events.KeyHandler.USES_KEYDOWN_ = goog.userAgent.IE ||
+ goog.userAgent.EDGE ||
+ goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525');
+
+
+/**
+ * If true, the alt key flag is saved during the key down and reused when
+ * handling the key press. FF on Mac does not set the alt flag in the key press
+ * event.
+ * @type {boolean}
+ * @private
+ */
+goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_ = goog.userAgent.MAC &&
+ goog.userAgent.GECKO;
+
+
+/**
+ * Records the keycode for browsers that only returns the keycode for key up/
+ * down events. For browser/key combinations that doesn't trigger a key pressed
+ * event it also fires the patched key event.
+ * @param {goog.events.BrowserEvent} e The key down event.
+ * @private
+ */
+goog.events.KeyHandler.prototype.handleKeyDown_ = function(e) {
+ // Ctrl-Tab and Alt-Tab can cause the focus to be moved to another window
+ // before we've caught a key-up event. If the last-key was one of these we
+ // reset the state.
+ if (goog.userAgent.WEBKIT || goog.userAgent.EDGE) {
+ if (this.lastKey_ == goog.events.KeyCodes.CTRL && !e.ctrlKey ||
+ this.lastKey_ == goog.events.KeyCodes.ALT && !e.altKey ||
+ goog.userAgent.MAC &&
+ this.lastKey_ == goog.events.KeyCodes.META && !e.metaKey) {
+ this.resetState();
+ }
+ }
+
+ if (this.lastKey_ == -1) {
+ if (e.ctrlKey && e.keyCode != goog.events.KeyCodes.CTRL) {
+ this.lastKey_ = goog.events.KeyCodes.CTRL;
+ } else if (e.altKey && e.keyCode != goog.events.KeyCodes.ALT) {
+ this.lastKey_ = goog.events.KeyCodes.ALT;
+ } else if (e.metaKey && e.keyCode != goog.events.KeyCodes.META) {
+ this.lastKey_ = goog.events.KeyCodes.META;
+ }
+ }
+
+ if (goog.events.KeyHandler.USES_KEYDOWN_ &&
+ !goog.events.KeyCodes.firesKeyPressEvent(e.keyCode,
+ this.lastKey_, e.shiftKey, e.ctrlKey, e.altKey)) {
+ this.handleEvent(e);
+ } else {
+ this.keyCode_ = goog.events.KeyCodes.normalizeKeyCode(e.keyCode);
+ if (goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_) {
+ this.altKey_ = e.altKey;
+ }
+ }
+};
+
+
+/**
+ * Resets the stored previous values. Needed to be called for webkit which will
+ * not generate a key up for meta key operations. This should only be called
+ * when having finished with repeat key possiblities.
+ */
+goog.events.KeyHandler.prototype.resetState = function() {
+ this.lastKey_ = -1;
+ this.keyCode_ = -1;
+};
+
+
+/**
+ * Clears the stored previous key value, resetting the key repeat status. Uses
+ * -1 because the Safari 3 Windows beta reports 0 for certain keys (like Home
+ * and End.)
+ * @param {goog.events.BrowserEvent} e The keyup event.
+ * @private
+ */
+goog.events.KeyHandler.prototype.handleKeyup_ = function(e) {
+ this.resetState();
+ this.altKey_ = e.altKey;
+};
+
+
+/**
+ * Handles the events on the element.
+ * @param {goog.events.BrowserEvent} e The keyboard event sent from the
+ * browser.
+ */
+goog.events.KeyHandler.prototype.handleEvent = function(e) {
+ var be = e.getBrowserEvent();
+ var keyCode, charCode;
+ var altKey = be.altKey;
+
+ // IE reports the character code in the keyCode field for keypress events.
+ // There are two exceptions however, Enter and Escape.
+ if (goog.userAgent.IE && e.type == goog.events.EventType.KEYPRESS) {
+ keyCode = this.keyCode_;
+ charCode = keyCode != goog.events.KeyCodes.ENTER &&
+ keyCode != goog.events.KeyCodes.ESC ?
+ be.keyCode : 0;
+
+ // Safari reports the character code in the keyCode field for keypress
+ // events but also has a charCode field.
+ } else if ((goog.userAgent.WEBKIT || goog.userAgent.EDGE) &&
+ e.type == goog.events.EventType.KEYPRESS) {
+ keyCode = this.keyCode_;
+ charCode = be.charCode >= 0 && be.charCode < 63232 &&
+ goog.events.KeyCodes.isCharacterKey(keyCode) ?
+ be.charCode : 0;
+
+ // Opera reports the keycode or the character code in the keyCode field.
+ } else if (goog.userAgent.OPERA && !goog.userAgent.WEBKIT) {
+ keyCode = this.keyCode_;
+ charCode = goog.events.KeyCodes.isCharacterKey(keyCode) ?
+ be.keyCode : 0;
+
+ // Mozilla reports the character code in the charCode field.
+ } else {
+ keyCode = be.keyCode || this.keyCode_;
+ charCode = be.charCode || 0;
+ if (goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_) {
+ altKey = this.altKey_;
+ }
+ // On the Mac, shift-/ triggers a question mark char code and no key code
+ // (normalized to WIN_KEY), so we synthesize the latter.
+ if (goog.userAgent.MAC &&
+ charCode == goog.events.KeyCodes.QUESTION_MARK &&
+ keyCode == goog.events.KeyCodes.WIN_KEY) {
+ keyCode = goog.events.KeyCodes.SLASH;
+ }
+ }
+
+ keyCode = goog.events.KeyCodes.normalizeKeyCode(keyCode);
+ var key = keyCode;
+ var keyIdentifier = be.keyIdentifier;
+
+ // Correct the key value for certain browser-specific quirks.
+ if (keyCode) {
+ if (keyCode >= 63232 && keyCode in goog.events.KeyHandler.safariKey_) {
+ // NOTE(nicksantos): Safari 3 has fixed this problem,
+ // this is only needed for Safari 2.
+ key = goog.events.KeyHandler.safariKey_[keyCode];
+ } else {
+
+ // Safari returns 25 for Shift+Tab instead of 9.
+ if (keyCode == 25 && e.shiftKey) {
+ key = 9;
+ }
+ }
+ } else if (keyIdentifier &&
+ keyIdentifier in goog.events.KeyHandler.keyIdentifier_) {
+ // This is needed for Safari Windows because it currently doesn't give a
+ // keyCode/which for non printable keys.
+ key = goog.events.KeyHandler.keyIdentifier_[keyIdentifier];
+ }
+
+ // If we get the same keycode as a keydown/keypress without having seen a
+ // keyup event, then this event was caused by key repeat.
+ var repeat = key == this.lastKey_;
+ this.lastKey_ = key;
+
+ var event = new goog.events.KeyEvent(key, charCode, repeat, be);
+ event.altKey = altKey;
+ this.dispatchEvent(event);
+};
+
+
+/**
+ * Returns the element listened on for the real keyboard events.
+ * @return {Element|Document|null} The element listened on for the real
+ * keyboard events.
+ */
+goog.events.KeyHandler.prototype.getElement = function() {
+ return this.element_;
+};
+
+
+/**
+ * Adds the proper key event listeners to the element.
+ * @param {Element|Document} element The element to listen on.
+ * @param {boolean=} opt_capture Whether to listen for browser events in
+ * capture phase (defaults to false).
+ */
+goog.events.KeyHandler.prototype.attach = function(element, opt_capture) {
+ if (this.keyUpKey_) {
+ this.detach();
+ }
+
+ this.element_ = element;
+
+ this.keyPressKey_ = goog.events.listen(this.element_,
+ goog.events.EventType.KEYPRESS,
+ this,
+ opt_capture);
+
+ // Most browsers (Safari 2 being the notable exception) doesn't include the
+ // keyCode in keypress events (IE has the char code in the keyCode field and
+ // Mozilla only included the keyCode if there's no charCode). Thus we have to
+ // listen for keydown to capture the keycode.
+ this.keyDownKey_ = goog.events.listen(this.element_,
+ goog.events.EventType.KEYDOWN,
+ this.handleKeyDown_,
+ opt_capture,
+ this);
+
+
+ this.keyUpKey_ = goog.events.listen(this.element_,
+ goog.events.EventType.KEYUP,
+ this.handleKeyup_,
+ opt_capture,
+ this);
+};
+
+
+/**
+ * Removes the listeners that may exist.
+ */
+goog.events.KeyHandler.prototype.detach = function() {
+ if (this.keyPressKey_) {
+ goog.events.unlistenByKey(this.keyPressKey_);
+ goog.events.unlistenByKey(this.keyDownKey_);
+ goog.events.unlistenByKey(this.keyUpKey_);
+ this.keyPressKey_ = null;
+ this.keyDownKey_ = null;
+ this.keyUpKey_ = null;
+ }
+ this.element_ = null;
+ this.lastKey_ = -1;
+ this.keyCode_ = -1;
+};
+
+
+/** @override */
+goog.events.KeyHandler.prototype.disposeInternal = function() {
+ goog.events.KeyHandler.superClass_.disposeInternal.call(this);
+ this.detach();
+};
+
+
+
+/**
+ * This class is used for the goog.events.KeyHandler.EventType.KEY event and
+ * it overrides the key code with the fixed key code.
+ * @param {number} keyCode The adjusted key code.
+ * @param {number} charCode The unicode character code.
+ * @param {boolean} repeat Whether this event was generated by keyboard repeat.
+ * @param {Event} browserEvent Browser event object.
+ * @constructor
+ * @extends {goog.events.BrowserEvent}
+ * @final
+ */
+goog.events.KeyEvent = function(keyCode, charCode, repeat, browserEvent) {
+ goog.events.BrowserEvent.call(this, browserEvent);
+ this.type = goog.events.KeyHandler.EventType.KEY;
+
+ /**
+ * Keycode of key press.
+ * @type {number}
+ */
+ this.keyCode = keyCode;
+
+ /**
+ * Unicode character code.
+ * @type {number}
+ */
+ this.charCode = charCode;
+
+ /**
+ * True if this event was generated by keyboard auto-repeat (i.e., the user is
+ * holding the key down.)
+ * @type {boolean}
+ */
+ this.repeat = repeat;
+};
+goog.inherits(goog.events.KeyEvent, goog.events.BrowserEvent);
+
+// 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 This event wrapper will dispatch an event when the user uses
+ * the mouse wheel to scroll an element. You can get the direction by checking
+ * the deltaX and deltaY properties of the event.
+ *
+ * This class aims to smooth out inconsistencies between browser platforms with
+ * regards to mousewheel events, but we do not cover every possible
+ * software/hardware combination out there, some of which occasionally produce
+ * very large deltas in mousewheel events. If your application wants to guard
+ * against extremely large deltas, use the setMaxDeltaX and setMaxDeltaY APIs
+ * to set maximum values that make sense for your application.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ * @see ../demos/mousewheelhandler.html
+ */
+
+goog.provide('goog.events.MouseWheelEvent');
+goog.provide('goog.events.MouseWheelHandler');
+goog.provide('goog.events.MouseWheelHandler.EventType');
+
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.EventTarget');
+goog.require('goog.math');
+goog.require('goog.style');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * This event handler allows you to catch mouse wheel events in a consistent
+ * manner.
+ * @param {Element|Document} element The element to listen to the mouse wheel
+ * event on.
+ * @param {boolean=} opt_capture Whether to handle the mouse wheel event in
+ * capture phase.
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+goog.events.MouseWheelHandler = function(element, opt_capture) {
+ goog.events.EventTarget.call(this);
+
+ /**
+ * This is the element that we will listen to the real mouse wheel events on.
+ * @type {Element|Document}
+ * @private
+ */
+ this.element_ = element;
+
+ var rtlElement = goog.dom.isElement(this.element_) ?
+ /** @type {Element} */ (this.element_) :
+ (this.element_ ? /** @type {Document} */ (this.element_).body : null);
+
+ /**
+ * True if the element exists and is RTL, false otherwise.
+ * @type {boolean}
+ * @private
+ */
+ this.isRtl_ = !!rtlElement && goog.style.isRightToLeft(rtlElement);
+
+ var type = goog.userAgent.GECKO ? 'DOMMouseScroll' : 'mousewheel';
+
+ /**
+ * The key returned from the goog.events.listen.
+ * @type {goog.events.Key}
+ * @private
+ */
+ this.listenKey_ = goog.events.listen(this.element_, type, this, opt_capture);
+};
+goog.inherits(goog.events.MouseWheelHandler, goog.events.EventTarget);
+
+
+/**
+ * Enum type for the events fired by the mouse wheel handler.
+ * @enum {string}
+ */
+goog.events.MouseWheelHandler.EventType = {
+ MOUSEWHEEL: 'mousewheel'
+};
+
+
+/**
+ * Optional maximum magnitude for x delta on each mousewheel event.
+ * @type {number|undefined}
+ * @private
+ */
+goog.events.MouseWheelHandler.prototype.maxDeltaX_;
+
+
+/**
+ * Optional maximum magnitude for y delta on each mousewheel event.
+ * @type {number|undefined}
+ * @private
+ */
+goog.events.MouseWheelHandler.prototype.maxDeltaY_;
+
+
+/**
+ * @param {number} maxDeltaX Maximum magnitude for x delta on each mousewheel
+ * event. Should be non-negative.
+ */
+goog.events.MouseWheelHandler.prototype.setMaxDeltaX = function(maxDeltaX) {
+ this.maxDeltaX_ = maxDeltaX;
+};
+
+
+/**
+ * @param {number} maxDeltaY Maximum magnitude for y delta on each mousewheel
+ * event. Should be non-negative.
+ */
+goog.events.MouseWheelHandler.prototype.setMaxDeltaY = function(maxDeltaY) {
+ this.maxDeltaY_ = maxDeltaY;
+};
+
+
+/**
+ * Handles the events on the element.
+ * @param {goog.events.BrowserEvent} e The underlying browser event.
+ */
+goog.events.MouseWheelHandler.prototype.handleEvent = function(e) {
+ var deltaX = 0;
+ var deltaY = 0;
+ var detail = 0;
+ var be = e.getBrowserEvent();
+ if (be.type == 'mousewheel') {
+ var wheelDeltaScaleFactor = 1;
+ if (goog.userAgent.IE ||
+ goog.userAgent.WEBKIT &&
+ (goog.userAgent.WINDOWS || goog.userAgent.isVersionOrHigher('532.0'))) {
+ // In IE we get a multiple of 120; we adjust to a multiple of 3 to
+ // represent number of lines scrolled (like Gecko).
+ // Newer versions of Webkit match IE behavior, and WebKit on
+ // Windows also matches IE behavior.
+ // See bug https://bugs.webkit.org/show_bug.cgi?id=24368
+ wheelDeltaScaleFactor = 40;
+ }
+
+ detail = goog.events.MouseWheelHandler.smartScale_(
+ -be.wheelDelta, wheelDeltaScaleFactor);
+ if (goog.isDef(be.wheelDeltaX)) {
+ // Webkit has two properties to indicate directional scroll, and
+ // can scroll both directions at once.
+ deltaX = goog.events.MouseWheelHandler.smartScale_(
+ -be.wheelDeltaX, wheelDeltaScaleFactor);
+ deltaY = goog.events.MouseWheelHandler.smartScale_(
+ -be.wheelDeltaY, wheelDeltaScaleFactor);
+ } else {
+ deltaY = detail;
+ }
+
+ // Historical note: Opera (pre 9.5) used to negate the detail value.
+ } else { // Gecko
+ // Gecko returns multiple of 3 (representing the number of lines scrolled)
+ detail = be.detail;
+
+ // Gecko sometimes returns really big values if the user changes settings to
+ // scroll a whole page per scroll
+ if (detail > 100) {
+ detail = 3;
+ } else if (detail < -100) {
+ detail = -3;
+ }
+
+ // Firefox 3.1 adds an axis field to the event to indicate direction of
+ // scroll. See https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
+ if (goog.isDef(be.axis) && be.axis === be.HORIZONTAL_AXIS) {
+ deltaX = detail;
+ } else {
+ deltaY = detail;
+ }
+ }
+
+ if (goog.isNumber(this.maxDeltaX_)) {
+ deltaX = goog.math.clamp(deltaX, -this.maxDeltaX_, this.maxDeltaX_);
+ }
+ if (goog.isNumber(this.maxDeltaY_)) {
+ deltaY = goog.math.clamp(deltaY, -this.maxDeltaY_, this.maxDeltaY_);
+ }
+ // Don't clamp 'detail', since it could be ambiguous which axis it refers to
+ // and because it's informally deprecated anyways.
+
+ // For horizontal scrolling we need to flip the value for RTL grids.
+ if (this.isRtl_) {
+ deltaX = -deltaX;
+ }
+ var newEvent = new goog.events.MouseWheelEvent(detail, be, deltaX, deltaY);
+ this.dispatchEvent(newEvent);
+};
+
+
+/**
+ * Helper for scaling down a mousewheel delta by a scale factor, if appropriate.
+ * @param {number} mouseWheelDelta Delta from a mouse wheel event. Expected to
+ * be an integer.
+ * @param {number} scaleFactor Factor to scale the delta down by. Expected to
+ * be an integer.
+ * @return {number} Scaled-down delta value, or the original delta if the
+ * scaleFactor does not appear to be applicable.
+ * @private
+ */
+goog.events.MouseWheelHandler.smartScale_ = function(mouseWheelDelta,
+ scaleFactor) {
+ // The basic problem here is that in Webkit on Mac and Linux, we can get two
+ // very different types of mousewheel events: from continuous devices
+ // (touchpads, Mighty Mouse) or non-continuous devices (normal wheel mice).
+ //
+ // Non-continuous devices in Webkit get their wheel deltas scaled up to
+ // behave like IE. Continuous devices return much smaller unscaled values
+ // (which most of the time will not be cleanly divisible by the IE scale
+ // factor), so we should not try to normalize them down.
+ //
+ // Detailed discussion:
+ // https://bugs.webkit.org/show_bug.cgi?id=29601
+ // http://trac.webkit.org/browser/trunk/WebKit/chromium/src/mac/WebInputEventFactory.mm#L1063
+ if (goog.userAgent.WEBKIT &&
+ (goog.userAgent.MAC || goog.userAgent.LINUX) &&
+ (mouseWheelDelta % scaleFactor) != 0) {
+ return mouseWheelDelta;
+ } else {
+ return mouseWheelDelta / scaleFactor;
+ }
+};
+
+
+/** @override */
+goog.events.MouseWheelHandler.prototype.disposeInternal = function() {
+ goog.events.MouseWheelHandler.superClass_.disposeInternal.call(this);
+ goog.events.unlistenByKey(this.listenKey_);
+ this.listenKey_ = null;
+};
+
+
+
+/**
+ * A base class for mouse wheel events. This is used with the
+ * MouseWheelHandler.
+ *
+ * @param {number} detail The number of rows the user scrolled.
+ * @param {Event} browserEvent Browser event object.
+ * @param {number} deltaX The number of rows the user scrolled in the X
+ * direction.
+ * @param {number} deltaY The number of rows the user scrolled in the Y
+ * direction.
+ * @constructor
+ * @extends {goog.events.BrowserEvent}
+ * @final
+ */
+goog.events.MouseWheelEvent = function(detail, browserEvent, deltaX, deltaY) {
+ goog.events.BrowserEvent.call(this, browserEvent);
+
+ this.type = goog.events.MouseWheelHandler.EventType.MOUSEWHEEL;
+
+ /**
+ * The number of lines the user scrolled
+ * @type {number}
+ * NOTE: Informally deprecated. Use deltaX and deltaY instead, they provide
+ * more information.
+ */
+ this.detail = detail;
+
+ /**
+ * The number of "lines" scrolled in the X direction.
+ *
+ * Note that not all browsers provide enough information to distinguish
+ * horizontal and vertical scroll events, so for these unsupported browsers,
+ * we will always have a deltaX of 0, even if the user scrolled their mouse
+ * wheel or trackpad sideways.
+ *
+ * Currently supported browsers are Webkit and Firefox 3.1 or later.
+ *
+ * @type {number}
+ */
+ this.deltaX = deltaX;
+
+ /**
+ * The number of lines scrolled in the Y direction.
+ * @type {number}
+ */
+ this.deltaY = deltaY;
+};
+goog.inherits(goog.events.MouseWheelEvent, goog.events.BrowserEvent);
+
+// 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 Basic strippable logging definitions.
+ * @see http://go/closurelogging
+ *
+ * @author johnlenz@google.com (John Lenz)
+ */
+
+goog.provide('goog.log');
+goog.provide('goog.log.Level');
+goog.provide('goog.log.LogRecord');
+goog.provide('goog.log.Logger');
+
+goog.require('goog.debug');
+goog.require('goog.debug.LogManager');
+goog.require('goog.debug.LogRecord');
+goog.require('goog.debug.Logger');
+
+
+/** @define {boolean} Whether logging is enabled. */
+goog.define('goog.log.ENABLED', goog.debug.LOGGING_ENABLED);
+
+
+/** @const */
+goog.log.ROOT_LOGGER_NAME = goog.debug.Logger.ROOT_LOGGER_NAME;
+
+
+
+/**
+ * @constructor
+ * @final
+ */
+goog.log.Logger = goog.debug.Logger;
+
+
+
+/**
+ * @constructor
+ * @final
+ */
+goog.log.Level = goog.debug.Logger.Level;
+
+
+
+/**
+ * @constructor
+ * @final
+ */
+goog.log.LogRecord = goog.debug.LogRecord;
+
+
+/**
+ * Finds or creates a logger for a named subsystem. If a logger has already been
+ * created with the given name it is returned. Otherwise a new logger is
+ * created. If a new logger is created its log level will be configured based
+ * on the goog.debug.LogManager configuration and it will configured to also
+ * send logging output to its parent's handlers.
+ * @see goog.debug.LogManager
+ *
+ * @param {string} name A name for the logger. This should be a dot-separated
+ * name and should normally be based on the package name or class name of
+ * the subsystem, such as goog.net.BrowserChannel.
+ * @param {goog.log.Level=} opt_level If provided, override the
+ * default logging level with the provided level.
+ * @return {goog.log.Logger} The named logger or null if logging is disabled.
+ */
+goog.log.getLogger = function(name, opt_level) {
+ if (goog.log.ENABLED) {
+ var logger = goog.debug.LogManager.getLogger(name);
+ if (opt_level && logger) {
+ logger.setLevel(opt_level);
+ }
+ return logger;
+ } else {
+ return null;
+ }
+};
+
+
+// TODO(johnlenz): try to tighten the types to these functions.
+/**
+ * Adds a handler to the logger. This doesn't use the event system because
+ * we want to be able to add logging to the event system.
+ * @param {goog.log.Logger} logger
+ * @param {Function} handler Handler function to add.
+ */
+goog.log.addHandler = function(logger, handler) {
+ if (goog.log.ENABLED && logger) {
+ logger.addHandler(handler);
+ }
+};
+
+
+/**
+ * Removes a handler from the logger. This doesn't use the event system because
+ * we want to be able to add logging to the event system.
+ * @param {goog.log.Logger} logger
+ * @param {Function} handler Handler function to remove.
+ * @return {boolean} Whether the handler was removed.
+ */
+goog.log.removeHandler = function(logger, handler) {
+ if (goog.log.ENABLED && logger) {
+ return logger.removeHandler(handler);
+ } else {
+ return false;
+ }
+};
+
+
+/**
+ * Logs a message. If the logger is currently enabled for the
+ * given message level then the given message is forwarded to all the
+ * registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.log.Level} level One of the level identifiers.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error|Object=} opt_exception An exception associated with the
+ * message.
+ */
+goog.log.log = function(logger, level, msg, opt_exception) {
+ if (goog.log.ENABLED && logger) {
+ logger.log(level, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Level.SEVERE level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.log.error = function(logger, msg, opt_exception) {
+ if (goog.log.ENABLED && logger) {
+ logger.severe(msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Level.WARNING level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.log.warning = function(logger, msg, opt_exception) {
+ if (goog.log.ENABLED && logger) {
+ logger.warning(msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Level.INFO level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.log.info = function(logger, msg, opt_exception) {
+ if (goog.log.ENABLED && logger) {
+ logger.info(msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Level.Fine level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.log.fine = function(logger, msg, opt_exception) {
+ if (goog.log.ENABLED && logger) {
+ logger.fine(msg, opt_exception);
+ }
+};
+
+// Based on https://github.com/Polymer/PointerEvents
+
+// Copyright (c) 2013 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+goog.provide('ol.pointer.PointerEvent');
+
+
+goog.require('goog.events');
+goog.require('goog.events.Event');
+
+
+
+/**
+ * A class for pointer events.
+ *
+ * This class is used as an abstraction for mouse events,
+ * touch events and even native pointer events.
+ *
+ * @constructor
+ * @extends {goog.events.Event}
+ * @param {string} type The type of the event to create.
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @param {Object.<string, ?>=} opt_eventDict An optional dictionary of
+ * initial event properties.
+ */
+ol.pointer.PointerEvent = function(type, browserEvent, opt_eventDict) {
+ goog.base(this, type);
+
+ /**
+ * @const
+ * @type {goog.events.BrowserEvent}
+ */
+ this.browserEvent = browserEvent;
+
+ var eventDict = opt_eventDict ? opt_eventDict : {};
+
+ /**
+ * @type {number}
+ */
+ this.buttons = this.getButtons_(eventDict);
+
+ /**
+ * @type {number}
+ */
+ this.pressure = this.getPressure_(eventDict, this.buttons);
+
+ // MouseEvent related properties
+
+ /**
+ * @type {boolean}
+ */
+ this.bubbles = 'bubbles' in eventDict ? eventDict['bubbles'] : false;
+
+ /**
+ * @type {boolean}
+ */
+ this.cancelable = 'cancelable' in eventDict ? eventDict['cancelable'] : false;
+
+ /**
+ * @type {Object}
+ */
+ this.view = 'view' in eventDict ? eventDict['view'] : null;
+
+ /**
+ * @type {number}
+ */
+ this.detail = 'detail' in eventDict ? eventDict['detail'] : null;
+
+ /**
+ * @type {number}
+ */
+ this.screenX = 'screenX' in eventDict ? eventDict['screenX'] : 0;
+
+ /**
+ * @type {number}
+ */
+ this.screenY = 'screenY' in eventDict ? eventDict['screenY'] : 0;
+
+ /**
+ * @type {number}
+ */
+ this.clientX = 'clientX' in eventDict ? eventDict['clientX'] : 0;
+
+ /**
+ * @type {number}
+ */
+ this.clientY = 'clientY' in eventDict ? eventDict['clientY'] : 0;
+
+ /**
+ * @type {boolean}
+ */
+ this.ctrlKey = 'ctrlKey' in eventDict ? eventDict['ctrlKey'] : false;
+
+ /**
+ * @type {boolean}
+ */
+ this.altKey = 'altKey' in eventDict ? eventDict['altKey'] : false;
+
+ /**
+ * @type {boolean}
+ */
+ this.shiftKey = 'shiftKey' in eventDict ? eventDict['shiftKey'] : false;
+
+ /**
+ * @type {boolean}
+ */
+ this.metaKey = 'metaKey' in eventDict ? eventDict['metaKey'] : false;
+
+ /**
+ * @type {number}
+ */
+ this.button = 'button' in eventDict ? eventDict['button'] : 0;
+
+ /**
+ * @type {Node}
+ */
+ this.relatedTarget = 'relatedTarget' in eventDict ?
+ eventDict['relatedTarget'] : null;
+
+ // PointerEvent related properties
+
+ /**
+ * @const
+ * @type {number}
+ */
+ this.pointerId = 'pointerId' in eventDict ? eventDict['pointerId'] : 0;
+
+ /**
+ * @type {number}
+ */
+ this.width = 'width' in eventDict ? eventDict['width'] : 0;
+
+ /**
+ * @type {number}
+ */
+ this.height = 'height' in eventDict ? eventDict['height'] : 0;
+
+ /**
+ * @type {number}
+ */
+ this.tiltX = 'tiltX' in eventDict ? eventDict['tiltX'] : 0;
+
+ /**
+ * @type {number}
+ */
+ this.tiltY = 'tiltY' in eventDict ? eventDict['tiltY'] : 0;
+
+ /**
+ * @type {string}
+ */
+ this.pointerType = 'pointerType' in eventDict ? eventDict['pointerType'] : '';
+
+ /**
+ * @type {number}
+ */
+ this.hwTimestamp = 'hwTimestamp' in eventDict ? eventDict['hwTimestamp'] : 0;
+
+ /**
+ * @type {boolean}
+ */
+ this.isPrimary = 'isPrimary' in eventDict ? eventDict['isPrimary'] : false;
+
+ // keep the semantics of preventDefault
+ if (browserEvent.preventDefault) {
+ this.preventDefault = function() {
+ browserEvent.preventDefault();
+ };
+ }
+};
+goog.inherits(ol.pointer.PointerEvent, goog.events.Event);
+
+
+/**
+ * @private
+ * @param {Object.<string, ?>} eventDict
+ * @return {number}
+ */
+ol.pointer.PointerEvent.prototype.getButtons_ = function(eventDict) {
+ // According to the w3c spec,
+ // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-button
+ // MouseEvent.button == 0 can mean either no mouse button depressed, or the
+ // left mouse button depressed.
+ //
+ // As of now, the only way to distinguish between the two states of
+ // MouseEvent.button is by using the deprecated MouseEvent.which property, as
+ // this maps mouse buttons to positive integers > 0, and uses 0 to mean that
+ // no mouse button is held.
+ //
+ // MouseEvent.which is derived from MouseEvent.button at MouseEvent creation,
+ // but initMouseEvent does not expose an argument with which to set
+ // MouseEvent.which. Calling initMouseEvent with a buttonArg of 0 will set
+ // MouseEvent.button == 0 and MouseEvent.which == 1, breaking the expectations
+ // of app developers.
+ //
+ // The only way to propagate the correct state of MouseEvent.which and
+ // MouseEvent.button to a new MouseEvent.button == 0 and MouseEvent.which == 0
+ // is to call initMouseEvent with a buttonArg value of -1.
+ //
+ // This is fixed with DOM Level 4's use of buttons
+ var buttons;
+ if (eventDict.buttons || ol.pointer.PointerEvent.HAS_BUTTONS) {
+ buttons = eventDict.buttons;
+ } else {
+ switch (eventDict.which) {
+ case 1: buttons = 1; break;
+ case 2: buttons = 4; break;
+ case 3: buttons = 2; break;
+ default: buttons = 0;
+ }
+ }
+ return buttons;
+};
+
+
+/**
+ * @private
+ * @param {Object.<string, ?>} eventDict
+ * @param {number} buttons
+ * @return {number}
+ */
+ol.pointer.PointerEvent.prototype.getPressure_ = function(eventDict, buttons) {
+ // Spec requires that pointers without pressure specified use 0.5 for down
+ // state and 0 for up state.
+ var pressure = 0;
+ if (eventDict.pressure) {
+ pressure = eventDict.pressure;
+ } else {
+ pressure = buttons ? 0.5 : 0;
+ }
+ return pressure;
+};
+
+
+/**
+ * Is the `buttons` property supported?
+ * @type {boolean}
+ */
+ol.pointer.PointerEvent.HAS_BUTTONS = false;
+
+
+/**
+ * Checks if the `buttons` property is supported.
+ */
+(function() {
+ try {
+ var ev = new MouseEvent('click', {buttons: 1});
+ ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1;
+ } catch (e) {
+ }
+})();
+
+// 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.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 = document.createElement('CANVAS');
+ if (opt_width) {
+ canvas.width = opt_width;
+ }
+ if (opt_height) {
+ canvas.height = opt_height;
+ }
+ return canvas.getContext('2d');
+};
+
+
+/**
+ * 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 (canUseCssTransform === undefined) {
+ goog.asserts.assert(document.body,
+ 'document.body should not be null');
+ if (!goog.global.getComputedStyle) {
+ // this browser is ancient
+ canUseCssTransform = false;
+ } else {
+ var el = document.createElement('P'),
+ has2d,
+ transforms = {
+ 'webkitTransform': '-webkit-transform',
+ 'OTransform': '-o-transform',
+ 'msTransform': '-ms-transform',
+ 'MozTransform': '-moz-transform',
+ 'transform': 'transform'
+ };
+ document.body.appendChild(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 (canUseCssTransform3D === undefined) {
+ goog.asserts.assert(document.body,
+ 'document.body should not be null');
+ if (!goog.global.getComputedStyle) {
+ // this browser is ancient
+ canUseCssTransform3D = false;
+ } else {
+ var el = document.createElement('P'),
+ has3d,
+ transforms = {
+ 'webkitTransform': '-webkit-transform',
+ 'OTransform': '-o-transform',
+ 'msTransform': '-ms-transform',
+ 'MozTransform': '-moz-transform',
+ 'transform': 'transform'
+ };
+ document.body.appendChild(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 && goog.userAgent.isVersionOrHigher('9.0')) {
+ element.style.transformOrigin = '0 0';
+ }
+};
+
+
+/**
+ * @param {!Element} element Element.
+ * @param {goog.vec.Mat4.Number} transform Matrix.
+ * @param {number=} opt_precision Precision.
+ */
+ol.dom.transformElement2D = function(element, transform, opt_precision) {
+ // using matrix() causes gaps in Chrome and Firefox on Mac OS X, so prefer
+ // matrix3d()
+ var i;
+ if (ol.dom.canUseCssTransform3D()) {
+ var value3D;
+
+ if (opt_precision !== undefined) {
+ /** @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 (opt_precision !== undefined) {
+ /** @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 {
+ 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 (context) {
+ return /** @type {!WebGLRenderingContext} */ (context);
+ }
+ } catch (e) {
+ }
+ }
+ return null;
+};
+
+goog.provide('ol.has');
+
+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 both the library and browser support Canvas. Always `false`
+ * if `ol.ENABLE_CANVAS` is set to `false` at compile time.
+ * @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 (!context) {
+ return false;
+ } else {
+ if (context.setLineDash !== undefined) {
+ 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 `ol.ENABLE_DOM` is set to `true` at compile time.
+ * @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 both OpenLayers and browser support WebGL. Always `false`
+ * if `ol.ENABLE_WEBGL` is set to `false` at compile time.
+ * @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} */
+ (document.createElement('CANVAS'));
+ var gl = ol.webgl.getContext(canvas, {
+ failIfMajorPerformanceCaveat: true
+ });
+ if (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');
+
+
+
+/**
+ * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @param {!Object.<string, function(goog.events.BrowserEvent)>} mapping
+ * @constructor
+ */
+ol.pointer.EventSource = function(dispatcher, mapping) {
+ /**
+ * @type {ol.pointer.PointerEventHandler}
+ */
+ this.dispatcher = dispatcher;
+
+ /**
+ * @private
+ * @const
+ * @type {!Object.<string, function(goog.events.BrowserEvent)>}
+ */
+ this.mapping_ = mapping;
+};
+
+
+/**
+ * List of events supported by this source.
+ * @return {Array.<string>} Event names
+ */
+ol.pointer.EventSource.prototype.getEvents = function() {
+ return Object.keys(this.mapping_);
+};
+
+
+/**
+ * Returns a mapping between the supported event types and
+ * the handlers that should handle an event.
+ * @return {Object.<string, function(goog.events.BrowserEvent)>}
+ * Event/Handler mapping
+ */
+ol.pointer.EventSource.prototype.getMapping = function() {
+ return this.mapping_;
+};
+
+
+/**
+ * Returns the handler that should handle a given event type.
+ * @param {string} eventType
+ * @return {function(goog.events.BrowserEvent)} Handler
+ */
+ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) {
+ return this.mapping_[eventType];
+};
+
+// Based on https://github.com/Polymer/PointerEvents
+
+// Copyright (c) 2013 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+goog.provide('ol.pointer.MouseSource');
+
+goog.require('ol.pointer.EventSource');
+
+
+
+/**
+ * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @constructor
+ * @extends {ol.pointer.EventSource}
+ */
+ol.pointer.MouseSource = function(dispatcher) {
+ var mapping = {
+ 'mousedown': this.mousedown,
+ 'mousemove': this.mousemove,
+ 'mouseup': this.mouseup,
+ 'mouseover': this.mouseover,
+ 'mouseout': this.mouseout
+ };
+ goog.base(this, dispatcher, mapping);
+
+ /**
+ * @const
+ * @type {!Object.<string, goog.events.BrowserEvent|Object>}
+ */
+ this.pointerMap = dispatcher.pointerMap;
+
+ /**
+ * @const
+ * @type {Array.<ol.Pixel>}
+ */
+ this.lastTouches = [];
+};
+goog.inherits(ol.pointer.MouseSource, ol.pointer.EventSource);
+
+
+/**
+ * @const
+ * @type {number}
+ */
+ol.pointer.MouseSource.POINTER_ID = 1;
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.pointer.MouseSource.POINTER_TYPE = 'mouse';
+
+
+/**
+ * Radius around touchend that swallows mouse events.
+ *
+ * @const
+ * @type {number}
+ */
+ol.pointer.MouseSource.DEDUP_DIST = 25;
+
+
+/**
+ * Detect if a mouse event was simulated from a touch by
+ * checking if previously there was a touch event at the
+ * same position.
+ *
+ * FIXME - Known problem with the native Android browser on
+ * Samsung GT-I9100 (Android 4.1.2):
+ * In case the page is scrolled, this function does not work
+ * correctly when a canvas is used (WebGL or canvas renderer).
+ * Mouse listeners on canvas elements (for this browser), create
+ * two mouse events: One 'good' and one 'bad' one (on other browsers or
+ * when a div is used, there is only one event). For the 'bad' one,
+ * clientX/clientY and also pageX/pageY are wrong when the page
+ * is scrolled. Because of that, this function can not detect if
+ * the events were simulated from a touch event. As result, a
+ * pointer event at a wrong position is dispatched, which confuses
+ * the map interactions.
+ * It is unclear, how one can get the correct position for the event
+ * or detect that the positions are invalid.
+ *
+ * @private
+ * @param {goog.events.BrowserEvent} inEvent
+ * @return {boolean} True, if the event was generated by a touch.
+ */
+ol.pointer.MouseSource.prototype.isEventSimulatedFromTouch_ =
+ function(inEvent) {
+ var lts = this.lastTouches;
+ var x = inEvent.clientX, y = inEvent.clientY;
+ for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) {
+ // simulated mouse events will be swallowed near a primary touchend
+ var dx = Math.abs(x - t[0]), dy = Math.abs(y - t[1]);
+ if (dx <= ol.pointer.MouseSource.DEDUP_DIST &&
+ dy <= ol.pointer.MouseSource.DEDUP_DIST) {
+ return true;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Creates a copy of the original event that will be used
+ * for the fake pointer event.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @return {Object}
+ */
+ol.pointer.MouseSource.prepareEvent = function(inEvent, dispatcher) {
+ var e = dispatcher.cloneEvent(inEvent, inEvent.getBrowserEvent());
+
+ // forward mouse preventDefault
+ var pd = e.preventDefault;
+ e.preventDefault = function() {
+ inEvent.preventDefault();
+ pd();
+ };
+
+ e.pointerId = ol.pointer.MouseSource.POINTER_ID;
+ e.isPrimary = true;
+ e.pointerType = ol.pointer.MouseSource.POINTER_TYPE;
+
+ return e;
+};
+
+
+/**
+ * Handler for `mousedown`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MouseSource.prototype.mousedown = function(inEvent) {
+ if (!this.isEventSimulatedFromTouch_(inEvent)) {
+ // TODO(dfreedman) workaround for some elements not sending mouseup
+ // http://crbug/149091
+ if (ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap) {
+ this.cancel(inEvent);
+ }
+ var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
+ this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()] = inEvent;
+ this.dispatcher.down(e, inEvent);
+ }
+};
+
+
+/**
+ * Handler for `mousemove`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MouseSource.prototype.mousemove = function(inEvent) {
+ if (!this.isEventSimulatedFromTouch_(inEvent)) {
+ var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
+ this.dispatcher.move(e, inEvent);
+ }
+};
+
+
+/**
+ * Handler for `mouseup`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MouseSource.prototype.mouseup = function(inEvent) {
+ if (!this.isEventSimulatedFromTouch_(inEvent)) {
+ 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);
+ this.dispatcher.up(e, inEvent);
+ this.cleanupMouse();
+ }
+ }
+};
+
+
+/**
+ * Handler for `mouseover`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MouseSource.prototype.mouseover = function(inEvent) {
+ if (!this.isEventSimulatedFromTouch_(inEvent)) {
+ var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
+ this.dispatcher.enterOver(e, inEvent);
+ }
+};
+
+
+/**
+ * Handler for `mouseout`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MouseSource.prototype.mouseout = function(inEvent) {
+ if (!this.isEventSimulatedFromTouch_(inEvent)) {
+ var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
+ this.dispatcher.leaveOut(e, inEvent);
+ }
+};
+
+
+/**
+ * Dispatches a `pointercancel` event.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MouseSource.prototype.cancel = function(inEvent) {
+ var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
+ this.dispatcher.cancel(e, inEvent);
+ this.cleanupMouse();
+};
+
+
+/**
+ * Remove the mouse from the list of active pointers.
+ */
+ol.pointer.MouseSource.prototype.cleanupMouse = function() {
+ delete this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()];
+};
+
+// Based on https://github.com/Polymer/PointerEvents
+
+// Copyright (c) 2013 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+goog.provide('ol.pointer.MsSource');
+
+goog.require('ol.pointer.EventSource');
+
+
+
+/**
+ * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @constructor
+ * @extends {ol.pointer.EventSource}
+ */
+ol.pointer.MsSource = function(dispatcher) {
+ var mapping = {
+ 'MSPointerDown': this.msPointerDown,
+ 'MSPointerMove': this.msPointerMove,
+ 'MSPointerUp': this.msPointerUp,
+ 'MSPointerOut': this.msPointerOut,
+ 'MSPointerOver': this.msPointerOver,
+ 'MSPointerCancel': this.msPointerCancel,
+ 'MSGotPointerCapture': this.msGotPointerCapture,
+ 'MSLostPointerCapture': this.msLostPointerCapture
+ };
+ goog.base(this, dispatcher, mapping);
+
+ /**
+ * @const
+ * @type {!Object.<string, goog.events.BrowserEvent|Object>}
+ */
+ this.pointerMap = dispatcher.pointerMap;
+
+ /**
+ * @const
+ * @type {Array.<string>}
+ */
+ this.POINTER_TYPES = [
+ '',
+ 'unavailable',
+ 'touch',
+ 'pen',
+ 'mouse'
+ ];
+};
+goog.inherits(ol.pointer.MsSource, ol.pointer.EventSource);
+
+
+/**
+ * Creates a copy of the original event that will be used
+ * for the fake pointer event.
+ *
+ * @private
+ * @param {goog.events.BrowserEvent} inEvent
+ * @return {Object}
+ */
+ol.pointer.MsSource.prototype.prepareEvent_ = function(inEvent) {
+ var e = inEvent;
+ if (goog.isNumber(inEvent.getBrowserEvent().pointerType)) {
+ e = this.dispatcher.cloneEvent(inEvent, inEvent.getBrowserEvent());
+ e.pointerType = this.POINTER_TYPES[inEvent.getBrowserEvent().pointerType];
+ }
+
+ return e;
+};
+
+
+/**
+ * Remove this pointer from the list of active pointers.
+ * @param {number} pointerId
+ */
+ol.pointer.MsSource.prototype.cleanup = function(pointerId) {
+ delete this.pointerMap[pointerId.toString()];
+};
+
+
+/**
+ * Handler for `msPointerDown`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) {
+ this.pointerMap[inEvent.getBrowserEvent().pointerId.toString()] = inEvent;
+ var e = this.prepareEvent_(inEvent);
+ this.dispatcher.down(e, inEvent);
+};
+
+
+/**
+ * Handler for `msPointerMove`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MsSource.prototype.msPointerMove = function(inEvent) {
+ var e = this.prepareEvent_(inEvent);
+ this.dispatcher.move(e, inEvent);
+};
+
+
+/**
+ * Handler for `msPointerUp`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MsSource.prototype.msPointerUp = function(inEvent) {
+ var e = this.prepareEvent_(inEvent);
+ this.dispatcher.up(e, inEvent);
+ this.cleanup(inEvent.getBrowserEvent().pointerId);
+};
+
+
+/**
+ * Handler for `msPointerOut`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MsSource.prototype.msPointerOut = function(inEvent) {
+ var e = this.prepareEvent_(inEvent);
+ this.dispatcher.leaveOut(e, inEvent);
+};
+
+
+/**
+ * Handler for `msPointerOver`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MsSource.prototype.msPointerOver = function(inEvent) {
+ var e = this.prepareEvent_(inEvent);
+ this.dispatcher.enterOver(e, inEvent);
+};
+
+
+/**
+ * Handler for `msPointerCancel`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MsSource.prototype.msPointerCancel = function(inEvent) {
+ var e = this.prepareEvent_(inEvent);
+ this.dispatcher.cancel(e, inEvent);
+ this.cleanup(inEvent.getBrowserEvent().pointerId);
+};
+
+
+/**
+ * Handler for `msLostPointerCapture`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MsSource.prototype.msLostPointerCapture = function(inEvent) {
+ var e = this.dispatcher.makeEvent('lostpointercapture',
+ inEvent.getBrowserEvent(), inEvent);
+ this.dispatcher.dispatchEvent(e);
+};
+
+
+/**
+ * Handler for `msGotPointerCapture`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.MsSource.prototype.msGotPointerCapture = function(inEvent) {
+ var e = this.dispatcher.makeEvent('gotpointercapture',
+ inEvent.getBrowserEvent(), inEvent);
+ this.dispatcher.dispatchEvent(e);
+};
+
+// Based on https://github.com/Polymer/PointerEvents
+
+// Copyright (c) 2013 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+goog.provide('ol.pointer.NativeSource');
+
+goog.require('ol.pointer.EventSource');
+
+
+
+/**
+ * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @constructor
+ * @extends {ol.pointer.EventSource}
+ */
+ol.pointer.NativeSource = function(dispatcher) {
+ var mapping = {
+ 'pointerdown': this.pointerDown,
+ 'pointermove': this.pointerMove,
+ 'pointerup': this.pointerUp,
+ 'pointerout': this.pointerOut,
+ 'pointerover': this.pointerOver,
+ 'pointercancel': this.pointerCancel,
+ 'gotpointercapture': this.gotPointerCapture,
+ 'lostpointercapture': this.lostPointerCapture
+ };
+ goog.base(this, dispatcher, mapping);
+};
+goog.inherits(ol.pointer.NativeSource, ol.pointer.EventSource);
+
+
+/**
+ * Handler for `pointerdown`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.NativeSource.prototype.pointerDown = function(inEvent) {
+ this.dispatcher.fireNativeEvent(inEvent);
+};
+
+
+/**
+ * Handler for `pointermove`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.NativeSource.prototype.pointerMove = function(inEvent) {
+ this.dispatcher.fireNativeEvent(inEvent);
+};
+
+
+/**
+ * Handler for `pointerup`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.NativeSource.prototype.pointerUp = function(inEvent) {
+ this.dispatcher.fireNativeEvent(inEvent);
+};
+
+
+/**
+ * Handler for `pointerout`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.NativeSource.prototype.pointerOut = function(inEvent) {
+ this.dispatcher.fireNativeEvent(inEvent);
+};
+
+
+/**
+ * Handler for `pointerover`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.NativeSource.prototype.pointerOver = function(inEvent) {
+ this.dispatcher.fireNativeEvent(inEvent);
+};
+
+
+/**
+ * Handler for `pointercancel`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.NativeSource.prototype.pointerCancel = function(inEvent) {
+ this.dispatcher.fireNativeEvent(inEvent);
+};
+
+
+/**
+ * Handler for `lostpointercapture`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.NativeSource.prototype.lostPointerCapture = function(inEvent) {
+ this.dispatcher.fireNativeEvent(inEvent);
+};
+
+
+/**
+ * Handler for `gotpointercapture`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.NativeSource.prototype.gotPointerCapture = function(inEvent) {
+ this.dispatcher.fireNativeEvent(inEvent);
+};
+
+// Based on https://github.com/Polymer/PointerEvents
+
+// Copyright (c) 2013 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+goog.provide('ol.pointer.TouchSource');
+
+goog.require('goog.array');
+goog.require('goog.object');
+goog.require('ol');
+goog.require('ol.pointer.EventSource');
+goog.require('ol.pointer.MouseSource');
+
+
+
+/**
+ * @constructor
+ * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @param {ol.pointer.MouseSource} mouseSource
+ * @extends {ol.pointer.EventSource}
+ */
+ol.pointer.TouchSource = function(dispatcher, mouseSource) {
+ var mapping = {
+ 'touchstart': this.touchstart,
+ 'touchmove': this.touchmove,
+ 'touchend': this.touchend,
+ 'touchcancel': this.touchcancel
+ };
+ goog.base(this, dispatcher, mapping);
+
+ /**
+ * @const
+ * @type {!Object.<string, goog.events.BrowserEvent|Object>}
+ */
+ this.pointerMap = dispatcher.pointerMap;
+
+ /**
+ * @const
+ * @type {ol.pointer.MouseSource}
+ */
+ this.mouseSource = mouseSource;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.firstTouchId_ = undefined;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.clickCount_ = 0;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.resetId_ = undefined;
+};
+goog.inherits(ol.pointer.TouchSource, ol.pointer.EventSource);
+
+
+/**
+ * Mouse event timeout: This should be long enough to
+ * ignore compat mouse events made by touch.
+ * @const
+ * @type {number}
+ */
+ol.pointer.TouchSource.DEDUP_TIMEOUT = 2500;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT = 200;
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.pointer.TouchSource.POINTER_TYPE = 'touch';
+
+
+/**
+ * @private
+ * @param {Touch} inTouch
+ * @return {boolean} True, if this is the primary touch.
+ */
+ol.pointer.TouchSource.prototype.isPrimaryTouch_ = function(inTouch) {
+ return this.firstTouchId_ === inTouch.identifier;
+};
+
+
+/**
+ * Set primary touch if there are no pointers, or the only pointer is the mouse.
+ * @param {Touch} inTouch
+ * @private
+ */
+ol.pointer.TouchSource.prototype.setPrimaryTouch_ = function(inTouch) {
+ var count = goog.object.getCount(this.pointerMap);
+ if (count === 0 || (count === 1 &&
+ ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap)) {
+ this.firstTouchId_ = inTouch.identifier;
+ this.cancelResetClickCount_();
+ }
+};
+
+
+/**
+ * @private
+ * @param {Object} inPointer
+ */
+ol.pointer.TouchSource.prototype.removePrimaryPointer_ = function(inPointer) {
+ if (inPointer.isPrimary) {
+ this.firstTouchId_ = undefined;
+ this.resetClickCount_();
+ }
+};
+
+
+/**
+ * @private
+ */
+ol.pointer.TouchSource.prototype.resetClickCount_ = function() {
+ this.resetId_ = goog.global.setTimeout(
+ goog.bind(this.resetClickCountHandler_, this),
+ ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT);
+};
+
+
+/**
+ * @private
+ */
+ol.pointer.TouchSource.prototype.resetClickCountHandler_ = function() {
+ this.clickCount_ = 0;
+ this.resetId_ = undefined;
+};
+
+
+/**
+ * @private
+ */
+ol.pointer.TouchSource.prototype.cancelResetClickCount_ = function() {
+ if (this.resetId_ !== undefined) {
+ goog.global.clearTimeout(this.resetId_);
+ }
+};
+
+
+/**
+ * @private
+ * @param {goog.events.BrowserEvent} browserEvent Browser event
+ * @param {Touch} inTouch Touch event
+ * @return {Object}
+ */
+ol.pointer.TouchSource.prototype.touchToPointer_ =
+ function(browserEvent, inTouch) {
+ var e = this.dispatcher.cloneEvent(browserEvent, inTouch);
+ // Spec specifies that pointerId 1 is reserved for Mouse.
+ // Touch identifiers can start at 0.
+ // Add 2 to the touch identifier for compatibility.
+ e.pointerId = inTouch.identifier + 2;
+ // TODO: check if this is necessary?
+ //e.target = findTarget(e);
+ e.bubbles = true;
+ e.cancelable = true;
+ e.detail = this.clickCount_;
+ e.button = 0;
+ e.buttons = 1;
+ e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0;
+ e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0;
+ e.pressure = inTouch.webkitForce || inTouch.force || 0.5;
+ e.isPrimary = this.isPrimaryTouch_(inTouch);
+ e.pointerType = ol.pointer.TouchSource.POINTER_TYPE;
+
+ // make sure that the properties that are different for
+ // each `Touch` object are not copied from the BrowserEvent object
+ e.clientX = inTouch.clientX;
+ e.clientY = inTouch.clientY;
+ e.screenX = inTouch.screenX;
+ e.screenY = inTouch.screenY;
+
+ return e;
+};
+
+
+/**
+ * @private
+ * @param {goog.events.BrowserEvent} inEvent Touch event
+ * @param {function(goog.events.BrowserEvent, Object)} inFunction
+ */
+ol.pointer.TouchSource.prototype.processTouches_ =
+ function(inEvent, inFunction) {
+ var touches = Array.prototype.slice.call(
+ inEvent.getBrowserEvent().changedTouches);
+ var count = touches.length;
+ function preventDefault() {
+ inEvent.preventDefault();
+ }
+ var i, pointer;
+ for (i = 0; i < count; ++i) {
+ pointer = this.touchToPointer_(inEvent, touches[i]);
+ // forward touch preventDefaults
+ pointer.preventDefault = preventDefault;
+ inFunction.call(this, inEvent, pointer);
+ }
+};
+
+
+/**
+ * @private
+ * @param {TouchList} touchList
+ * @param {number} searchId
+ * @return {boolean} True, if the `Touch` with the given id is in the list.
+ */
+ol.pointer.TouchSource.prototype.findTouch_ = function(touchList, searchId) {
+ var l = touchList.length;
+ var touch;
+ for (var i = 0; i < l; i++) {
+ touch = touchList[i];
+ if (touch.identifier === searchId) {
+ return true;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * In some instances, a touchstart can happen without a touchend. This
+ * leaves the pointermap in a broken state.
+ * Therefore, on every touchstart, we remove the touches that did not fire a
+ * touchend event.
+ * To keep state globally consistent, we fire a pointercancel for
+ * this "abandoned" touch
+ *
+ * @private
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.TouchSource.prototype.vacuumTouches_ = function(inEvent) {
+ var touchList = inEvent.getBrowserEvent().touches;
+ // pointerMap.getCount() should be < touchList.length here,
+ // as the touchstart has not been processed yet.
+ var keys = Object.keys(this.pointerMap);
+ var count = keys.length;
+ if (count >= touchList.length) {
+ var d = [];
+ var i, key, value;
+ for (i = 0; i < count; ++i) {
+ key = keys[i];
+ value = this.pointerMap[key];
+ // Never remove pointerId == 1, which is mouse.
+ // Touch identifiers are 2 smaller than their pointerId, which is the
+ // index in pointermap.
+ if (key != ol.pointer.MouseSource.POINTER_ID &&
+ !this.findTouch_(touchList, key - 2)) {
+ d.push(value.out);
+ }
+ }
+ for (i = 0; i < d.length; ++i) {
+ this.cancelOut_(inEvent, d[i]);
+ }
+ }
+};
+
+
+/**
+ * Handler for `touchstart`, triggers `pointerover`,
+ * `pointerenter` and `pointerdown` events.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.TouchSource.prototype.touchstart = function(inEvent) {
+ this.vacuumTouches_(inEvent);
+ this.setPrimaryTouch_(inEvent.getBrowserEvent().changedTouches[0]);
+ this.dedupSynthMouse_(inEvent);
+ this.clickCount_++;
+ this.processTouches_(inEvent, this.overDown_);
+};
+
+
+/**
+ * @private
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @param {Object} inPointer
+ */
+ol.pointer.TouchSource.prototype.overDown_ = function(browserEvent, inPointer) {
+ 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);
+};
+
+
+/**
+ * Handler for `touchmove`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.TouchSource.prototype.touchmove = function(inEvent) {
+ inEvent.preventDefault();
+ this.processTouches_(inEvent, this.moveOverOut_);
+};
+
+
+/**
+ * @private
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @param {Object} inPointer
+ */
+ol.pointer.TouchSource.prototype.moveOverOut_ =
+ function(browserEvent, inPointer) {
+ var event = inPointer;
+ var pointer = this.pointerMap[event.pointerId];
+ // a finger drifted off the screen, ignore it
+ if (!pointer) {
+ return;
+ }
+ var outEvent = pointer.out;
+ var outTarget = pointer.outTarget;
+ this.dispatcher.move(event, browserEvent);
+ if (outEvent && outTarget !== event.target) {
+ outEvent.relatedTarget = event.target;
+ event.relatedTarget = outTarget;
+ // recover from retargeting by shadow
+ outEvent.target = outTarget;
+ if (event.target) {
+ this.dispatcher.leaveOut(outEvent, browserEvent);
+ this.dispatcher.enterOver(event, browserEvent);
+ } else {
+ // clean up case when finger leaves the screen
+ event.target = outTarget;
+ event.relatedTarget = null;
+ this.cancelOut_(browserEvent, event);
+ }
+ }
+ pointer.out = event;
+ pointer.outTarget = event.target;
+};
+
+
+/**
+ * Handler for `touchend`, triggers `pointerup`,
+ * `pointerout` and `pointerleave` events.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.TouchSource.prototype.touchend = function(inEvent) {
+ this.dedupSynthMouse_(inEvent);
+ this.processTouches_(inEvent, this.upOut_);
+};
+
+
+/**
+ * @private
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @param {Object} inPointer
+ */
+ol.pointer.TouchSource.prototype.upOut_ = function(browserEvent, inPointer) {
+ this.dispatcher.up(inPointer, browserEvent);
+ this.dispatcher.out(inPointer, browserEvent);
+ this.dispatcher.leave(inPointer, browserEvent);
+ this.cleanUpPointer_(inPointer);
+};
+
+
+/**
+ * Handler for `touchcancel`, triggers `pointercancel`,
+ * `pointerout` and `pointerleave` events.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.TouchSource.prototype.touchcancel = function(inEvent) {
+ this.processTouches_(inEvent, this.cancelOut_);
+};
+
+
+/**
+ * @private
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @param {Object} inPointer
+ */
+ol.pointer.TouchSource.prototype.cancelOut_ =
+ function(browserEvent, inPointer) {
+ this.dispatcher.cancel(inPointer, browserEvent);
+ this.dispatcher.out(inPointer, browserEvent);
+ this.dispatcher.leave(inPointer, browserEvent);
+ this.cleanUpPointer_(inPointer);
+};
+
+
+/**
+ * @private
+ * @param {Object} inPointer
+ */
+ol.pointer.TouchSource.prototype.cleanUpPointer_ = function(inPointer) {
+ delete this.pointerMap[inPointer.pointerId];
+ this.removePrimaryPointer_(inPointer);
+};
+
+
+/**
+ * Prevent synth mouse events from creating pointer events.
+ *
+ * @private
+ * @param {goog.events.BrowserEvent} inEvent
+ */
+ol.pointer.TouchSource.prototype.dedupSynthMouse_ = function(inEvent) {
+ var lts = this.mouseSource.lastTouches;
+ var t = inEvent.getBrowserEvent().changedTouches[0];
+ // only the primary finger will synth mouse events
+ if (this.isPrimaryTouch_(t)) {
+ // remember x/y of last touch
+ var lt = /** @type {ol.Pixel} */ ([t.clientX, t.clientY]);
+ lts.push(lt);
+
+ goog.global.setTimeout(function() {
+ // remove touch after timeout
+ goog.array.remove(lts, lt);
+ }, ol.pointer.TouchSource.DEDUP_TIMEOUT);
+ }
+};
+
+// Based on https://github.com/Polymer/PointerEvents
+
+// Copyright (c) 2013 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+goog.provide('ol.pointer.PointerEventHandler');
+
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.EventTarget');
+
+goog.require('ol.has');
+goog.require('ol.pointer.MouseSource');
+goog.require('ol.pointer.MsSource');
+goog.require('ol.pointer.NativeSource');
+goog.require('ol.pointer.PointerEvent');
+goog.require('ol.pointer.TouchSource');
+
+
+
+/**
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @param {Element|HTMLDocument} element Viewport element.
+ */
+ol.pointer.PointerEventHandler = function(element) {
+ goog.base(this);
+
+ /**
+ * @const
+ * @private
+ * @type {Element|HTMLDocument}
+ */
+ this.element_ = element;
+
+ /**
+ * @const
+ * @type {!Object.<string, goog.events.BrowserEvent|Object>}
+ */
+ this.pointerMap = {};
+
+ /**
+ * @type {Object.<string, function(goog.events.BrowserEvent)>}
+ * @private
+ */
+ this.eventMap_ = {};
+
+ /**
+ * @type {Array.<ol.pointer.EventSource>}
+ * @private
+ */
+ this.eventSourceList_ = [];
+
+ this.registerSources();
+};
+goog.inherits(ol.pointer.PointerEventHandler, goog.events.EventTarget);
+
+
+/**
+ * Set up the event sources (mouse, touch and native pointers)
+ * that generate pointer events.
+ */
+ol.pointer.PointerEventHandler.prototype.registerSources = function() {
+ if (ol.has.POINTER) {
+ this.registerSource('native', new ol.pointer.NativeSource(this));
+ } else if (ol.has.MSPOINTER) {
+ this.registerSource('ms', new ol.pointer.MsSource(this));
+ } else {
+ var mouseSource = new ol.pointer.MouseSource(this);
+ this.registerSource('mouse', mouseSource);
+
+ if (ol.has.TOUCH) {
+ this.registerSource('touch',
+ new ol.pointer.TouchSource(this, mouseSource));
+ }
+ }
+
+ // register events on the viewport element
+ this.register_();
+};
+
+
+/**
+ * Add a new event source that will generate pointer events.
+ *
+ * @param {string} name A name for the event source
+ * @param {ol.pointer.EventSource} source
+ */
+ol.pointer.PointerEventHandler.prototype.registerSource =
+ function(name, source) {
+ var s = source;
+ var newEvents = s.getEvents();
+
+ if (newEvents) {
+ newEvents.forEach(function(e) {
+ var handler = s.getHandlerForEvent(e);
+
+ if (handler) {
+ this.eventMap_[e] = goog.bind(handler, s);
+ }
+ }, this);
+ this.eventSourceList_.push(s);
+ }
+};
+
+
+/**
+ * Set up the events for all registered event sources.
+ * @private
+ */
+ol.pointer.PointerEventHandler.prototype.register_ = function() {
+ var l = this.eventSourceList_.length;
+ var eventSource;
+ for (var i = 0; i < l; i++) {
+ eventSource = this.eventSourceList_[i];
+ this.addEvents_(eventSource.getEvents());
+ }
+};
+
+
+/**
+ * Remove all registered events.
+ * @private
+ */
+ol.pointer.PointerEventHandler.prototype.unregister_ = function() {
+ var l = this.eventSourceList_.length;
+ var eventSource;
+ for (var i = 0; i < l; i++) {
+ eventSource = this.eventSourceList_[i];
+ this.removeEvents_(eventSource.getEvents());
+ }
+};
+
+
+/**
+ * Calls the right handler for a new event.
+ * @private
+ * @param {goog.events.BrowserEvent} inEvent Browser event.
+ */
+ol.pointer.PointerEventHandler.prototype.eventHandler_ = function(inEvent) {
+ var type = inEvent.type;
+ var handler = this.eventMap_[type];
+ if (handler) {
+ handler(inEvent);
+ }
+};
+
+
+/**
+ * Setup listeners for the given events.
+ * @private
+ * @param {Array.<string>} events List of events.
+ */
+ol.pointer.PointerEventHandler.prototype.addEvents_ = function(events) {
+ events.forEach(function(eventName) {
+ goog.events.listen(this.element_, eventName,
+ this.eventHandler_, false, this);
+ }, this);
+};
+
+
+/**
+ * Unregister listeners for the given events.
+ * @private
+ * @param {Array.<string>} events List of events.
+ */
+ol.pointer.PointerEventHandler.prototype.removeEvents_ = function(events) {
+ events.forEach(function(e) {
+ goog.events.unlisten(this.element_, e,
+ this.eventHandler_, false, this);
+ }, this);
+};
+
+
+/**
+ * Returns a snapshot of inEvent, with writable properties.
+ *
+ * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @param {Event|Touch} inEvent An event that contains
+ * properties to copy.
+ * @return {Object} An object containing shallow copies of
+ * `inEvent`'s properties.
+ */
+ol.pointer.PointerEventHandler.prototype.cloneEvent =
+ function(browserEvent, inEvent) {
+ var eventCopy = {}, p;
+ for (var i = 0, ii = ol.pointer.CLONE_PROPS.length; i < ii; i++) {
+ p = ol.pointer.CLONE_PROPS[i][0];
+ eventCopy[p] =
+ browserEvent[p] ||
+ inEvent[p] ||
+ ol.pointer.CLONE_PROPS[i][1];
+ }
+
+ return eventCopy;
+};
+
+
+// EVENTS
+
+
+/**
+ * Triggers a 'pointerdown' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent} browserEvent
+ */
+ol.pointer.PointerEventHandler.prototype.down =
+ function(pointerEventData, browserEvent) {
+ this.fireEvent(ol.pointer.EventType.POINTERDOWN,
+ pointerEventData, browserEvent);
+};
+
+
+/**
+ * Triggers a 'pointermove' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent} browserEvent
+ */
+ol.pointer.PointerEventHandler.prototype.move =
+ function(pointerEventData, browserEvent) {
+ this.fireEvent(ol.pointer.EventType.POINTERMOVE,
+ pointerEventData, browserEvent);
+};
+
+
+/**
+ * Triggers a 'pointerup' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent} browserEvent
+ */
+ol.pointer.PointerEventHandler.prototype.up =
+ function(pointerEventData, browserEvent) {
+ this.fireEvent(ol.pointer.EventType.POINTERUP,
+ pointerEventData, browserEvent);
+};
+
+
+/**
+ * Triggers a 'pointerenter' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent} browserEvent
+ */
+ol.pointer.PointerEventHandler.prototype.enter =
+ function(pointerEventData, browserEvent) {
+ pointerEventData.bubbles = false;
+ this.fireEvent(ol.pointer.EventType.POINTERENTER,
+ pointerEventData, browserEvent);
+};
+
+
+/**
+ * Triggers a 'pointerleave' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent} browserEvent
+ */
+ol.pointer.PointerEventHandler.prototype.leave =
+ function(pointerEventData, browserEvent) {
+ pointerEventData.bubbles = false;
+ this.fireEvent(ol.pointer.EventType.POINTERLEAVE,
+ pointerEventData, browserEvent);
+};
+
+
+/**
+ * Triggers a 'pointerover' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent} browserEvent
+ */
+ol.pointer.PointerEventHandler.prototype.over =
+ function(pointerEventData, browserEvent) {
+ pointerEventData.bubbles = true;
+ this.fireEvent(ol.pointer.EventType.POINTEROVER,
+ pointerEventData, browserEvent);
+};
+
+
+/**
+ * Triggers a 'pointerout' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent} browserEvent
+ */
+ol.pointer.PointerEventHandler.prototype.out =
+ function(pointerEventData, browserEvent) {
+ pointerEventData.bubbles = true;
+ this.fireEvent(ol.pointer.EventType.POINTEROUT,
+ pointerEventData, browserEvent);
+};
+
+
+/**
+ * Triggers a 'pointercancel' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent} browserEvent
+ */
+ol.pointer.PointerEventHandler.prototype.cancel =
+ function(pointerEventData, browserEvent) {
+ this.fireEvent(ol.pointer.EventType.POINTERCANCEL,
+ pointerEventData, browserEvent);
+};
+
+
+/**
+ * Triggers a combination of 'pointerout' and 'pointerleave' events.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent} browserEvent
+ */
+ol.pointer.PointerEventHandler.prototype.leaveOut =
+ function(pointerEventData, browserEvent) {
+ this.out(pointerEventData, browserEvent);
+ if (!this.contains_(
+ pointerEventData.target,
+ pointerEventData.relatedTarget)) {
+ this.leave(pointerEventData, browserEvent);
+ }
+};
+
+
+/**
+ * Triggers a combination of 'pointerover' and 'pointerevents' events.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent} browserEvent
+ */
+ol.pointer.PointerEventHandler.prototype.enterOver =
+ function(pointerEventData, browserEvent) {
+ this.over(pointerEventData, browserEvent);
+ if (!this.contains_(
+ pointerEventData.target,
+ pointerEventData.relatedTarget)) {
+ this.enter(pointerEventData, browserEvent);
+ }
+};
+
+
+/**
+ * @private
+ * @param {Element} container
+ * @param {Element} contained
+ * @return {boolean} Returns true if the container element
+ * contains the other element.
+ */
+ol.pointer.PointerEventHandler.prototype.contains_ =
+ function(container, contained) {
+ if (!contained) {
+ return false;
+ }
+ return goog.dom.contains(container, contained);
+};
+
+
+// EVENT CREATION AND TRACKING
+/**
+ * Creates a new Event of type `inType`, based on the information in
+ * `pointerEventData`.
+ *
+ * @param {string} inType A string representing the type of event to create.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @return {ol.pointer.PointerEvent} A PointerEvent of type `inType`.
+ */
+ol.pointer.PointerEventHandler.prototype.makeEvent =
+ function(inType, pointerEventData, browserEvent) {
+ return new ol.pointer.PointerEvent(inType, browserEvent, pointerEventData);
+};
+
+
+/**
+ * Make and dispatch an event in one call.
+ * @param {string} inType A string representing the type of event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent} browserEvent
+ */
+ol.pointer.PointerEventHandler.prototype.fireEvent =
+ function(inType, pointerEventData, browserEvent) {
+ var e = this.makeEvent(inType, pointerEventData, browserEvent);
+ this.dispatchEvent(e);
+};
+
+
+/**
+ * Creates a pointer event from a native pointer event
+ * and dispatches this event.
+ * @param {goog.events.BrowserEvent} nativeEvent A platform event with a target.
+ */
+ol.pointer.PointerEventHandler.prototype.fireNativeEvent =
+ function(nativeEvent) {
+ var e = this.makeEvent(nativeEvent.type, nativeEvent.getBrowserEvent(),
+ nativeEvent);
+ this.dispatchEvent(e);
+};
+
+
+/**
+ * Wrap a native mouse event into a pointer event.
+ * This proxy method is required for the legacy IE support.
+ * @param {string} eventType The pointer event type.
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @return {ol.pointer.PointerEvent}
+ */
+ol.pointer.PointerEventHandler.prototype.wrapMouseEvent =
+ function(eventType, browserEvent) {
+ var pointerEvent = this.makeEvent(
+ eventType,
+ ol.pointer.MouseSource.prepareEvent(browserEvent, this),
+ browserEvent
+ );
+ return pointerEvent;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.pointer.PointerEventHandler.prototype.disposeInternal = function() {
+ this.unregister_();
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * Constants for event names.
+ * @enum {string}
+ */
+ol.pointer.EventType = {
+ POINTERMOVE: 'pointermove',
+ POINTERDOWN: 'pointerdown',
+ POINTERUP: 'pointerup',
+ POINTEROVER: 'pointerover',
+ POINTEROUT: 'pointerout',
+ POINTERENTER: 'pointerenter',
+ POINTERLEAVE: 'pointerleave',
+ POINTERCANCEL: 'pointercancel'
+};
+
+
+/**
+ * Properties to copy when cloning an event, with default values.
+ * @type {Array.<Array>}
+ */
+ol.pointer.CLONE_PROPS = [
+ // MouseEvent
+ ['bubbles', false],
+ ['cancelable', false],
+ ['view', null],
+ ['detail', null],
+ ['screenX', 0],
+ ['screenY', 0],
+ ['clientX', 0],
+ ['clientY', 0],
+ ['ctrlKey', false],
+ ['altKey', false],
+ ['shiftKey', false],
+ ['metaKey', false],
+ ['button', 0],
+ ['relatedTarget', null],
+ // DOM Level 3
+ ['buttons', 0],
+ // PointerEvent
+ ['pointerId', 0],
+ ['width', 0],
+ ['height', 0],
+ ['pressure', 0],
+ ['tiltX', 0],
+ ['tiltY', 0],
+ ['pointerType', ''],
+ ['hwTimestamp', 0],
+ ['isPrimary', false],
+ // event instance
+ ['type', ''],
+ ['target', null],
+ ['currentTarget', null],
+ ['which', 0]
+];
+
+goog.provide('ol.MapBrowserEvent');
+goog.provide('ol.MapBrowserEvent.EventType');
+goog.provide('ol.MapBrowserEventHandler');
+goog.provide('ol.MapBrowserPointerEvent');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('ol');
+goog.require('ol.Coordinate');
+goog.require('ol.MapEvent');
+goog.require('ol.Pixel');
+goog.require('ol.pointer.PointerEvent');
+goog.require('ol.pointer.PointerEventHandler');
+
+
+
+/**
+ * @classdesc
+ * Events emitted as map browser events are instances of this type.
+ * See {@link ol.Map} for which events trigger a map browser event.
+ *
+ * @constructor
+ * @extends {ol.MapEvent}
+ * @implements {oli.MapBrowserEvent}
+ * @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_dragging,
+ opt_frameState) {
+
+ goog.base(this, type, map, opt_frameState);
+
+ /**
+ * @const
+ * @type {goog.events.BrowserEvent}
+ */
+ this.browserEvent = browserEvent;
+
+ /**
+ * The original browser event.
+ * @const
+ * @type {Event}
+ * @api stable
+ */
+ 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 = opt_dragging !== undefined ? opt_dragging : false;
+
+};
+goog.inherits(ol.MapBrowserEvent, ol.MapEvent);
+
+
+/**
+ * Prevents the default browser action.
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault
+ * @override
+ * @api stable
+ */
+ol.MapBrowserEvent.prototype.preventDefault = function() {
+ goog.base(this, 'preventDefault');
+ this.browserEvent.preventDefault();
+};
+
+
+/**
+ * Prevents further propagation of the current event.
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation
+ * @override
+ * @api stable
+ */
+ol.MapBrowserEvent.prototype.stopPropagation = function() {
+ goog.base(this, 'stopPropagation');
+ this.browserEvent.stopPropagation();
+};
+
+
+
+/**
+ * @constructor
+ * @extends {ol.MapBrowserEvent}
+ * @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_dragging,
+ opt_frameState) {
+
+ goog.base(this, type, map, pointerEvent.browserEvent, opt_dragging,
+ opt_frameState);
+
+ /**
+ * @const
+ * @type {ol.pointer.PointerEvent}
+ */
+ this.pointerEvent = pointerEvent;
+
+};
+goog.inherits(ol.MapBrowserPointerEvent, ol.MapBrowserEvent);
+
+
+
+/**
+ * @param {ol.Map} map The map with the viewport to listen to events on.
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+ol.MapBrowserEventHandler = function(map) {
+
+ goog.base(this);
+
+ /**
+ * This is the element that we will listen to the real events on.
+ * @type {ol.Map}
+ * @private
+ */
+ this.map_ = map;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.clickTimeoutId_ = 0;
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.dragging_ = false;
+
+ /**
+ * @type {Array.<goog.events.Key>}
+ * @private
+ */
+ this.dragListenerKeys_ = null;
+
+ /**
+ * @type {goog.events.Key}
+ * @private
+ */
+ this.pointerdownListenerKey_ = null;
+
+ /**
+ * The most recent "down" type event (or null if none have occurred).
+ * Set on pointerdown.
+ * @type {ol.pointer.PointerEvent}
+ * @private
+ */
+ this.down_ = null;
+
+ var element = this.map_.getViewport();
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.activePointers_ = 0;
+
+ /**
+ * @type {Object.<number, boolean>}
+ * @private
+ */
+ this.trackedTouches_ = {};
+
+ /**
+ * Event handler which generates pointer events for
+ * the viewport element.
+ *
+ * @type {ol.pointer.PointerEventHandler}
+ * @private
+ */
+ this.pointerEventHandler_ = new ol.pointer.PointerEventHandler(element);
+
+ /**
+ * Event handler which generates pointer events for
+ * the document (used when dragging).
+ *
+ * @type {ol.pointer.PointerEventHandler}
+ * @private
+ */
+ this.documentPointerEventHandler_ = null;
+
+ this.pointerdownListenerKey_ = goog.events.listen(this.pointerEventHandler_,
+ ol.pointer.EventType.POINTERDOWN,
+ this.handlePointerDown_, false, this);
+
+ this.relayedListenerKey_ = goog.events.listen(this.pointerEventHandler_,
+ ol.pointer.EventType.POINTERMOVE,
+ this.relayEvent_, false, this);
+
+};
+goog.inherits(ol.MapBrowserEventHandler, goog.events.EventTarget);
+
+
+/**
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.emulateClick_ = function(pointerEvent) {
+ var newEvent;
+ newEvent = new ol.MapBrowserPointerEvent(
+ ol.MapBrowserEvent.EventType.CLICK, this.map_, pointerEvent);
+ this.dispatchEvent(newEvent);
+ if (this.clickTimeoutId_ !== 0) {
+ // double-click
+ goog.global.clearTimeout(this.clickTimeoutId_);
+ this.clickTimeoutId_ = 0;
+ newEvent = new ol.MapBrowserPointerEvent(
+ ol.MapBrowserEvent.EventType.DBLCLICK, this.map_, pointerEvent);
+ this.dispatchEvent(newEvent);
+ } else {
+ // click
+ this.clickTimeoutId_ = goog.global.setTimeout(goog.bind(function() {
+ this.clickTimeoutId_ = 0;
+ var newEvent = new ol.MapBrowserPointerEvent(
+ ol.MapBrowserEvent.EventType.SINGLECLICK, this.map_, pointerEvent);
+ this.dispatchEvent(newEvent);
+ }, this), 250);
+ }
+};
+
+
+/**
+ * Keeps track on how many pointers are currently active.
+ *
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.updateActivePointers_ =
+ function(pointerEvent) {
+ var event = pointerEvent;
+
+ if (event.type == ol.MapBrowserEvent.EventType.POINTERUP ||
+ event.type == ol.MapBrowserEvent.EventType.POINTERCANCEL) {
+ delete this.trackedTouches_[event.pointerId];
+ } else if (event.type == ol.MapBrowserEvent.EventType.POINTERDOWN) {
+ this.trackedTouches_[event.pointerId] = true;
+ }
+ this.activePointers_ = goog.object.getCount(this.trackedTouches_);
+};
+
+
+/**
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) {
+ this.updateActivePointers_(pointerEvent);
+ var newEvent = new ol.MapBrowserPointerEvent(
+ ol.MapBrowserEvent.EventType.POINTERUP, this.map_, pointerEvent);
+ this.dispatchEvent(newEvent);
+
+ // 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(this.down_, 'this.down_ must be truthy');
+ this.emulateClick_(this.down_);
+ }
+
+ goog.asserts.assert(this.activePointers_ >= 0,
+ 'this.activePointers_ should be equal to or larger than 0');
+ if (this.activePointers_ === 0) {
+ this.dragListenerKeys_.forEach(goog.events.unlistenByKey);
+ this.dragListenerKeys_ = null;
+ this.dragging_ = false;
+ this.down_ = null;
+ goog.dispose(this.documentPointerEventHandler_);
+ this.documentPointerEventHandler_ = null;
+ }
+};
+
+
+/**
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @return {boolean} If the left mouse button was pressed.
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.isMouseActionButton_ =
+ function(pointerEvent) {
+ return pointerEvent.button === 0;
+};
+
+
+/**
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.handlePointerDown_ =
+ function(pointerEvent) {
+ this.updateActivePointers_(pointerEvent);
+ var newEvent = new ol.MapBrowserPointerEvent(
+ ol.MapBrowserEvent.EventType.POINTERDOWN, this.map_, pointerEvent);
+ this.dispatchEvent(newEvent);
+
+ this.down_ = pointerEvent;
+
+ if (!this.dragListenerKeys_) {
+ /* Set up a pointer event handler on the `document`,
+ * which is required when the pointer is moved outside
+ * the viewport when dragging.
+ */
+ this.documentPointerEventHandler_ =
+ new ol.pointer.PointerEventHandler(document);
+
+ this.dragListenerKeys_ = [
+ goog.events.listen(this.documentPointerEventHandler_,
+ ol.MapBrowserEvent.EventType.POINTERMOVE,
+ this.handlePointerMove_, false, this),
+ goog.events.listen(this.documentPointerEventHandler_,
+ ol.MapBrowserEvent.EventType.POINTERUP,
+ this.handlePointerUp_, false, this),
+ /* Note that the listener for `pointercancel is set up on
+ * `pointerEventHandler_` and not `documentPointerEventHandler_` like
+ * the `pointerup` and `pointermove` listeners.
+ *
+ * The reason for this is the following: `TouchSource.vacuumTouches_()`
+ * issues `pointercancel` events, when there was no `touchend` for a
+ * `touchstart`. Now, let's say a first `touchstart` is registered on
+ * `pointerEventHandler_`. The `documentPointerEventHandler_` is set up.
+ * But `documentPointerEventHandler_` doesn't know about the first
+ * `touchstart`. If there is no `touchend` for the `touchstart`, we can
+ * only receive a `touchcancel` from `pointerEventHandler_`, because it is
+ * only registered there.
+ */
+ goog.events.listen(this.pointerEventHandler_,
+ ol.MapBrowserEvent.EventType.POINTERCANCEL,
+ this.handlePointerUp_, false, this)
+ ];
+ }
+};
+
+
+/**
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.handlePointerMove_ =
+ function(pointerEvent) {
+ // Fix IE10 on windows Surface : When you tap the tablet, it triggers
+ // multiple pointermove events between pointerdown and pointerup with
+ // 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 (this.isMoving_(pointerEvent)) {
+ this.dragging_ = true;
+ var newEvent = new ol.MapBrowserPointerEvent(
+ ol.MapBrowserEvent.EventType.POINTERDRAG, this.map_, pointerEvent,
+ this.dragging_);
+ this.dispatchEvent(newEvent);
+ }
+
+ // Some native android browser triggers mousemove events during small period
+ // of time. See: https://code.google.com/p/android/issues/detail?id=5491 or
+ // https://code.google.com/p/android/issues/detail?id=19827
+ // ex: Galaxy Tab P3110 + Android 4.1.1
+ pointerEvent.preventDefault();
+};
+
+
+/**
+ * Wrap and relay a pointer event. Note that this requires that the type
+ * string for the MapBrowserPointerEvent matches the PointerEvent type.
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) {
+ var dragging = !!(this.down_ && this.isMoving_(pointerEvent));
+ this.dispatchEvent(new ol.MapBrowserPointerEvent(
+ 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;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.MapBrowserEventHandler.prototype.disposeInternal = function() {
+ if (this.relayedListenerKey_) {
+ goog.events.unlistenByKey(this.relayedListenerKey_);
+ this.relayedListenerKey_ = null;
+ }
+ if (this.pointerdownListenerKey_) {
+ goog.events.unlistenByKey(this.pointerdownListenerKey_);
+ this.pointerdownListenerKey_ = null;
+ }
+ if (this.dragListenerKeys_) {
+ this.dragListenerKeys_.forEach(goog.events.unlistenByKey);
+ this.dragListenerKeys_ = null;
+ }
+ if (this.documentPointerEventHandler_) {
+ goog.dispose(this.documentPointerEventHandler_);
+ this.documentPointerEventHandler_ = null;
+ }
+ if (this.pointerEventHandler_) {
+ goog.dispose(this.pointerEventHandler_);
+ this.pointerEventHandler_ = null;
+ }
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * Constants for event names.
+ * @enum {string}
+ */
+ol.MapBrowserEvent.EventType = {
+
+ /**
+ * 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 stable
+ */
+ SINGLECLICK: 'singleclick',
+
+ /**
+ * A click with no dragging. A double click will fire two of this.
+ * @event ol.MapBrowserEvent#click
+ * @api stable
+ */
+ CLICK: goog.events.EventType.CLICK,
+
+ /**
+ * A true double click, with no dragging.
+ * @event ol.MapBrowserEvent#dblclick
+ * @api stable
+ */
+ DBLCLICK: goog.events.EventType.DBLCLICK,
+
+ /**
+ * Triggered when a pointer is dragged.
+ * @event ol.MapBrowserEvent#pointerdrag
+ * @api
+ */
+ POINTERDRAG: 'pointerdrag',
+
+ /**
+ * 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 stable
+ */
+ POINTERMOVE: 'pointermove',
+
+ POINTERDOWN: 'pointerdown',
+ POINTERUP: 'pointerup',
+ POINTEROVER: 'pointerover',
+ POINTEROUT: 'pointerout',
+ POINTERENTER: 'pointerenter',
+ POINTERLEAVE: 'pointerleave',
+ POINTERCANCEL: 'pointercancel'
+};
+
+goog.provide('ol.layer.Base');
+goog.provide('ol.layer.LayerProperty');
+goog.provide('ol.layer.LayerState');
+
+goog.require('goog.object');
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.math');
+goog.require('ol.source.State');
+
+
+/**
+ * @enum {string}
+ */
+ol.layer.LayerProperty = {
+ OPACITY: 'opacity',
+ VISIBLE: 'visible',
+ EXTENT: 'extent',
+ Z_INDEX: 'zIndex',
+ MAX_RESOLUTION: 'maxResolution',
+ MIN_RESOLUTION: 'minResolution',
+ SOURCE: 'source'
+};
+
+
+/**
+ * @typedef {{layer: ol.layer.Layer,
+ * opacity: number,
+ * sourceState: ol.source.State,
+ * visible: boolean,
+ * managed: boolean,
+ * extent: (ol.Extent|undefined),
+ * zIndex: number,
+ * maxResolution: number,
+ * minResolution: number}}
+ */
+ol.layer.LayerState;
+
+
+
+/**
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Note that with `ol.layer.Base` and all its subclasses, any property set in
+ * the options is set as a {@link ol.Object} property on the layer object, so
+ * is observable, and has get/set accessors.
+ *
+ * @constructor
+ * @extends {ol.Object}
+ * @param {olx.layer.BaseOptions} options Layer options.
+ * @api stable
+ */
+ol.layer.Base = function(options) {
+
+ goog.base(this);
+
+ /**
+ * @type {Object.<string, *>}
+ */
+ var properties = goog.object.clone(options);
+ properties[ol.layer.LayerProperty.OPACITY] =
+ options.opacity !== undefined ? options.opacity : 1;
+ properties[ol.layer.LayerProperty.VISIBLE] =
+ options.visible !== undefined ? options.visible : true;
+ properties[ol.layer.LayerProperty.Z_INDEX] =
+ options.zIndex !== undefined ? options.zIndex : 0;
+ properties[ol.layer.LayerProperty.MAX_RESOLUTION] =
+ options.maxResolution !== undefined ? options.maxResolution : Infinity;
+ properties[ol.layer.LayerProperty.MIN_RESOLUTION] =
+ options.minResolution !== undefined ? options.minResolution : 0;
+
+ this.setProperties(properties);
+};
+goog.inherits(ol.layer.Base, ol.Object);
+
+
+/**
+ * @return {ol.layer.LayerState} Layer state.
+ */
+ol.layer.Base.prototype.getLayerState = function() {
+ var opacity = this.getOpacity();
+ var sourceState = this.getSourceState();
+ var visible = this.getVisible();
+ var extent = this.getExtent();
+ var zIndex = this.getZIndex();
+ var maxResolution = this.getMaxResolution();
+ var minResolution = this.getMinResolution();
+ return {
+ layer: /** @type {ol.layer.Layer} */ (this),
+ opacity: ol.math.clamp(opacity, 0, 1),
+ sourceState: sourceState,
+ visible: visible,
+ managed: true,
+ extent: extent,
+ zIndex: zIndex,
+ maxResolution: maxResolution,
+ minResolution: Math.max(minResolution, 0)
+ };
+};
+
+
+/**
+ * @param {Array.<ol.layer.Layer>=} opt_array Array of layers (to be
+ * modified in place).
+ * @return {Array.<ol.layer.Layer>} Array of layers.
+ */
+ol.layer.Base.prototype.getLayersArray = goog.abstractMethod;
+
+
+/**
+ * @param {Array.<ol.layer.LayerState>=} opt_states Optional list of layer
+ * states (to be modified in place).
+ * @return {Array.<ol.layer.LayerState>} List of layer states.
+ */
+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
+ */
+ol.layer.Base.prototype.getExtent = function() {
+ return /** @type {ol.Extent|undefined} */ (
+ this.get(ol.layer.LayerProperty.EXTENT));
+};
+
+
+/**
+ * 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} */ (
+ this.get(ol.layer.LayerProperty.MAX_RESOLUTION));
+};
+
+
+/**
+ * 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} */ (
+ this.get(ol.layer.LayerProperty.MIN_RESOLUTION));
+};
+
+
+/**
+ * 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} */ (this.get(ol.layer.LayerProperty.OPACITY));
+};
+
+
+/**
+ * @return {ol.source.State} Source state.
+ */
+ol.layer.Base.prototype.getSourceState = goog.abstractMethod;
+
+
+/**
+ * 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} */ (this.get(ol.layer.LayerProperty.VISIBLE));
+};
+
+
+/**
+ * Return the Z-index of the layer, which is used to order layers before
+ * rendering. The default Z-index is 0.
+ * @return {number} The Z-index of the layer.
+ * @observable
+ * @api
+ */
+ol.layer.Base.prototype.getZIndex = function() {
+ return /** @type {number} */ (this.get(ol.layer.LayerProperty.Z_INDEX));
+};
+
+
+/**
+ * Set the extent at which the layer is visible. If `undefined`, the layer
+ * will be visible at all extents.
+ * @param {ol.Extent|undefined} extent The extent of the layer.
+ * @observable
+ * @api stable
+ */
+ol.layer.Base.prototype.setExtent = function(extent) {
+ this.set(ol.layer.LayerProperty.EXTENT, extent);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * Set Z-index of the layer, which is used to order layers before rendering.
+ * The default Z-index is 0.
+ * @param {number} zindex The z-index of the layer.
+ * @observable
+ * @api
+ */
+ol.layer.Base.prototype.setZIndex = function(zindex) {
+ this.set(ol.layer.LayerProperty.Z_INDEX, zindex);
+};
+
+goog.provide('ol.render.VectorContext');
+
+
+
+/**
+ * Context for drawing geometries. A vector context is available on render
+ * events and does not need to be constructed directly.
+ * @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|ol.render.Feature} lineStringGeometry Line
+ * string geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.VectorContext.prototype.drawLineStringGeometry =
+ goog.abstractMethod;
+
+
+/**
+ * @param {ol.geom.MultiLineString|ol.render.Feature} multiLineStringGeometry
+ * MultiLineString geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.VectorContext.prototype.drawMultiLineStringGeometry =
+ goog.abstractMethod;
+
+
+/**
+ * @param {ol.geom.MultiPoint|ol.render.Feature} multiPointGeometry MultiPoint
+ * geometry.
+ * @param {ol.Feature|ol.render.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|ol.render.Feature} pointGeometry Point geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.VectorContext.prototype.drawPointGeometry = goog.abstractMethod;
+
+
+/**
+ * @param {ol.geom.Polygon|ol.render.Feature} polygonGeometry Polygon
+ * geometry.
+ * @param {ol.Feature|ol.render.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|ol.render.Feature} geometry Geometry.
+ * @param {ol.Feature|ol.render.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;
+
+ /**
+ * An object representing the current render frame state.
+ * @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.events');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.layer.Base');
+goog.require('ol.layer.LayerProperty');
+goog.require('ol.render.EventType');
+goog.require('ol.source.State');
+
+
+
+/**
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * A visual representation of raster or vector map data.
+ * Layers group together those properties that pertain to how the data is to be
+ * displayed, irrespective of the source of that data.
+ *
+ * Layers are usually added to a map with {@link ol.Map#addLayer}. Components
+ * like {@link ol.interaction.Select} use unmanaged layers internally. These
+ * unmanaged layers are associated with the map using
+ * {@link ol.layer.Layer#setMap} instead.
+ *
+ * A generic `change` event is fired when the state of the source changes.
+ *
+ * @constructor
+ * @extends {ol.layer.Base}
+ * @fires ol.render.Event
+ * @param {olx.layer.LayerOptions} options Layer options.
+ * @api stable
+ */
+ol.layer.Layer = function(options) {
+
+ var baseOptions = goog.object.clone(options);
+ delete baseOptions.source;
+
+ 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 (options.map) {
+ this.setMap(options.map);
+ }
+
+ goog.events.listen(this,
+ ol.Object.getChangeEventType(ol.layer.LayerProperty.SOURCE),
+ this.handleSourcePropertyChange_, false, this);
+
+ var source = options.source ? options.source : null;
+ this.setSource(source);
+};
+goog.inherits(ol.layer.Layer, ol.layer.Base);
+
+
+/**
+ * Return `true` if the layer is visible, and if the passed resolution is
+ * between the layer's minResolution and maxResolution. The comparison is
+ * inclusive for `minResolution` and exclusive for `maxResolution`.
+ * @param {ol.layer.LayerState} layerState Layer state.
+ * @param {number} resolution Resolution.
+ * @return {boolean} The layer is visible at the given resolution.
+ */
+ol.layer.Layer.visibleAtResolution = function(layerState, resolution) {
+ return layerState.visible && resolution >= layerState.minResolution &&
+ resolution < layerState.maxResolution;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.layer.Layer.prototype.getLayersArray = function(opt_array) {
+ var array = opt_array ? opt_array : [];
+ array.push(this);
+ return array;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.layer.Layer.prototype.getLayerStatesArray = function(opt_states) {
+ var states = opt_states ? opt_states : [];
+ states.push(this.getLayerState());
+ return states;
+};
+
+
+/**
+ * Get the layer source.
+ * @return {ol.source.Source} The layer source (or `null` if not yet set).
+ * @observable
+ * @api stable
+ */
+ol.layer.Layer.prototype.getSource = function() {
+ var source = this.get(ol.layer.LayerProperty.SOURCE);
+ return /** @type {ol.source.Source} */ (source) || null;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.layer.Layer.prototype.getSourceState = function() {
+ var source = this.getSource();
+ return !source ? ol.source.State.UNDEFINED : source.getState();
+};
+
+
+/**
+ * @private
+ */
+ol.layer.Layer.prototype.handleSourceChange_ = function() {
+ this.changed();
+};
+
+
+/**
+ * @private
+ */
+ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() {
+ if (this.sourceChangeKey_) {
+ goog.events.unlistenByKey(this.sourceChangeKey_);
+ this.sourceChangeKey_ = null;
+ }
+ var source = this.getSource();
+ if (source) {
+ this.sourceChangeKey_ = goog.events.listen(source,
+ goog.events.EventType.CHANGE, this.handleSourceChange_, false, this);
+ }
+ this.changed();
+};
+
+
+/**
+ * Sets the layer to be rendered on top of other layers on a map. The map will
+ * not manage this layer in its layers collection, and the callback in
+ * {@link ol.Map#forEachLayerAtPixel} will receive `null` as layer. 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.mapPrecomposeKey_ = null;
+ if (!map) {
+ this.changed();
+ }
+ goog.events.unlistenByKey(this.mapRenderKey_);
+ this.mapRenderKey_ = null;
+ if (map) {
+ this.mapPrecomposeKey_ = goog.events.listen(
+ map, ol.render.EventType.PRECOMPOSE, function(evt) {
+ var layerState = this.getLayerState();
+ layerState.managed = false;
+ layerState.zIndex = Infinity;
+ 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);
+ this.changed();
+ }
+};
+
+
+/**
+ * Set the layer source.
+ * @param {ol.source.Source} source The layer source.
+ * @observable
+ * @api stable
+ */
+ol.layer.Layer.prototype.setSource = function(source) {
+ this.set(ol.layer.LayerProperty.SOURCE, source);
+};
+
+goog.provide('ol.ImageBase');
+goog.provide('ol.ImageState');
+
+goog.require('goog.asserts');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('ol.Attribution');
+goog.require('ol.Extent');
+
+
+/**
+ * @enum {number}
+ */
+ol.ImageState = {
+ IDLE: 0,
+ LOADING: 1,
+ LOADED: 2,
+ ERROR: 3
+};
+
+
+
+/**
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @param {ol.Extent} extent Extent.
+ * @param {number|undefined} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.ImageState} state State.
+ * @param {Array.<ol.Attribution>} attributions Attributions.
+ */
+ol.ImageBase = function(extent, resolution, pixelRatio, state, attributions) {
+
+ goog.base(this);
+
+ /**
+ * @private
+ * @type {Array.<ol.Attribution>}
+ */
+ this.attributions_ = attributions;
+
+ /**
+ * @protected
+ * @type {ol.Extent}
+ */
+ this.extent = extent;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.pixelRatio_ = pixelRatio;
+
+ /**
+ * @protected
+ * @type {number|undefined}
+ */
+ this.resolution = resolution;
+
+ /**
+ * @protected
+ * @type {ol.ImageState}
+ */
+ this.state = state;
+
+};
+goog.inherits(ol.ImageBase, goog.events.EventTarget);
+
+
+/**
+ * @protected
+ */
+ol.ImageBase.prototype.changed = function() {
+ this.dispatchEvent(goog.events.EventType.CHANGE);
+};
+
+
+/**
+ * @return {Array.<ol.Attribution>} Attributions.
+ */
+ol.ImageBase.prototype.getAttributions = function() {
+ return this.attributions_;
+};
+
+
+/**
+ * @return {ol.Extent} Extent.
+ */
+ol.ImageBase.prototype.getExtent = function() {
+ return this.extent;
+};
+
+
+/**
+ * @param {Object=} opt_context Object.
+ * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
+ */
+ol.ImageBase.prototype.getImage = goog.abstractMethod;
+
+
+/**
+ * @return {number} PixelRatio.
+ */
+ol.ImageBase.prototype.getPixelRatio = function() {
+ return this.pixelRatio_;
+};
+
+
+/**
+ * @return {number} Resolution.
+ */
+ol.ImageBase.prototype.getResolution = function() {
+ goog.asserts.assert(this.resolution !== undefined, 'resolution not yet set');
+ return this.resolution;
+};
+
+
+/**
+ * @return {ol.ImageState} State.
+ */
+ol.ImageBase.prototype.getState = function() {
+ return this.state;
+};
+
+
+/**
+ * Load not yet loaded URI.
+ */
+ol.ImageBase.prototype.load = goog.abstractMethod;
+
+goog.provide('ol.vec.Mat4');
+goog.provide('ol.vec.Mat4.Number');
+
+goog.require('goog.vec.Mat4');
+
+
+/**
+ * A alias for the goog.vec.Number type.
+ * @typedef {goog.vec.Number}
+ */
+ol.vec.Mat4.Number;
+
+
+/**
+ * @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.renderer.Layer');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.functions');
+goog.require('ol');
+goog.require('ol.ImageState');
+goog.require('ol.Observable');
+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');
+goog.require('ol.vec.Mat4');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.Observable}
+ * @param {ol.layer.Layer} layer Layer.
+ * @struct
+ */
+ol.renderer.Layer = function(layer) {
+
+ goog.base(this);
+
+ /**
+ * @private
+ * @type {ol.layer.Layer}
+ */
+ this.layer_ = layer;
+
+
+};
+goog.inherits(ol.renderer.Layer, ol.Observable);
+
+
+/**
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {function(this: S, (ol.Feature|ol.render.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 = ol.nullFunction;
+
+
+/**
+ * @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;
+ }
+};
+
+
+/**
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState Frame state.
+ * @return {boolean} Is there a feature at the given coordinate?
+ */
+ol.renderer.Layer.prototype.hasFeatureAtCoordinate = goog.functions.FALSE;
+
+
+/**
+ * Create a function that adds loaded tiles to the tile lookup.
+ * @param {ol.source.Tile} source Tile source.
+ * @param {ol.proj.Projection} projection Projection of the tiles.
+ * @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.renderer.Layer.prototype.createLoadedTileFinder =
+ function(source, projection, 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(projection, zoom,
+ tileRange, function(tile) {
+ if (!tiles[zoom]) {
+ tiles[zoom] = {};
+ }
+ tiles[zoom][tile.tileCoord.toString()] = tile;
+ });
+ });
+};
+
+
+/**
+ * @return {ol.layer.Layer} Layer.
+ */
+ol.renderer.Layer.prototype.getLayer = function() {
+ return this.layer_;
+};
+
+
+/**
+ * Handle changes in image state.
+ * @param {goog.events.Event} event Image change event.
+ * @private
+ */
+ol.renderer.Layer.prototype.handleImageChange_ = function(event) {
+ var image = /** @type {ol.Image} */ (event.target);
+ if (image.getState() === ol.ImageState.LOADED) {
+ this.renderIfReadyAndVisible();
+ }
+};
+
+
+/**
+ * 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.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 == ol.ImageState.LOADED,
+ 'imageState is "loading" or "loaded"');
+ }
+ return imageState == ol.ImageState.LOADED;
+};
+
+
+/**
+ * @protected
+ */
+ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() {
+ var layer = this.getLayer();
+ if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) {
+ this.changed();
+ }
+};
+
+
+/**
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.source.Tile} tileSource Tile source.
+ * @protected
+ */
+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.viewState.projection,
+ frameState.usedTiles[tileSourceKey]);
+ }, tileSource));
+ }
+};
+
+
+/**
+ * @param {Object.<string, ol.Attribution>} attributionsSet Attributions
+ * set (target).
+ * @param {Array.<ol.Attribution>} attributions Attributions (source).
+ * @protected
+ */
+ol.renderer.Layer.prototype.updateAttributions =
+ function(attributionsSet, attributions) {
+ if (attributions) {
+ var attribution, i, ii;
+ for (i = 0, ii = attributions.length; i < ii; ++i) {
+ attribution = attributions[i];
+ attributionsSet[goog.getUid(attribution).toString()] = attribution;
+ }
+ }
+};
+
+
+/**
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.source.Source} source Source.
+ * @protected
+ */
+ol.renderer.Layer.prototype.updateLogos = function(frameState, source) {
+ var logo = source.getLogo();
+ if (logo !== undefined) {
+ 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.
+ * @protected
+ */
+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.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {ol.Size} size Size.
+ * @protected
+ * @return {ol.Coordinate} Snapped center.
+ */
+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} 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.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 (opt_tileCallback !== undefined) {
+ opt_tileCallback.call(opt_this, tile);
+ }
+ } else {
+ tileSource.useTile(z, x, y, projection);
+ }
+ }
+ }
+ }
+};
+
+goog.provide('ol.style.Image');
+goog.provide('ol.style.ImageState');
+
+
+/**
+ * @enum {number}
+ */
+ol.style.ImageState = {
+ IDLE: 0,
+ LOADING: 1,
+ LOADED: 2,
+ ERROR: 3
+};
+
+
+/**
+ * @typedef {{opacity: number,
+ * rotateWithView: boolean,
+ * rotation: number,
+ * scale: number,
+ * snapToPixel: boolean}}
+ */
+ol.style.ImageOptions;
+
+
+
+/**
+ * @classdesc
+ * A base class used for creating subclasses and not instantiated in
+ * apps. Base class for {@link ol.style.Icon}, {@link ol.style.Circle} and
+ * {@link ol.style.RegularShape}.
+ *
+ * @constructor
+ * @param {ol.style.ImageOptions} options Options.
+ * @api
+ */
+ol.style.Image = function(options) {
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.opacity_ = options.opacity;
+
+ /**
+ * @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;
+
+};
+
+
+/**
+ * Get the symbolizer opacity.
+ * @return {number} Opacity.
+ * @api
+ */
+ol.style.Image.prototype.getOpacity = function() {
+ return this.opacity_;
+};
+
+
+/**
+ * Determine whether the symbolizer rotates with the map.
+ * @return {boolean} Rotate with map.
+ * @api
+ */
+ol.style.Image.prototype.getRotateWithView = function() {
+ return this.rotateWithView_;
+};
+
+
+/**
+ * Get the symoblizer rotation.
+ * @return {number} Rotation.
+ * @api
+ */
+ol.style.Image.prototype.getRotation = function() {
+ return this.rotation_;
+};
+
+
+/**
+ * Get the symbolizer scale.
+ * @return {number} Scale.
+ * @api
+ */
+ol.style.Image.prototype.getScale = function() {
+ return this.scale_;
+};
+
+
+/**
+ * Determine whether the symbolizer should be snapped to a pixel.
+ * @return {boolean} The symbolizer should snap to a pixel.
+ * @api
+ */
+ol.style.Image.prototype.getSnapToPixel = function() {
+ return this.snapToPixel_;
+};
+
+
+/**
+ * Get the anchor point. The anchor determines the center point for the
+ * symbolizer. Its units are determined by `anchorXUnits` and `anchorYUnits`.
+ * @function
+ * @return {Array.<number>} Anchor.
+ */
+ol.style.Image.prototype.getAnchor = goog.abstractMethod;
+
+
+/**
+ * Get the image element for the symbolizer.
+ * @function
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
+ */
+ol.style.Image.prototype.getImage = goog.abstractMethod;
+
+
+/**
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
+ */
+ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod;
+
+
+/**
+ * @return {ol.style.ImageState} Image state.
+ */
+ol.style.Image.prototype.getImageState = goog.abstractMethod;
+
+
+/**
+ * @return {ol.Size} Image size.
+ */
+ol.style.Image.prototype.getImageSize = goog.abstractMethod;
+
+
+/**
+ * @return {ol.Size} Size of the hit-detection image.
+ */
+ol.style.Image.prototype.getHitDetectionImageSize = goog.abstractMethod;
+
+
+/**
+ * Get the origin of the symbolizer.
+ * @function
+ * @return {Array.<number>} Origin.
+ */
+ol.style.Image.prototype.getOrigin = goog.abstractMethod;
+
+
+/**
+ * Get the size of the symbolizer (in pixels).
+ * @function
+ * @return {ol.Size} Size.
+ */
+ol.style.Image.prototype.getSize = goog.abstractMethod;
+
+
+/**
+ * Set the opacity.
+ *
+ * @param {number} opacity Opacity.
+ * @api
+ */
+ol.style.Image.prototype.setOpacity = function(opacity) {
+ this.opacity_ = opacity;
+};
+
+
+/**
+ * Set whether to rotate the style with the view.
+ *
+ * @param {boolean} rotateWithView Rotate with map.
+ */
+ol.style.Image.prototype.setRotateWithView = function(rotateWithView) {
+ this.rotateWithView_ = rotateWithView;
+};
+
+
+/**
+ * Set the rotation.
+ *
+ * @param {number} rotation Rotation.
+ * @api
+ */
+ol.style.Image.prototype.setRotation = function(rotation) {
+ this.rotation_ = rotation;
+};
+
+
+/**
+ * Set the scale.
+ *
+ * @param {number} scale Scale.
+ * @api
+ */
+ol.style.Image.prototype.setScale = function(scale) {
+ this.scale_ = scale;
+};
+
+
+/**
+ * Set whether to snap the image to the closest pixel.
+ *
+ * @param {boolean} snapToPixel Snap to pixel?
+ */
+ol.style.Image.prototype.setSnapToPixel = function(snapToPixel) {
+ this.snapToPixel_ = snapToPixel;
+};
+
+
+/**
+ * @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.style.Image.prototype.listenImageChange = goog.abstractMethod;
+
+
+/**
+ * Load not yet loaded URI.
+ */
+ol.style.Image.prototype.load = goog.abstractMethod;
+
+
+/**
+ * @param {function(this: T, goog.events.Event)} listener Listener function.
+ * @param {T} thisArg Value to use as `this` when executing `listener`.
+ * @template T
+ */
+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.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');
+
+
+/**
+ * Icon anchor units. One of 'fraction', 'pixels'.
+ * @enum {string}
+ * @api
+ */
+ol.style.IconAnchorUnits = {
+ FRACTION: 'fraction',
+ PIXELS: 'pixels'
+};
+
+
+/**
+ * Icon origin. One of 'bottom-left', 'bottom-right', 'top-left', 'top-right'.
+ * @enum {string}
+ * @api
+ */
+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
+ * @param {olx.style.IconOptions=} opt_options Options.
+ * @extends {ol.style.Image}
+ * @api
+ */
+ol.style.Icon = function(opt_options) {
+
+ var options = opt_options || {};
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.anchor_ = options.anchor !== undefined ? options.anchor : [0.5, 0.5];
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.normalizedAnchor_ = null;
+
+ /**
+ * @private
+ * @type {ol.style.IconOrigin}
+ */
+ this.anchorOrigin_ = options.anchorOrigin !== undefined ?
+ options.anchorOrigin : ol.style.IconOrigin.TOP_LEFT;
+
+ /**
+ * @private
+ * @type {ol.style.IconAnchorUnits}
+ */
+ this.anchorXUnits_ = options.anchorXUnits !== undefined ?
+ options.anchorXUnits : ol.style.IconAnchorUnits.FRACTION;
+
+ /**
+ * @private
+ * @type {ol.style.IconAnchorUnits}
+ */
+ this.anchorYUnits_ = options.anchorYUnits !== undefined ?
+ options.anchorYUnits : ol.style.IconAnchorUnits.FRACTION;
+
+ /**
+ * @type {?string}
+ */
+ var crossOrigin =
+ options.crossOrigin !== undefined ? options.crossOrigin : null;
+
+ /**
+ * @type {Image|HTMLCanvasElement}
+ */
+ var image = options.img !== undefined ? options.img : null;
+
+ /**
+ * @type {ol.Size}
+ */
+ var imgSize = options.imgSize !== undefined ? options.imgSize : null;
+
+ /**
+ * @type {string|undefined}
+ */
+ var src = options.src;
+
+ goog.asserts.assert(!(src !== undefined && image),
+ 'image and src can not provided at the same time');
+ goog.asserts.assert(
+ src === undefined || (src !== undefined && !imgSize),
+ 'imgSize should not be set when src is provided');
+ goog.asserts.assert(
+ !image || (image && imgSize),
+ 'imgSize must be set when image is provided');
+
+ if ((src === undefined || src.length === 0) && image) {
+ src = image.src || goog.getUid(image).toString();
+ }
+ goog.asserts.assert(src !== undefined && src.length > 0,
+ 'must provide a defined and non-empty src or image');
+
+ /**
+ * @type {ol.style.ImageState}
+ */
+ var imageState = options.src !== undefined ?
+ 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_ = options.offset !== undefined ? options.offset : [0, 0];
+
+ /**
+ * @private
+ * @type {ol.style.IconOrigin}
+ */
+ this.offsetOrigin_ = options.offsetOrigin !== undefined ?
+ options.offsetOrigin : ol.style.IconOrigin.TOP_LEFT;
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.origin_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.size_ = options.size !== undefined ? options.size : null;
+
+ /**
+ * @type {number}
+ */
+ var opacity = options.opacity !== undefined ? options.opacity : 1;
+
+ /**
+ * @type {boolean}
+ */
+ var rotateWithView = options.rotateWithView !== undefined ?
+ options.rotateWithView : false;
+
+ /**
+ * @type {number}
+ */
+ var rotation = options.rotation !== undefined ? options.rotation : 0;
+
+ /**
+ * @type {number}
+ */
+ var scale = options.scale !== undefined ? options.scale : 1;
+
+ /**
+ * @type {boolean}
+ */
+ var snapToPixel = options.snapToPixel !== undefined ?
+ options.snapToPixel : true;
+
+ goog.base(this, {
+ opacity: opacity,
+ rotation: rotation,
+ scale: scale,
+ snapToPixel: snapToPixel,
+ rotateWithView: rotateWithView
+ });
+
+};
+goog.inherits(ol.style.Icon, ol.style.Image);
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.Icon.prototype.getAnchor = function() {
+ if (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 (!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 (!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_;
+};
+
+
+/**
+ * Get the image icon.
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {Image|HTMLCanvasElement} Image or Canvas element.
+ * @api
+ */
+ol.style.Icon.prototype.getImage = function(pixelRatio) {
+ return this.iconImage_.getImage(pixelRatio);
+};
+
+
+/**
+ * Real Image size used.
+ * @return {ol.Size} Size.
+ */
+ol.style.Icon.prototype.getImageSize = function() {
+ return this.iconImage_.getSize();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Icon.prototype.getHitDetectionImageSize = function() {
+ return this.getImageSize();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Icon.prototype.getImageState = function() {
+ return this.iconImage_.getImageState();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) {
+ return this.iconImage_.getHitDetectionImage(pixelRatio);
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.Icon.prototype.getOrigin = function() {
+ if (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 (!size || !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_;
+};
+
+
+/**
+ * Get the image URL.
+ * @return {string|undefined} Image src.
+ * @api
+ */
+ol.style.Icon.prototype.getSrc = function() {
+ return this.iconImage_.getSrc();
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.Icon.prototype.getSize = function() {
+ return !this.size_ ? this.iconImage_.getSize() : this.size_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Icon.prototype.listenImageChange = function(listener, thisArg) {
+ return goog.events.listen(this.iconImage_, goog.events.EventType.CHANGE,
+ listener, false, thisArg);
+};
+
+
+/**
+ * Load not yet loaded URI.
+ * When rendering a feature with an icon style, the vector renderer will
+ * automatically call this method. However, you might want to call this
+ * method yourself for preloading or other purposes.
+ * @api
+ */
+ol.style.Icon.prototype.load = function() {
+ this.iconImage_.load();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) {
+ goog.events.unlisten(this.iconImage_, goog.events.EventType.CHANGE,
+ listener, false, thisArg);
+};
+
+
+
+/**
+ * @constructor
+ * @param {Image|HTMLCanvasElement} 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.IconImage_ = function(image, src, size, crossOrigin, imageState) {
+
+ goog.base(this);
+
+ /**
+ * @private
+ * @type {Image|HTMLCanvasElement}
+ */
+ this.hitDetectionImage_ = null;
+
+ /**
+ * @private
+ * @type {Image|HTMLCanvasElement}
+ */
+ this.image_ = !image ? new Image() : image;
+
+ if (crossOrigin !== null) {
+ this.image_.crossOrigin = crossOrigin;
+ }
+
+ /**
+ * @private
+ * @type {Array.<goog.events.Key>}
+ */
+ this.imageListenerKeys_ = null;
+
+ /**
+ * @private
+ * @type {ol.style.ImageState}
+ */
+ this.imageState_ = imageState;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.size_ = size;
+
+ /**
+ * @private
+ * @type {string|undefined}
+ */
+ this.src_ = src;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.tainting_ = false;
+ if (this.imageState_ == ol.style.ImageState.LOADED) {
+ this.determineTainting_();
+ }
+
+};
+goog.inherits(ol.style.IconImage_, goog.events.EventTarget);
+
+
+/**
+ * @param {Image|HTMLCanvasElement} 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.IconImage_.get = function(image, src, size, crossOrigin, imageState) {
+ var iconImageCache = ol.style.IconImageCache.getInstance();
+ var iconImage = iconImageCache.get(src, crossOrigin);
+ if (!iconImage) {
+ iconImage = new ol.style.IconImage_(
+ image, src, size, crossOrigin, imageState);
+ iconImageCache.set(src, crossOrigin, iconImage);
+ }
+ return iconImage;
+};
+
+
+/**
+ * @private
+ */
+ol.style.IconImage_.prototype.determineTainting_ = function() {
+ var context = ol.dom.createCanvasContext2D(1, 1);
+ try {
+ context.drawImage(this.image_, 0, 0);
+ context.getImageData(0, 0, 1, 1);
+ } catch (e) {
+ this.tainting_ = true;
+ }
+};
+
+
+/**
+ * @private
+ */
+ol.style.IconImage_.prototype.dispatchChangeEvent_ = function() {
+ this.dispatchEvent(goog.events.EventType.CHANGE);
+};
+
+
+/**
+ * @private
+ */
+ol.style.IconImage_.prototype.handleImageError_ = function() {
+ this.imageState_ = ol.style.ImageState.ERROR;
+ this.unlistenImage_();
+ this.dispatchChangeEvent_();
+};
+
+
+/**
+ * @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_();
+};
+
+
+/**
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {Image|HTMLCanvasElement} Image or Canvas element.
+ */
+ol.style.IconImage_.prototype.getImage = function(pixelRatio) {
+ return this.image_;
+};
+
+
+/**
+ * @return {ol.style.ImageState} Image state.
+ */
+ol.style.IconImage_.prototype.getImageState = function() {
+ return this.imageState_;
+};
+
+
+/**
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {Image|HTMLCanvasElement} Image element.
+ */
+ol.style.IconImage_.prototype.getHitDetectionImage = function(pixelRatio) {
+ if (!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} Image size.
+ */
+ol.style.IconImage_.prototype.getSize = function() {
+ return this.size_;
+};
+
+
+/**
+ * @return {string|undefined} Image src.
+ */
+ol.style.IconImage_.prototype.getSrc = function() {
+ return this.src_;
+};
+
+
+/**
+ * Load not yet loaded URI.
+ */
+ol.style.IconImage_.prototype.load = function() {
+ if (this.imageState_ == ol.style.ImageState.IDLE) {
+ goog.asserts.assert(this.src_ !== undefined,
+ 'this.src_ must not be undefined');
+ goog.asserts.assert(!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_();
+ }
+ }
+};
+
+
+/**
+ * Discards event handlers which listen for load completion or errors.
+ *
+ * @private
+ */
+ol.style.IconImage_.prototype.unlistenImage_ = function() {
+ goog.asserts.assert(this.imageListenerKeys_,
+ 'we must have listeners registered');
+ this.imageListenerKeys_.forEach(goog.events.unlistenByKey);
+ this.imageListenerKeys_ = null;
+};
+
+
+
+/**
+ * @constructor
+ */
+ol.style.IconImageCache = function() {
+
+ /**
+ * @type {Object.<string, ol.style.IconImage_>}
+ * @private
+ */
+ this.cache_ = {};
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.cacheSize_ = 0;
+
+ /**
+ * @const
+ * @type {number}
+ * @private
+ */
+ this.maxCacheSize_ = 32;
+};
+goog.addSingletonGetter(ol.style.IconImageCache);
+
+
+/**
+ * @param {string} src Src.
+ * @param {?string} crossOrigin Cross origin.
+ * @return {string} Cache key.
+ */
+ol.style.IconImageCache.getKey = function(src, crossOrigin) {
+ goog.asserts.assert(crossOrigin !== undefined,
+ 'argument crossOrigin must be defined');
+ return crossOrigin + ':' + src;
+};
+
+
+/**
+ * FIXME empty description for jsdoc
+ */
+ol.style.IconImageCache.prototype.clear = function() {
+ this.cache_ = {};
+ this.cacheSize_ = 0;
+};
+
+
+/**
+ * FIXME empty description for jsdoc
+ */
+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_;
+ }
+ }
+ }
+};
+
+
+/**
+ * @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.
+ */
+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.RendererType');
+goog.provide('ol.renderer.Map');
+
+goog.require('goog.Disposable');
+goog.require('goog.asserts');
+goog.require('goog.dispose');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.functions');
+goog.require('goog.object');
+goog.require('goog.vec.Mat4');
+goog.require('ol');
+goog.require('ol.extent');
+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.
+ * @struct
+ */
+ol.renderer.Map = function(container, map) {
+
+ goog.base(this);
+
+
+ /**
+ * @private
+ * @type {ol.Map}
+ */
+ this.map_ = map;
+
+ /**
+ * @private
+ * @type {Object.<string, ol.renderer.Layer>}
+ */
+ this.layerRenderers_ = {};
+
+ /**
+ * @private
+ * @type {Object.<string, goog.events.Key>}
+ */
+ this.layerRendererListeners_ = {};
+
+};
+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(coordinateToPixelMatrix,
+ 'frameState has a 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');
+};
+
+
+/**
+ * @param {ol.layer.Layer} layer Layer.
+ * @protected
+ * @return {ol.renderer.Layer} layerRenderer Layer renderer.
+ */
+ol.renderer.Map.prototype.createLayerRenderer = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.Map.prototype.disposeInternal = function() {
+ goog.object.forEach(this.layerRenderers_, goog.dispose);
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @param {ol.Map} map Map.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
+ */
+ol.renderer.Map.expireIconCache_ = function(map, frameState) {
+ ol.style.IconImageCache.getInstance().expire();
+};
+
+
+/**
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState FrameState.
+ * @param {function(this: S, (ol.Feature|ol.render.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 {Object.<string, boolean>} */
+ var features = {};
+
+ /**
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @return {?} Callback result.
+ */
+ function forEachFeatureAtCoordinate(feature) {
+ goog.asserts.assert(feature !== undefined, '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;
+
+ 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]];
+ }
+ }
+
+ 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);
+ if (layer.getSource()) {
+ result = layerRenderer.forEachFeatureAtCoordinate(
+ layer.getSource().getWrapX() ? translatedCoordinate : coordinate,
+ frameState,
+ layerState.managed ? callback : forEachFeatureAtCoordinate,
+ thisArg);
+ }
+ if (result) {
+ return result;
+ }
+ }
+ }
+ return undefined;
+};
+
+
+/**
+ * @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.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;
+};
+
+
+/**
+ * @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.renderer.Map.prototype.hasFeatureAtCoordinate =
+ function(coordinate, frameState, layerFilter, thisArg) {
+ var hasFeature = this.forEachFeatureAtCoordinate(
+ coordinate, frameState, goog.functions.TRUE, this, layerFilter, thisArg);
+
+ return hasFeature !== 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;
+ this.layerRendererListeners_[layerKey] = goog.events.listen(layerRenderer,
+ goog.events.EventType.CHANGE, this.handleLayerRendererChange_,
+ false, this);
+
+ return layerRenderer;
+ }
+};
+
+
+/**
+ * @param {string} layerKey Layer key.
+ * @protected
+ * @return {ol.renderer.Layer} Layer renderer.
+ */
+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];
+};
+
+
+/**
+ * @protected
+ * @return {Object.<number, ol.renderer.Layer>} Layer renderers.
+ */
+ol.renderer.Map.prototype.getLayerRenderers = function() {
+ return this.layerRenderers_;
+};
+
+
+/**
+ * @return {ol.Map} Map.
+ */
+ol.renderer.Map.prototype.getMap = function() {
+ return this.map_;
+};
+
+
+/**
+ * @return {string} Type
+ */
+ol.renderer.Map.prototype.getType = goog.abstractMethod;
+
+
+/**
+ * Handle changes in a layer renderer.
+ * @private
+ */
+ol.renderer.Map.prototype.handleLayerRendererChange_ = function() {
+ this.map_.render();
+};
+
+
+/**
+ * @param {string} layerKey Layer key.
+ * @return {ol.renderer.Layer} Layer renderer.
+ * @private
+ */
+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;
+};
+
+
+/**
+ * Render.
+ * @param {?olx.FrameState} frameState Frame state.
+ */
+ol.renderer.Map.prototype.renderFrame = ol.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 (!frameState || !(layerKey in frameState.layerStates)) {
+ goog.dispose(this.removeLayerRendererByKey_(layerKey));
+ }
+ }
+};
+
+
+/**
+ * @param {olx.FrameState} frameState Frame state.
+ * @protected
+ */
+ol.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) {
+ frameState.postRenderFunctions.push(ol.renderer.Map.expireIconCache_);
+};
+
+
+/**
+ * @param {!olx.FrameState} frameState Frame state.
+ * @protected
+ */
+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;
+ }
+ }
+};
+
+
+/**
+ * @param {ol.layer.LayerState} state1
+ * @param {ol.layer.LayerState} state2
+ * @return {number}
+ */
+ol.renderer.Map.sortByZIndex = function(state1, state2) {
+ return state1.zIndex - state2.zIndex;
+};
+
+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 {function(T): number} priorityFunction Priority function.
+ * @param {function(T): string} keyFunction Key function.
+ * @struct
+ * @template T
+ */
+ol.structs.PriorityQueue = function(priorityFunction, keyFunction) {
+
+ /**
+ * @type {function(T): number}
+ * @private
+ */
+ this.priorityFunction_ = priorityFunction;
+
+ /**
+ * @type {function(T): string}
+ * @private
+ */
+ this.keyFunction_ = keyFunction;
+
+ /**
+ * @type {Array.<T>}
+ * @private
+ */
+ this.elements_ = [];
+
+ /**
+ * @type {Array.<number>}
+ * @private
+ */
+ this.priorities_ = [];
+
+ /**
+ * @type {Object.<string, boolean>}
+ * @private
+ */
+ this.queuedElements_ = {};
+
+};
+
+
+/**
+ * @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_);
+};
+
+
+/**
+ * Remove and return the highest-priority element. O(log N).
+ * @return {T} Element.
+ */
+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);
+ }
+ 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;
+};
+
+
+/**
+ * Enqueue an element. O(log N).
+ * @param {T} element Element.
+ * @return {boolean} The element was added to the queue.
+ */
+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 true;
+ }
+ return false;
+};
+
+
+/**
+ * @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.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.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.structs.PriorityQueue.prototype.getParentIndex_ = function(index) {
+ return (index - 1) >> 1;
+};
+
+
+/**
+ * Make this a heap. O(N).
+ * @private
+ */
+ol.structs.PriorityQueue.prototype.heapify_ = function() {
+ var i;
+ for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) {
+ this.siftUp_(i);
+ }
+};
+
+
+/**
+ * @return {boolean} Is empty.
+ */
+ol.structs.PriorityQueue.prototype.isEmpty = function() {
+ return this.elements_.length === 0;
+};
+
+
+/**
+ * @param {string} key Key.
+ * @return {boolean} Is key queued.
+ */
+ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) {
+ return key in this.queuedElements_;
+};
+
+
+/**
+ * @param {T} element Element.
+ * @return {boolean} Is queued.
+ */
+ol.structs.PriorityQueue.prototype.isQueued = function(element) {
+ return this.isKeyQueued(this.keyFunction_(element));
+};
+
+
+/**
+ * @param {number} index The index of the node to move down.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @param {number} startIndex The index of the root.
+ * @param {number} index The index of the node to move up.
+ * @private
+ */
+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;
+};
+
+
+/**
+ * FIXME empty description for jsdoc
+ */
+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.TileQueue = function(tilePriorityFunction, tileChangeCallback) {
+
+ 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;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.tilesLoading_ = 0;
+
+ /**
+ * @private
+ * @type {Object.<string,boolean>}
+ */
+ this.tilesLoadingKeys_ = {};
+
+};
+goog.inherits(ol.TileQueue, ol.structs.PriorityQueue);
+
+
+/**
+ * @inheritDoc
+ */
+ol.TileQueue.prototype.enqueue = function(element) {
+ var added = goog.base(this, 'enqueue', element);
+ if (added) {
+ var tile = element[0];
+ goog.events.listen(tile, goog.events.EventType.CHANGE,
+ this.handleTileChange, false, this);
+ }
+ return added;
+};
+
+
+/**
+ * @return {number} Number of tiles loading.
+ */
+ol.TileQueue.prototype.getTilesLoading = function() {
+ return this.tilesLoading_;
+};
+
+
+/**
+ * @param {goog.events.Event} event Event.
+ * @protected
+ */
+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);
+ var tileKey = tile.getKey();
+ if (tileKey in this.tilesLoadingKeys_) {
+ delete this.tilesLoadingKeys_[tileKey];
+ --this.tilesLoading_;
+ }
+ this.tileChangeCallback_();
+ }
+};
+
+
+/**
+ * @param {number} maxTotalLoading Maximum number tiles to load simultaneously.
+ * @param {number} maxNewLoads Maximum number of new tiles to load.
+ */
+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) {
+ tile.load();
+ this.tilesLoadingKeys_[tile.getKey()] = true;
+ ++this.tilesLoading_;
+ ++newLoads;
+ }
+ }
+};
+
+goog.provide('ol.Kinetic');
+
+goog.require('ol.Coordinate');
+goog.require('ol.PreRenderFunction');
+goog.require('ol.animation');
+
+
+
+/**
+ * @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.Kinetic = function(decay, minVelocity, delay) {
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.decay_ = decay;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.minVelocity_ = minVelocity;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.delay_ = delay;
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.points_ = [];
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.angle_ = 0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.initialVelocity_ = 0;
+};
+
+
+/**
+ * FIXME empty description for jsdoc
+ */
+ol.Kinetic.prototype.begin = function() {
+ this.points_.length = 0;
+ this.angle_ = 0;
+ this.initialVelocity_ = 0;
+};
+
+
+/**
+ * @param {number} x X.
+ * @param {number} y Y.
+ */
+ol.Kinetic.prototype.update = function(x, y) {
+ this.points_.push(x, y, Date.now());
+};
+
+
+/**
+ * @return {boolean} Whether we should do kinetic animation.
+ */
+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 = Date.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;
+ }
+
+ // 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 {ol.Coordinate} source Source coordinate for the animation.
+ * @return {ol.PreRenderFunction} Pre-render function for kinetic animation.
+ */
+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
+ });
+};
+
+
+/**
+ * @private
+ * @return {number} Duration of animation (milliseconds).
+ */
+ol.Kinetic.prototype.getDuration_ = function() {
+ return Math.log(this.minVelocity_ / this.initialVelocity_) / this.decay_;
+};
+
+
+/**
+ * @return {number} Total distance travelled (pixels).
+ */
+ol.Kinetic.prototype.getDistance = function() {
+ return (this.minVelocity_ - this.initialVelocity_) / this.decay_;
+};
+
+
+/**
+ * @return {number} Angle of the kinetic panning animation (radians).
+ */
+ol.Kinetic.prototype.getAngle = function() {
+ return this.angle_;
+};
+
+// FIXME factor out key precondition (shift et. al)
+
+goog.provide('ol.interaction.Interaction');
+goog.provide('ol.interaction.InteractionProperty');
+
+goog.require('ol');
+goog.require('ol.MapBrowserEvent');
+goog.require('ol.Object');
+goog.require('ol.animation');
+goog.require('ol.easing');
+
+
+/**
+ * @enum {string}
+ */
+ol.interaction.InteractionProperty = {
+ ACTIVE: 'active'
+};
+
+
+
+/**
+ * @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 {olx.interaction.InteractionOptions} options Options.
+ * @extends {ol.Object}
+ * @api
+ */
+ol.interaction.Interaction = function(options) {
+
+ goog.base(this);
+
+ /**
+ * @private
+ * @type {ol.Map}
+ */
+ this.map_ = null;
+
+ this.setActive(true);
+
+ /**
+ * @type {function(ol.MapBrowserEvent):boolean}
+ */
+ this.handleEvent = options.handleEvent;
+
+};
+goog.inherits(ol.interaction.Interaction, ol.Object);
+
+
+/**
+ * Return whether the interaction is currently active.
+ * @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));
+};
+
+
+/**
+ * Get the map associated with this interaction.
+ * @return {ol.Map} Map.
+ * @api
+ */
+ol.interaction.Interaction.prototype.getMap = function() {
+ return this.map_;
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * @param {ol.Map} map Map.
+ * @param {ol.View} view View.
+ * @param {ol.Coordinate} delta Delta.
+ * @param {number=} opt_duration Duration.
+ */
+ol.interaction.Interaction.pan = function(map, view, delta, opt_duration) {
+ var currentCenter = view.getCenter();
+ if (currentCenter) {
+ if (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);
+ }
+};
+
+
+/**
+ * @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.
+ */
+ol.interaction.Interaction.rotateWithoutConstraints =
+ function(map, view, rotation, opt_anchor, opt_duration) {
+ if (rotation !== undefined) {
+ var currentRotation = view.getRotation();
+ var currentCenter = view.getCenter();
+ if (currentRotation !== undefined && currentCenter &&
+ opt_duration && opt_duration > 0) {
+ map.beforeRender(ol.animation.rotate({
+ rotation: currentRotation,
+ duration: opt_duration,
+ easing: ol.easing.easeOut
+ }));
+ if (opt_anchor) {
+ map.beforeRender(ol.animation.pan({
+ source: currentCenter,
+ duration: opt_duration,
+ easing: ol.easing.easeOut
+ }));
+ }
+ }
+ view.rotate(rotation, opt_anchor);
+ }
+};
+
+
+/**
+ * @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.
+ */
+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);
+};
+
+
+/**
+ * @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.interaction.Interaction.zoomWithoutConstraints =
+ function(map, view, resolution, opt_anchor, opt_duration) {
+ if (resolution) {
+ var currentResolution = view.getResolution();
+ var currentCenter = view.getCenter();
+ if (currentResolution !== undefined && currentCenter &&
+ resolution !== currentResolution &&
+ opt_duration && opt_duration > 0) {
+ map.beforeRender(ol.animation.zoom({
+ resolution: currentResolution,
+ duration: opt_duration,
+ easing: ol.easing.easeOut
+ }));
+ if (opt_anchor) {
+ map.beforeRender(ol.animation.pan({
+ source: currentCenter,
+ duration: opt_duration,
+ easing: ol.easing.easeOut
+ }));
+ }
+ }
+ if (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');
+
+
+
+/**
+ * @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.interaction.DoubleClickZoom = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.delta_ = options.delta ? options.delta : 1;
+
+ goog.base(this, {
+ handleEvent: ol.interaction.DoubleClickZoom.handleEvent
+ });
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.duration_ = options.duration !== undefined ? options.duration : 250;
+
+};
+goog.inherits(ol.interaction.DoubleClickZoom, ol.interaction.Interaction);
+
+
+/**
+ * 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.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(view, 'map must have a view');
+ 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.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
+ */
+ol.events.ConditionType;
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * Return always true.
+ *
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True.
+ * @function
+ * @api stable
+ */
+ol.events.condition.always = goog.functions.TRUE;
+
+
+/**
+ * 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.events.condition.click = function(mapBrowserEvent) {
+ return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.CLICK;
+};
+
+
+/**
+ * Return always false.
+ *
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} False.
+ * @function
+ * @api stable
+ */
+ol.events.condition.never = goog.functions.FALSE;
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.events.condition.doubleClick = function(mapBrowserEvent) {
+ return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK;
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * Return `true` if only the platform-modifier-key (the meta-key on Mac,
+ * ctrl-key otherwise) 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);
+};
+
+
+/**
+ * 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.events.condition.shiftKeyOnly = function(mapBrowserEvent) {
+ var browserEvent = mapBrowserEvent.browserEvent;
+ return (
+ !browserEvent.altKey &&
+ !browserEvent.platformModifierKey &&
+ browserEvent.shiftKey);
+};
+
+
+/**
+ * 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.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 !== 'INPUT' &&
+ tagName !== 'SELECT' &&
+ tagName !== 'TEXTAREA');
+};
+
+
+/**
+ * Return `true` if the event originates from a mouse device.
+ *
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True if the event originates from a mouse device.
+ * @api stable
+ */
+ol.events.condition.mouseOnly = function(mapBrowserEvent) {
+ // see http://www.w3.org/TR/pointerevents/#widl-PointerEvent-pointerType
+ return mapBrowserEvent.pointerEvent.pointerType == 'mouse';
+};
+
+goog.provide('ol.interaction.Pointer');
+
+goog.require('goog.functions');
+goog.require('goog.object');
+goog.require('ol');
+goog.require('ol.MapBrowserEvent.EventType');
+goog.require('ol.MapBrowserPointerEvent');
+goog.require('ol.Pixel');
+goog.require('ol.interaction.Interaction');
+
+
+
+/**
+ * @classdesc
+ * 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 {olx.interaction.PointerOptions=} opt_options Options.
+ * @extends {ol.interaction.Interaction}
+ * @api
+ */
+ol.interaction.Pointer = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ var handleEvent = options.handleEvent ?
+ options.handleEvent : ol.interaction.Pointer.handleEvent;
+
+ goog.base(this, {
+ handleEvent: handleEvent
+ });
+
+ /**
+ * @type {function(ol.MapBrowserPointerEvent):boolean}
+ * @private
+ */
+ this.handleDownEvent_ = options.handleDownEvent ?
+ options.handleDownEvent : ol.interaction.Pointer.handleDownEvent;
+
+ /**
+ * @type {function(ol.MapBrowserPointerEvent)}
+ * @private
+ */
+ this.handleDragEvent_ = options.handleDragEvent ?
+ options.handleDragEvent : ol.interaction.Pointer.handleDragEvent;
+
+ /**
+ * @type {function(ol.MapBrowserPointerEvent)}
+ * @private
+ */
+ this.handleMoveEvent_ = options.handleMoveEvent ?
+ options.handleMoveEvent : ol.interaction.Pointer.handleMoveEvent;
+
+ /**
+ * @type {function(ol.MapBrowserPointerEvent):boolean}
+ * @private
+ */
+ this.handleUpEvent_ = options.handleUpEvent ?
+ options.handleUpEvent : ol.interaction.Pointer.handleUpEvent;
+
+ /**
+ * @type {boolean}
+ * @protected
+ */
+ this.handlingDownUpSequence = false;
+
+ /**
+ * @type {Object.<number, ol.pointer.PointerEvent>}
+ * @private
+ */
+ this.trackedPointers_ = {};
+
+ /**
+ * @type {Array.<ol.pointer.PointerEvent>}
+ * @protected
+ */
+ this.targetPointers = [];
+
+};
+goog.inherits(ol.interaction.Pointer, ol.interaction.Interaction);
+
+
+/**
+ * @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];
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @private
+ */
+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_);
+ }
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.Pointer}
+ */
+ol.interaction.Pointer.handleDragEvent = ol.nullFunction;
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Capture dragging.
+ * @this {ol.interaction.Pointer}
+ */
+ol.interaction.Pointer.handleUpEvent = goog.functions.FALSE;
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Capture dragging.
+ * @this {ol.interaction.Pointer}
+ */
+ol.interaction.Pointer.handleDownEvent = goog.functions.FALSE;
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.Pointer}
+ */
+ol.interaction.Pointer.handleMoveEvent = ol.nullFunction;
+
+
+/**
+ * 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;
+ }
+
+ 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;
+};
+
+
+/**
+ * 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.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
+ * 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
+ */
+ol.interaction.DragPan = function(opt_options) {
+
+ goog.base(this, {
+ handleDownEvent: ol.interaction.DragPan.handleDownEvent_,
+ handleDragEvent: ol.interaction.DragPan.handleDragEvent_,
+ handleUpEvent: ol.interaction.DragPan.handleUpEvent_
+ });
+
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * @private
+ * @type {ol.Kinetic|undefined}
+ */
+ this.kinetic_ = options.kinetic;
+
+ /**
+ * @private
+ * @type {?ol.PreRenderFunction}
+ */
+ this.kineticPreRenderFn_ = null;
+
+ /**
+ * @type {ol.Pixel}
+ */
+ this.lastCentroid = null;
+
+ /**
+ * @private
+ * @type {ol.events.ConditionType}
+ */
+ this.condition_ = options.condition ?
+ options.condition : ol.events.condition.noModifierKeys;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.noKinetic_ = false;
+
+};
+goog.inherits(ol.interaction.DragPan, ol.interaction.Pointer);
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.DragPan}
+ * @private
+ */
+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 (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.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragPan}
+ * @private
+ */
+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(center !== undefined, '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.setHint(ol.ViewHint.INTERACTING, -1);
+ map.render();
+ return false;
+ } else {
+ this.lastCentroid = null;
+ return true;
+ }
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragPan}
+ * @private
+ */
+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 (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;
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.DragPan.prototype.shouldStopEvent = goog.functions.FALSE;
+
+goog.provide('ol.interaction.DragRotate');
+
+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');
+
+
+
+/**
+ * @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.
+ *
+ * This interaction is only supported for mouse devices.
+ *
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @param {olx.interaction.DragRotateOptions=} opt_options Options.
+ * @api stable
+ */
+ol.interaction.DragRotate = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ 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_ = options.condition ?
+ options.condition : ol.events.condition.altShiftKeysOnly;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.lastAngle_ = undefined;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.duration_ = options.duration !== undefined ? options.duration : 250;
+};
+goog.inherits(ol.interaction.DragRotate, ol.interaction.Pointer);
+
+
+/**
+ * @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 (this.lastAngle_ !== undefined) {
+ var delta = theta - this.lastAngle_;
+ var view = map.getView();
+ var rotation = view.getRotation();
+ map.render();
+ ol.interaction.Interaction.rotateWithoutConstraints(
+ map, view, rotation - delta);
+ }
+ this.lastAngle_ = theta;
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragRotate}
+ * @private
+ */
+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.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragRotate}
+ * @private
+ */
+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;
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.DragRotate.prototype.shouldStopEvent = goog.functions.FALSE;
+
+// FIXME add rotation
+
+goog.provide('ol.render.Box');
+
+goog.require('goog.Disposable');
+goog.require('goog.asserts');
+goog.require('ol.geom.Polygon');
+
+
+
+/**
+ * @constructor
+ * @extends {goog.Disposable}
+ * @param {string} className CSS class name.
+ */
+ol.render.Box = function(className) {
+
+ /**
+ * @type {ol.geom.Polygon}
+ * @private
+ */
+ this.geometry_ = null;
+
+ /**
+ * @type {HTMLDivElement}
+ * @private
+ */
+ this.element_ = /** @type {HTMLDivElement} */ (document.createElement('div'));
+ this.element_.style.position = 'absolute';
+ this.element_.className = 'ol-box ' + className;
+
+ /**
+ * @private
+ * @type {ol.Map}
+ */
+ this.map_ = null;
+
+ /**
+ * @private
+ * @type {ol.Pixel}
+ */
+ this.startPixel_ = null;
+
+ /**
+ * @private
+ * @type {ol.Pixel}
+ */
+ this.endPixel_ = null;
+
+};
+goog.inherits(ol.render.Box, goog.Disposable);
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.Box.prototype.disposeInternal = function() {
+ this.setMap(null);
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @private
+ */
+ol.render.Box.prototype.render_ = function() {
+ var startPixel = this.startPixel_;
+ var endPixel = this.endPixel_;
+ goog.asserts.assert(startPixel, 'this.startPixel_ must be truthy');
+ goog.asserts.assert(endPixel, 'this.endPixel_ must be truthy');
+ var px = 'px';
+ var style = this.element_.style;
+ style.left = Math.min(startPixel[0], endPixel[0]) + px;
+ style.top = Math.min(startPixel[1], endPixel[1]) + px;
+ style.width = Math.abs(endPixel[0] - startPixel[0]) + px;
+ style.height = Math.abs(endPixel[1] - startPixel[1]) + px;
+};
+
+
+/**
+ * @param {ol.Map} map Map.
+ */
+ol.render.Box.prototype.setMap = function(map) {
+ if (this.map_) {
+ this.map_.getOverlayContainer().removeChild(this.element_);
+ var style = this.element_.style;
+ style.left = style.top = style.width = style.height = 'inherit';
+ }
+ this.map_ = map;
+ if (this.map_) {
+ this.map_.getOverlayContainer().appendChild(this.element_);
+ }
+};
+
+
+/**
+ * @param {ol.Pixel} startPixel Start pixel.
+ * @param {ol.Pixel} endPixel End pixel.
+ */
+ol.render.Box.prototype.setPixels = function(startPixel, endPixel) {
+ this.startPixel_ = startPixel;
+ this.endPixel_ = endPixel;
+ this.createOrUpdateGeometry();
+ this.render_();
+};
+
+
+/**
+ * Creates or updates the cached geometry.
+ */
+ol.render.Box.prototype.createOrUpdateGeometry = function() {
+ goog.asserts.assert(this.startPixel_,
+ 'this.startPixel_ must be truthy');
+ goog.asserts.assert(this.endPixel_,
+ 'this.endPixel_ must be truthy');
+ goog.asserts.assert(this.map_, 'this.map_ must be truthy');
+ var startPixel = this.startPixel_;
+ var endPixel = this.endPixel_;
+ var pixels = [
+ startPixel,
+ [startPixel[0], endPixel[1]],
+ endPixel,
+ [endPixel[0], startPixel[1]]
+ ];
+ var coordinates = pixels.map(this.map_.getCoordinateFromPixel, this.map_);
+ // close the polygon
+ coordinates[4] = coordinates[0].slice();
+ if (!this.geometry_) {
+ this.geometry_ = new ol.geom.Polygon([coordinates]);
+ } else {
+ this.geometry_.setCoordinates([coordinates]);
+ }
+};
+
+
+/**
+ * @return {ol.geom.Polygon} Geometry.
+ */
+ol.render.Box.prototype.getGeometry = function() {
+ return this.geometry_;
+};
+
+// 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}
+ */
+ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED =
+ ol.DRAG_BOX_HYSTERESIS_PIXELS *
+ ol.DRAG_BOX_HYSTERESIS_PIXELS;
+
+
+/**
+ * @enum {string}
+ */
+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'
+};
+
+
+
+/**
+ * @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
+ * 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
+ */
+ol.interaction.DragBox = function(opt_options) {
+
+ goog.base(this, {
+ handleDownEvent: ol.interaction.DragBox.handleDownEvent_,
+ handleDragEvent: ol.interaction.DragBox.handleDragEvent_,
+ handleUpEvent: ol.interaction.DragBox.handleUpEvent_
+ });
+
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * @type {ol.render.Box}
+ * @private
+ */
+ this.box_ = new ol.render.Box(options.className || 'ol-dragbox');
+
+ /**
+ * @type {ol.Pixel}
+ * @private
+ */
+ this.startPixel_ = null;
+
+ /**
+ * @private
+ * @type {ol.events.ConditionType}
+ */
+ this.condition_ = 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;
+ }
+
+ this.box_.setPixels(this.startPixel_, mapBrowserEvent.pixel);
+};
+
+
+/**
+ * Returns geometry of last drawn box.
+ * @return {ol.geom.Polygon} Geometry.
+ * @api stable
+ */
+ol.interaction.DragBox.prototype.getGeometry = function() {
+ return this.box_.getGeometry();
+};
+
+
+/**
+ * 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 = ol.nullFunction;
+
+
+/**
+ * @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);
+
+ 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.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragBox}
+ * @private
+ */
+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;
+ }
+};
+
+goog.provide('ol.interaction.DragZoom');
+
+goog.require('goog.asserts');
+goog.require('ol.animation');
+goog.require('ol.easing');
+goog.require('ol.events.condition');
+goog.require('ol.extent');
+goog.require('ol.interaction.DragBox');
+
+
+
+/**
+ * @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.
+ *
+ * To change the style of the box, use CSS and the `.ol-dragzoom` selector, or
+ * your custom one configured with `className`.
+ *
+ * @constructor
+ * @extends {ol.interaction.DragBox}
+ * @param {olx.interaction.DragZoomOptions=} opt_options Options.
+ * @api stable
+ */
+ol.interaction.DragZoom = function(opt_options) {
+ var options = opt_options ? opt_options : {};
+
+ var condition = options.condition ?
+ options.condition : ol.events.condition.shiftKeyOnly;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.duration_ = options.duration !== undefined ? options.duration : 200;
+
+ goog.base(this, {
+ condition: condition,
+ className: options.className || 'ol-dragzoom'
+ });
+
+};
+goog.inherits(ol.interaction.DragZoom, ol.interaction.DragBox);
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.DragZoom.prototype.onBoxEnd = function() {
+ var map = this.getMap();
+
+ var view = map.getView();
+ goog.asserts.assert(view, 'map must have view');
+
+ var size = map.getSize();
+ goog.asserts.assert(size !== undefined, 'size should be defined');
+
+ var extent = this.getGeometry().getExtent();
+
+ var resolution = view.constrainResolution(
+ view.getResolutionForExtent(extent, size));
+
+ var currentResolution = view.getResolution();
+ goog.asserts.assert(currentResolution !== undefined, 'res should be defined');
+
+ var currentCenter = view.getCenter();
+ goog.asserts.assert(currentCenter !== undefined, 'center should be defined');
+
+ map.beforeRender(ol.animation.zoom({
+ resolution: currentResolution,
+ duration: this.duration_,
+ easing: ol.easing.easeOut
+ }));
+ map.beforeRender(ol.animation.pan({
+ source: currentCenter,
+ duration: this.duration_,
+ easing: ol.easing.easeOut
+ }));
+
+ view.setCenter(ol.extent.getCenter(extent));
+ view.setResolution(resolution);
+};
+
+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');
+
+
+
+/**
+ * @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
+ */
+ol.interaction.KeyboardPan = function(opt_options) {
+
+ goog.base(this, {
+ handleEvent: ol.interaction.KeyboardPan.handleEvent
+ });
+
+ var options = opt_options || {};
+
+ /**
+ * @private
+ * @type {ol.events.ConditionType}
+ */
+ this.condition_ = options.condition !== undefined ?
+ options.condition :
+ goog.functions.and(ol.events.condition.noModifierKeys,
+ ol.events.condition.targetNotEditable);
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.duration_ = options.duration !== undefined ? options.duration : 100;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.pixelDelta_ = options.pixelDelta !== undefined ?
+ options.pixelDelta : 128;
+
+};
+goog.inherits(ol.interaction.KeyboardPan, ol.interaction.Interaction);
+
+
+/**
+ * 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
+ */
+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(view, 'map must have view');
+ var mapUnitsDelta = view.getResolution() * 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, view.getRotation());
+ ol.interaction.Interaction.pan(map, view, delta, this.duration_);
+ mapBrowserEvent.preventDefault();
+ stopEvent = true;
+ }
+ }
+ return !stopEvent;
+};
+
+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
+ */
+ol.interaction.KeyboardZoom = function(opt_options) {
+
+ goog.base(this, {
+ handleEvent: ol.interaction.KeyboardZoom.handleEvent
+ });
+
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * @private
+ * @type {ol.events.ConditionType}
+ */
+ this.condition_ = options.condition ? options.condition :
+ ol.events.condition.targetNotEditable;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.delta_ = options.delta ? options.delta : 1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.duration_ = options.duration !== undefined ? options.duration : 100;
+
+};
+goog.inherits(ol.interaction.KeyboardZoom, ol.interaction.Interaction);
+
+
+/**
+ * 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
+ */
+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(view, 'map must have view');
+ 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('ol');
+goog.require('ol.Coordinate');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.math');
+
+
+
+/**
+ * @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.base(this, {
+ handleEvent: ol.interaction.MouseWheelZoom.handleEvent
+ });
+
+ var options = opt_options || {};
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.delta_ = 0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.duration_ = options.duration !== undefined ? options.duration : 250;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.useAnchor_ = options.useAnchor !== undefined ? options.useAnchor : true;
+
+ /**
+ * @private
+ * @type {?ol.Coordinate}
+ */
+ this.lastAnchor_ = null;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.startTime_ = undefined;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.timeoutId_ = undefined;
+
+};
+goog.inherits(ol.interaction.MouseWheelZoom, ol.interaction.Interaction);
+
+
+/**
+ * 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');
+
+ if (this.useAnchor_) {
+ this.lastAnchor_ = mapBrowserEvent.coordinate;
+ }
+
+ this.delta_ += mouseWheelEvent.deltaY;
+
+ if (this.startTime_ === undefined) {
+ this.startTime_ = Date.now();
+ }
+
+ var duration = ol.MOUSEWHEELZOOM_TIMEOUT_DURATION;
+ var timeLeft = Math.max(duration - (Date.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;
+};
+
+
+/**
+ * @private
+ * @param {ol.Map} map Map.
+ */
+ol.interaction.MouseWheelZoom.prototype.doZoom_ = function(map) {
+ var maxDelta = ol.MOUSEWHEELZOOM_MAXDELTA;
+ var delta = ol.math.clamp(this.delta_, -maxDelta, maxDelta);
+
+ var view = map.getView();
+ goog.asserts.assert(view, 'map must have 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;
+};
+
+
+/**
+ * Enable or disable using the mouse's location as an anchor when zooming
+ * @param {boolean} useAnchor true to zoom to the mouse's location, false
+ * to zoom to the center of the map
+ * @api
+ */
+ol.interaction.MouseWheelZoom.prototype.setMouseAnchor = function(useAnchor) {
+ this.useAnchor_ = useAnchor;
+ if (!useAnchor) {
+ this.lastAnchor_ = null;
+ }
+};
+
+goog.provide('ol.interaction.PinchRotate');
+
+goog.require('goog.asserts');
+goog.require('goog.functions');
+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');
+
+
+
+/**
+ * @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
+ */
+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 = opt_options || {};
+
+ /**
+ * @private
+ * @type {ol.Coordinate}
+ */
+ this.anchor_ = null;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.lastAngle_ = undefined;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.rotating_ = false;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.rotationDelta_ = 0.0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.threshold_ = options.threshold !== undefined ? options.threshold : 0.3;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.duration_ = options.duration !== undefined ? options.duration : 250;
+
+};
+goog.inherits(ol.interaction.PinchRotate, ol.interaction.Pointer);
+
+
+/**
+ * @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;
+
+ var touch0 = this.targetPointers[0];
+ var touch1 = this.targetPointers[1];
+
+ // angle between touches
+ var angle = Math.atan2(
+ touch1.clientY - touch0.clientY,
+ touch1.clientX - touch0.clientX);
+
+ if (this.lastAngle_ !== undefined) {
+ 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;
+
+ var map = mapBrowserEvent.map;
+
+ // 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);
+
+ // rotate
+ if (this.rotating_) {
+ var view = map.getView();
+ var rotation = view.getRotation();
+ map.render();
+ ol.interaction.Interaction.rotateWithoutConstraints(map, view,
+ rotation + rotationDelta, this.anchor_);
+ }
+};
+
+
+/**
+ * @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;
+ }
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.PinchRotate}
+ * @private
+ */
+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;
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.PinchRotate.prototype.shouldStopEvent = goog.functions.FALSE;
+
+goog.provide('ol.interaction.PinchZoom');
+
+goog.require('goog.asserts');
+goog.require('goog.functions');
+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');
+
+
+
+/**
+ * @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
+ */
+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 = opt_options ? opt_options : {};
+
+ /**
+ * @private
+ * @type {ol.Coordinate}
+ */
+ this.anchor_ = null;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.duration_ = options.duration !== undefined ? options.duration : 400;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.lastDistance_ = undefined;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.lastScaleDelta_ = 1;
+
+};
+goog.inherits(ol.interaction.PinchZoom, ol.interaction.Pointer);
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.PinchZoom}
+ * @private
+ */
+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 (this.lastDistance_ !== undefined) {
+ 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_);
+
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.PinchZoom}
+ * @private
+ */
+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;
+ }
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.PinchZoom}
+ * @private
+ */
+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;
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.PinchZoom.prototype.shouldStopEvent = goog.functions.FALSE;
+
+goog.provide('ol.interaction');
+
+goog.require('ol');
+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');
+
+
+/**
+ * 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}
+ *
+ * @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) {
+
+ var options = opt_options ? opt_options : {};
+
+ var interactions = new ol.Collection();
+
+ var kinetic = new ol.Kinetic(-0.005, 0.05, 100);
+
+ var altShiftDragRotate = options.altShiftDragRotate !== undefined ?
+ options.altShiftDragRotate : true;
+ if (altShiftDragRotate) {
+ interactions.push(new ol.interaction.DragRotate());
+ }
+
+ var doubleClickZoom = options.doubleClickZoom !== undefined ?
+ options.doubleClickZoom : true;
+ if (doubleClickZoom) {
+ interactions.push(new ol.interaction.DoubleClickZoom({
+ delta: options.zoomDelta,
+ duration: options.zoomDuration
+ }));
+ }
+
+ var dragPan = options.dragPan !== undefined ? options.dragPan : true;
+ if (dragPan) {
+ interactions.push(new ol.interaction.DragPan({
+ kinetic: kinetic
+ }));
+ }
+
+ var pinchRotate = options.pinchRotate !== undefined ? options.pinchRotate :
+ true;
+ if (pinchRotate) {
+ interactions.push(new ol.interaction.PinchRotate());
+ }
+
+ var pinchZoom = options.pinchZoom !== undefined ? options.pinchZoom : true;
+ if (pinchZoom) {
+ interactions.push(new ol.interaction.PinchZoom({
+ duration: options.zoomDuration
+ }));
+ }
+
+ var keyboard = options.keyboard !== undefined ? 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 = options.mouseWheelZoom !== undefined ?
+ options.mouseWheelZoom : true;
+ if (mouseWheelZoom) {
+ interactions.push(new ol.interaction.MouseWheelZoom({
+ duration: options.zoomDuration
+ }));
+ }
+
+ var shiftDragZoom = options.shiftDragZoom !== undefined ?
+ options.shiftDragZoom : true;
+ if (shiftDragZoom) {
+ interactions.push(new ol.interaction.DragZoom({
+ duration: options.zoomDuration
+ }));
+ }
+
+ return interactions;
+
+};
+
+goog.provide('ol.layer.Group');
+
+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.Object');
+goog.require('ol.ObjectEventType');
+goog.require('ol.extent');
+goog.require('ol.layer.Base');
+goog.require('ol.source.State');
+
+
+/**
+ * @enum {string}
+ */
+ol.layer.GroupProperty = {
+ LAYERS: 'layers'
+};
+
+
+
+/**
+ * @classdesc
+ * A {@link ol.Collection} of layers that are handled together.
+ *
+ * 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.layer.Group = function(opt_options) {
+
+ var 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 (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);
+
+
+/**
+ * @private
+ */
+ol.layer.Group.prototype.handleLayerChange_ = function() {
+ if (this.getVisible()) {
+ this.changed();
+ }
+};
+
+
+/**
+ * @param {goog.events.Event} event Event.
+ * @private
+ */
+ol.layer.Group.prototype.handleLayersChanged_ = function(event) {
+ this.layersListenerKeys_.forEach(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) {
+ keys.forEach(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();
+};
+
+
+/**
+ * @param {ol.CollectionEvent} collectionEvent Collection event.
+ * @private
+ */
+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();
+};
+
+
+/**
+ * @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.asserts.assert(key in this.listenerKeys_, 'no listeners to unregister');
+ this.listenerKeys_[key].forEach(goog.events.unlistenByKey);
+ delete this.listenerKeys_[key];
+ this.changed();
+};
+
+
+/**
+ * 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.layer.Group.prototype.getLayers = function() {
+ return /** @type {!ol.Collection.<ol.layer.Base>} */ (this.get(
+ ol.layer.GroupProperty.LAYERS));
+};
+
+
+/**
+ * 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.layer.Group.prototype.setLayers = function(layers) {
+ this.set(ol.layer.GroupProperty.LAYERS, layers);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.layer.Group.prototype.getLayersArray = function(opt_array) {
+ var array = opt_array !== undefined ? opt_array : [];
+ this.getLayers().forEach(function(layer) {
+ layer.getLayersArray(array);
+ });
+ return array;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.layer.Group.prototype.getLayerStatesArray = function(opt_states) {
+ var states = opt_states !== undefined ? 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.opacity *= ownLayerState.opacity;
+ layerState.visible = layerState.visible && ownLayerState.visible;
+ layerState.maxResolution = Math.min(
+ layerState.maxResolution, ownLayerState.maxResolution);
+ layerState.minResolution = Math.max(
+ layerState.minResolution, ownLayerState.minResolution);
+ if (ownLayerState.extent !== undefined) {
+ if (layerState.extent !== undefined) {
+ layerState.extent = ol.extent.getIntersection(
+ layerState.extent, ownLayerState.extent);
+ } else {
+ layerState.extent = ownLayerState.extent;
+ }
+ }
+ }
+
+ return states;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.layer.Group.prototype.getSourceState = function() {
+ return ol.source.State.READY;
+};
+
+goog.provide('ol.proj.EPSG3857');
+
+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
+ */
+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);
+
+
+/**
+ * @inheritDoc
+ */
+ol.proj.EPSG3857_.prototype.getPointResolution = function(resolution, point) {
+ return resolution / ol.math.cosh(point[1] / ol.proj.EPSG3857.RADIUS);
+};
+
+
+/**
+ * @const
+ * @type {number}
+ */
+ol.proj.EPSG3857.RADIUS = 6378137;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+ol.proj.EPSG3857.HALF_SIZE = Math.PI * ol.proj.EPSG3857.RADIUS;
+
+
+/**
+ * @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
+];
+
+
+/**
+ * @const
+ * @type {ol.Extent}
+ */
+ol.proj.EPSG3857.WORLD_EXTENT = [-180, -85, 180, 85];
+
+
+/**
+ * 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'
+];
+
+
+/**
+ * Projections equal to EPSG:3857.
+ *
+ * @const
+ * @type {Array.<ol.proj.Projection>}
+ */
+ol.proj.EPSG3857.PROJECTIONS = ol.proj.EPSG3857.CODES.map(function(code) {
+ return new ol.proj.EPSG3857_(code);
+});
+
+
+/**
+ * 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 (output === undefined) {
+ 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;
+};
+
+
+/**
+ * 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 (output === undefined) {
+ 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.proj.EPSG4326');
+
+goog.require('ol.proj');
+goog.require('ol.proj.Projection');
+goog.require('ol.proj.Units');
+
+
+
+/**
+ * @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.
+ * @private
+ */
+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);
+
+
+/**
+ * @inheritDoc
+ */
+ol.proj.EPSG4326_.prototype.getPointResolution = function(resolution, point) {
+ return resolution;
+};
+
+
+/**
+ * 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
+ */
+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.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
+ */
+ol.layer.Image = function(opt_options) {
+ var options = opt_options ? opt_options : {};
+ goog.base(this, /** @type {olx.layer.LayerOptions} */ (options));
+};
+goog.inherits(ol.layer.Image, ol.layer.Layer);
+
+
+/**
+ * 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;
+
+goog.provide('ol.layer.Tile');
+
+goog.require('goog.object');
+goog.require('ol');
+goog.require('ol.layer.Layer');
+
+
+/**
+ * @enum {string}
+ */
+ol.layer.TileProperty = {
+ PRELOAD: 'preload',
+ USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError'
+};
+
+
+
+/**
+ * @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 = opt_options ? opt_options : {};
+
+ var baseOptions = goog.object.clone(options);
+
+ delete baseOptions.preload;
+ delete baseOptions.useInterimTilesOnError;
+ goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));
+
+ this.setPreload(options.preload !== undefined ? options.preload : 0);
+ this.setUseInterimTilesOnError(options.useInterimTilesOnError !== undefined ?
+ options.useInterimTilesOnError : true);
+};
+goog.inherits(ol.layer.Tile, ol.layer.Layer);
+
+
+/**
+ * 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.layer.Tile.prototype.getPreload = function() {
+ return /** @type {number} */ (this.get(ol.layer.TileProperty.PRELOAD));
+};
+
+
+/**
+ * Return the associated {@link ol.source.Tile tilesource} of the layer.
+ * @function
+ * @return {ol.source.Tile} Source.
+ * @api stable
+ */
+ol.layer.Tile.prototype.getSource;
+
+
+/**
+ * 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.layer.Tile.prototype.setPreload = function(preload) {
+ this.set(ol.layer.TileProperty.PRELOAD, preload);
+};
+
+
+/**
+ * Whether we use interim tiles on error.
+ * @return {boolean} Use interim tiles on error.
+ * @observable
+ * @api
+ */
+ol.layer.Tile.prototype.getUseInterimTilesOnError = function() {
+ return /** @type {boolean} */ (
+ this.get(ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR));
+};
+
+
+/**
+ * Set whether we use interim tiles on error.
+ * @param {boolean} useInterimTilesOnError Use interim tiles on error.
+ * @observable
+ * @api
+ */
+ol.layer.Tile.prototype.setUseInterimTilesOnError =
+ function(useInterimTilesOnError) {
+ this.set(
+ ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
+};
+
+goog.provide('ol.render.canvas');
+
+
+/**
+ * @typedef {{fillStyle: string}}
+ */
+ol.render.canvas.FillState;
+
+
+/**
+ * @typedef {{lineCap: string,
+ * lineDash: Array.<number>,
+ * lineJoin: string,
+ * lineWidth: number,
+ * miterLimit: number,
+ * strokeStyle: string}}
+ */
+ol.render.canvas.StrokeState;
+
+
+/**
+ * @typedef {{font: string,
+ * textAlign: string,
+ * textBaseline: string}}
+ */
+ol.render.canvas.TextState;
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.canvas.defaultFont = '10px sans-serif';
+
+
+/**
+ * @const
+ * @type {ol.Color}
+ */
+ol.render.canvas.defaultFillStyle = [0, 0, 0, 1];
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.canvas.defaultLineCap = 'round';
+
+
+/**
+ * @const
+ * @type {Array.<number>}
+ */
+ol.render.canvas.defaultLineDash = [];
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.canvas.defaultLineJoin = 'round';
+
+
+/**
+ * @const
+ * @type {number}
+ */
+ol.render.canvas.defaultMiterLimit = 10;
+
+
+/**
+ * @const
+ * @type {ol.Color}
+ */
+ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1];
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.canvas.defaultTextAlign = 'center';
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.canvas.defaultTextBaseline = 'middle';
+
+
+/**
+ * @const
+ * @type {number}
+ */
+ol.render.canvas.defaultLineWidth = 1;
+
+goog.provide('ol.structs.IHasChecksum');
+
+
+
+/**
+ * @interface
+ */
+ol.structs.IHasChecksum = function() {
+};
+
+
+/**
+ * @return {string} The checksum.
+ */
+ol.structs.IHasChecksum.prototype.getChecksum = function() {
+};
+
+goog.provide('ol.style.Fill');
+
+goog.require('ol.color');
+goog.require('ol.structs.IHasChecksum');
+
+
+
+/**
+ * @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) {
+
+ var options = opt_options || {};
+
+ /**
+ * @private
+ * @type {ol.Color|string}
+ */
+ this.color_ = options.color !== undefined ? options.color : null;
+
+ /**
+ * @private
+ * @type {string|undefined}
+ */
+ this.checksum_ = undefined;
+};
+
+
+/**
+ * Get the fill color.
+ * @return {ol.Color|string} Color.
+ * @api
+ */
+ol.style.Fill.prototype.getColor = function() {
+ return this.color_;
+};
+
+
+/**
+ * Set the color.
+ *
+ * @param {ol.Color|string} color Color.
+ * @api
+ */
+ol.style.Fill.prototype.setColor = function(color) {
+ this.color_ = color;
+ this.checksum_ = undefined;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Fill.prototype.getChecksum = function() {
+ if (this.checksum_ === undefined) {
+ this.checksum_ = 'f' + (this.color_ ?
+ ol.color.asString(this.color_) : '-');
+ }
+
+ return this.checksum_;
+};
+
+// 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.
+ */
+
+goog.provide('goog.crypt');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+
+
+/**
+ * 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.
+ */
+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;
+};
+
+
+/**
+ * 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;
+
+ // Special-case the simple case for speed's sake.
+ if (bytes.length <= CHUNK_SIZE) {
+ return String.fromCharCode.apply(null, bytes);
+ }
+
+ // The remaining logic splits conversion by chunks since
+ // Function#apply() has a maximum parameter count.
+ // See discussion: http://goo.gl/LrWmZ9
+
+ 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;
+};
+
+
+/**
+ * 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('');
+};
+
+
+/**
+ * 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.
+ */
+goog.crypt.stringToUtf8ByteArray = function(str) {
+ // TODO(user): Use native implementations if/when available
+ 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;
+};
+
+
+/**
+ * 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.
+ */
+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);
+ }
+ }
+ return out.join('');
+};
+
+
+/**
+ * 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');
+
+ 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.
+ *
+ */
+
+goog.provide('goog.crypt.Hash');
+
+
+
+/**
+ * Create a cryptographic hash instance.
+ *
+ * @constructor
+ * @struct
+ */
+goog.crypt.Hash = function() {
+ /**
+ * The block size for the hasher.
+ * @type {number}
+ */
+ this.blockSize = -1;
+};
+
+
+/**
+ * Resets the internal accumulator.
+ */
+goog.crypt.Hash.prototype.reset = goog.abstractMethod;
+
+
+/**
+ * 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.
+ */
+goog.crypt.Hash.prototype.update = goog.abstractMethod;
+
+
+/**
+ * @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
+ *
+ */
+
+goog.provide('goog.crypt.Md5');
+
+goog.require('goog.crypt.Hash');
+
+
+
+/**
+ * MD5 cryptographic hash constructor.
+ * @constructor
+ * @extends {goog.crypt.Hash}
+ * @final
+ * @struct
+ */
+goog.crypt.Md5 = function() {
+ goog.crypt.Md5.base(this, 'constructor');
+
+ this.blockSize = 512 / 8;
+
+ /**
+ * Holds the current values of accumulated A-D variables (MD buffer).
+ * @type {!Array<number>}
+ * @private
+ */
+ this.chain_ = new Array(4);
+
+ /**
+ * A buffer holding the data until the whole block can be processed.
+ * @type {!Array<number>}
+ * @private
+ */
+ this.block_ = new Array(this.blockSize);
+
+ /**
+ * The length of yet-unprocessed data as collected in the block.
+ * @type {number}
+ * @private
+ */
+ this.blockLength_ = 0;
+
+ /**
+ * The total length of the message so far.
+ * @type {number}
+ * @private
+ */
+ this.totalLength_ = 0;
+
+ this.reset();
+};
+goog.inherits(goog.crypt.Md5, goog.crypt.Hash);
+
+
+/**
+ * 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
+];
+ */
+
+/**
+ * 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
+];
+ */
+
+
+/** @override */
+goog.crypt.Md5.prototype.reset = function() {
+ this.chain_[0] = 0x67452301;
+ this.chain_[1] = 0xefcdab89;
+ this.chain_[2] = 0x98badcfe;
+ this.chain_[3] = 0x10325476;
+
+ this.blockLength_ = 0;
+ this.totalLength_ = 0;
+};
+
+
+/**
+ * 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
+ */
+goog.crypt.Md5.prototype.compress_ = function(buf, opt_offset) {
+ if (!opt_offset) {
+ opt_offset = 0;
+ }
+
+ // We allocate the array every time, but it's cheap in practice.
+ var X = new Array(16);
+
+ // 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 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) {
+
+ 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;
+ }
+
+ 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 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.
+ */
+ 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;
+};
+
+
+/** @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;
+
+ // 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;
+
+ // 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 (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;
+ }
+ }
+ }
+ }
+
+ this.blockLength_ = blockLength;
+ this.totalLength_ += opt_length;
+};
+
+
+/** @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_);
+
+ // 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!
+ }
+ 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;
+ }
+ }
+ return digest;
+};
+
+goog.provide('ol.style.Stroke');
+
+goog.require('goog.crypt');
+goog.require('goog.crypt.Md5');
+goog.require('ol.color');
+goog.require('ol.structs.IHasChecksum');
+
+
+
+/**
+ * @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.
+ *
+ * @constructor
+ * @param {olx.style.StrokeOptions=} opt_options Options.
+ * @implements {ol.structs.IHasChecksum}
+ * @api
+ */
+ol.style.Stroke = function(opt_options) {
+
+ var options = opt_options || {};
+
+ /**
+ * @private
+ * @type {ol.Color|string}
+ */
+ this.color_ = options.color !== undefined ? options.color : null;
+
+ /**
+ * @private
+ * @type {string|undefined}
+ */
+ this.lineCap_ = options.lineCap;
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.lineDash_ = options.lineDash !== undefined ? options.lineDash : null;
+
+ /**
+ * @private
+ * @type {string|undefined}
+ */
+ this.lineJoin_ = options.lineJoin;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.miterLimit_ = options.miterLimit;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.width_ = options.width;
+
+ /**
+ * @private
+ * @type {string|undefined}
+ */
+ this.checksum_ = undefined;
+};
+
+
+/**
+ * Get the stroke color.
+ * @return {ol.Color|string} Color.
+ * @api
+ */
+ol.style.Stroke.prototype.getColor = function() {
+ return this.color_;
+};
+
+
+/**
+ * Get the line cap type for the stroke.
+ * @return {string|undefined} Line cap.
+ * @api
+ */
+ol.style.Stroke.prototype.getLineCap = function() {
+ return this.lineCap_;
+};
+
+
+/**
+ * Get the line dash style for the stroke.
+ * @return {Array.<number>} Line dash.
+ * @api
+ */
+ol.style.Stroke.prototype.getLineDash = function() {
+ return this.lineDash_;
+};
+
+
+/**
+ * Get the line join type for the stroke.
+ * @return {string|undefined} Line join.
+ * @api
+ */
+ol.style.Stroke.prototype.getLineJoin = function() {
+ return this.lineJoin_;
+};
+
+
+/**
+ * Get the miter limit for the stroke.
+ * @return {number|undefined} Miter limit.
+ * @api
+ */
+ol.style.Stroke.prototype.getMiterLimit = function() {
+ return this.miterLimit_;
+};
+
+
+/**
+ * Get the stroke width.
+ * @return {number|undefined} Width.
+ * @api
+ */
+ol.style.Stroke.prototype.getWidth = function() {
+ return this.width_;
+};
+
+
+/**
+ * Set the color.
+ *
+ * @param {ol.Color|string} color Color.
+ * @api
+ */
+ol.style.Stroke.prototype.setColor = function(color) {
+ this.color_ = color;
+ this.checksum_ = undefined;
+};
+
+
+/**
+ * Set the line cap.
+ *
+ * @param {string|undefined} lineCap Line cap.
+ * @api
+ */
+ol.style.Stroke.prototype.setLineCap = function(lineCap) {
+ this.lineCap_ = lineCap;
+ this.checksum_ = undefined;
+};
+
+
+/**
+ * Set the line dash.
+ *
+ * @param {Array.<number>} lineDash Line dash.
+ * @api
+ */
+ol.style.Stroke.prototype.setLineDash = function(lineDash) {
+ this.lineDash_ = lineDash;
+ this.checksum_ = undefined;
+};
+
+
+/**
+ * Set the line join.
+ *
+ * @param {string|undefined} lineJoin Line join.
+ * @api
+ */
+ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
+ this.lineJoin_ = lineJoin;
+ this.checksum_ = undefined;
+};
+
+
+/**
+ * Set the miter limit.
+ *
+ * @param {number|undefined} miterLimit Miter limit.
+ * @api
+ */
+ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
+ this.miterLimit_ = miterLimit;
+ this.checksum_ = undefined;
+};
+
+
+/**
+ * Set the width.
+ *
+ * @param {number|undefined} width Width.
+ * @api
+ */
+ol.style.Stroke.prototype.setWidth = function(width) {
+ this.width_ = width;
+ this.checksum_ = undefined;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Stroke.prototype.getChecksum = function() {
+ if (this.checksum_ === undefined) {
+ var raw = 's' +
+ (this.color_ ?
+ ol.color.asString(this.color_) : '-') + ',' +
+ (this.lineCap_ !== undefined ?
+ this.lineCap_.toString() : '-') + ',' +
+ (this.lineDash_ ?
+ this.lineDash_.toString() : '-') + ',' +
+ (this.lineJoin_ !== undefined ?
+ this.lineJoin_ : '-') + ',' +
+ (this.miterLimit_ !== undefined ?
+ this.miterLimit_.toString() : '-') + ',' +
+ (this.width_ !== undefined ?
+ this.width_.toString() : '-');
+
+ var md5 = new goog.crypt.Md5();
+ md5.update(raw);
+ this.checksum_ = goog.crypt.byteArrayToString(md5.digest());
+ }
+
+ return this.checksum_;
+};
+
+goog.provide('ol.style.Circle');
+
+goog.require('goog.asserts');
+goog.require('ol');
+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 = 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_ = options.fill !== undefined ? options.fill : null;
+
+ /**
+ * @private
+ * @type {ol.style.Stroke}
+ */
+ this.stroke_ = options.stroke !== undefined ? options.stroke : null;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.radius_ = options.radius;
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.origin_ = [0, 0];
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.anchor_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.size_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.imageSize_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.hitDetectionImageSize_ = null;
+
+ this.render_(options.atlasManager);
+
+ /**
+ * @type {boolean}
+ */
+ var snapToPixel = options.snapToPixel !== undefined ?
+ options.snapToPixel : true;
+
+ goog.base(this, {
+ opacity: 1,
+ rotateWithView: false,
+ rotation: 0,
+ scale: 1,
+ snapToPixel: snapToPixel
+ });
+
+};
+goog.inherits(ol.style.Circle, ol.style.Image);
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Circle.prototype.getAnchor = function() {
+ return this.anchor_;
+};
+
+
+/**
+ * Get the fill style for the circle.
+ * @return {ol.style.Fill} Fill style.
+ * @api
+ */
+ol.style.Circle.prototype.getFill = function() {
+ return this.fill_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Circle.prototype.getHitDetectionImage = function(pixelRatio) {
+ return this.hitDetectionCanvas_;
+};
+
+
+/**
+ * Get the image used to render the circle.
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {HTMLCanvasElement} Canvas element.
+ * @api
+ */
+ol.style.Circle.prototype.getImage = function(pixelRatio) {
+ return this.canvas_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Circle.prototype.getImageState = function() {
+ return ol.style.ImageState.LOADED;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Circle.prototype.getImageSize = function() {
+ return this.imageSize_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Circle.prototype.getHitDetectionImageSize = function() {
+ return this.hitDetectionImageSize_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Circle.prototype.getOrigin = function() {
+ return this.origin_;
+};
+
+
+/**
+ * Get the circle radius.
+ * @return {number} Radius.
+ * @api
+ */
+ol.style.Circle.prototype.getRadius = function() {
+ return this.radius_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Circle.prototype.getSize = function() {
+ return this.size_;
+};
+
+
+/**
+ * Get the stroke style for the circle.
+ * @return {ol.style.Stroke} Stroke style.
+ * @api
+ */
+ol.style.Circle.prototype.getStroke = function() {
+ return this.stroke_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Circle.prototype.listenImageChange = ol.nullFunction;
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Circle.prototype.load = ol.nullFunction;
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Circle.prototype.unlistenImageChange = ol.nullFunction;
+
+
+/**
+ * @typedef {{strokeStyle: (string|undefined), strokeWidth: number,
+ * size: number, lineDash: Array.<number>}}
+ */
+ol.style.Circle.RenderOptions;
+
+
+/**
+ * @private
+ * @param {ol.style.AtlasManager|undefined} atlasManager
+ */
+ol.style.Circle.prototype.render_ = function(atlasManager) {
+ var imageSize;
+ var lineDash = null;
+ var strokeStyle;
+ var strokeWidth = 0;
+
+ if (this.stroke_) {
+ strokeStyle = ol.color.asString(this.stroke_.getColor());
+ strokeWidth = this.stroke_.getWidth();
+ if (strokeWidth === undefined) {
+ 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 (atlasManager === undefined) {
+ // no atlas manager is used, create a new canvas
+ this.canvas_ = /** @type {HTMLCanvasElement} */
+ (document.createElement('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 = !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, 'circle radius is too large');
+
+ 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];
+ }
+ }
+
+ 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).
+ */
+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);
+
+ context.beginPath();
+ context.arc(
+ renderOptions.size / 2, renderOptions.size / 2,
+ this.radius_, 0, 2 * Math.PI, true);
+
+ if (this.fill_) {
+ context.fillStyle = ol.color.asString(this.fill_.getColor());
+ context.fill();
+ }
+ if (this.stroke_) {
+ context.strokeStyle = renderOptions.strokeStyle;
+ context.lineWidth = renderOptions.strokeWidth;
+ if (renderOptions.lineDash) {
+ context.setLineDash(renderOptions.lineDash);
+ }
+ context.stroke();
+ }
+ context.closePath();
+};
+
+
+/**
+ * @private
+ * @param {ol.style.Circle.RenderOptions} renderOptions
+ */
+ol.style.Circle.prototype.createHitDetectionCanvas_ = function(renderOptions) {
+ this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
+ if (this.fill_) {
+ this.hitDetectionCanvas_ = this.canvas_;
+ return;
+ }
+
+ // if no fill style is set, create an extra hit-detection image with a
+ // default fill style
+ this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
+ (document.createElement('CANVAS'));
+ var canvas = this.hitDetectionCanvas_;
+
+ canvas.height = renderOptions.size;
+ canvas.width = renderOptions.size;
+
+ var context = /** @type {CanvasRenderingContext2D} */
+ (canvas.getContext('2d'));
+ this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
+};
+
+
+/**
+ * @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);
+
+ // then move to (x, y)
+ context.translate(x, y);
+
+ context.beginPath();
+ context.arc(
+ renderOptions.size / 2, renderOptions.size / 2,
+ this.radius_, 0, 2 * Math.PI, true);
+
+ context.fillStyle = ol.color.asString(ol.render.canvas.defaultFillStyle);
+ context.fill();
+ if (this.stroke_) {
+ context.strokeStyle = renderOptions.strokeStyle;
+ context.lineWidth = renderOptions.strokeWidth;
+ if (renderOptions.lineDash) {
+ context.setLineDash(renderOptions.lineDash);
+ }
+ context.stroke();
+ }
+ context.closePath();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Circle.prototype.getChecksum = function() {
+ var strokeChecksum = this.stroke_ ?
+ this.stroke_.getChecksum() : '-';
+ var fillChecksum = this.fill_ ?
+ this.fill_.getChecksum() : '-';
+
+ var recalculate = !this.checksums_ ||
+ (strokeChecksum != this.checksums_[1] ||
+ fillChecksum != this.checksums_[2] ||
+ this.radius_ != this.checksums_[3]);
+
+ if (recalculate) {
+ var checksum = 'c' + strokeChecksum + fillChecksum +
+ (this.radius_ !== undefined ? this.radius_.toString() : '-');
+ this.checksums_ = [checksum, strokeChecksum, fillChecksum, this.radius_];
+ }
+
+ return this.checksums_[0];
+};
+
+goog.provide('ol.style.GeometryFunction');
+goog.provide('ol.style.Style');
+goog.provide('ol.style.StyleFunction');
+goog.provide('ol.style.defaultGeometryFunction');
+
+goog.require('goog.asserts');
+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
+ * @param {olx.style.StyleOptions=} opt_options Style options.
+ * @api
+ */
+ol.style.Style = function(opt_options) {
+
+ var options = opt_options || {};
+
+ /**
+ * @private
+ * @type {string|ol.geom.Geometry|ol.style.GeometryFunction}
+ */
+ this.geometry_ = null;
+
+ /**
+ * @private
+ * @type {!ol.style.GeometryFunction}
+ */
+ this.geometryFunction_ = ol.style.defaultGeometryFunction;
+
+ if (options.geometry !== undefined) {
+ this.setGeometry(options.geometry);
+ }
+
+ /**
+ * @private
+ * @type {ol.style.Fill}
+ */
+ this.fill_ = options.fill !== undefined ? options.fill : null;
+
+ /**
+ * @private
+ * @type {ol.style.Image}
+ */
+ this.image_ = options.image !== undefined ? options.image : null;
+
+ /**
+ * @private
+ * @type {ol.style.Stroke}
+ */
+ this.stroke_ = options.stroke !== undefined ? options.stroke : null;
+
+ /**
+ * @private
+ * @type {ol.style.Text}
+ */
+ this.text_ = options.text !== undefined ? options.text : null;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.zIndex_ = options.zIndex;
+
+};
+
+
+/**
+ * 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.style.Style.prototype.getGeometry = function() {
+ return this.geometry_;
+};
+
+
+/**
+ * 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.style.Style.prototype.getGeometryFunction = function() {
+ return this.geometryFunction_;
+};
+
+
+/**
+ * Get the fill style.
+ * @return {ol.style.Fill} Fill style.
+ * @api
+ */
+ol.style.Style.prototype.getFill = function() {
+ return this.fill_;
+};
+
+
+/**
+ * Get the image style.
+ * @return {ol.style.Image} Image style.
+ * @api
+ */
+ol.style.Style.prototype.getImage = function() {
+ return this.image_;
+};
+
+
+/**
+ * Get the stroke style.
+ * @return {ol.style.Stroke} Stroke style.
+ * @api
+ */
+ol.style.Style.prototype.getStroke = function() {
+ return this.stroke_;
+};
+
+
+/**
+ * Get the text style.
+ * @return {ol.style.Text} Text style.
+ * @api
+ */
+ol.style.Style.prototype.getText = function() {
+ return this.text_;
+};
+
+
+/**
+ * Get the z-index for the style.
+ * @return {number|undefined} ZIndex.
+ * @api
+ */
+ol.style.Style.prototype.getZIndex = function() {
+ return this.zIndex_;
+};
+
+
+/**
+ * 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.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 (result) {
+ goog.asserts.assertInstanceof(result, ol.geom.Geometry,
+ 'feature geometry must be an ol.geom.Geometry instance');
+ }
+ return result;
+ };
+ } else if (!geometry) {
+ this.geometryFunction_ = ol.style.defaultGeometryFunction;
+ } else if (geometry !== undefined) {
+ goog.asserts.assertInstanceof(geometry, ol.geom.Geometry,
+ 'geometry must be an ol.geom.Geometry instance');
+ this.geometryFunction_ = function() {
+ return geometry;
+ };
+ }
+ this.geometry_ = geometry;
+};
+
+
+/**
+ * Set the z-index.
+ *
+ * @param {number|undefined} zIndex ZIndex.
+ * @api
+ */
+ol.style.Style.prototype.setZIndex = function(zIndex) {
+ this.zIndex_ = zIndex;
+};
+
+
+/**
+ * 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.
+ *
+ * @typedef {function((ol.Feature|ol.render.Feature), number):
+ * (ol.style.Style|Array.<ol.style.Style>)}
+ * @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) {
+ var styleFunction;
+
+ if (goog.isFunction(obj)) {
+ 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 = function() {
+ return styles;
+ };
+ }
+ return styleFunction;
+};
+
+
+/**
+ * @type {Array.<ol.style.Style>}
+ * @private
+ */
+ol.style.defaultStyle_ = null;
+
+
+/**
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {number} resolution Resolution.
+ * @return {Array.<ol.style.Style>} Style.
+ */
+ol.style.defaultStyleFunction = function(feature, resolution) {
+ // 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.)
+ if (!ol.style.defaultStyle_) {
+ var fill = new ol.style.Fill({
+ color: 'rgba(255,255,255,0.4)'
+ });
+ var stroke = new ol.style.Stroke({
+ color: '#3399CC',
+ width: 1.25
+ });
+ ol.style.defaultStyle_ = [
+ new ol.style.Style({
+ image: new ol.style.Circle({
+ fill: fill,
+ stroke: stroke,
+ radius: 5
+ }),
+ fill: fill,
+ stroke: stroke
+ })
+ ];
+ }
+ return ol.style.defaultStyle_;
+};
+
+
+/**
+ * Default styles for editing features.
+ * @return {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} Styles
+ */
+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.CIRCLE] =
+ styles[ol.geom.GeometryType.POLYGON].concat(
+ 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.LINE_STRING],
+ styles[ol.geom.GeometryType.POINT]
+ );
+
+ return styles;
+};
+
+
+/**
+ * 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.render.Feature)):
+ * (ol.geom.Geometry|ol.render.Feature|undefined)}
+ * @api
+ */
+ol.style.GeometryFunction;
+
+
+/**
+ * Function that is called with a feature and returns its default geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature to get the geometry
+ * for.
+ * @return {ol.geom.Geometry|ol.render.Feature|undefined} Geometry to render.
+ */
+ol.style.defaultGeometryFunction = function(feature) {
+ goog.asserts.assert(feature, 'feature must not be null');
+ return feature.getGeometry();
+};
+
+goog.provide('ol.layer.Vector');
+
+goog.require('goog.asserts');
+goog.require('goog.object');
+goog.require('ol');
+goog.require('ol.layer.Layer');
+goog.require('ol.style.Style');
+
+
+/**
+ * @enum {string}
+ */
+ol.layer.VectorProperty = {
+ RENDER_ORDER: 'renderOrder'
+};
+
+
+
+/**
+ * @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.layer.Vector = function(opt_options) {
+
+ var options = opt_options ?
+ opt_options : /** @type {olx.layer.VectorOptions} */ ({});
+
+ goog.asserts.assert(
+ options.renderOrder === undefined || !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_ = options.renderBuffer !== undefined ?
+ 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_ = options.updateWhileAnimating !== undefined ?
+ options.updateWhileAnimating : false;
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.updateWhileInteracting_ = options.updateWhileInteracting !== undefined ?
+ options.updateWhileInteracting : false;
+
+};
+goog.inherits(ol.layer.Vector, ol.layer.Layer);
+
+
+/**
+ * @return {number|undefined} Render buffer.
+ */
+ol.layer.Vector.prototype.getRenderBuffer = function() {
+ return this.renderBuffer_;
+};
+
+
+/**
+ * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render
+ * order.
+ */
+ol.layer.Vector.prototype.getRenderOrder = function() {
+ return /** @type {function(ol.Feature, ol.Feature):number|null|undefined} */ (
+ this.get(ol.layer.VectorProperty.RENDER_ORDER));
+};
+
+
+/**
+ * Return the associated {@link ol.source.Vector vectorsource} of the layer.
+ * @function
+ * @return {ol.source.Vector} Source.
+ * @api stable
+ */
+ol.layer.Vector.prototype.getSource;
+
+
+/**
+ * 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.layer.Vector.prototype.getStyle = function() {
+ return this.style_;
+};
+
+
+/**
+ * Get the style function.
+ * @return {ol.style.StyleFunction|undefined} Layer style function.
+ * @api stable
+ */
+ol.layer.Vector.prototype.getStyleFunction = function() {
+ return this.styleFunction_;
+};
+
+
+/**
+ * @return {boolean} Whether the rendered layer should be updated while
+ * animating.
+ */
+ol.layer.Vector.prototype.getUpdateWhileAnimating = function() {
+ return this.updateWhileAnimating_;
+};
+
+
+/**
+ * @return {boolean} Whether the rendered layer should be updated while
+ * interacting.
+ */
+ol.layer.Vector.prototype.getUpdateWhileInteracting = function() {
+ return this.updateWhileInteracting_;
+};
+
+
+/**
+ * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder
+ * Render order.
+ */
+ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) {
+ goog.asserts.assert(
+ renderOrder === undefined || !renderOrder ||
+ goog.isFunction(renderOrder),
+ 'renderOrder must be a comparator function');
+ this.set(ol.layer.VectorProperty.RENDER_ORDER, renderOrder);
+};
+
+
+/**
+ * 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|null|undefined}
+ * style Layer style.
+ * @api stable
+ */
+ol.layer.Vector.prototype.setStyle = function(style) {
+ this.style_ = style !== undefined ? style : ol.style.defaultStyleFunction;
+ this.styleFunction_ = style === null ?
+ undefined : ol.style.createStyleFunction(this.style_);
+ this.changed();
+};
+
+goog.provide('ol.layer.VectorTile');
+
+goog.require('goog.object');
+goog.require('ol.layer.Vector');
+
+
+/**
+ * @enum {string}
+ */
+ol.layer.VectorTileProperty = {
+ PRELOAD: 'preload',
+ USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError'
+};
+
+
+
+/**
+ * @classdesc
+ * Layer for vector tile 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.Vector}
+ * @param {olx.layer.VectorTileOptions=} opt_options Options.
+ * @api
+ */
+ol.layer.VectorTile = function(opt_options) {
+ var options = opt_options ? opt_options : {};
+
+ var baseOptions = goog.object.clone(options);
+
+ delete baseOptions.preload;
+ delete baseOptions.useInterimTilesOnError;
+ goog.base(this, /** @type {olx.layer.VectorOptions} */ (baseOptions));
+
+ this.setPreload(options.preload ? options.preload : 0);
+ this.setUseInterimTilesOnError(options.useInterimTilesOnError ?
+ options.useInterimTilesOnError : true);
+
+};
+goog.inherits(ol.layer.VectorTile, ol.layer.Vector);
+
+
+/**
+ * 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.layer.VectorTile.prototype.getPreload = function() {
+ return /** @type {number} */ (this.get(ol.layer.VectorTileProperty.PRELOAD));
+};
+
+
+/**
+ * Return the associated {@link ol.source.VectorTile source} of the layer.
+ * @function
+ * @return {ol.source.VectorTile} Source.
+ * @api
+ */
+ol.layer.VectorTile.prototype.getSource;
+
+
+/**
+ * Whether we use interim tiles on error.
+ * @return {boolean} Use interim tiles on error.
+ * @observable
+ * @api
+ */
+ol.layer.VectorTile.prototype.getUseInterimTilesOnError = function() {
+ return /** @type {boolean} */ (
+ this.get(ol.layer.VectorTileProperty.USE_INTERIM_TILES_ON_ERROR));
+};
+
+
+/**
+ * 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.layer.VectorTile.prototype.setPreload = function(preload) {
+ this.set(ol.layer.TileProperty.PRELOAD, preload);
+};
+
+
+/**
+ * Set whether we use interim tiles on error.
+ * @param {boolean} useInterimTilesOnError Use interim tiles on error.
+ * @observable
+ * @api
+ */
+ol.layer.VectorTile.prototype.setUseInterimTilesOnError =
+ function(useInterimTilesOnError) {
+ this.set(
+ ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
+};
+
+// 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?
+
+goog.provide('ol.render.canvas.Immediate');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.vec.Mat4');
+goog.require('ol.array');
+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');
+
+
+
+/**
+ * @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.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;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.viewRotation_ = viewRotation;
+
+ /**
+ * @private
+ * @type {?ol.render.canvas.FillState}
+ */
+ this.contextFillState_ = null;
+
+ /**
+ * @private
+ * @type {?ol.render.canvas.StrokeState}
+ */
+ this.contextStrokeState_ = 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;
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.text_ = '';
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.textOffsetX_ = 0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.textOffsetY_ = 0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.textRotation_ = 0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.textScale_ = 0;
+
+ /**
+ * @private
+ * @type {?ol.render.canvas.FillState}
+ */
+ this.textFillState_ = null;
+
+ /**
+ * @private
+ * @type {?ol.render.canvas.StrokeState}
+ */
+ this.textStrokeState_ = null;
+
+ /**
+ * @private
+ * @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 (!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;
+ }
+};
+
+
+/**
+ * @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 (!this.textState_ || this.text_ === '') {
+ return;
+ }
+ if (this.textFillState_) {
+ this.setContextFillState_(this.textFillState_);
+ }
+ if (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 (this.textStrokeState_) {
+ context.strokeText(this.text_, x, y);
+ }
+ if (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 (callbacks !== undefined) {
+ callbacks.push(callback);
+ } else {
+ this.callbacksByZIndex_[zIndexKey] = [callback];
+ }
+};
+
+
+/**
+ * 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.
+ * @api
+ */
+ol.render.canvas.Immediate.prototype.drawCircleGeometry =
+ function(circleGeometry) {
+ if (!ol.extent.intersects(this.extent_, circleGeometry.getExtent())) {
+ return;
+ }
+ if (this.fillState_ || this.strokeState_) {
+ if (this.fillState_) {
+ this.setContextFillState_(this.fillState_);
+ }
+ if (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 (this.fillState_) {
+ context.fill();
+ }
+ if (this.strokeState_) {
+ context.stroke();
+ }
+ }
+ if (this.text_ !== '') {
+ this.drawText_(circleGeometry.getCenter(), 0, 2, 2);
+ }
+};
+
+
+/**
+ * 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 {ol.Feature} feature Feature.
+ * @param {ol.style.Style} style Style.
+ * @api
+ */
+ol.render.canvas.Immediate.prototype.drawFeature = function(feature, style) {
+ var geometry = style.getGeometryFunction()(feature);
+ if (!geometry ||
+ !ol.extent.intersects(this.extent_, geometry.getExtent())) {
+ return;
+ }
+ var zIndex = style.getZIndex();
+ if (zIndex === undefined) {
+ 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(renderGeometry !== undefined,
+ 'renderGeometry should be defined');
+ 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.
+ */
+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(geometryRenderer !== undefined,
+ 'geometryRenderer should be defined');
+ geometryRenderer.call(this, geometry, feature);
+ }
+};
+
+
+/**
+ * Render a Point geometry into the canvas. Rendering is immediate and uses
+ * the current style.
+ *
+ * @param {ol.geom.Point|ol.render.Feature} pointGeometry Point geometry.
+ * @api
+ */
+ol.render.canvas.Immediate.prototype.drawPointGeometry =
+ function(pointGeometry) {
+ var flatCoordinates = pointGeometry.getFlatCoordinates();
+ var stride = pointGeometry.getStride();
+ if (this.image_) {
+ this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride);
+ }
+ if (this.text_ !== '') {
+ this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride);
+ }
+};
+
+
+/**
+ * Render a MultiPoint geometry into the canvas. Rendering is immediate and
+ * uses the current style.
+ *
+ * @param {ol.geom.MultiPoint|ol.render.Feature} multiPointGeometry MultiPoint
+ * geometry.
+ * @api
+ */
+ol.render.canvas.Immediate.prototype.drawMultiPointGeometry =
+ function(multiPointGeometry) {
+ var flatCoordinates = multiPointGeometry.getFlatCoordinates();
+ var stride = multiPointGeometry.getStride();
+ if (this.image_) {
+ this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride);
+ }
+ if (this.text_ !== '') {
+ this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride);
+ }
+};
+
+
+/**
+ * Render a LineString into the canvas. Rendering is immediate and uses
+ * the current style.
+ *
+ * @param {ol.geom.LineString|ol.render.Feature} lineStringGeometry Line
+ * string geometry.
+ * @api
+ */
+ol.render.canvas.Immediate.prototype.drawLineStringGeometry =
+ function(lineStringGeometry) {
+ if (!ol.extent.intersects(this.extent_, lineStringGeometry.getExtent())) {
+ return;
+ }
+ if (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);
+ }
+};
+
+
+/**
+ * Render a MultiLineString geometry into the canvas. Rendering is immediate
+ * and uses the current style.
+ *
+ * @param {ol.geom.MultiLineString|ol.render.Feature} multiLineStringGeometry
+ * MultiLineString geometry.
+ * @api
+ */
+ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry =
+ function(multiLineStringGeometry) {
+ var geometryExtent = multiLineStringGeometry.getExtent();
+ if (!ol.extent.intersects(this.extent_, geometryExtent)) {
+ return;
+ }
+ if (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);
+ }
+};
+
+
+/**
+ * Render a Polygon geometry into the canvas. Rendering is immediate and uses
+ * the current style.
+ *
+ * @param {ol.geom.Polygon|ol.render.Feature} polygonGeometry Polygon
+ * geometry.
+ * @api
+ */
+ol.render.canvas.Immediate.prototype.drawPolygonGeometry =
+ function(polygonGeometry) {
+ if (!ol.extent.intersects(this.extent_, polygonGeometry.getExtent())) {
+ return;
+ }
+ if (this.strokeState_ || this.fillState_) {
+ if (this.fillState_) {
+ this.setContextFillState_(this.fillState_);
+ }
+ if (this.strokeState_) {
+ this.setContextStrokeState_(this.strokeState_);
+ }
+ var context = this.context_;
+ context.beginPath();
+ this.drawRings_(polygonGeometry.getOrientedFlatCoordinates(),
+ 0, polygonGeometry.getEnds(), polygonGeometry.getStride());
+ if (this.fillState_) {
+ context.fill();
+ }
+ if (this.strokeState_) {
+ context.stroke();
+ }
+ }
+ if (this.text_ !== '') {
+ var flatInteriorPoint = polygonGeometry.getFlatInteriorPoint();
+ this.drawText_(flatInteriorPoint, 0, 2, 2);
+ }
+};
+
+
+/**
+ * Render MultiPolygon geometry into the canvas. Rendering is immediate and
+ * uses the current style.
+ * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry.
+ * @api
+ */
+ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry =
+ function(multiPolygonGeometry) {
+ if (!ol.extent.intersects(this.extent_, multiPolygonGeometry.getExtent())) {
+ return;
+ }
+ if (this.strokeState_ || this.fillState_) {
+ if (this.fillState_) {
+ this.setContextFillState_(this.fillState_);
+ }
+ if (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 (this.fillState_) {
+ context.fill();
+ }
+ if (this.strokeState_) {
+ context.stroke();
+ }
+ }
+ }
+ if (this.text_ !== '') {
+ var flatInteriorPoints = multiPolygonGeometry.getFlatInteriorPoints();
+ this.drawText_(flatInteriorPoints, 0, flatInteriorPoints.length, 2);
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.Immediate.prototype.drawText = goog.abstractMethod;
+
+
+/**
+ * FIXME: empty description for jsdoc
+ */
+ol.render.canvas.Immediate.prototype.flush = function() {
+ /** @type {Array.<number>} */
+ var zs = Object.keys(this.callbacksByZIndex_).map(Number);
+ zs.sort(ol.array.numberSafeCompareFunction);
+ 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);
+ }
+ }
+};
+
+
+/**
+ * @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 (!contextFillState) {
+ context.fillStyle = fillState.fillStyle;
+ this.contextFillState_ = {
+ fillStyle: fillState.fillStyle
+ };
+ } else {
+ if (contextFillState.fillStyle != fillState.fillStyle) {
+ contextFillState.fillStyle = context.fillStyle = fillState.fillStyle;
+ }
+ }
+};
+
+
+/**
+ * @param {ol.render.canvas.StrokeState} strokeState Stroke state.
+ * @private
+ */
+ol.render.canvas.Immediate.prototype.setContextStrokeState_ =
+ function(strokeState) {
+ var context = this.context_;
+ var contextStrokeState = this.contextStrokeState_;
+ if (!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;
+ }
+ }
+};
+
+
+/**
+ * @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 (!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;
+ }
+ }
+};
+
+
+/**
+ * Set the fill and stroke style for subsequent draw operations. To clear
+ * either fill or stroke styles, pass null for the appropriate parameter.
+ *
+ * @param {ol.style.Fill} fillStyle Fill style.
+ * @param {ol.style.Stroke} strokeStyle Stroke style.
+ * @api
+ */
+ol.render.canvas.Immediate.prototype.setFillStrokeStyle =
+ function(fillStyle, strokeStyle) {
+ if (!fillStyle) {
+ this.fillState_ = null;
+ } else {
+ var fillStyleColor = fillStyle.getColor();
+ this.fillState_ = {
+ fillStyle: ol.color.asString(fillStyleColor ?
+ fillStyleColor : ol.render.canvas.defaultFillStyle)
+ };
+ }
+ if (!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: strokeStyleLineCap !== undefined ?
+ strokeStyleLineCap : ol.render.canvas.defaultLineCap,
+ lineDash: strokeStyleLineDash ?
+ strokeStyleLineDash : ol.render.canvas.defaultLineDash,
+ lineJoin: strokeStyleLineJoin !== undefined ?
+ strokeStyleLineJoin : ol.render.canvas.defaultLineJoin,
+ lineWidth: this.pixelRatio_ * (strokeStyleWidth !== undefined ?
+ strokeStyleWidth : ol.render.canvas.defaultLineWidth),
+ miterLimit: strokeStyleMiterLimit !== undefined ?
+ strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit,
+ strokeStyle: ol.color.asString(strokeStyleColor ?
+ strokeStyleColor : ol.render.canvas.defaultStrokeStyle)
+ };
+ }
+};
+
+
+/**
+ * Set the image style for subsequent draw operations. Pass null to remove
+ * the image style.
+ *
+ * @param {ol.style.Image} imageStyle Image style.
+ * @api
+ */
+ol.render.canvas.Immediate.prototype.setImageStyle = function(imageStyle) {
+ if (!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(imageAnchor, 'imageAnchor must be truthy');
+ goog.asserts.assert(imageImage, 'imageImage must be truthy');
+ goog.asserts.assert(imageOrigin, 'imageOrigin must be truthy');
+ goog.asserts.assert(imageSize, 'imageSize must be truthy');
+ 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];
+ }
+};
+
+
+/**
+ * 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 (!textStyle) {
+ this.text_ = '';
+ } else {
+ var textFillStyle = textStyle.getFill();
+ if (!textFillStyle) {
+ this.textFillState_ = null;
+ } else {
+ var textFillStyleColor = textFillStyle.getColor();
+ this.textFillState_ = {
+ fillStyle: ol.color.asString(textFillStyleColor ?
+ textFillStyleColor : ol.render.canvas.defaultFillStyle)
+ };
+ }
+ var textStrokeStyle = textStyle.getStroke();
+ if (!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: textStrokeStyleLineCap !== undefined ?
+ textStrokeStyleLineCap : ol.render.canvas.defaultLineCap,
+ lineDash: textStrokeStyleLineDash ?
+ textStrokeStyleLineDash : ol.render.canvas.defaultLineDash,
+ lineJoin: textStrokeStyleLineJoin !== undefined ?
+ textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin,
+ lineWidth: textStrokeStyleWidth !== undefined ?
+ textStrokeStyleWidth : ol.render.canvas.defaultLineWidth,
+ miterLimit: textStrokeStyleMiterLimit !== undefined ?
+ textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit,
+ strokeStyle: ol.color.asString(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: textFont !== undefined ?
+ textFont : ol.render.canvas.defaultFont,
+ textAlign: textTextAlign !== undefined ?
+ textTextAlign : ol.render.canvas.defaultTextAlign,
+ textBaseline: textTextBaseline !== undefined ?
+ textTextBaseline : ol.render.canvas.defaultTextBaseline
+ };
+ this.text_ = textText !== undefined ? textText : '';
+ this.textOffsetX_ =
+ textOffsetX !== undefined ? (this.pixelRatio_ * textOffsetX) : 0;
+ this.textOffsetY_ =
+ textOffsetY !== undefined ? (this.pixelRatio_ * textOffsetY) : 0;
+ this.textRotation_ = textRotation !== undefined ? textRotation : 0;
+ this.textScale_ = this.pixelRatio_ * (textScale !== undefined ?
+ textScale : 1);
+ }
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Object.<ol.geom.GeometryType,
+ * function(this: ol.render.canvas.Immediate,
+ * (ol.geom.Geometry|ol.render.Feature), Object)>}
+ */
+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.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');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.Layer}
+ * @param {ol.layer.Layer} layer Layer.
+ */
+ol.renderer.canvas.Layer = function(layer) {
+
+ goog.base(this, layer);
+
+ /**
+ * @private
+ * @type {!goog.vec.Mat4.Number}
+ */
+ this.transform_ = goog.vec.Mat4.createNumber();
+
+};
+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.
+ */
+ol.renderer.canvas.Layer.prototype.composeFrame =
+ function(frameState, layerState, context) {
+
+ this.dispatchPreComposeEvent(context, frameState);
+
+ var image = this.getImage();
+ if (image) {
+
+ // clipped rendering if layer extent is set
+ var extent = layerState.extent;
+ var clipped = extent !== undefined;
+ if (clipped) {
+ goog.asserts.assert(extent !== undefined,
+ '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();
+ }
+
+ 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));
+ } 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.dispatchPostComposeEvent(context, frameState);
+
+};
+
+
+/**
+ * @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.renderer.canvas.Layer.prototype.dispatchComposeEvent_ =
+ function(type, context, frameState, opt_transform) {
+ var layer = this.getLayer();
+ if (layer.hasListener(type)) {
+ var transform = opt_transform !== undefined ?
+ 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 {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {goog.vec.Mat4.Number=} opt_transform Transform.
+ * @protected
+ */
+ol.renderer.canvas.Layer.prototype.dispatchPostComposeEvent =
+ function(context, frameState, opt_transform) {
+ this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, context,
+ frameState, opt_transform);
+};
+
+
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {goog.vec.Mat4.Number=} opt_transform Transform.
+ * @protected
+ */
+ol.renderer.canvas.Layer.prototype.dispatchPreComposeEvent =
+ function(context, frameState, opt_transform) {
+ this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, context,
+ frameState, opt_transform);
+};
+
+
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {goog.vec.Mat4.Number=} opt_transform Transform.
+ * @protected
+ */
+ol.renderer.canvas.Layer.prototype.dispatchRenderEvent =
+ function(context, frameState, opt_transform) {
+ this.dispatchComposeEvent_(ol.render.EventType.RENDER, context,
+ frameState, opt_transform);
+};
+
+
+/**
+ * @return {HTMLCanvasElement|HTMLVideoElement|Image} Canvas.
+ */
+ol.renderer.canvas.Layer.prototype.getImage = goog.abstractMethod;
+
+
+/**
+ * @return {!goog.vec.Mat4.Number} Image transform.
+ */
+ol.renderer.canvas.Layer.prototype.getImageTransform = goog.abstractMethod;
+
+
+/**
+ * @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 {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;
+
+
+/**
+ * @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.renderer.canvas.Layer.prototype.getPixelOnCanvas =
+ function(pixelOnMap, imageTransformInv) {
+ var pixelOnCanvas = [0, 0];
+ ol.vec.Mat4.multVec2(imageTransformInv, pixelOnMap, pixelOnCanvas);
+ return pixelOnCanvas;
+};
+
+
+/**
+ * @param {ol.Size} size Size.
+ * @return {boolean} True when the canvas with the current size does not exceed
+ * the maximum dimensions.
+ */
+ol.renderer.canvas.Layer.testCanvasSize = (function() {
+
+ /**
+ * @type {CanvasRenderingContext2D}
+ */
+ var context = null;
+
+ /**
+ * @type {ImageData}
+ */
+ var imageData = null;
+
+ return function(size) {
+ if (!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.render.IReplayGroup');
+
+goog.require('ol.render.VectorContext');
+
+
+/**
+ * @enum {string}
+ */
+ol.render.ReplayType = {
+ IMAGE: 'Image',
+ LINE_STRING: 'LineString',
+ POLYGON: 'Polygon',
+ TEXT: 'Text'
+};
+
+
+/**
+ * @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
+];
+
+
+
+/**
+ * @interface
+ */
+ol.render.IReplayGroup = function() {
+};
+
+
+/**
+ * @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 {boolean} Is empty.
+ */
+ol.render.IReplayGroup.prototype.isEmpty = function() {
+};
+
+// 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.ImageReplay');
+goog.provide('ol.render.canvas.LineStringReplay');
+goog.provide('ol.render.canvas.PolygonReplay');
+goog.provide('ol.render.canvas.Replay');
+goog.provide('ol.render.canvas.ReplayGroup');
+goog.provide('ol.render.canvas.TextReplay');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.object');
+goog.require('goog.vec.Mat4');
+goog.require('ol');
+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.VectorContext');
+goog.require('ol.render.canvas');
+goog.require('ol.vec.Mat4');
+
+
+/**
+ * @enum {number}
+ */
+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
+};
+
+
+
+/**
+ * @constructor
+ * @extends {ol.render.VectorContext}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @protected
+ * @struct
+ */
+ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) {
+ goog.base(this);
+
+ /**
+ * @protected
+ * @type {number}
+ */
+ this.tolerance = tolerance;
+
+ /**
+ * @protected
+ * @const
+ * @type {ol.Extent}
+ */
+ this.maxExtent = maxExtent;
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.bufferedMaxExtent_ = null;
+
+ /**
+ * @protected
+ * @type {number}
+ */
+ this.maxLineWidth = 0;
+
+ /**
+ * @protected
+ * @const
+ * @type {number}
+ */
+ this.resolution = resolution;
+
+ /**
+ * @private
+ * @type {Array.<*>}
+ */
+ this.beginGeometryInstruction1_ = null;
+
+ /**
+ * @private
+ * @type {Array.<*>}
+ */
+ this.beginGeometryInstruction2_ = null;
+
+ /**
+ * @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 {
+ 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];
+ }
+ return myEnd;
+};
+
+
+/**
+ * @protected
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+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_);
+};
+
+
+/**
+ * @private
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @param {number} viewRotation View rotation.
+ * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+ * to skip.
+ * @param {Array.<*>} instructions Instructions array.
+ * @param {function((ol.Feature|ol.render.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.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 skipFeatures = !goog.object.isEmpty(skippedFeaturesHash);
+ 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_;
+ var prevX, prevY, roundX, roundY;
+ 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|ol.render.Feature} */ (instruction[1]);
+ if ((skipFeatures &&
+ skippedFeaturesHash[goog.getUid(feature).toString()]) ||
+ !feature.getGeometry()) {
+ i = /** @type {number} */ (instruction[2]);
+ } else if (opt_hitExtent !== undefined && !ol.extent.intersects(
+ /** @type {Array<number>} */ (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;
+ }
+
+ 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]),
+ '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));
+ }
+
+ // Support multiple lines separated by \n
+ var lines = text.split('\n');
+ var numLines = lines.length;
+ var fontSize, lineY;
+ if (numLines > 1) {
+ // Estimate line height using width of capital M, and add padding
+ fontSize = Math.round(context.measureText('M').width * 1.5);
+ lineY = y - (((numLines - 1) / 2) * fontSize);
+ } else {
+ // No need to calculate line height/offset for a single line
+ fontSize = 0;
+ lineY = y;
+ }
+
+ for (var lineIndex = 0; lineIndex < numLines; lineIndex++) {
+ var line = lines[lineIndex];
+ if (stroke) {
+ context.strokeText(line, x, lineY);
+ }
+ if (fill) {
+ context.fillText(line, x, lineY);
+ }
+
+ // Move next line down by fontSize px
+ lineY = lineY + fontSize;
+ }
+
+ if (scale != 1 || rotation !== 0) {
+ context.setTransform(1, 0, 0, 1, 0, 0);
+ }
+ }
+ ++i;
+ break;
+ case ol.render.canvas.Instruction.END_GEOMETRY:
+ if (featureCallback !== undefined) {
+ feature =
+ /** @type {ol.Feature|ol.render.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]);
+ x = pixelCoordinates[d];
+ y = pixelCoordinates[d + 1];
+ roundX = (x + 0.5) | 0;
+ roundY = (y + 0.5) | 0;
+ if (roundX !== prevX || roundY !== prevY) {
+ context.moveTo(x, y);
+ prevX = roundX;
+ prevY = roundY;
+ }
+ for (d += 2; d < dd; d += 2) {
+ x = pixelCoordinates[d];
+ y = pixelCoordinates[d + 1];
+ roundX = (x + 0.5) | 0;
+ roundY = (y + 0.5) | 0;
+ if (roundX !== prevX || roundY !== prevY) {
+ context.lineTo(x, y);
+ prevX = roundX;
+ prevY = roundY;
+ }
+ }
+ ++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(instruction[6],
+ '7th instruction should not be null');
+ var usePixelRatio = instruction[7] !== undefined ?
+ 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]));
+ }
+ prevX = NaN;
+ prevY = NaN;
+ ++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;
+ }
+ }
+ // assert that all instructions were consumed
+ goog.asserts.assert(i == instructions.length,
+ 'all instructions should be consumed');
+ 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.<string, boolean>} skippedFeaturesHash Ids of features
+ * to skip.
+ */
+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);
+};
+
+
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @param {number} viewRotation View rotation.
+ * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+ * to skip.
+ * @param {function((ol.Feature|ol.render.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.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);
+};
+
+
+/**
+ * @private
+ */
+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;
+ }
+ }
+};
+
+
+/**
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.canvas.Replay.prototype.endGeometry = function(geometry, feature) {
+ goog.asserts.assert(this.beginGeometryInstruction1_,
+ 'this.beginGeometryInstruction1_ should not be null');
+ this.beginGeometryInstruction1_[2] = this.instructions.length;
+ this.beginGeometryInstruction1_ = null;
+ goog.asserts.assert(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);
+};
+
+
+/**
+ * FIXME empty description for jsdoc
+ */
+ol.render.canvas.Replay.prototype.finish = ol.nullFunction;
+
+
+/**
+ * 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.render.canvas.Replay.prototype.getBufferedMaxExtent = function() {
+ return this.maxExtent;
+};
+
+
+
+/**
+ * @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.ImageReplay = function(tolerance, maxExtent, resolution) {
+ goog.base(this, tolerance, maxExtent, resolution);
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement|HTMLVideoElement|Image}
+ */
+ this.hitDetectionImage_ = null;
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement|HTMLVideoElement|Image}
+ */
+ this.image_ = null;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.anchorX_ = undefined;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.anchorY_ = undefined;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.height_ = undefined;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.opacity_ = undefined;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.originX_ = undefined;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.originY_ = undefined;
+
+ /**
+ * @private
+ * @type {boolean|undefined}
+ */
+ this.rotateWithView_ = undefined;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.rotation_ = undefined;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.scale_ = undefined;
+
+ /**
+ * @private
+ * @type {boolean|undefined}
+ */
+ this.snapToPixel_ = undefined;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.width_ = undefined;
+
+};
+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.
+ */
+ol.render.canvas.ImageReplay.prototype.drawCoordinates_ =
+ function(flatCoordinates, offset, end, stride) {
+ return this.appendFlatCoordinates(
+ flatCoordinates, offset, end, stride, false);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.ImageReplay.prototype.drawPointGeometry =
+ function(pointGeometry, feature) {
+ if (!this.image_) {
+ return;
+ }
+ goog.asserts.assert(this.anchorX_ !== undefined,
+ 'this.anchorX_ should be defined');
+ goog.asserts.assert(this.anchorY_ !== undefined,
+ 'this.anchorY_ should be defined');
+ goog.asserts.assert(this.height_ !== undefined,
+ 'this.height_ should be defined');
+ goog.asserts.assert(this.opacity_ !== undefined,
+ 'this.opacity_ should be defined');
+ goog.asserts.assert(this.originX_ !== undefined,
+ 'this.originX_ should be defined');
+ goog.asserts.assert(this.originY_ !== undefined,
+ 'this.originY_ should be defined');
+ goog.asserts.assert(this.rotateWithView_ !== undefined,
+ 'this.rotateWithView_ should be defined');
+ goog.asserts.assert(this.rotation_ !== undefined,
+ 'this.rotation_ should be defined');
+ goog.asserts.assert(this.scale_ !== undefined,
+ 'this.scale_ should be defined');
+ goog.asserts.assert(this.width_ !== undefined,
+ '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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.ImageReplay.prototype.drawMultiPointGeometry =
+ function(multiPointGeometry, feature) {
+ if (!this.image_) {
+ return;
+ }
+ goog.asserts.assert(this.anchorX_ !== undefined,
+ 'this.anchorX_ should be defined');
+ goog.asserts.assert(this.anchorY_ !== undefined,
+ 'this.anchorY_ should be defined');
+ goog.asserts.assert(this.height_ !== undefined,
+ 'this.height_ should be defined');
+ goog.asserts.assert(this.opacity_ !== undefined,
+ 'this.opacity_ should be defined');
+ goog.asserts.assert(this.originX_ !== undefined,
+ 'this.originX_ should be defined');
+ goog.asserts.assert(this.originY_ !== undefined,
+ 'this.originY_ should be defined');
+ goog.asserts.assert(this.rotateWithView_ !== undefined,
+ 'this.rotateWithView_ should be defined');
+ goog.asserts.assert(this.rotation_ !== undefined,
+ 'this.rotation_ should be defined');
+ goog.asserts.assert(this.scale_ !== undefined,
+ 'this.scale_ should be defined');
+ goog.asserts.assert(this.width_ !== undefined,
+ '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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+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;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) {
+ goog.asserts.assert(imageStyle, 'imageStyle should not be null');
+ var anchor = imageStyle.getAnchor();
+ goog.asserts.assert(anchor, 'anchor should not be null');
+ var size = imageStyle.getSize();
+ goog.asserts.assert(size, 'size should not be null');
+ var hitDetectionImage = imageStyle.getHitDetectionImage(1);
+ goog.asserts.assert(hitDetectionImage,
+ 'hitDetectionImage should not be null');
+ var image = imageStyle.getImage(1);
+ goog.asserts.assert(image, 'image should not be null');
+ var origin = imageStyle.getOrigin();
+ goog.asserts.assert(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];
+};
+
+
+
+/**
+ * @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.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
+ };
+
+};
+goog.inherits(ol.render.canvas.LineStringReplay, 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} 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;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.LineStringReplay.prototype.getBufferedMaxExtent = function() {
+ if (!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_;
+};
+
+
+/**
+ * @private
+ */
+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(strokeStyle !== undefined,
+ 'strokeStyle should be defined');
+ goog.asserts.assert(lineCap !== undefined, 'lineCap should be defined');
+ goog.asserts.assert(lineDash, 'lineDash should not be null');
+ goog.asserts.assert(lineJoin !== undefined, 'lineJoin should be defined');
+ goog.asserts.assert(lineWidth !== undefined, 'lineWidth should be defined');
+ goog.asserts.assert(miterLimit !== undefined, '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;
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.LineStringReplay.prototype.drawLineStringGeometry =
+ function(lineStringGeometry, feature) {
+ var state = this.state_;
+ goog.asserts.assert(state, 'state should not be null');
+ var strokeStyle = state.strokeStyle;
+ var lineWidth = state.lineWidth;
+ if (strokeStyle === undefined || lineWidth === undefined) {
+ 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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.LineStringReplay.prototype.drawMultiLineStringGeometry =
+ function(multiLineStringGeometry, feature) {
+ var state = this.state_;
+ goog.asserts.assert(state, 'state should not be null');
+ var strokeStyle = state.strokeStyle;
+ var lineWidth = state.lineWidth;
+ if (strokeStyle === undefined || lineWidth === undefined) {
+ 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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.LineStringReplay.prototype.finish = function() {
+ var state = this.state_;
+ goog.asserts.assert(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;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle =
+ function(fillStyle, strokeStyle) {
+ goog.asserts.assert(this.state_, 'this.state_ should not be null');
+ goog.asserts.assert(!fillStyle, 'fillStyle should be null');
+ goog.asserts.assert(strokeStyle, 'strokeStyle should not be null');
+ var strokeStyleColor = strokeStyle.getColor();
+ this.state_.strokeStyle = ol.color.asString(strokeStyleColor ?
+ strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
+ var strokeStyleLineCap = strokeStyle.getLineCap();
+ this.state_.lineCap = strokeStyleLineCap !== undefined ?
+ strokeStyleLineCap : ol.render.canvas.defaultLineCap;
+ var strokeStyleLineDash = strokeStyle.getLineDash();
+ this.state_.lineDash = strokeStyleLineDash ?
+ strokeStyleLineDash : ol.render.canvas.defaultLineDash;
+ var strokeStyleLineJoin = strokeStyle.getLineJoin();
+ this.state_.lineJoin = strokeStyleLineJoin !== undefined ?
+ strokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
+ var strokeStyleWidth = strokeStyle.getWidth();
+ this.state_.lineWidth = strokeStyleWidth !== undefined ?
+ strokeStyleWidth : ol.render.canvas.defaultLineWidth;
+ var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
+ this.state_.miterLimit = strokeStyleMiterLimit !== undefined ?
+ strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
+
+ if (this.state_.lineWidth > this.maxLineWidth) {
+ this.maxLineWidth = this.state_.lineWidth;
+ // invalidate the buffered max extent cache
+ this.bufferedMaxExtent_ = null;
+ }
+};
+
+
+
+/**
+ * @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) {
+
+ 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
+ };
+
+};
+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.
+ */
+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 (state.fillStyle !== undefined) {
+ this.instructions.push(fillInstruction);
+ }
+ if (state.strokeStyle !== undefined) {
+ goog.asserts.assert(state.lineWidth !== undefined,
+ 'state.lineWidth should be defined');
+ var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
+ this.instructions.push(strokeInstruction);
+ this.hitDetectionInstructions.push(strokeInstruction);
+ }
+ return offset;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.PolygonReplay.prototype.drawCircleGeometry =
+ function(circleGeometry, feature) {
+ var state = this.state_;
+ goog.asserts.assert(state, 'state should not be null');
+ var fillStyle = state.fillStyle;
+ var strokeStyle = state.strokeStyle;
+ if (fillStyle === undefined && strokeStyle === undefined) {
+ return;
+ }
+ if (strokeStyle !== undefined) {
+ goog.asserts.assert(state.lineWidth !== undefined,
+ '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 (state.strokeStyle !== undefined) {
+ 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 (state.fillStyle !== undefined) {
+ this.instructions.push(fillInstruction);
+ }
+ if (state.strokeStyle !== undefined) {
+ goog.asserts.assert(state.lineWidth !== undefined,
+ 'state.lineWidth should be defined');
+ var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
+ this.instructions.push(strokeInstruction);
+ this.hitDetectionInstructions.push(strokeInstruction);
+ }
+ this.endGeometry(circleGeometry, feature);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.PolygonReplay.prototype.drawPolygonGeometry =
+ function(polygonGeometry, feature) {
+ var state = this.state_;
+ goog.asserts.assert(state, 'state should not be null');
+ var fillStyle = state.fillStyle;
+ var strokeStyle = state.strokeStyle;
+ if (fillStyle === undefined && strokeStyle === undefined) {
+ return;
+ }
+ if (strokeStyle !== undefined) {
+ goog.asserts.assert(state.lineWidth !== undefined,
+ '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 (state.strokeStyle !== undefined) {
+ 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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.PolygonReplay.prototype.drawMultiPolygonGeometry =
+ function(multiPolygonGeometry, feature) {
+ var state = this.state_;
+ goog.asserts.assert(state, 'state should not be null');
+ var fillStyle = state.fillStyle;
+ var strokeStyle = state.strokeStyle;
+ if (fillStyle === undefined && strokeStyle === undefined) {
+ return;
+ }
+ if (strokeStyle !== undefined) {
+ goog.asserts.assert(state.lineWidth !== undefined,
+ '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 (state.strokeStyle !== undefined) {
+ 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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.PolygonReplay.prototype.finish = function() {
+ goog.asserts.assert(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);
+ }
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.PolygonReplay.prototype.getBufferedMaxExtent = function() {
+ if (!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_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle =
+ function(fillStyle, strokeStyle) {
+ goog.asserts.assert(this.state_, 'this.state_ should not be null');
+ goog.asserts.assert(fillStyle || strokeStyle,
+ 'fillStyle or strokeStyle should not be null');
+ var state = this.state_;
+ if (fillStyle) {
+ var fillStyleColor = fillStyle.getColor();
+ state.fillStyle = ol.color.asString(fillStyleColor ?
+ fillStyleColor : ol.render.canvas.defaultFillStyle);
+ } else {
+ state.fillStyle = undefined;
+ }
+ if (strokeStyle) {
+ var strokeStyleColor = strokeStyle.getColor();
+ state.strokeStyle = ol.color.asString(strokeStyleColor ?
+ strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
+ var strokeStyleLineCap = strokeStyle.getLineCap();
+ state.lineCap = strokeStyleLineCap !== undefined ?
+ strokeStyleLineCap : ol.render.canvas.defaultLineCap;
+ var strokeStyleLineDash = strokeStyle.getLineDash();
+ state.lineDash = strokeStyleLineDash ?
+ strokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash;
+ var strokeStyleLineJoin = strokeStyle.getLineJoin();
+ state.lineJoin = strokeStyleLineJoin !== undefined ?
+ strokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
+ var strokeStyleWidth = strokeStyle.getWidth();
+ state.lineWidth = strokeStyleWidth !== undefined ?
+ strokeStyleWidth : ol.render.canvas.defaultLineWidth;
+ var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
+ state.miterLimit = strokeStyleMiterLimit !== undefined ?
+ strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
+
+ 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;
+ }
+};
+
+
+/**
+ * @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 (fillStyle !== undefined && state.currentFillStyle != fillStyle) {
+ this.instructions.push(
+ [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle]);
+ state.currentFillStyle = state.fillStyle;
+ }
+ if (strokeStyle !== undefined) {
+ goog.asserts.assert(lineCap !== undefined, 'lineCap should be defined');
+ goog.asserts.assert(lineDash, 'lineDash should not be null');
+ goog.asserts.assert(lineJoin !== undefined, 'lineJoin should be defined');
+ goog.asserts.assert(lineWidth !== undefined, 'lineWidth should be defined');
+ goog.asserts.assert(miterLimit !== undefined,
+ '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;
+ }
+ }
+};
+
+
+
+/**
+ * @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.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;
+
+ /**
+ * @private
+ * @type {?ol.render.canvas.TextState}
+ */
+ this.replayTextState_ = null;
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.text_ = '';
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.textOffsetX_ = 0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.textOffsetY_ = 0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.textRotation_ = 0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.textScale_ = 0;
+
+ /**
+ * @private
+ * @type {?ol.render.canvas.FillState}
+ */
+ this.textFillState_ = null;
+
+ /**
+ * @private
+ * @type {?ol.render.canvas.StrokeState}
+ */
+ this.textStrokeState_ = null;
+
+ /**
+ * @private
+ * @type {?ol.render.canvas.TextState}
+ */
+ this.textState_ = null;
+
+};
+goog.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay);
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.TextReplay.prototype.drawText =
+ function(flatCoordinates, offset, end, stride, geometry, feature) {
+ if (this.text_ === '' || !this.textState_ ||
+ (!this.textFillState_ && !this.textStrokeState_)) {
+ return;
+ }
+ if (this.textFillState_) {
+ this.setReplayFillState_(this.textFillState_);
+ }
+ if (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 = !!this.textFillState_;
+ var stroke = !!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);
+};
+
+
+/**
+ * @param {ol.render.canvas.FillState} fillState Fill state.
+ * @private
+ */
+ol.render.canvas.TextReplay.prototype.setReplayFillState_ =
+ function(fillState) {
+ var replayFillState = this.replayFillState_;
+ if (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 (!replayFillState) {
+ this.replayFillState_ = {
+ fillStyle: fillState.fillStyle
+ };
+ } else {
+ replayFillState.fillStyle = fillState.fillStyle;
+ }
+};
+
+
+/**
+ * @param {ol.render.canvas.StrokeState} strokeState Stroke state.
+ * @private
+ */
+ol.render.canvas.TextReplay.prototype.setReplayStrokeState_ =
+ function(strokeState) {
+ var replayStrokeState = this.replayStrokeState_;
+ if (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 (!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;
+ }
+};
+
+
+/**
+ * @param {ol.render.canvas.TextState} textState Text state.
+ * @private
+ */
+ol.render.canvas.TextReplay.prototype.setReplayTextState_ =
+ function(textState) {
+ var replayTextState = this.replayTextState_;
+ if (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 (!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;
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) {
+ if (!textStyle) {
+ this.text_ = '';
+ } else {
+ var textFillStyle = textStyle.getFill();
+ if (!textFillStyle) {
+ this.textFillState_ = null;
+ } else {
+ var textFillStyleColor = textFillStyle.getColor();
+ var fillStyle = ol.color.asString(textFillStyleColor ?
+ textFillStyleColor : ol.render.canvas.defaultFillStyle);
+ if (!this.textFillState_) {
+ this.textFillState_ = {
+ fillStyle: fillStyle
+ };
+ } else {
+ var textFillState = this.textFillState_;
+ textFillState.fillStyle = fillStyle;
+ }
+ }
+ var textStrokeStyle = textStyle.getStroke();
+ if (!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 = textStrokeStyleLineCap !== undefined ?
+ textStrokeStyleLineCap : ol.render.canvas.defaultLineCap;
+ var lineDash = textStrokeStyleLineDash ?
+ textStrokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash;
+ var lineJoin = textStrokeStyleLineJoin !== undefined ?
+ textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
+ var lineWidth = textStrokeStyleWidth !== undefined ?
+ textStrokeStyleWidth : ol.render.canvas.defaultLineWidth;
+ var miterLimit = textStrokeStyleMiterLimit !== undefined ?
+ textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
+ var strokeStyle = ol.color.asString(textStrokeStyleColor ?
+ textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle);
+ if (!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 = textFont !== undefined ?
+ textFont : ol.render.canvas.defaultFont;
+ var textAlign = textTextAlign !== undefined ?
+ textTextAlign : ol.render.canvas.defaultTextAlign;
+ var textBaseline = textTextBaseline !== undefined ?
+ textTextBaseline : ol.render.canvas.defaultTextBaseline;
+ if (!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_ = textText !== undefined ? textText : '';
+ this.textOffsetX_ = textOffsetX !== undefined ? textOffsetX : 0;
+ this.textOffsetY_ = textOffsetY !== undefined ? textOffsetY : 0;
+ this.textRotation_ = textRotation !== undefined ? textRotation : 0;
+ this.textScale_ = textScale !== undefined ? textScale : 1;
+ }
+};
+
+
+
+/**
+ * @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.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();
+
+};
+
+
+/**
+ * FIXME empty description for jsdoc
+ */
+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();
+ }
+ }
+};
+
+
+/**
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} resolution Resolution.
+ * @param {number} rotation Rotation.
+ * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+ * to skip.
+ * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature
+ * callback.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+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 (this.renderBuffer_ !== undefined) {
+ 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|ol.render.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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.ReplayGroup.prototype.getReplay =
+ function(zIndex, replayType) {
+ var zIndexKey = zIndex !== undefined ? zIndex.toString() : '0';
+ var replays = this.replaysByZIndex_[zIndexKey];
+ if (replays === undefined) {
+ replays = {};
+ this.replaysByZIndex_[zIndexKey] = replays;
+ }
+ var replay = replays[replayType];
+ if (replay === undefined) {
+ var Constructor = ol.render.canvas.BATCH_CONSTRUCTORS_[replayType];
+ goog.asserts.assert(Constructor !== undefined,
+ 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.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.<string, boolean>} skippedFeaturesHash Ids of features
+ * to skip.
+ */
+ol.render.canvas.ReplayGroup.prototype.replay = function(
+ context, pixelRatio, transform, viewRotation, skippedFeaturesHash) {
+
+ /** @type {Array.<number>} */
+ var zs = Object.keys(this.replaysByZIndex_).map(Number);
+ zs.sort(ol.array.numberSafeCompareFunction);
+
+ // 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();
+
+ var i, ii, j, jj, replays, replay;
+ 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 (replay !== undefined) {
+ replay.replay(context, pixelRatio, transform, viewRotation,
+ skippedFeaturesHash);
+ }
+ }
+ }
+
+ context.restore();
+};
+
+
+/**
+ * @private
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @param {number} viewRotation View rotation.
+ * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+ * to skip.
+ * @param {function((ol.Feature|ol.render.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.ReplayGroup.prototype.replayHitDetection_ = function(
+ context, transform, viewRotation, skippedFeaturesHash,
+ featureCallback, opt_hitExtent) {
+ /** @type {Array.<number>} */
+ var zs = Object.keys(this.replaysByZIndex_).map(Number);
+ zs.sort(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 (replay !== undefined) {
+ result = replay.replayHitDetection(context, transform, viewRotation,
+ skippedFeaturesHash, featureCallback, opt_hitExtent);
+ if (result) {
+ return result;
+ }
+ }
+ }
+ }
+ 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.render.Feature');
+
+
+goog.require('goog.asserts');
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryType');
+
+
+
+/**
+ * Lightweight, read-only, {@link ol.Feature} and {@link ol.geom.Geometry} like
+ * structure, optimized for rendering and styling. Geometry access through the
+ * API is limited to getting the type and extent of the geometry.
+ *
+ * @constructor
+ * @param {ol.geom.GeometryType} type Geometry type.
+ * @param {Array.<number>} flatCoordinates Flat coordinates. These always need
+ * to be right-handed for polygons.
+ * @param {Array.<number>|Array.<Array.<number>>} ends Ends or Endss.
+ * @param {Object.<string, *>} properties Properties.
+ */
+ol.render.Feature = function(type, flatCoordinates, ends, properties) {
+
+ /**
+ * @private
+ * @type {ol.Extent|undefined}
+ */
+ this.extent_;
+
+ goog.asserts.assert(type === ol.geom.GeometryType.POINT ||
+ type === ol.geom.GeometryType.MULTI_POINT ||
+ type === ol.geom.GeometryType.LINE_STRING ||
+ type === ol.geom.GeometryType.MULTI_LINE_STRING ||
+ type === ol.geom.GeometryType.POLYGON,
+ 'Need a Point, MultiPoint, LineString, MultiLineString or Polygon type');
+
+ /**
+ * @private
+ * @type {ol.geom.GeometryType}
+ */
+ this.type_ = type;
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.flatCoordinates_ = flatCoordinates;
+
+ /**
+ * @private
+ * @type {Array.<number>|Array.<Array.<number>>}
+ */
+ this.ends_ = ends;
+
+ /**
+ * @private
+ * @type {Object.<string, *>}
+ */
+ this.properties_ = properties;
+
+};
+
+
+/**
+ * Get a feature property by its key.
+ * @param {string} key Key
+ * @return {*} Value for the requested key.
+ * @api
+ */
+ol.render.Feature.prototype.get = function(key) {
+ return this.properties_[key];
+};
+
+
+/**
+ * @return {Array.<number>|Array.<Array.<number>>} Ends or endss.
+ */
+ol.render.Feature.prototype.getEnds = function() {
+ return this.ends_;
+};
+
+
+/**
+ * Get the extent of this feature's geometry.
+ * @return {ol.Extent} Extent.
+ * @api
+ */
+ol.render.Feature.prototype.getExtent = function() {
+ if (!this.extent_) {
+ this.extent_ = this.type_ === ol.geom.GeometryType.POINT ?
+ ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates_) :
+ ol.extent.createOrUpdateFromFlatCoordinates(
+ this.flatCoordinates_, 0, this.flatCoordinates_.length, 2);
+
+ }
+ return this.extent_;
+};
+
+
+/**
+ * @return {Array.<number>} Flat coordinates.
+ */
+ol.render.Feature.prototype.getFlatCoordinates =
+ ol.render.Feature.prototype.getOrientedFlatCoordinates = function() {
+ return this.flatCoordinates_;
+};
+
+
+/**
+ * Get the feature for working with its geometry.
+ * @return {ol.render.Feature} Feature.
+ * @api
+ */
+ol.render.Feature.prototype.getGeometry = function() {
+ return this;
+};
+
+
+/**
+ * Get the feature properties.
+ * @return {Object.<string, *>} Feature properties.
+ * @api
+ */
+ol.render.Feature.prototype.getProperties = function() {
+ return this.properties_;
+};
+
+
+/**
+ * Get the feature for working with its geometry.
+ * @return {ol.render.Feature} Feature.
+ */
+ol.render.Feature.prototype.getSimplifiedGeometry =
+ ol.render.Feature.prototype.getGeometry;
+
+
+/**
+ * @return {number} Stride.
+ */
+ol.render.Feature.prototype.getStride = function() {
+ return 2;
+};
+
+
+/**
+ * @return {undefined}
+ */
+ol.render.Feature.prototype.getStyleFunction = ol.nullFunction;
+
+
+/**
+ * Get the type of this feature's geometry.
+ * @return {ol.geom.GeometryType} Geometry type.
+ * @api
+ */
+ol.render.Feature.prototype.getType = function() {
+ return this.type_;
+};
+
+goog.provide('ol.renderer.vector');
+
+goog.require('goog.asserts');
+goog.require('ol.render.Feature');
+goog.require('ol.render.IReplayGroup');
+goog.require('ol.style.ImageState');
+goog.require('ol.style.Style');
+
+
+/**
+ * @param {ol.Feature|ol.render.Feature} feature1 Feature 1.
+ * @param {ol.Feature|ol.render.Feature} feature2 Feature 2.
+ * @return {number} Order.
+ */
+ol.renderer.vector.defaultOrder = function(feature1, feature2) {
+ return goog.getUid(feature1) - goog.getUid(feature2);
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Circle} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
+ * @private
+ */
+ol.renderer.vector.renderCircleGeometry_ =
+ function(replayGroup, geometry, style, feature) {
+ var fillStyle = style.getFill();
+ var strokeStyle = style.getStroke();
+ if (fillStyle || strokeStyle) {
+ var polygonReplay = replayGroup.getReplay(
+ style.getZIndex(), ol.render.ReplayType.POLYGON);
+ polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
+ polygonReplay.drawCircleGeometry(geometry, feature);
+ }
+ var textStyle = style.getText();
+ if (textStyle) {
+ var textReplay = replayGroup.getReplay(
+ style.getZIndex(), ol.render.ReplayType.TEXT);
+ textReplay.setTextStyle(textStyle);
+ textReplay.drawText(geometry.getCenter(), 0, 2, 2, geometry, feature);
+ }
+};
+
+
+/**
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.Feature|ol.render.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.vector.renderFeature = function(
+ replayGroup, feature, style, squaredTolerance, listener, thisArg) {
+ var loading = false;
+ var imageStyle, imageState;
+ imageStyle = style.getImage();
+ if (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;
+};
+
+
+/**
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.Feature|ol.render.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 (!geometry) {
+ return;
+ }
+ var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
+ var geometryRenderer =
+ ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()];
+ goog.asserts.assert(geometryRenderer !== undefined,
+ 'geometryRenderer should be defined');
+ geometryRenderer(replayGroup, simplifiedGeometry, style, feature);
+};
+
+
+/**
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.GeometryCollection} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
+ * @private
+ */
+ol.renderer.vector.renderGeometryCollectionGeometry_ =
+ function(replayGroup, geometry, style, feature) {
+ 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(geometryRenderer !== undefined,
+ 'geometryRenderer should be defined');
+ geometryRenderer(replayGroup, geometries[i], style, feature);
+ }
+};
+
+
+/**
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.LineString|ol.render.Feature} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @private
+ */
+ol.renderer.vector.renderLineStringGeometry_ =
+ function(replayGroup, geometry, style, feature) {
+ var strokeStyle = style.getStroke();
+ if (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 (textStyle) {
+ var textReplay = replayGroup.getReplay(
+ style.getZIndex(), ol.render.ReplayType.TEXT);
+ textReplay.setTextStyle(textStyle);
+ textReplay.drawText(geometry.getFlatMidpoint(), 0, 2, 2, geometry, feature);
+ }
+};
+
+
+/**
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.MultiLineString|ol.render.Feature} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @private
+ */
+ol.renderer.vector.renderMultiLineStringGeometry_ =
+ function(replayGroup, geometry, style, feature) {
+ var strokeStyle = style.getStroke();
+ if (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 (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);
+ }
+};
+
+
+/**
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.MultiPolygon} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
+ * @private
+ */
+ol.renderer.vector.renderMultiPolygonGeometry_ =
+ function(replayGroup, geometry, style, feature) {
+ var fillStyle = style.getFill();
+ var strokeStyle = style.getStroke();
+ if (strokeStyle || fillStyle) {
+ var polygonReplay = replayGroup.getReplay(
+ style.getZIndex(), ol.render.ReplayType.POLYGON);
+ polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
+ polygonReplay.drawMultiPolygonGeometry(geometry, feature);
+ }
+ var textStyle = style.getText();
+ if (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);
+ }
+};
+
+
+/**
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Point|ol.render.Feature} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @private
+ */
+ol.renderer.vector.renderPointGeometry_ =
+ function(replayGroup, geometry, style, feature) {
+ var imageStyle = style.getImage();
+ if (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 (textStyle) {
+ var textReplay = replayGroup.getReplay(
+ style.getZIndex(), ol.render.ReplayType.TEXT);
+ textReplay.setTextStyle(textStyle);
+ textReplay.drawText(geometry.getFlatCoordinates(), 0, 2, 2, geometry,
+ feature);
+ }
+};
+
+
+/**
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.MultiPoint|ol.render.Feature} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @private
+ */
+ol.renderer.vector.renderMultiPointGeometry_ =
+ function(replayGroup, geometry, style, feature) {
+ var imageStyle = style.getImage();
+ if (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 (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);
+ }
+};
+
+
+/**
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Polygon|ol.render.Feature} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @private
+ */
+ol.renderer.vector.renderPolygonGeometry_ =
+ function(replayGroup, geometry, style, feature) {
+ var fillStyle = style.getFill();
+ var strokeStyle = style.getStroke();
+ if (fillStyle || strokeStyle) {
+ var polygonReplay = replayGroup.getReplay(
+ style.getZIndex(), ol.render.ReplayType.POLYGON);
+ polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
+ polygonReplay.drawPolygonGeometry(geometry, feature);
+ }
+ var textStyle = style.getText();
+ if (textStyle) {
+ var textReplay = replayGroup.getReplay(
+ style.getZIndex(), ol.render.ReplayType.TEXT);
+ textReplay.setTextStyle(textStyle);
+ textReplay.drawText(
+ geometry.getFlatInteriorPoint(), 0, 2, 2, geometry, feature);
+ }
+};
+
+
+/**
+ * @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_
+};
+
+goog.provide('ol.ImageCanvas');
+
+goog.require('goog.asserts');
+goog.require('ol.ImageBase');
+goog.require('ol.ImageState');
+
+
+
+/**
+ * @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.
+ * @param {ol.ImageCanvasLoader=} opt_loader Optional loader function to
+ * support asynchronous canvas drawing.
+ */
+ol.ImageCanvas = function(extent, resolution, pixelRatio, attributions,
+ canvas, opt_loader) {
+
+ /**
+ * Optional canvas loader function.
+ * @type {?ol.ImageCanvasLoader}
+ * @private
+ */
+ this.loader_ = opt_loader !== undefined ? opt_loader : null;
+
+ var state = opt_loader !== undefined ?
+ ol.ImageState.IDLE : ol.ImageState.LOADED;
+
+ goog.base(this, extent, resolution, pixelRatio, state, attributions);
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement}
+ */
+ this.canvas_ = canvas;
+
+ /**
+ * @private
+ * @type {Error}
+ */
+ this.error_ = null;
+
+};
+goog.inherits(ol.ImageCanvas, ol.ImageBase);
+
+
+/**
+ * Get any error associated with asynchronous rendering.
+ * @return {Error} Any error that occurred during rendering.
+ */
+ol.ImageCanvas.prototype.getError = function() {
+ return this.error_;
+};
+
+
+/**
+ * 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();
+};
+
+
+/**
+ * Trigger drawing on canvas.
+ */
+ol.ImageCanvas.prototype.load = function() {
+ if (this.state == ol.ImageState.IDLE) {
+ goog.asserts.assert(this.loader_, 'this.loader_ must be set');
+ this.state = ol.ImageState.LOADING;
+ this.changed();
+ this.loader_(goog.bind(this.handleLoad_, this));
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.ImageCanvas.prototype.getImage = function(opt_context) {
+ return this.canvas_;
+};
+
+
+/**
+ * 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.reproj');
+
+goog.require('goog.labs.userAgent.browser');
+goog.require('goog.labs.userAgent.platform');
+goog.require('goog.math');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.math');
+goog.require('ol.proj');
+
+
+/**
+ * We need to employ more sophisticated solution
+ * if the web browser antialiases clipping edges on canvas.
+ *
+ * Currently only Chrome does not antialias the edges, but this is probably
+ * going to be "fixed" in the future: http://crbug.com/424291
+ *
+ * @type {boolean}
+ * @private
+ */
+ol.reproj.browserAntialiasesClip_ = !goog.labs.userAgent.browser.isChrome() ||
+ goog.labs.userAgent.platform.isIos();
+
+
+/**
+ * Calculates ideal resolution to use from the source in order to achieve
+ * pixel mapping as close as possible to 1:1 during reprojection.
+ * The resolution is calculated regardless of what resolutions
+ * are actually available in the dataset (TileGrid, Image, ...).
+ *
+ * @param {ol.proj.Projection} sourceProj Source projection.
+ * @param {ol.proj.Projection} targetProj Target projection.
+ * @param {ol.Coordinate} targetCenter Target center.
+ * @param {number} targetResolution Target resolution.
+ * @return {number} The best resolution to use. Can be +-Infinity, NaN or 0.
+ */
+ol.reproj.calculateSourceResolution = function(sourceProj, targetProj,
+ targetCenter, targetResolution) {
+
+ var sourceCenter = ol.proj.transform(targetCenter, targetProj, sourceProj);
+
+ // calculate the ideal resolution of the source data
+ var sourceResolution =
+ targetProj.getPointResolution(targetResolution, targetCenter);
+
+ var targetMetersPerUnit = targetProj.getMetersPerUnit();
+ if (targetMetersPerUnit !== undefined) {
+ sourceResolution *= targetMetersPerUnit;
+ }
+ var sourceMetersPerUnit = sourceProj.getMetersPerUnit();
+ if (sourceMetersPerUnit !== undefined) {
+ sourceResolution /= sourceMetersPerUnit;
+ }
+
+ // Based on the projection properties, the point resolution at the specified
+ // coordinates may be slightly different. We need to reverse-compensate this
+ // in order to achieve optimal results.
+
+ var compensationFactor =
+ sourceProj.getPointResolution(sourceResolution, sourceCenter) /
+ sourceResolution;
+
+ if (goog.math.isFiniteNumber(compensationFactor) && compensationFactor > 0) {
+ sourceResolution /= compensationFactor;
+ }
+
+ return sourceResolution;
+};
+
+
+/**
+ * Enlarge the clipping triangle point by 1 pixel to ensure the edges overlap
+ * in order to mask gaps caused by antialiasing.
+ *
+ * @param {number} centroidX Centroid of the triangle (x coordinate in pixels).
+ * @param {number} centroidY Centroid of the triangle (y coordinate in pixels).
+ * @param {number} x X coordinate of the point (in pixels).
+ * @param {number} y Y coordinate of the point (in pixels).
+ * @return {ol.Coordinate} New point 1 px farther from the centroid.
+ * @private
+ */
+ol.reproj.enlargeClipPoint_ = function(centroidX, centroidY, x, y) {
+ var dX = x - centroidX, dY = y - centroidY;
+ var distance = Math.sqrt(dX * dX + dY * dY);
+ return [Math.round(x + dX / distance), Math.round(y + dY / distance)];
+};
+
+
+/**
+ * Renders the source data into new canvas based on the triangulation.
+ *
+ * @param {number} width Width of the canvas.
+ * @param {number} height Height of the canvas.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {number} sourceResolution Source resolution.
+ * @param {ol.Extent} sourceExtent Extent of the data source.
+ * @param {number} targetResolution Target resolution.
+ * @param {ol.Extent} targetExtent Target extent.
+ * @param {ol.reproj.Triangulation} triangulation Calculated triangulation.
+ * @param {Array.<{extent: ol.Extent,
+ * image: (HTMLCanvasElement|Image|HTMLVideoElement)}>} sources
+ * Array of sources.
+ * @param {boolean=} opt_renderEdges Render reprojection edges.
+ * @return {HTMLCanvasElement} Canvas with reprojected data.
+ */
+ol.reproj.render = function(width, height, pixelRatio,
+ sourceResolution, sourceExtent, targetResolution, targetExtent,
+ triangulation, sources, opt_renderEdges) {
+
+ var context = ol.dom.createCanvasContext2D(Math.round(pixelRatio * width),
+ Math.round(pixelRatio * height));
+
+ if (sources.length === 0) {
+ return context.canvas;
+ }
+
+ context.scale(pixelRatio, pixelRatio);
+
+ var sourceDataExtent = ol.extent.createEmpty();
+ sources.forEach(function(src, i, arr) {
+ ol.extent.extend(sourceDataExtent, src.extent);
+ });
+
+ var canvasWidthInUnits = ol.extent.getWidth(sourceDataExtent);
+ var canvasHeightInUnits = ol.extent.getHeight(sourceDataExtent);
+ var stitchContext = ol.dom.createCanvasContext2D(
+ Math.round(pixelRatio * canvasWidthInUnits / sourceResolution),
+ Math.round(pixelRatio * canvasHeightInUnits / sourceResolution));
+
+ stitchContext.scale(pixelRatio / sourceResolution,
+ pixelRatio / sourceResolution);
+ stitchContext.translate(-sourceDataExtent[0], sourceDataExtent[3]);
+
+ sources.forEach(function(src, i, arr) {
+ var xPos = src.extent[0];
+ var yPos = -src.extent[3];
+ var srcWidth = ol.extent.getWidth(src.extent);
+ var srcHeight = ol.extent.getHeight(src.extent);
+
+ stitchContext.drawImage(src.image, xPos, yPos, srcWidth, srcHeight);
+ });
+
+ var targetTopLeft = ol.extent.getTopLeft(targetExtent);
+
+ triangulation.getTriangles().forEach(function(triangle, i, arr) {
+ /* Calculate affine transform (src -> dst)
+ * Resulting matrix can be used to transform coordinate
+ * from `sourceProjection` to destination pixels.
+ *
+ * To optimize number of context calls and increase numerical stability,
+ * we also do the following operations:
+ * trans(-topLeftExtentCorner), scale(1 / targetResolution), scale(1, -1)
+ * here before solving the linear system so [ui, vi] are pixel coordinates.
+ *
+ * Src points: xi, yi
+ * Dst points: ui, vi
+ * Affine coefficients: aij
+ *
+ * | x0 y0 1 0 0 0 | |a00| |u0|
+ * | x1 y1 1 0 0 0 | |a01| |u1|
+ * | x2 y2 1 0 0 0 | x |a02| = |u2|
+ * | 0 0 0 x0 y0 1 | |a10| |v0|
+ * | 0 0 0 x1 y1 1 | |a11| |v1|
+ * | 0 0 0 x2 y2 1 | |a12| |v2|
+ */
+ var source = triangle.source, target = triangle.target;
+ var x0 = source[0][0], y0 = source[0][1],
+ x1 = source[1][0], y1 = source[1][1],
+ x2 = source[2][0], y2 = source[2][1];
+ var u0 = (target[0][0] - targetTopLeft[0]) / targetResolution,
+ v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution;
+ var u1 = (target[1][0] - targetTopLeft[0]) / targetResolution,
+ v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution;
+ var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution,
+ v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;
+
+ // Shift all the source points to improve numerical stability
+ // of all the subsequent calculations. The [x0, y0] is used here.
+ // This is also used to simplify the linear system.
+ var sourceNumericalShiftX = x0, sourceNumericalShiftY = y0;
+ x0 = 0;
+ y0 = 0;
+ x1 -= sourceNumericalShiftX;
+ y1 -= sourceNumericalShiftY;
+ x2 -= sourceNumericalShiftX;
+ y2 -= sourceNumericalShiftY;
+
+ var augmentedMatrix = [
+ [x1, y1, 0, 0, u1 - u0],
+ [x2, y2, 0, 0, u2 - u0],
+ [0, 0, x1, y1, v1 - v0],
+ [0, 0, x2, y2, v2 - v0]
+ ];
+ var affineCoefs = ol.math.solveLinearSystem(augmentedMatrix);
+ if (!affineCoefs) {
+ return;
+ }
+
+ context.save();
+ context.beginPath();
+ if (ol.reproj.browserAntialiasesClip_) {
+ var centroidX = (u0 + u1 + u2) / 3, centroidY = (v0 + v1 + v2) / 3;
+ var p0 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u0, v0);
+ var p1 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u1, v1);
+ var p2 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u2, v2);
+
+ context.moveTo(p0[0], p0[1]);
+ context.lineTo(p1[0], p1[1]);
+ context.lineTo(p2[0], p2[1]);
+ } else {
+ context.moveTo(u0, v0);
+ context.lineTo(u1, v1);
+ context.lineTo(u2, v2);
+ }
+ context.closePath();
+ context.clip();
+
+ context.transform(
+ affineCoefs[0], affineCoefs[2], affineCoefs[1], affineCoefs[3], u0, v0);
+
+ context.translate(sourceDataExtent[0] - sourceNumericalShiftX,
+ sourceDataExtent[3] - sourceNumericalShiftY);
+
+ context.scale(sourceResolution / pixelRatio,
+ -sourceResolution / pixelRatio);
+
+ context.drawImage(stitchContext.canvas, 0, 0);
+ context.restore();
+ });
+
+ if (opt_renderEdges) {
+ context.save();
+
+ context.strokeStyle = 'black';
+ context.lineWidth = 1;
+
+ triangulation.getTriangles().forEach(function(triangle, i, arr) {
+ var target = triangle.target;
+ var u0 = (target[0][0] - targetTopLeft[0]) / targetResolution,
+ v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution;
+ var u1 = (target[1][0] - targetTopLeft[0]) / targetResolution,
+ v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution;
+ var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution,
+ v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;
+
+ context.beginPath();
+ context.moveTo(u0, v0);
+ context.lineTo(u1, v1);
+ context.lineTo(u2, v2);
+ context.closePath();
+ context.stroke();
+ });
+
+ context.restore();
+ }
+ return context.canvas;
+};
+
+goog.provide('ol.reproj.Triangulation');
+
+goog.require('goog.asserts');
+goog.require('goog.math');
+goog.require('ol.extent');
+goog.require('ol.proj');
+
+
+/**
+ * Single triangle; consists of 3 source points and 3 target points.
+ *
+ * @typedef {{source: Array.<ol.Coordinate>,
+ * target: Array.<ol.Coordinate>}}
+ */
+ol.reproj.Triangle;
+
+
+
+/**
+ * @classdesc
+ * Class containing triangulation of the given target extent.
+ * Used for determining source data and the reprojection itself.
+ *
+ * @param {ol.proj.Projection} sourceProj Source projection.
+ * @param {ol.proj.Projection} targetProj Target projection.
+ * @param {ol.Extent} targetExtent Target extent to triangulate.
+ * @param {ol.Extent} maxSourceExtent Maximal source extent that can be used.
+ * @param {number} errorThreshold Acceptable error (in source units).
+ * @constructor
+ */
+ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent,
+ maxSourceExtent, errorThreshold) {
+
+ /**
+ * @type {ol.proj.Projection}
+ * @private
+ */
+ this.sourceProj_ = sourceProj;
+
+ /**
+ * @type {ol.proj.Projection}
+ * @private
+ */
+ this.targetProj_ = targetProj;
+
+ /** @type {!Object.<string, ol.Coordinate>} */
+ var transformInvCache = {};
+ var transformInv = ol.proj.getTransform(this.targetProj_, this.sourceProj_);
+
+ /**
+ * @param {ol.Coordinate} c
+ * @return {ol.Coordinate}
+ * @private
+ */
+ this.transformInv_ = function(c) {
+ var key = c[0] + '/' + c[1];
+ if (!transformInvCache[key]) {
+ transformInvCache[key] = transformInv(c);
+ }
+ return transformInvCache[key];
+ };
+
+ /**
+ * @type {ol.Extent}
+ * @private
+ */
+ this.maxSourceExtent_ = maxSourceExtent;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.errorThresholdSquared_ = errorThreshold * errorThreshold;
+
+ /**
+ * @type {Array.<ol.reproj.Triangle>}
+ * @private
+ */
+ this.triangles_ = [];
+
+ /**
+ * Indicates that the triangulation crosses edge of the source projection.
+ * @type {boolean}
+ * @private
+ */
+ this.wrapsXInSource_ = false;
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.canWrapXInSource_ = this.sourceProj_.canWrapX() &&
+ !!maxSourceExtent &&
+ !!this.sourceProj_.getExtent() &&
+ (ol.extent.getWidth(maxSourceExtent) ==
+ ol.extent.getWidth(this.sourceProj_.getExtent()));
+
+ /**
+ * @type {?number}
+ * @private
+ */
+ this.sourceWorldWidth_ = this.sourceProj_.getExtent() ?
+ ol.extent.getWidth(this.sourceProj_.getExtent()) : null;
+
+ /**
+ * @type {?number}
+ * @private
+ */
+ this.targetWorldWidth_ = this.targetProj_.getExtent() ?
+ ol.extent.getWidth(this.targetProj_.getExtent()) : null;
+
+ var destinationTopLeft = ol.extent.getTopLeft(targetExtent);
+ var destinationTopRight = ol.extent.getTopRight(targetExtent);
+ var destinationBottomRight = ol.extent.getBottomRight(targetExtent);
+ var destinationBottomLeft = ol.extent.getBottomLeft(targetExtent);
+ var sourceTopLeft = this.transformInv_(destinationTopLeft);
+ var sourceTopRight = this.transformInv_(destinationTopRight);
+ var sourceBottomRight = this.transformInv_(destinationBottomRight);
+ var sourceBottomLeft = this.transformInv_(destinationBottomLeft);
+
+ this.addQuad_(
+ destinationTopLeft, destinationTopRight,
+ destinationBottomRight, destinationBottomLeft,
+ sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft,
+ ol.RASTER_REPROJECTION_MAX_SUBDIVISION);
+
+ if (this.wrapsXInSource_) {
+ // Fix coordinates (ol.proj returns wrapped coordinates, "unwrap" here).
+ // This significantly simplifies the rest of the reprojection process.
+
+ goog.asserts.assert(this.sourceWorldWidth_ !== null);
+ var leftBound = Infinity;
+ this.triangles_.forEach(function(triangle, i, arr) {
+ leftBound = Math.min(leftBound,
+ triangle.source[0][0], triangle.source[1][0], triangle.source[2][0]);
+ });
+
+ // Shift triangles to be as close to `leftBound` as possible
+ // (if the distance is more than `worldWidth / 2` it can be closer.
+ this.triangles_.forEach(function(triangle) {
+ if (Math.max(triangle.source[0][0], triangle.source[1][0],
+ triangle.source[2][0]) - leftBound > this.sourceWorldWidth_ / 2) {
+ var newTriangle = [[triangle.source[0][0], triangle.source[0][1]],
+ [triangle.source[1][0], triangle.source[1][1]],
+ [triangle.source[2][0], triangle.source[2][1]]];
+ if ((newTriangle[0][0] - leftBound) > this.sourceWorldWidth_ / 2) {
+ newTriangle[0][0] -= this.sourceWorldWidth_;
+ }
+ if ((newTriangle[1][0] - leftBound) > this.sourceWorldWidth_ / 2) {
+ newTriangle[1][0] -= this.sourceWorldWidth_;
+ }
+ if ((newTriangle[2][0] - leftBound) > this.sourceWorldWidth_ / 2) {
+ newTriangle[2][0] -= this.sourceWorldWidth_;
+ }
+
+ // Rarely (if the extent contains both the dateline and prime meridian)
+ // the shift can in turn break some triangles.
+ // Detect this here and don't shift in such cases.
+ var minX = Math.min(
+ newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]);
+ var maxX = Math.max(
+ newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]);
+ if ((maxX - minX) < this.sourceWorldWidth_ / 2) {
+ triangle.source = newTriangle;
+ }
+ }
+ }, this);
+ }
+
+ transformInvCache = {};
+};
+
+
+/**
+ * Adds triangle to the triangulation.
+ * @param {ol.Coordinate} a
+ * @param {ol.Coordinate} b
+ * @param {ol.Coordinate} c
+ * @param {ol.Coordinate} aSrc
+ * @param {ol.Coordinate} bSrc
+ * @param {ol.Coordinate} cSrc
+ * @private
+ */
+ol.reproj.Triangulation.prototype.addTriangle_ = function(a, b, c,
+ aSrc, bSrc, cSrc) {
+ this.triangles_.push({
+ source: [aSrc, bSrc, cSrc],
+ target: [a, b, c]
+ });
+};
+
+
+/**
+ * Adds quad (points in clock-wise order) to the triangulation
+ * (and reprojects the vertices) if valid.
+ * Performs quad subdivision if needed to increase precision.
+ *
+ * @param {ol.Coordinate} a
+ * @param {ol.Coordinate} b
+ * @param {ol.Coordinate} c
+ * @param {ol.Coordinate} d
+ * @param {ol.Coordinate} aSrc
+ * @param {ol.Coordinate} bSrc
+ * @param {ol.Coordinate} cSrc
+ * @param {ol.Coordinate} dSrc
+ * @param {number} maxSubdivision Maximal allowed subdivision of the quad.
+ * @private
+ */
+ol.reproj.Triangulation.prototype.addQuad_ = function(a, b, c, d,
+ aSrc, bSrc, cSrc, dSrc, maxSubdivision) {
+
+ var sourceQuadExtent = ol.extent.boundingExtent([aSrc, bSrc, cSrc, dSrc]);
+ var sourceCoverageX = this.sourceWorldWidth_ ?
+ ol.extent.getWidth(sourceQuadExtent) / this.sourceWorldWidth_ : null;
+
+ // when the quad is wrapped in the source projection
+ // it covers most of the projection extent, but not fully
+ var wrapsX = this.sourceProj_.canWrapX() &&
+ sourceCoverageX > 0.5 && sourceCoverageX < 1;
+
+ var needsSubdivision = false;
+
+ if (maxSubdivision > 0) {
+ if (this.targetProj_.isGlobal() && this.targetWorldWidth_) {
+ var targetQuadExtent = ol.extent.boundingExtent([a, b, c, d]);
+ var targetCoverageX =
+ ol.extent.getWidth(targetQuadExtent) / this.targetWorldWidth_;
+ needsSubdivision |=
+ targetCoverageX > ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH;
+ }
+ if (!wrapsX && this.sourceProj_.isGlobal() && sourceCoverageX) {
+ needsSubdivision |=
+ sourceCoverageX > ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH;
+ }
+ }
+
+ if (!needsSubdivision && this.maxSourceExtent_) {
+ if (!ol.extent.intersects(sourceQuadExtent, this.maxSourceExtent_)) {
+ // whole quad outside source projection extent -> ignore
+ return;
+ }
+ }
+
+ if (!needsSubdivision) {
+ if (!isFinite(aSrc[0]) || !isFinite(aSrc[1]) ||
+ !isFinite(bSrc[0]) || !isFinite(bSrc[1]) ||
+ !isFinite(cSrc[0]) || !isFinite(cSrc[1]) ||
+ !isFinite(dSrc[0]) || !isFinite(dSrc[1])) {
+ if (maxSubdivision > 0) {
+ needsSubdivision = true;
+ } else {
+ return;
+ }
+ }
+ }
+
+ if (maxSubdivision > 0) {
+ if (!needsSubdivision) {
+ var center = [(a[0] + c[0]) / 2, (a[1] + c[1]) / 2];
+ var centerSrc = this.transformInv_(center);
+
+ var dx;
+ if (wrapsX) {
+ goog.asserts.assert(this.sourceWorldWidth_);
+ var centerSrcEstimX =
+ (goog.math.modulo(aSrc[0], this.sourceWorldWidth_) +
+ goog.math.modulo(cSrc[0], this.sourceWorldWidth_)) / 2;
+ dx = centerSrcEstimX -
+ goog.math.modulo(centerSrc[0], this.sourceWorldWidth_);
+ } else {
+ dx = (aSrc[0] + cSrc[0]) / 2 - centerSrc[0];
+ }
+ var dy = (aSrc[1] + cSrc[1]) / 2 - centerSrc[1];
+ var centerSrcErrorSquared = dx * dx + dy * dy;
+ needsSubdivision = centerSrcErrorSquared > this.errorThresholdSquared_;
+ }
+ if (needsSubdivision) {
+ if (Math.abs(a[0] - c[0]) <= Math.abs(a[1] - c[1])) {
+ // split horizontally (top & bottom)
+ var bc = [(b[0] + c[0]) / 2, (b[1] + c[1]) / 2];
+ var bcSrc = this.transformInv_(bc);
+ var da = [(d[0] + a[0]) / 2, (d[1] + a[1]) / 2];
+ var daSrc = this.transformInv_(da);
+
+ this.addQuad_(
+ a, b, bc, da, aSrc, bSrc, bcSrc, daSrc, maxSubdivision - 1);
+ this.addQuad_(
+ da, bc, c, d, daSrc, bcSrc, cSrc, dSrc, maxSubdivision - 1);
+ } else {
+ // split vertically (left & right)
+ var ab = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
+ var abSrc = this.transformInv_(ab);
+ var cd = [(c[0] + d[0]) / 2, (c[1] + d[1]) / 2];
+ var cdSrc = this.transformInv_(cd);
+
+ this.addQuad_(
+ a, ab, cd, d, aSrc, abSrc, cdSrc, dSrc, maxSubdivision - 1);
+ this.addQuad_(
+ ab, b, c, cd, abSrc, bSrc, cSrc, cdSrc, maxSubdivision - 1);
+ }
+ return;
+ }
+ }
+
+ if (wrapsX) {
+ if (!this.canWrapXInSource_) {
+ return;
+ }
+ this.wrapsXInSource_ = true;
+ }
+
+ this.addTriangle_(a, c, d, aSrc, cSrc, dSrc);
+ this.addTriangle_(a, b, c, aSrc, bSrc, cSrc);
+};
+
+
+/**
+ * Calculates extent of the 'source' coordinates from all the triangles.
+ *
+ * @return {ol.Extent} Calculated extent.
+ */
+ol.reproj.Triangulation.prototype.calculateSourceExtent = function() {
+ var extent = ol.extent.createEmpty();
+
+ this.triangles_.forEach(function(triangle, i, arr) {
+ var src = triangle.source;
+ ol.extent.extendCoordinate(extent, src[0]);
+ ol.extent.extendCoordinate(extent, src[1]);
+ ol.extent.extendCoordinate(extent, src[2]);
+ });
+
+ return extent;
+};
+
+
+/**
+ * @return {Array.<ol.reproj.Triangle>} Array of the calculated triangles.
+ */
+ol.reproj.Triangulation.prototype.getTriangles = function() {
+ return this.triangles_;
+};
+
+goog.provide('ol.reproj.Image');
+goog.provide('ol.reproj.ImageFunctionType');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('ol.ImageBase');
+goog.require('ol.ImageState');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.reproj');
+goog.require('ol.reproj.Triangulation');
+
+
+/**
+ * @typedef {function(ol.Extent, number, number) : ol.ImageBase}
+ */
+ol.reproj.ImageFunctionType;
+
+
+
+/**
+ * @classdesc
+ * Class encapsulating single reprojected image.
+ * See {@link ol.source.Image}.
+ *
+ * @constructor
+ * @extends {ol.ImageBase}
+ * @param {ol.proj.Projection} sourceProj Source projection (of the data).
+ * @param {ol.proj.Projection} targetProj Target projection.
+ * @param {ol.Extent} targetExtent Target extent.
+ * @param {number} targetResolution Target resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.reproj.ImageFunctionType} getImageFunction
+ * Function returning source images (extent, resolution, pixelRatio).
+ */
+ol.reproj.Image = function(sourceProj, targetProj,
+ targetExtent, targetResolution, pixelRatio, getImageFunction) {
+
+ /**
+ * @private
+ * @type {ol.proj.Projection}
+ */
+ this.targetProj_ = targetProj;
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.maxSourceExtent_ = sourceProj.getExtent();
+ var maxTargetExtent = targetProj.getExtent();
+
+ var limitedTargetExtent = maxTargetExtent ?
+ ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent;
+
+ var targetCenter = ol.extent.getCenter(limitedTargetExtent);
+ var sourceResolution = ol.reproj.calculateSourceResolution(
+ sourceProj, targetProj, targetCenter, targetResolution);
+
+ var errorThresholdInPixels = ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD;
+
+ /**
+ * @private
+ * @type {!ol.reproj.Triangulation}
+ */
+ this.triangulation_ = new ol.reproj.Triangulation(
+ sourceProj, targetProj, limitedTargetExtent, this.maxSourceExtent_,
+ sourceResolution * errorThresholdInPixels);
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.targetResolution_ = targetResolution;
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.targetExtent_ = targetExtent;
+
+ var sourceExtent = this.triangulation_.calculateSourceExtent();
+
+ /**
+ * @private
+ * @type {ol.ImageBase}
+ */
+ this.sourceImage_ =
+ getImageFunction(sourceExtent, sourceResolution, pixelRatio);
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.sourcePixelRatio_ =
+ this.sourceImage_ ? this.sourceImage_.getPixelRatio() : 1;
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement}
+ */
+ this.canvas_ = null;
+
+ /**
+ * @private
+ * @type {goog.events.Key}
+ */
+ this.sourceListenerKey_ = null;
+
+
+ var state = ol.ImageState.LOADED;
+ var attributions = [];
+
+ if (this.sourceImage_) {
+ state = ol.ImageState.IDLE;
+ attributions = this.sourceImage_.getAttributions();
+ }
+
+ goog.base(this, targetExtent, targetResolution, this.sourcePixelRatio_,
+ state, attributions);
+};
+goog.inherits(ol.reproj.Image, ol.ImageBase);
+
+
+/**
+ * @inheritDoc
+ */
+ol.reproj.Image.prototype.disposeInternal = function() {
+ if (this.state == ol.ImageState.LOADING) {
+ this.unlistenSource_();
+ }
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.reproj.Image.prototype.getImage = function(opt_context) {
+ return this.canvas_;
+};
+
+
+/**
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.reproj.Image.prototype.getProjection = function() {
+ return this.targetProj_;
+};
+
+
+/**
+ * @private
+ */
+ol.reproj.Image.prototype.reproject_ = function() {
+ var sourceState = this.sourceImage_.getState();
+ if (sourceState == ol.ImageState.LOADED) {
+ var width = ol.extent.getWidth(this.targetExtent_) / this.targetResolution_;
+ var height =
+ ol.extent.getHeight(this.targetExtent_) / this.targetResolution_;
+
+ this.canvas_ = ol.reproj.render(width, height, this.sourcePixelRatio_,
+ this.sourceImage_.getResolution(), this.maxSourceExtent_,
+ this.targetResolution_, this.targetExtent_, this.triangulation_, [{
+ extent: this.sourceImage_.getExtent(),
+ image: this.sourceImage_.getImage()
+ }]);
+ }
+ this.state = sourceState;
+ this.changed();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.reproj.Image.prototype.load = function() {
+ if (this.state == ol.ImageState.IDLE) {
+ this.state = ol.ImageState.LOADING;
+ this.changed();
+
+ var sourceState = this.sourceImage_.getState();
+ if (sourceState == ol.ImageState.LOADED ||
+ sourceState == ol.ImageState.ERROR) {
+ this.reproject_();
+ } else {
+ this.sourceListenerKey_ = this.sourceImage_.listen(
+ goog.events.EventType.CHANGE, function(e) {
+ var sourceState = this.sourceImage_.getState();
+ if (sourceState == ol.ImageState.LOADED ||
+ sourceState == ol.ImageState.ERROR) {
+ this.unlistenSource_();
+ this.reproject_();
+ }
+ }, false, this);
+ this.sourceImage_.load();
+ }
+ }
+};
+
+
+/**
+ * @private
+ */
+ol.reproj.Image.prototype.unlistenSource_ = function() {
+ goog.asserts.assert(this.sourceListenerKey_,
+ 'this.sourceListenerKey_ should not be null');
+ goog.events.unlistenByKey(this.sourceListenerKey_);
+ this.sourceListenerKey_ = null;
+};
+
+goog.provide('ol.source.Image');
+goog.provide('ol.source.ImageEvent');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events.Event');
+goog.require('ol.Attribution');
+goog.require('ol.ImageState');
+goog.require('ol.array');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.reproj.Image');
+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
+ * 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.source.Source}
+ * @param {ol.source.ImageOptions} options Single image source options.
+ * @api
+ */
+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_ = options.resolutions !== undefined ?
+ options.resolutions : null;
+ goog.asserts.assert(!this.resolutions_ ||
+ goog.array.isSorted(this.resolutions_,
+ function(a, b) {
+ return b - a;
+ }, true), 'resolutions must be null or sorted in descending order');
+
+
+ /**
+ * @private
+ * @type {ol.reproj.Image}
+ */
+ this.reprojectedImage_ = null;
+
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.reprojectedRevision_ = 0;
+
+};
+goog.inherits(ol.source.Image, ol.source.Source);
+
+
+/**
+ * @return {Array.<number>} Resolutions.
+ */
+ol.source.Image.prototype.getResolutions = function() {
+ return this.resolutions_;
+};
+
+
+/**
+ * @protected
+ * @param {number} resolution Resolution.
+ * @return {number} Resolution.
+ */
+ol.source.Image.prototype.findNearestResolution =
+ function(resolution) {
+ if (this.resolutions_) {
+ var idx = ol.array.linearFindNearest(this.resolutions_, resolution, 0);
+ resolution = this.resolutions_[idx];
+ }
+ return resolution;
+};
+
+
+/**
+ * @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 =
+ function(extent, resolution, pixelRatio, projection) {
+ var sourceProjection = this.getProjection();
+ if (!ol.ENABLE_RASTER_REPROJECTION ||
+ !sourceProjection ||
+ !projection ||
+ ol.proj.equivalent(sourceProjection, projection)) {
+ if (sourceProjection) {
+ projection = sourceProjection;
+ }
+ return this.getImageInternal(extent, resolution, pixelRatio, projection);
+ } else {
+ if (this.reprojectedImage_) {
+ if (this.reprojectedRevision_ == this.getRevision() &&
+ ol.proj.equivalent(
+ this.reprojectedImage_.getProjection(), projection) &&
+ this.reprojectedImage_.getResolution() == resolution &&
+ this.reprojectedImage_.getPixelRatio() == pixelRatio &&
+ ol.extent.equals(this.reprojectedImage_.getExtent(), extent)) {
+ return this.reprojectedImage_;
+ }
+ this.reprojectedImage_.dispose();
+ this.reprojectedImage_ = null;
+ }
+
+ this.reprojectedImage_ = new ol.reproj.Image(
+ sourceProjection, projection, extent, resolution, pixelRatio,
+ goog.bind(function(extent, resolution, pixelRatio) {
+ return this.getImageInternal(extent, resolution,
+ pixelRatio, sourceProjection);
+ }, this));
+ this.reprojectedRevision_ = this.getRevision();
+
+ return this.reprojectedImage_;
+ }
+};
+
+
+/**
+ * @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.
+ * @protected
+ */
+ol.source.Image.prototype.getImageInternal = goog.abstractMethod;
+
+
+/**
+ * Handle image change events.
+ * @param {goog.events.Event} event Event.
+ * @protected
+ */
+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;
+ }
+};
+
+
+/**
+ * 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;
+};
+
+
+
+/**
+ * @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.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);
+
+
+/**
+ * @enum {string}
+ */
+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');
+
+
+
+/**
+ * @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.source.ImageCanvas = function(options) {
+
+ goog.base(this, {
+ attributions: options.attributions,
+ logo: options.logo,
+ projection: options.projection,
+ resolutions: options.resolutions,
+ state: options.state !== undefined ?
+ /** @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_ = options.ratio !== undefined ?
+ options.ratio : 1.5;
+
+};
+goog.inherits(ol.source.ImageCanvas, ol.source.Image);
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.ImageCanvas.prototype.getImageInternal =
+ function(extent, resolution, pixelRatio, projection) {
+ resolution = this.findNearestResolution(resolution);
+
+ var canvas = this.canvas_;
+ if (canvas &&
+ this.renderedRevision_ == this.getRevision() &&
+ canvas.getResolution() == resolution &&
+ canvas.getPixelRatio() == pixelRatio &&
+ ol.extent.containsExtent(canvas.getExtent(), extent)) {
+ return canvas;
+ }
+
+ 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 (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('ol');
+goog.require('ol.Object');
+goog.require('ol.geom.Geometry');
+goog.require('ol.style.Style');
+
+
+
+/**
+ * @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.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 (opt_geometryOrProperties !== undefined) {
+ if (opt_geometryOrProperties instanceof ol.geom.Geometry ||
+ !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);
+ }
+ }
+};
+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 (geometry) {
+ clone.setGeometry(geometry.clone());
+ }
+ var style = this.getStyle();
+ if (style) {
+ clone.setStyle(style);
+ }
+ return clone;
+};
+
+
+/**
+ * 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.Feature.prototype.getGeometry = function() {
+ return /** @type {ol.geom.Geometry|undefined} */ (
+ this.get(this.geometryName_));
+};
+
+
+/**
+ * Get the feature identifier. This is a stable identifier for the feature and
+ * is either set when reading data from a remote source or set explicitly by
+ * calling {@link ol.Feature#setId}.
+ * @return {number|string|undefined} Id.
+ * @api stable
+ * @observable
+ */
+ol.Feature.prototype.getId = function() {
+ return this.id_;
+};
+
+
+/**
+ * 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.Feature.prototype.getGeometryName = function() {
+ return this.geometryName_;
+};
+
+
+/**
+ * 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.Feature.prototype.getStyle = function() {
+ return this.style_;
+};
+
+
+/**
+ * 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_;
+};
+
+
+/**
+ * @private
+ */
+ol.Feature.prototype.handleGeometryChange_ = function() {
+ this.changed();
+};
+
+
+/**
+ * @private
+ */
+ol.Feature.prototype.handleGeometryChanged_ = function() {
+ if (this.geometryChangeKey_) {
+ goog.events.unlistenByKey(this.geometryChangeKey_);
+ this.geometryChangeKey_ = null;
+ }
+ var geometry = this.getGeometry();
+ if (geometry) {
+ this.geometryChangeKey_ = goog.events.listen(geometry,
+ goog.events.EventType.CHANGE, this.handleGeometryChange_, false, this);
+ }
+ this.changed();
+};
+
+
+/**
+ * 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.Feature.prototype.setGeometry = function(geometry) {
+ this.set(this.geometryName_, geometry);
+};
+
+
+/**
+ * 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.Feature.prototype.setStyle = function(style) {
+ this.style_ = style;
+ this.styleFunction_ = !style ?
+ undefined : ol.Feature.createStyleFunction(style);
+ this.changed();
+};
+
+
+/**
+ * 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.Feature.prototype.setId = function(id) {
+ this.id_ = id;
+ this.changed();
+};
+
+
+/**
+ * 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.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_();
+};
+
+
+/**
+ * A function that returns an array of {@link ol.style.Style styles} given a
+ * resolution. The `this` keyword inside the function references the
+ * {@link ol.Feature} to be styled.
+ *
+ * @typedef {function(this: ol.Feature, number):
+ * (ol.style.Style|Array.<ol.style.Style>)}
+ * @api stable
+ */
+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) {
+ var styleFunction;
+
+ if (goog.isFunction(obj)) {
+ 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 should be an ol.style.Style');
+ styles = [obj];
+ }
+ styleFunction = function() {
+ return styles;
+ };
+ }
+ 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.
+
+/**
+ * @fileoverview Common events for the network classes.
+ */
+
+
+goog.provide('goog.net.EventType');
+
+
+/**
+ * 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'
+};
+
+// 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}.
+ *
+ * @interface
+ * @extends {IThenable<TYPE>}
+ * @template TYPE
+ */
+goog.Thenable = function() {};
+
+
+/**
+ * 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): VALUE)=} 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 {RESULT} A new Promise that will receive the result
+ * of the fulfillment or rejection callback.
+ * @template VALUE
+ * @template THIS
+ *
+ * When a Promise (or thenable) is returned from the fulfilled callback,
+ * the result is the payload of that promise, not the promise itself.
+ *
+ * @template RESULT := type('goog.Promise',
+ * cond(isUnknown(VALUE), unknown(),
+ * mapunion(VALUE, (V) =>
+ * cond(isTemplatized(V) && sub(rawTypeOf(V), 'IThenable'),
+ * templateTypeOf(V, 0),
+ * cond(sub(V, 'Thenable'),
+ * unknown(),
+ * V)))))
+ * =:
+ *
+ */
+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;
+ }
+};
+
+
+/**
+ * @param {*} object
+ * @return {boolean} Whether a given instance implements {@code goog.Thenable}.
+ * The class/superclass of the instance must call {@code addImplementation}.
+ */
+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;
+ }
+};
+
+// Copyright 2015 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 freelist.
+ *
+ * An anterative to goog.structs.SimplePool, it imposes the requirement that the
+ * objects in the list contain a "next" property that can be used to maintain
+ * the pool.
+ */
+
+goog.provide('goog.async.FreeList');
+
+
+/**
+ * @template ITEM
+ */
+goog.async.FreeList = goog.defineClass(null, {
+ /**
+ * @param {function():ITEM} create
+ * @param {function(ITEM):void} reset
+ * @param {number} limit
+ */
+ constructor: function(create, reset, limit) {
+ /** @const {number} */
+ this.limit_ = limit;
+ /** @const {function()} */
+ this.create_ = create;
+ /** @const {function(ITEM):void} */
+ this.reset_ = reset;
+
+ /** @type {number} */
+ this.occupants_ = 0;
+ /** @type {ITEM} */
+ this.head_ = null;
+ },
+
+ /**
+ * @return {ITEM}
+ */
+ get: function() {
+ var item;
+ if (this.occupants_ > 0) {
+ this.occupants_--;
+ item = this.head_;
+ this.head_ = item.next;
+ item.next = null;
+ } else {
+ item = this.create_();
+ }
+ return item;
+ },
+
+ /**
+ * @param {ITEM} item An item available for possible future reuse.
+ */
+ put: function(item) {
+ this.reset_(item);
+ if (this.occupants_ < this.limit_) {
+ this.occupants_++;
+ item.next = this.head_;
+ this.head_ = item;
+ }
+ },
+
+ /**
+ * Visible for testing.
+ * @package
+ * @return {number}
+ */
+ occupants: function() {
+ return this.occupants_;
+ }
+});
+
+
+
+
+// Copyright 2015 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.WorkItem');
+goog.provide('goog.async.WorkQueue');
+
+goog.require('goog.asserts');
+goog.require('goog.async.FreeList');
+
+
+// TODO(johnlenz): generalize the WorkQueue if this is used by more
+// than goog.async.run.
+
+
+
+/**
+ * A low GC workqueue. The key elements of this design:
+ * - avoids the need for goog.bind or equivalent by carrying scope
+ * - avoids the need for array reallocation by using a linked list
+ * - minimizes work entry objects allocation by recycling objects
+ * @constructor
+ * @final
+ * @struct
+ */
+goog.async.WorkQueue = function() {
+ this.workHead_ = null;
+ this.workTail_ = null;
+};
+
+
+/** @define {number} The maximum number of entries to keep for recycling. */
+goog.define('goog.async.WorkQueue.DEFAULT_MAX_UNUSED', 100);
+
+
+/** @const @private {goog.async.FreeList<goog.async.WorkItem>} */
+goog.async.WorkQueue.freelist_ = new goog.async.FreeList(
+ function() {return new goog.async.WorkItem(); },
+ function(item) {item.reset()},
+ goog.async.WorkQueue.DEFAULT_MAX_UNUSED);
+
+
+/**
+ * @param {function()} fn
+ * @param {Object|null|undefined} scope
+ */
+goog.async.WorkQueue.prototype.add = function(fn, scope) {
+ var item = this.getUnusedItem_();
+ item.set(fn, scope);
+
+ if (this.workTail_) {
+ this.workTail_.next = item;
+ this.workTail_ = item;
+ } else {
+ goog.asserts.assert(!this.workHead_);
+ this.workHead_ = item;
+ this.workTail_ = item;
+ }
+};
+
+
+/**
+ * @return {goog.async.WorkItem}
+ */
+goog.async.WorkQueue.prototype.remove = function() {
+ var item = null;
+
+ if (this.workHead_) {
+ item = this.workHead_;
+ this.workHead_ = this.workHead_.next;
+ if (!this.workHead_) {
+ this.workTail_ = null;
+ }
+ item.next = null;
+ }
+ return item;
+};
+
+
+/**
+ * @param {goog.async.WorkItem} item
+ */
+goog.async.WorkQueue.prototype.returnUnused = function(item) {
+ goog.async.WorkQueue.freelist_.put(item);
+};
+
+
+/**
+ * @return {goog.async.WorkItem}
+ * @private
+ */
+goog.async.WorkQueue.prototype.getUnusedItem_ = function() {
+ return goog.async.WorkQueue.freelist_.get();
+};
+
+
+
+/**
+ * @constructor
+ * @final
+ * @struct
+ */
+goog.async.WorkItem = function() {
+ /** @type {?function()} */
+ this.fn = null;
+ /** @type {Object|null|undefined} */
+ this.scope = null;
+ /** @type {?goog.async.WorkItem} */
+ this.next = null;
+};
+
+
+/**
+ * @param {function()} fn
+ * @param {Object|null|undefined} scope
+ */
+goog.async.WorkItem.prototype.set = function(fn, scope) {
+ this.fn = fn;
+ this.scope = scope;
+ this.next = null;
+};
+
+
+/** Reset the work item so they don't prevent GC before reuse */
+goog.async.WorkItem.prototype.reset = function() {
+ this.fn = null;
+ this.scope = null;
+ this.next = 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.
+
+/**
+ * @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_ = [];
+
+
+/**
+ * Fires clock reset watching functions.
+ */
+goog.testing.watchers.signalClockReset = function() {
+ var watchers = goog.testing.watchers.resetWatchers_;
+ for (var i = 0; i < watchers.length; i++) {
+ goog.testing.watchers.resetWatchers_[i]();
+ }
+};
+
+
+/**
+ * Enqueues a function to be called when the clock used for setTimeout is reset.
+ * @param {function()} fn
+ */
+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.WorkQueue');
+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
+ */
+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_.add(callback, opt_context);
+};
+
+
+/**
+ * Initializes the function to use to process the work queue.
+ * @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(undefined);
+ goog.async.run.schedule_ = function() {
+ promise.then(goog.async.run.processWorkQueue);
+ };
+ } else {
+ goog.async.run.schedule_ = function() {
+ goog.async.nextTick(goog.async.run.processWorkQueue);
+ };
+ }
+};
+
+
+/**
+ * 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.
+ *
+ * However, we also want to run promises if the MockClock is no longer in
+ * control so we schedule a backup "setTimeout" to the unmocked timeout if
+ * provided.
+ *
+ * @param {function(function())=} opt_realSetTimeout
+ */
+goog.async.run.forceNextTick = function(opt_realSetTimeout) {
+ goog.async.run.schedule_ = function() {
+ goog.async.nextTick(goog.async.run.processWorkQueue);
+ if (opt_realSetTimeout) {
+ opt_realSetTimeout(goog.async.run.processWorkQueue);
+ }
+ };
+};
+
+
+/**
+ * The function used to schedule work asynchronousely.
+ * @private {function()}
+ */
+goog.async.run.schedule_;
+
+
+/** @private {boolean} */
+goog.async.run.workQueueScheduled_ = false;
+
+
+/** @private {!goog.async.WorkQueue} */
+goog.async.run.workQueue_ = new goog.async.WorkQueue();
+
+
+if (goog.DEBUG) {
+ /**
+ * Reset the work queue.
+ * @private
+ */
+ goog.async.run.resetQueue_ = function() {
+ goog.async.run.workQueueScheduled_ = false;
+ goog.async.run.workQueue_ = new goog.async.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_);
+}
+
+
+/**
+ * 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 added while processing.
+ var item = null;
+ while (item = goog.async.run.workQueue_.remove()) {
+ try {
+ item.fn.call(item.scope);
+ } catch (e) {
+ goog.async.throwException(e);
+ }
+ goog.async.run.workQueue_.returnUnused(item);
+ }
+
+ // There are no more work items, allow processing to be scheduled again.
+ goog.async.run.workQueueScheduled_ = 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.
+
+goog.provide('goog.promise.Resolver');
+
+
+
+/**
+ * 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
+ */
+goog.promise.Resolver = function() {};
+
+
+/**
+ * The promise that created this resolver.
+ * @type {!goog.Promise<TYPE>}
+ */
+goog.promise.Resolver.prototype.promise;
+
+
+/**
+ * Resolves this resolver with the specified value.
+ * @type {function((TYPE|goog.Promise<TYPE>|Thenable)=)}
+ */
+goog.promise.Resolver.prototype.resolve;
+
+
+/**
+ * Rejects this resolver with the specified reason.
+ * @type {function(*=): void}
+ */
+goog.promise.Resolver.prototype.reject;
+
+// 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');
+
+goog.require('goog.Thenable');
+goog.require('goog.asserts');
+goog.require('goog.async.FreeList');
+goog.require('goog.async.run');
+goog.require('goog.async.throwException');
+goog.require('goog.debug.Error');
+goog.require('goog.promise.Resolver');
+
+
+
+/**
+ * Promises provide a result that may be resolved asynchronously. A Promise may
+ * be resolved by being fulfilled with a fulfillment value, rejected with a
+ * rejection reason, or blocked by another Promise. A Promise is said to be
+ * settled if it is either fulfilled or rejected. Once settled, the Promise
+ * result is immutable.
+ *
+ * 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
+ * is settled, 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 settled, 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
+ * @struct
+ * @final
+ * @implements {goog.Thenable<TYPE>}
+ * @template TYPE,RESOLVER_CONTEXT
+ */
+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 settled 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 linked list of {@code onFulfilled} and {@code onRejected} callbacks
+ * added to this Promise by calls to {@code then()}.
+ * @private {?goog.Promise.CallbackEntry_}
+ */
+ this.callbackEntries_ = null;
+
+ /**
+ * The tail of the linked list of {@code onFulfilled} and {@code onRejected}
+ * callbacks added to this Promise by calls to {@code then()}.
+ * @private {?goog.Promise.CallbackEntry_}
+ */
+ this.callbackEntriesTail_ = null;
+
+ /**
+ * 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;
+ }
+
+ 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;
+ }
+
+ // As an optimization, we can skip this if resolver is goog.nullFunction.
+ // This value is passed internally when creating a promise which will be
+ // resolved through a more optimized path.
+ if (resolver != goog.nullFunction) {
+ 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);
+ }
+ }
+};
+
+
+/**
+ * @define {boolean} Whether traces of {@code then} calls should be included in
+ * exceptions thrown
+ */
+goog.define('goog.Promise.LONG_STACK_TRACES', false);
+
+
+/**
+ * @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.
+ */
+goog.define('goog.Promise.UNHANDLED_REJECTION_DELAY', 0);
+
+
+/**
+ * The possible internal states for a Promise. These states are not directly
+ * observable to external callers.
+ * @enum {number}
+ * @private
+ */
+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
+};
+
+
+
+/**
+ * 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 settled.
+ *
+ * @private @final @struct @constructor
+ */
+goog.Promise.CallbackEntry_ = function() {
+ /** @type {?goog.Promise} */
+ this.child = null;
+ /** @type {Function} */
+ this.onFulfilled = null;
+ /** @type {Function} */
+ this.onRejected = null;
+ /** @type {?} */
+ this.context = null;
+ /** @type {?goog.Promise.CallbackEntry_} */
+ this.next = null;
+
+ /**
+ * A boolean value to indicate this is a "thenAlways" callback entry.
+ * Unlike a normal "then/thenVoid" a "thenAlways doesn't participate
+ * in "cancel" considerations but is simply an observer and requires
+ * special handling.
+ * @type {boolean}
+ */
+ this.always = false;
+};
+
+
+/** clear the object prior to reuse */
+goog.Promise.CallbackEntry_.prototype.reset = function() {
+ this.child = null;
+ this.onFulfilled = null;
+ this.onRejected = null;
+ this.context = null;
+ this.always = false;
+};
+
+
+/**
+ * @define {number} The number of currently unused objects to keep around for
+ * reuse.
+ */
+goog.define('goog.Promise.DEFAULT_MAX_UNUSED', 100);
+
+
+/** @const @private {goog.async.FreeList<!goog.Promise.CallbackEntry_>} */
+goog.Promise.freelist_ = new goog.async.FreeList(
+ function() {
+ return new goog.Promise.CallbackEntry_();
+ },
+ function(item) {
+ item.reset();
+ },
+ goog.Promise.DEFAULT_MAX_UNUSED);
+
+
+/**
+ * @param {Function} onFulfilled
+ * @param {Function} onRejected
+ * @param {?} context
+ * @return {!goog.Promise.CallbackEntry_}
+ * @private
+ */
+goog.Promise.getCallbackEntry_ = function(onFulfilled, onRejected, context) {
+ var entry = goog.Promise.freelist_.get();
+ entry.onFulfilled = onFulfilled;
+ entry.onRejected = onRejected;
+ entry.context = context;
+ return entry;
+};
+
+
+/**
+ * @param {!goog.Promise.CallbackEntry_} entry
+ * @private
+ */
+goog.Promise.returnEntry_ = function(entry) {
+ goog.Promise.freelist_.put(entry);
+};
+
+
+// NOTE: this is the same template expression as is used for
+// goog.IThenable.prototype.then
+
+
+/**
+ * @param {VALUE=} opt_value
+ * @return {RESULT} A new Promise that is immediately resolved
+ * with the given value. If the input value is already a goog.Promise, it
+ * will be returned immediately without creating a new instance.
+ * @template VALUE
+ * @template RESULT := type('goog.Promise',
+ * cond(isUnknown(VALUE), unknown(),
+ * mapunion(VALUE, (V) =>
+ * cond(isTemplatized(V) && sub(rawTypeOf(V), 'IThenable'),
+ * templateTypeOf(V, 0),
+ * cond(sub(V, 'Thenable'),
+ * unknown(),
+ * V)))))
+ * =:
+ */
+goog.Promise.resolve = function(opt_value) {
+ if (opt_value instanceof goog.Promise) {
+ // Avoid creating a new object if we already have a promise object
+ // of the correct type.
+ return opt_value;
+ }
+
+ // Passing goog.nullFunction will cause the constructor to take an optimized
+ // path that skips calling the resolver function.
+ var promise = new goog.Promise(goog.nullFunction);
+ promise.resolve_(goog.Promise.State_.FULFILLED, opt_value);
+ return promise;
+};
+
+
+/**
+ * @param {*=} opt_reason
+ * @return {!goog.Promise} A new Promise that is immediately rejected with the
+ * given reason.
+ */
+goog.Promise.reject = function(opt_reason) {
+ return new goog.Promise(function(resolve, reject) {
+ reject(opt_reason);
+ });
+};
+
+
+/**
+ * This is identical to
+ * {@code goog.Promise.resolve(value).then(onFulfilled, onRejected)}, but it
+ * avoids creating an unnecessary wrapper Promise when {@code value} is already
+ * thenable.
+ *
+ * @param {?(goog.Thenable<TYPE>|Thenable|TYPE)} value
+ * @param {function(TYPE): ?} onFulfilled
+ * @param {function(*): *} onRejected
+ * @template TYPE
+ * @private
+ */
+goog.Promise.resolveThen_ = function(value, onFulfilled, onRejected) {
+ var isThenable = goog.Promise.maybeThen_(
+ value, onFulfilled, onRejected, null);
+ if (!isThenable) {
+ goog.async.run(goog.partial(onFulfilled, value));
+ }
+};
+
+
+/**
+ * @param {!Array<?(goog.Promise<TYPE>|goog.Thenable<TYPE>|Thenable|*)>}
+ * promises
+ * @return {!goog.Promise<TYPE>} A Promise that receives the result of the
+ * first Promise (or Promise-like) input to settle immediately after it
+ * settles.
+ * @template TYPE
+ */
+goog.Promise.race = function(promises) {
+ return new goog.Promise(function(resolve, reject) {
+ if (!promises.length) {
+ resolve(undefined);
+ }
+ for (var i = 0, promise; i < promises.length; i++) {
+ promise = promises[i];
+ goog.Promise.resolveThen_(promise, resolve, reject);
+ }
+ });
+};
+
+
+/**
+ * @param {!Array<?(goog.Promise<TYPE>|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 with the first rejection reason
+ * immediately after it is rejected.
+ * @template TYPE
+ */
+goog.Promise.all = function(promises) {
+ return new goog.Promise(function(resolve, reject) {
+ var toFulfill = promises.length;
+ var values = [];
+
+ if (!toFulfill) {
+ resolve(values);
+ return;
+ }
+
+ var onFulfill = function(index, value) {
+ toFulfill--;
+ values[index] = value;
+ if (toFulfill == 0) {
+ resolve(values);
+ }
+ };
+
+ var onReject = function(reason) {
+ reject(reason);
+ };
+
+ for (var i = 0, promise; i < promises.length; i++) {
+ promise = promises[i];
+ goog.Promise.resolveThen_(
+ promise, goog.partial(onFulfill, i), onReject);
+ }
+ });
+};
+
+
+/**
+ * @param {!Array<?(goog.Promise<TYPE>|goog.Thenable<TYPE>|Thenable|*)>}
+ * promises
+ * @return {!goog.Promise<!Array<{
+ * fulfilled: boolean,
+ * value: (TYPE|undefined),
+ * reason: (*|undefined)}>>} A Promise that resolves with a list of
+ * result objects once all input Promises (or Promise-like) have
+ * settled. Each result object contains a 'fulfilled' boolean indicating
+ * whether an input Promise was fulfilled or rejected. For fulfilled
+ * Promises, the resulting value is stored in the 'value' field. For
+ * rejected Promises, the rejection reason is stored in the 'reason'
+ * field.
+ * @template TYPE
+ */
+goog.Promise.allSettled = function(promises) {
+ return new goog.Promise(function(resolve, reject) {
+ var toSettle = promises.length;
+ var results = [];
+
+ if (!toSettle) {
+ resolve(results);
+ return;
+ }
+
+ var onSettled = function(index, fulfilled, result) {
+ toSettle--;
+ results[index] = fulfilled ?
+ {fulfilled: true, value: result} :
+ {fulfilled: false, reason: result};
+ if (toSettle == 0) {
+ resolve(results);
+ }
+ };
+
+ for (var i = 0, promise; i < promises.length; i++) {
+ promise = promises[i];
+ goog.Promise.resolveThen_(promise,
+ goog.partial(onSettled, i, true /* fulfilled */),
+ goog.partial(onSettled, i, false /* fulfilled */));
+ }
+ });
+};
+
+
+/**
+ * @param {!Array<?(goog.Promise<TYPE>|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
+ */
+goog.Promise.firstFulfilled = function(promises) {
+ return new goog.Promise(function(resolve, reject) {
+ var toReject = promises.length;
+ var reasons = [];
+
+ if (!toReject) {
+ resolve(undefined);
+ return;
+ }
+
+ 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; i < promises.length; i++) {
+ promise = promises[i];
+ goog.Promise.resolveThen_(
+ promise, onFulfill, goog.partial(onReject, i));
+ }
+ });
+};
+
+
+/**
+ * @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
+ */
+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);
+};
+
+
+/**
+ * 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
+ */
+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.');
+ }
+ 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.Promise.LONG_STACK_TRACES) {
+ this.addStackTrace_(new Error('then'));
+ }
+
+ 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 callbacks that will operate on the result of the Promise without
+ * returning a child Promise (unlike "then").
+ *
+ * If the Promise is fulfilled, the {@code onFulfilled} callback will be invoked
+ * with the fulfillment value as argument.
+ *
+ * If the Promise is rejected, the {@code onRejected} callback will be invoked
+ * with the rejection reason as argument.
+ *
+ * @param {?(function(this:THIS, TYPE):?)=} opt_onFulfilled A
+ * function that will be invoked with the fulfillment value if the Promise
+ * is fulfilled.
+ * @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.
+ * @package
+ * @template THIS
+ */
+goog.Promise.prototype.thenVoid = function(
+ opt_onFulfilled, opt_onRejected, opt_context) {
+
+ 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?');
+ }
+
+ if (goog.Promise.LONG_STACK_TRACES) {
+ this.addStackTrace_(new Error('then'));
+ }
+
+ // Note: no default rejection handler is provided here as we need to
+ // distinguish unhandled rejections.
+ this.addCallbackEntry_(goog.Promise.getCallbackEntry_(
+ opt_onFulfilled || goog.nullFunction,
+ opt_onRejected || null,
+ opt_context));
+};
+
+
+/**
+ * Adds a callback that will be invoked when the Promise is settled (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} onSettled A function that will be invoked
+ * when the Promise is settled (fulfilled or 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<TYPE>} This Promise, for chaining additional calls.
+ * @template THIS
+ */
+goog.Promise.prototype.thenAlways = function(onSettled, opt_context) {
+ if (goog.Promise.LONG_STACK_TRACES) {
+ this.addStackTrace_(new Error('thenAlways'));
+ }
+
+ var entry = goog.Promise.getCallbackEntry_(onSettled, onSettled, opt_context);
+ entry.always = true;
+ this.addCallbackEntry_(entry);
+ return this;
+};
+
+
+/**
+ * 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
+ */
+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);
+};
+
+
+/**
+ * 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.
+ */
+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);
+ }
+};
+
+
+/**
+ * Cancels this Promise with the given error.
+ *
+ * @param {!Error} err The cancellation error.
+ * @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);
+ this.parent_ = null;
+ } else {
+ this.resolve_(goog.Promise.State_.REJECTED, err);
+ }
+ }
+};
+
+
+/**
+ * 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.
+ * @private
+ */
+goog.Promise.prototype.cancelChild_ = function(childPromise, err) {
+ if (!this.callbackEntries_) {
+ return;
+ }
+ var childCount = 0;
+ var childEntry = null;
+ var beforeChildEntry = null;
+
+ // Find the callback entry for the childPromise, and count whether there are
+ // additional child Promises.
+ for (var entry = this.callbackEntries_; entry; entry = entry.next) {
+ if (!entry.always) {
+ childCount++;
+ if (entry.child == childPromise) {
+ childEntry = entry;
+ }
+ if (childEntry && childCount > 1) {
+ break;
+ }
+ }
+ if (!childEntry) {
+ beforeChildEntry = entry;
+ }
+ }
+
+ // Can a child entry be missing?
+
+ // If the child Promise was the only child, cancel this Promise as well.
+ // Otherwise, reject only the child Promise with the cancel error.
+ if (childEntry) {
+ if (this.state_ == goog.Promise.State_.PENDING && childCount == 1) {
+ this.cancelInternal_(err);
+ } else {
+ if (beforeChildEntry) {
+ this.removeEntryAfter_(beforeChildEntry);
+ } else {
+ this.popEntry_();
+ }
+
+ this.executeCallback_(
+ childEntry, goog.Promise.State_.REJECTED, err);
+ }
+ }
+};
+
+
+/**
+ * Adds a callback entry to the current Promise, and schedules callback
+ * execution if the Promise has already been settled.
+ *
+ * @param {goog.Promise.CallbackEntry_} callbackEntry Record containing
+ * {@code onFulfilled} and {@code onRejected} callbacks to execute after
+ * the Promise is settled.
+ * @private
+ */
+goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) {
+ if (!this.hasEntry_() &&
+ (this.state_ == goog.Promise.State_.FULFILLED ||
+ this.state_ == goog.Promise.State_.REJECTED)) {
+ this.scheduleCallbacks_();
+ }
+ this.queueEntry_(callbackEntry);
+};
+
+
+/**
+ * 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
+ */
+goog.Promise.prototype.addChildPromise_ = function(
+ onFulfilled, onRejected, opt_context) {
+
+ /** @type {goog.Promise.CallbackEntry_} */
+ var callbackEntry = goog.Promise.getCallbackEntry_(null, null, 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_(callbackEntry);
+ return callbackEntry.child;
+};
+
+
+/**
+ * Unblocks the Promise and fulfills it with the given value.
+ *
+ * @param {TYPE} value
+ * @private
+ */
+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);
+};
+
+
+/**
+ * Unblocks the Promise and rejects it with the given rejection reason.
+ *
+ * @param {*} reason
+ * @private
+ */
+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);
+};
+
+
+/**
+ * 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 settled with the same state and result as the Thenable once it is itself
+ * settled.
+ *
+ * If the given result is not a Thenable, the Promise will be settled (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
+ */
+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');
+ }
+
+ this.state_ = goog.Promise.State_.BLOCKED;
+ var isThenable = goog.Promise.maybeThen_(
+ x, this.unblockAndFulfill_, this.unblockAndReject_, this);
+ if (isThenable) {
+ return;
+ }
+
+ this.result_ = x;
+ this.state_ = state;
+ // Since we can no longer be canceled, remove link to parent, so that the
+ // child promise does not keep the parent promise alive.
+ this.parent_ = null;
+ this.scheduleCallbacks_();
+
+ if (state == goog.Promise.State_.REJECTED &&
+ !(x instanceof goog.Promise.CancellationError)) {
+ goog.Promise.addUnhandledRejection_(this, x);
+ }
+};
+
+
+/**
+ * Invokes the "then" method of an input value if that value is a Thenable. This
+ * is a no-op if the value is not thenable.
+ *
+ * @param {*} value A potentially thenable value.
+ * @param {!Function} onFulfilled
+ * @param {!Function} onRejected
+ * @param {*} context
+ * @return {boolean} Whether the input value was thenable.
+ * @private
+ */
+goog.Promise.maybeThen_ = function(value, onFulfilled, onRejected, context) {
+ if (value instanceof goog.Promise) {
+ value.thenVoid(onFulfilled, onRejected, context);
+ return true;
+ } else if (goog.Thenable.isImplementedBy(value)) {
+ value = /** @type {!goog.Thenable} */ (value);
+ value.then(onFulfilled, onRejected, context);
+ return true;
+ } else if (goog.isObject(value)) {
+ try {
+ var then = value['then'];
+ if (goog.isFunction(then)) {
+ goog.Promise.tryThen_(
+ value, then, onFulfilled, onRejected, context);
+ return true;
+ }
+ } catch (e) {
+ onRejected.call(context, e);
+ return true;
+ }
+ }
+
+ return false;
+};
+
+
+/**
+ * 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.
+ * @param {!Function} onFulfilled
+ * @param {!Function} onRejected
+ * @param {*} context
+ * @private
+ */
+goog.Promise.tryThen_ = function(
+ thenable, then, onFulfilled, onRejected, context) {
+
+ var called = false;
+ var resolve = function(value) {
+ if (!called) {
+ called = true;
+ onFulfilled.call(context, value);
+ }
+ };
+
+ var reject = function(reason) {
+ if (!called) {
+ called = true;
+ onRejected.call(context, reason);
+ }
+ };
+
+ try {
+ then.call(thenable, resolve, reject);
+ } catch (e) {
+ reject(e);
+ }
+};
+
+
+/**
+ * Executes the pending callbacks of a settled 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
+ */
+goog.Promise.prototype.scheduleCallbacks_ = function() {
+ if (!this.executing_) {
+ this.executing_ = true;
+ goog.async.run(this.executeCallbacks_, this);
+ }
+};
+
+
+/**
+ * @return {boolean} Whether there are any pending callbacks queued.
+ * @private
+ */
+goog.Promise.prototype.hasEntry_ = function() {
+ return !!this.callbackEntries_;
+};
+
+
+/**
+ * @param {goog.Promise.CallbackEntry_} entry
+ * @private
+ */
+goog.Promise.prototype.queueEntry_ = function(entry) {
+ goog.asserts.assert(entry.onFulfilled != null);
+
+ if (this.callbackEntriesTail_) {
+ this.callbackEntriesTail_.next = entry;
+ this.callbackEntriesTail_ = entry;
+ } else {
+ // It the work queue was empty set the head too.
+ this.callbackEntries_ = entry;
+ this.callbackEntriesTail_ = entry;
+ }
+};
+
+
+/**
+ * @return {goog.Promise.CallbackEntry_} entry
+ * @private
+ */
+goog.Promise.prototype.popEntry_ = function() {
+ var entry = null;
+ if (this.callbackEntries_) {
+ entry = this.callbackEntries_;
+ this.callbackEntries_ = entry.next;
+ entry.next = null;
+ }
+ // It the work queue is empty clear the tail too.
+ if (!this.callbackEntries_) {
+ this.callbackEntriesTail_ = null;
+ }
+
+ if (entry != null) {
+ goog.asserts.assert(entry.onFulfilled != null);
+ }
+ return entry;
+};
+
+
+/**
+ * @param {goog.Promise.CallbackEntry_} previous
+ * @private
+ */
+goog.Promise.prototype.removeEntryAfter_ = function(previous) {
+ goog.asserts.assert(this.callbackEntries_);
+ goog.asserts.assert(previous != null);
+ // If the last entry is being removed, update the tail
+ if (previous.next == this.callbackEntriesTail_) {
+ this.callbackEntriesTail_ = previous;
+ }
+
+ previous.next = previous.next.next;
+};
+
+
+/**
+ * Executes all pending callbacks for this Promise.
+ *
+ * @private
+ */
+goog.Promise.prototype.executeCallbacks_ = function() {
+ var entry = null;
+ while (entry = this.popEntry_()) {
+ if (goog.Promise.LONG_STACK_TRACES) {
+ this.currentStep_++;
+ }
+ this.executeCallback_(entry, this.state_, this.result_);
+ }
+ this.executing_ = false;
+};
+
+
+/**
+ * Executes a pending callback for this Promise. Invokes an {@code onFulfilled}
+ * or {@code onRejected} callback based on the settled 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 settled result of the Promise.
+ * @private
+ */
+goog.Promise.prototype.executeCallback_ = function(
+ callbackEntry, state, result) {
+ // Cancel an unhandled rejection if the then/thenVoid call had an onRejected.
+ if (state == goog.Promise.State_.REJECTED &&
+ callbackEntry.onRejected && !callbackEntry.always) {
+ this.removeUnhandledRejection_();
+ }
+
+ if (callbackEntry.child) {
+ // When the parent is settled, the child no longer needs to hold on to it,
+ // as the parent can no longer be canceled.
+ callbackEntry.child.parent_ = null;
+ goog.Promise.invokeCallback_(callbackEntry, state, result);
+ } else {
+ // Callbacks created with thenAlways or thenVoid do not have the rejection
+ // handling code normally set up in the child Promise.
+ try {
+ callbackEntry.always ?
+ callbackEntry.onFulfilled.call(callbackEntry.context) :
+ goog.Promise.invokeCallback_(callbackEntry, state, result);
+ } catch (err) {
+ goog.Promise.handleRejection_.call(null, err);
+ }
+ }
+ goog.Promise.returnEntry_(callbackEntry);
+};
+
+
+/**
+ * Executes the onFulfilled or onRejected callback for a callbackEntry.
+ *
+ * @param {!goog.Promise.CallbackEntry_} callbackEntry
+ * @param {goog.Promise.State_} state
+ * @param {*} result
+ * @private
+ */
+goog.Promise.invokeCallback_ = function(callbackEntry, state, result) {
+ if (state == goog.Promise.State_.FULFILLED) {
+ callbackEntry.onFulfilled.call(callbackEntry.context, result);
+ } else if (callbackEntry.onRejected) {
+ callbackEntry.onRejected.call(callbackEntry.context, result);
+ }
+};
+
+
+/**
+ * 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
+ */
+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);
+ }
+};
+
+
+/**
+ * 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
+ */
+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');
+ }
+};
+
+
+/**
+ * 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
+ */
+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;
+ }
+ }
+};
+
+
+/**
+ * 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
+ */
+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);
+ }
+ });
+ }
+};
+
+
+/**
+ * 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
+ */
+goog.Promise.handleRejection_ = goog.async.throwException;
+
+
+/**
+ * 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}.
+ */
+goog.Promise.setUnhandledRejectionHandler = function(handler) {
+ goog.Promise.handleRejection_ = handler;
+};
+
+
+
+/**
+ * Error used as a rejection reason for canceled Promises.
+ *
+ * @param {string=} opt_message
+ * @constructor
+ * @extends {goog.debug.Error}
+ * @final
+ */
+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';
+
+
+
+/**
+ * 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
+ */
+goog.Promise.Resolver_ = function(promise, resolve, reject) {
+ /** @const */
+ this.promise = promise;
+
+ /** @const */
+ this.resolve = resolve;
+
+ /** @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.
+
+/**
+ * @fileoverview A timer class to which other classes and objects can
+ * listen on. This is only an abstraction above setInterval.
+ *
+ * @see ../demos/timers.html
+ */
+
+goog.provide('goog.Timer');
+
+goog.require('goog.Promise');
+goog.require('goog.events.EventTarget');
+
+
+
+/**
+ * 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 {goog.events.EventTarget}
+ */
+goog.Timer = function(opt_interval, opt_timerObject) {
+ goog.events.EventTarget.call(this);
+
+ /**
+ * Number of ms between ticks
+ * @type {number}
+ * @private
+ */
+ 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
+ */
+ this.timerObject_ = opt_timerObject || goog.Timer.defaultTimerObject;
+
+ /**
+ * Cached tick_ bound to the object for later use in the timer.
+ * @type {Function}
+ * @private
+ */
+ this.boundTick_ = goog.bind(this.tick_, this);
+
+ /**
+ * 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}
+ * @private
+ */
+ this.last_ = goog.now();
+};
+goog.inherits(goog.Timer, goog.events.EventTarget);
+
+
+/**
+ * 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;
+
+
+/**
+ * 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;
+
+
+/**
+ * Whether this timer is enabled
+ * @type {boolean}
+ */
+goog.Timer.prototype.enabled = false;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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;
+
+
+/**
+ * Variable for storing the result of setInterval
+ * @type {?number}
+ * @private
+ */
+goog.Timer.prototype.timer_ = null;
+
+
+/**
+ * Gets the interval of the timer.
+ * @return {number} interval Number of ms between ticks.
+ */
+goog.Timer.prototype.getInterval = function() {
+ return this.interval_;
+};
+
+
+/**
+ * Sets the interval of the timer.
+ * @param {number} interval Number of ms between ticks.
+ */
+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();
+ }
+};
+
+
+/**
+ * Callback for the setTimeout used by the timer
+ * @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();
+ }
+ }
+};
+
+
+/**
+ * Dispatches the TICK event. This is its own method so subclasses can override.
+ */
+goog.Timer.prototype.dispatchTick = function() {
+ this.dispatchEvent(goog.Timer.TICK);
+};
+
+
+/**
+ * Starts the timer.
+ */
+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();
+ }
+};
+
+
+/**
+ * Stops the timer.
+ */
+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_;
+};
+
+
+/**
+ * Constant for the timer's event type
+ * @type {string}
+ */
+goog.Timer.TICK = 'tick';
+
+
+/**
+ * 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
+ */
+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);
+ }
+};
+
+
+/**
+ * Clears a timeout initiated by callOnce
+ * @param {?number} timerId a timer ID.
+ */
+goog.Timer.clear = function(timerId) {
+ goog.Timer.defaultTimerObject.clearTimeout(timerId);
+};
+
+
+/**
+ * @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
+ */
+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.
+
+/**
+ * @fileoverview JSON utility functions.
+ * @author arv@google.com (Erik Arvidsson)
+ */
+
+
+goog.provide('goog.json');
+goog.provide('goog.json.Replacer');
+goog.provide('goog.json.Reviver');
+goog.provide('goog.json.Serializer');
+
+
+/**
+ * @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);
+
+
+/**
+ * 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;
+ }
+
+ // 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, ''));
+};
+
+
+/**
+ * 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);
+ };
+
+
+/**
+ * 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.
+ */
+goog.json.unsafeParse = goog.json.USE_NATIVE_JSON ?
+ /** @type {function(string):Object} */ (goog.global['JSON']['parse']) :
+ function(s) {
+ return /** @type {Object} */ (eval('(' + s + ')'));
+ };
+
+
+/**
+ * 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, *): *}
+ */
+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
+ *
+ * @typedef {function(this:Object, string, *): *}
+ */
+goog.json.Reviver;
+
+
+/**
+ * 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);
+ };
+
+
+
+/**
+ * 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
+ */
+ this.replacer_ = opt_replacer;
+};
+
+
+/**
+ * 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('');
+};
+
+
+/**
+ * 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) {
+ if (object == null) {
+ // undefined == null so this branch covers undefined as well as null
+ sb.push('null');
+ return;
+ }
+
+ if (typeof object == 'object') {
+ if (goog.isArray(object)) {
+ this.serializeArray(object, sb);
+ return;
+ } else if (object instanceof String ||
+ object instanceof Number ||
+ object instanceof Boolean) {
+ object = object.valueOf();
+ // Fall through to switch below.
+ } else {
+ this.serializeObject_(/** @type {Object} */ (object), sb);
+ return;
+ }
+ }
+
+ switch (typeof object) {
+ case 'string':
+ this.serializeString_(object, sb);
+ break;
+ case 'number':
+ this.serializeNumber_(object, sb);
+ break;
+ case 'boolean':
+ sb.push(object);
+ break;
+ case 'function':
+ sb.push('null');
+ break;
+ default:
+ throw Error('Unknown type: ' + typeof object);
+ }
+};
+
+
+/**
+ * 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',
+
+ '\x0B': '\\u000b' // '\v' is not supported in JScript
+};
+
+
+/**
+ * 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;
+
+
+/**
+ * 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
+ var rv = goog.json.Serializer.charToJsonCharCache_[c];
+ if (!rv) {
+ rv = '\\u' + (c.charCodeAt(0) | 0x10000).toString(16).substr(1);
+ goog.json.Serializer.charToJsonCharCache_[c] = rv;
+ }
+ return rv;
+ }), '"');
+};
+
+
+/**
+ * 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');
+};
+
+
+/**
+ * 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
+ */
+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(']');
+};
+
+
+/**
+ * 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.
+ */
+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.
+ 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.
+
+/**
+ * @fileoverview Error codes shared between goog.net.IframeIo and
+ * goog.net.XhrIo.
+ */
+
+goog.provide('goog.net.ErrorCode');
+
+
+/**
+ * Error codes
+ * @enum {number}
+ */
+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,
+
+ /**
+ * The resource is not available offline.
+ */
+ OFFLINE: 9
+};
+
+
+/**
+ * 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.
+ */
+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';
+
+ case goog.net.ErrorCode.ABORT:
+ return 'Request was aborted';
+
+ case goog.net.ErrorCode.TIMEOUT:
+ return 'Request timed out';
+
+ case goog.net.ErrorCode.OFFLINE:
+ return 'The resource is not available offline';
+
+ 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.
+
+/**
+ * @fileoverview Constants for HTTP status codes.
+ */
+
+goog.provide('goog.net.HttpStatus');
+
+
+/**
+ * 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,
+
+ // Successful 2xx
+ OK: 200,
+ CREATED: 201,
+ ACCEPTED: 202,
+ NON_AUTHORITATIVE_INFORMATION: 203,
+ NO_CONTENT: 204,
+ RESET_CONTENT: 205,
+ PARTIAL_CONTENT: 206,
+
+ // Redirection 3xx
+ MULTIPLE_CHOICES: 300,
+ MOVED_PERMANENTLY: 301,
+ FOUND: 302,
+ SEE_OTHER: 303,
+ NOT_MODIFIED: 304,
+ USE_PROXY: 305,
+ TEMPORARY_REDIRECT: 307,
+
+ // 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,
+
+ // 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
+};
+
+
+/**
+ * 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.
+ */
+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;
+ }
+};
+
+// 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');
+
+
+
+/**
+ * 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() {};
+
+
+/**
+ * Typedef that refers to either native or custom-implemented XHR objects.
+ * @typedef {!goog.net.XhrLike|!XMLHttpRequest}
+ */
+goog.net.XhrLike.OrNative;
+
+
+/**
+ * @type {function()|null|undefined}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange
+ */
+goog.net.XhrLike.prototype.onreadystatechange;
+
+
+/**
+ * @type {string}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute
+ */
+goog.net.XhrLike.prototype.responseText;
+
+
+/**
+ * @type {Document}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsexml-attribute
+ */
+goog.net.XhrLike.prototype.responseXML;
+
+
+/**
+ * @type {number}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#readystate
+ */
+goog.net.XhrLike.prototype.readyState;
+
+
+/**
+ * @type {number}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#status
+ */
+goog.net.XhrLike.prototype.status;
+
+
+/**
+ * @type {string}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#statustext
+ */
+goog.net.XhrLike.prototype.statusText;
+
+
+/**
+ * @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
+ */
+goog.net.XhrLike.prototype.send = function(opt_data) {};
+
+
+/**
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-abort()-method
+ */
+goog.net.XhrLike.prototype.abort = function() {};
+
+
+/**
+ * @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
+ */
+goog.net.XhrLike.prototype.getResponseHeader = function(header) {};
+
+
+/**
+ * @return {string}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders()-method
+ */
+goog.net.XhrLike.prototype.getAllResponseHeaders = function() {};
+
+// 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 Interface for a factory for creating XMLHttpRequest objects
+ * and metadata about them.
+ * @author dbk@google.com (David Barrett-Kahn)
+ */
+
+goog.provide('goog.net.XmlHttpFactory');
+
+/** @suppress {extraRequire} Typedef. */
+goog.require('goog.net.XhrLike');
+
+
+
+/**
+ * Abstract base class for an XmlHttpRequest factory.
+ * @constructor
+ */
+goog.net.XmlHttpFactory = function() {
+};
+
+
+/**
+ * Cache of options - we only actually call internalGetOptions once.
+ * @type {Object}
+ * @private
+ */
+goog.net.XmlHttpFactory.prototype.cachedOptions_ = null;
+
+
+/**
+ * @return {!goog.net.XhrLike.OrNative} A new XhrLike instance.
+ */
+goog.net.XmlHttpFactory.prototype.createInstance = goog.abstractMethod;
+
+
+/**
+ * @return {Object} Options describing how xhr objects obtained from this
+ * factory should be used.
+ */
+goog.net.XmlHttpFactory.prototype.getOptions = function() {
+ return this.cachedOptions_ ||
+ (this.cachedOptions_ = this.internalGetOptions());
+};
+
+
+/**
+ * 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
+ */
+goog.net.XmlHttpFactory.prototype.internalGetOptions = goog.abstractMethod;
+
+// 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)
+ */
+
+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
+ * @final
+ */
+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_();
+};
+
+
+/** @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.
+
+/**
+ * @fileoverview Low level handling of XMLHttpRequest.
+ * @author arv@google.com (Erik Arvidsson)
+ * @author dbk@google.com (David Barrett-Kahn)
+ */
+
+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');
+
+
+/**
+ * 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();
+};
+
+
+/**
+ * @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);
+
+
+/** @const */
+goog.net.XmlHttpDefines = {};
+
+
+/**
+ * @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);
+
+
+/**
+ * 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();
+};
+
+
+/**
+ * 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
+};
+
+
+/**
+ * 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
+ */
+ UNINITIALIZED: 0,
+
+ /**
+ * Constant for when xmlhttprequest.readyState is loading.
+ */
+ LOADING: 1,
+
+ /**
+ * Constant for when xmlhttprequest.readyState is loaded.
+ */
+ LOADED: 2,
+
+ /**
+ * Constant for when xmlhttprequest.readyState is in an interactive state.
+ */
+ INTERACTIVE: 3,
+
+ /**
+ * Constant for when xmlhttprequest.readyState is completed
+ */
+ COMPLETE: 4
+};
+
+
+/**
+ * The global factory instance for creating XMLHttpRequest objects.
+ * @type {goog.net.XmlHttpFactory}
+ * @private
+ */
+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)));
+};
+
+
+/**
+ * Sets the global factory object.
+ * @param {!goog.net.XmlHttpFactory} factory New global factory object.
+ */
+goog.net.XmlHttp.setGlobalFactory = function(factory) {
+ goog.net.XmlHttp.factory_ = factory;
+};
+
+
+
+/**
+ * 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 {
+ return new XMLHttpRequest();
+ }
+};
+
+
+/** @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;
+};
+
+
+/**
+ * The ActiveX PROG ID string to use to create xhr's in IE. Lazily initialized.
+ * @type {string|undefined}
+ * @private
+ */
+goog.net.DefaultXmlHttpFactory.prototype.ieProgId_;
+
+
+/**
+ * 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 '';
+ }
+
+ // 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');
+ }
+
+ 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 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 operate on strings directly, rather than decoding them and
+ * then 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');
+
+
+/**
+ * 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 + ':';
+ }
+
+ if (opt_domain) {
+ out += '//';
+
+ if (opt_userInfo) {
+ out += opt_userInfo + '@';
+ }
+
+ out += opt_domain;
+
+ if (opt_port) {
+ out += ':' + opt_port;
+ }
+ }
+
+ 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
+ '$');
+
+
+/**
+ * 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
+};
+
+
+/**
+ * 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) {
+ // See @return comment -- never null.
+ return /** @type {!Array<string|undefined>} */ (
+ uri.match(goog.uri.utils.splitRe_));
+};
+
+
+/**
+ * @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;
+ }
+
+ return opt_preserveReserved ? decodeURI(uri) : decodeURIComponent(uri);
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * 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.
+ */
+goog.uri.utils.getEffectiveScheme = function(uri) {
+ var scheme = goog.uri.utils.getScheme(uri);
+ if (!scheme && goog.global.self && goog.global.self.location) {
+ var protocol = goog.global.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() : '';
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The user name still encoded, or null if none.
+ */
+goog.uri.utils.getUserInfoEncoded = function(uri) {
+ return goog.uri.utils.getComponentByIndex_(
+ goog.uri.utils.ComponentIndex.USER_INFO, uri);
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The decoded user info, or null if none.
+ */
+goog.uri.utils.getUserInfo = function(uri) {
+ return goog.uri.utils.decodeIfPossible_(
+ goog.uri.utils.getUserInfoEncoded(uri));
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The domain name still encoded, or null if none.
+ */
+goog.uri.utils.getDomainEncoded = function(uri) {
+ return goog.uri.utils.getComponentByIndex_(
+ goog.uri.utils.ComponentIndex.DOMAIN, uri);
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The decoded domain, or null if none.
+ */
+goog.uri.utils.getDomain = function(uri) {
+ return goog.uri.utils.decodeIfPossible_(
+ goog.uri.utils.getDomainEncoded(uri), true /* opt_preserveReserved */);
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?number} The port number, or null if none.
+ */
+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;
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @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.
+ */
+goog.uri.utils.getQueryData = function(uri) {
+ return goog.uri.utils.getComponentByIndex_(
+ goog.uri.utils.ComponentIndex.QUERY_DATA, uri);
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @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 : '');
+};
+
+
+/**
+ * @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));
+};
+
+
+/**
+ * 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]);
+};
+
+
+/**
+ * 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]);
+};
+
+
+/**
+ * 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.
+ *
+ * @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];
+};
+
+
+/**
+ * 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 + ']');
+ }
+};
+
+
+/**
+ * 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;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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.
+ */
+goog.uri.utils.parseQueryData = function(encodedQuery, callback) {
+ if (!encodedQuery) {
+ return;
+ }
+ 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) : '');
+ }
+};
+
+
+/**
+ * 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
+ */
+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('');
+};
+
+
+/**
+ * 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
+ */
+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));
+ }
+};
+
+
+/**
+ * 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
+ */
+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.');
+
+ for (var i = opt_startIndex || 0; i < keysAndValues.length; i += 2) {
+ goog.uri.utils.appendKeyValuePairs_(
+ keysAndValues[i], keysAndValues[i + 1], buffer);
+ }
+
+ return buffer;
+};
+
+
+/**
+ * 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.
+ * @private
+ */
+goog.uri.utils.buildQueryDataBufferFromMap_ = function(buffer, map) {
+ for (var key in map) {
+ goog.uri.utils.appendKeyValuePairs_(key, map[key], buffer);
+ }
+
+ return buffer;
+};
+
+
+/**
+ * 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'.
+ */
+goog.uri.utils.buildQueryDataFromMap = function(map) {
+ var buffer = goog.uri.utils.buildQueryDataBufferFromMap_([], map);
+ buffer[0] = '';
+ return buffer.join('');
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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.
+ */
+goog.uri.utils.appendParamsFromMap = function(uri, map) {
+ return goog.uri.utils.appendQueryData_(
+ goog.uri.utils.buildQueryDataBufferFromMap_([uri], map));
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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;
+
+ // 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;
+ }
+
+ return -1;
+};
+
+
+/**
+ * Regular expression for finding a hash mark or end of string.
+ * @type {RegExp}
+ * @private
+ */
+goog.uri.utils.hashOrEndRe_ = /#|$/;
+
+
+/**
+ * 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.
+ */
+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));
+ }
+};
+
+
+/**
+ * Gets all values 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 {!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 = [];
+
+ 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;
+ }
+
+ // 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;
+};
+
+
+/**
+ * Regexp to find trailing question marks and ampersands.
+ * @type {RegExp}
+ * @private
+ */
+goog.uri.utils.trailingQueryPunctuationRe_ = /[?&]($|#)/;
+
+
+/**
+ * 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 = [];
+
+ // 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);
+ }
+
+ // 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');
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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]);
+};
+
+
+/**
+ * Standard supported query parameters.
+ * @enum {string}
+ */
+goog.uri.utils.StandardQueryParam = {
+
+ /** Unused parameter for unique-ifying. */
+ RANDOM: 'zx'
+};
+
+
+/**
+ * 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 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 on success, failure, finishing,
+ * ready-state change, or progress.
+ *
+ * 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. Progress events are fired as they are
+ * received. 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.
+ *
+ * When progress events are supported by the browser, and progress is
+ * enabled via .setProgressEventsEnabled(true), the
+ * goog.net.EventType.PROGRESS event will be the re-dispatched browser
+ * progress event.
+ *
+ * Tested = IE6, FF1.5, Safari, Opera 8.5
+ *
+ * TODO(user): Error cases aren't playing nicely in Safari.
+ *
+ */
+
+
+goog.provide('goog.net.XhrIo');
+goog.provide('goog.net.XhrIo.ResponseType');
+
+goog.require('goog.Timer');
+goog.require('goog.array');
+goog.require('goog.asserts');
+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;
+
+ /**
+ * Whether progress events are enabled for this request. This is
+ * disabled by default because setting a progress event handler
+ * causes pre-flight OPTIONS requests to be sent for CORS requests,
+ * even in cases where a pre-flight request would not otherwise be
+ * sent.
+ *
+ * @see http://xhr.spec.whatwg.org/#security-considerations
+ *
+ * Note that this can cause problems for Firefox 22 and below, as an
+ * older "LSProgressEvent" will be dispatched by the browser. That
+ * progress event is no longer supported, and can lead to failures,
+ * including throwing exceptions.
+ *
+ * @see http://bugzilla.mozilla.org/show_bug.cgi?id=845631
+ * @see b/23469793
+ *
+ * @private {boolean}
+ */
+ this.progressEventsEnabled_ = 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
+ */
+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';
+
+
+/**
+ * The pattern matching the 'http' and 'https' URI schemes
+ * @type {!RegExp}
+ */
+goog.net.XhrIo.HTTP_SCHEME_PATTERN = /^https?$/i;
+
+
+/**
+ * The methods that typically come along with form data. We set different
+ * headers depending on whether the HTTP action is one of these.
+ */
+goog.net.XhrIo.METHODS_WITH_FORM_DATA = ['POST', 'PUT'];
+
+
+/**
+ * The Content-Type HTTP header value for a url-encoded form
+ * @type {string}
+ */
+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
+ */
+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
+ */
+goog.net.XhrIo.XHR2_ON_TIMEOUT_ = 'ontimeout';
+
+
+/**
+ * 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.net.XhrIo.sendInstances_ = [];
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.net.XhrIo.cleanup = function() {
+ var instances = goog.net.XhrIo.sendInstances_;
+ while (instances.length) {
+ instances.pop().dispose();
+ }
+};
+
+
+/**
+ * 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.net.XhrIo.protectEntryPoints = function(errorHandler) {
+ goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ =
+ errorHandler.protectEntryPoint(
+ goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_);
+};
+
+
+/**
+ * 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.net.XhrIo.prototype.cleanupSend_ = function() {
+ this.dispose();
+ goog.array.remove(goog.net.XhrIo.sendInstances_, this);
+};
+
+
+/**
+ * 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.net.XhrIo.prototype.getTimeoutInterval = function() {
+ return this.timeoutInterval_;
+};
+
+
+/**
+ * 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.net.XhrIo.prototype.setTimeoutInterval = function(ms) {
+ this.timeoutInterval_ = Math.max(0, ms);
+};
+
+
+/**
+ * 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.net.XhrIo.prototype.setResponseType = function(type) {
+ this.responseType_ = type;
+};
+
+
+/**
+ * Gets the desired type for the response.
+ * @return {goog.net.XhrIo.ResponseType} The desired type for the response.
+ */
+goog.net.XhrIo.prototype.getResponseType = function() {
+ return this.responseType_;
+};
+
+
+/**
+ * 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.net.XhrIo.prototype.setWithCredentials = function(withCredentials) {
+ this.withCredentials_ = withCredentials;
+};
+
+
+/**
+ * Gets whether a "credentialed" request is to be sent.
+ * @return {boolean} The desired type for the response.
+ */
+goog.net.XhrIo.prototype.getWithCredentials = function() {
+ return this.withCredentials_;
+};
+
+
+/**
+ * Sets whether progress events are enabled for this request. Note
+ * that progress events require pre-flight OPTIONS request handling
+ * for CORS requests, and may cause trouble with older browsers. See
+ * progressEventsEnabled_ for details.
+ * @param {boolean} enabled Whether progress events should be enabled.
+ */
+goog.net.XhrIo.prototype.setProgressEventsEnabled = function(enabled) {
+ this.progressEventsEnabled_ = enabled;
+};
+
+
+/**
+ * Gets whether progress events are enabled.
+ * @return {boolean} Whether progress events are enabled for this request.
+ */
+goog.net.XhrIo.prototype.getProgressEventsEnabled = function() {
+ return this.progressEventsEnabled_;
+};
+
+
+/**
+ * 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.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);
+
+ // Set up upload/download progress events, if progress events are supported.
+ if (this.getProgressEventsEnabled() && 'onprogress' in this.xhr_) {
+ this.xhr_.onprogress = goog.bind(this.onProgressHandler_, this);
+ if (this.xhr_.upload) {
+ this.xhr_.upload.onprogress = goog.bind(this.onProgressHandler_, 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);
+ }
+};
+
+
+/**
+ * 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.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_]);
+};
+
+
+/**
+ * @param {string} header An HTTP header key.
+ * @return {boolean} Whether the key is a content type header (ignoring
+ * case.
+ * @private
+ */
+goog.net.XhrIo.isContentTypeHeader_ = function(header) {
+ return goog.string.caseInsensitiveEquals(
+ goog.net.XhrIo.CONTENT_TYPE_HEADER, header);
+};
+
+
+/**
+ * Creates a new XHR object.
+ * @return {!goog.net.XhrLike.OrNative} The newly created XHR object.
+ * @protected
+ */
+goog.net.XhrIo.prototype.createXhr = function() {
+ return this.xmlHttpFactory_ ?
+ this.xmlHttpFactory_.createInstance() : goog.net.XmlHttp();
+};
+
+
+/**
+ * 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.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);
+ }
+};
+
+
+/**
+ * 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.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_();
+};
+
+
+/**
+ * Dispatches COMPLETE and ERROR in case of an error. This ensures that we do
+ * not dispatch multiple error events.
+ * @private
+ */
+goog.net.XhrIo.prototype.dispatchErrors_ = function() {
+ if (!this.errorDispatched_) {
+ this.errorDispatched_ = true;
+ this.dispatchEvent(goog.net.EventType.COMPLETE);
+ this.dispatchEvent(goog.net.EventType.ERROR);
+ }
+};
+
+
+/**
+ * Abort the current XMLHttpRequest
+ * @param {goog.net.ErrorCode=} opt_failureCode Optional error code to use -
+ * defaults to ABORT.
+ */
+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_();
+ }
+};
+
+
+/**
+ * Nullifies all callbacks to reduce risks of leaks.
+ * @override
+ * @protected
+ */
+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');
+};
+
+
+/**
+ * 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.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_();
+ }
+};
+
+
+/**
+ * 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.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ = function() {
+ this.onReadyStateChangeHelper_();
+};
+
+
+/**
+ * Helper for {@link #onReadyStateChange_}. This is used so that
+ * entry point calls to {@link #onReadyStateChange_} can be routed through
+ * {@link #onReadyStateChangeEntryPoint_}.
+ * @private
+ */
+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 {
+
+ // 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);
+
+ // readyState indicates the transfer has finished
+ if (this.isComplete()) {
+ goog.log.fine(this.logger_, this.formatMsg_('Request complete'));
+
+ 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_();
+ }
+ }
+ }
+};
+
+
+/**
+ * Internal handler for the XHR object's onprogress event.
+ * @param {!ProgressEvent} e XHR progress event.
+ * @private
+ */
+goog.net.XhrIo.prototype.onProgressHandler_ = function(e) {
+ goog.asserts.assert(e.type === goog.net.EventType.PROGRESS,
+ 'goog.net.EventType.PROGRESS is of the same type as raw XHR progress.');
+ // Redispatch the progress event.
+ this.dispatchEvent(e);
+};
+
+
+/**
+ * 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.net.XhrIo.prototype.cleanUpXhr_ = function(opt_fromDispose) {
+ if (this.xhr_) {
+ // Cancel any pending timeout event handler.
+ this.cleanUpTimeoutTimer_();
+
+ // 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);
+ }
+
+ 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);
+ }
+ }
+};
+
+
+/**
+ * Make sure the timeout timer isn't running.
+ * @private
+ */
+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;
+ }
+};
+
+
+/**
+ * @return {boolean} Whether there is an active request.
+ */
+goog.net.XhrIo.prototype.isActive = function() {
+ return !!this.xhr_;
+};
+
+
+/**
+ * @return {boolean} Whether the request has completed.
+ */
+goog.net.XhrIo.prototype.isComplete = function() {
+ return this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE;
+};
+
+
+/**
+ * @return {boolean} Whether the request completed with a success.
+ */
+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_();
+};
+
+
+/**
+ * @return {boolean} whether the effective scheme of the last URI that was
+ * fetched was 'http' or 'https'.
+ * @private
+ */
+goog.net.XhrIo.prototype.isLastUriEffectiveSchemeHttp_ = function() {
+ var scheme = goog.uri.utils.getEffectiveScheme(String(this.lastUri_));
+ return goog.net.XhrIo.HTTP_SCHEME_PATTERN.test(scheme);
+};
+
+
+/**
+ * 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.net.XhrIo.prototype.getReadyState = function() {
+ return this.xhr_ ?
+ /** @type {goog.net.XmlHttp.ReadyState} */ (this.xhr_.readyState) :
+ goog.net.XmlHttp.ReadyState.UNINITIALIZED;
+};
+
+
+/**
+ * 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.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;
+ }
+};
+
+
+/**
+ * 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.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 '';
+ }
+};
+
+
+/**
+ * Get the last Uri that was requested
+ * @return {string} Last Uri.
+ */
+goog.net.XhrIo.prototype.getLastUri = function() {
+ return String(this.lastUri_);
+};
+
+
+/**
+ * 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.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 '';
+ }
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+ }
+};
+
+
+/**
+ * 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.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);
+ }
+
+ 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
+ *
+ * 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.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;
+ }
+};
+
+
+/**
+ * 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.net.XhrIo.prototype.getResponseHeader = function(key) {
+ return this.xhr_ && this.isComplete() ?
+ this.xhr_.getResponseHeader(key) : undefined;
+};
+
+
+/**
+ * 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.net.XhrIo.prototype.getAllResponseHeaders = function() {
+ return this.xhr_ && this.isComplete() ?
+ this.xhr_.getAllResponseHeaders() : '';
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * Get the last error message
+ * @return {goog.net.ErrorCode} Last error code.
+ */
+goog.net.XhrIo.prototype.getLastErrorCode = function() {
+ return this.lastErrorCode_;
+};
+
+
+/**
+ * Get the last error message
+ * @return {string} Last error message.
+ */
+goog.net.XhrIo.prototype.getLastError = function() {
+ return goog.isString(this.lastError_) ? this.lastError_ :
+ String(this.lastError_);
+};
+
+
+/**
+ * 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.net.XhrIo.prototype.formatMsg_ = function(msg) {
+ return msg + ' [' + this.lastMethod_ + ' ' + this.lastUri_ + ' ' +
+ this.getStatus() + ']';
+};
+
+
+// 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_);
+ });
+
+goog.provide('ol.TileLoadFunctionType');
+goog.provide('ol.TileVectorLoadFunctionType');
+
+
+/**
+ * A function that takes an {@link ol.Tile} for the tile and a
+ * `{string}` for the url as arguments.
+ *
+ * @typedef {function(ol.Tile, string)}
+ * @api
+ */
+ol.TileLoadFunctionType;
+
+
+/**
+ * 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.TileVectorLoadFunctionType;
+
+goog.provide('ol.VectorTile');
+
+goog.require('ol.Tile');
+goog.require('ol.TileCoord');
+goog.require('ol.TileLoadFunctionType');
+goog.require('ol.TileState');
+goog.require('ol.proj.Projection');
+
+
+/**
+ * @typedef {{
+ * dirty: boolean,
+ * renderedRenderOrder: (null|function(ol.Feature, ol.Feature):number),
+ * renderedRevision: number,
+ * replayGroup: ol.render.IReplayGroup}}
+ */
+ol.TileReplayState;
+
+
+
+/**
+ * @constructor
+ * @extends {ol.Tile}
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.TileState} state State.
+ * @param {string} src Data source url.
+ * @param {ol.format.Feature} format Feature format.
+ * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
+ * @param {ol.proj.Projection} projection Feature projection.
+ */
+ol.VectorTile =
+ function(tileCoord, state, src, format, tileLoadFunction, projection) {
+
+ goog.base(this, tileCoord, state);
+
+ /**
+ * @private
+ * @type {ol.format.Feature}
+ */
+ this.format_ = format;
+
+ /**
+ * @private
+ * @type {Array.<ol.Feature>}
+ */
+ this.features_ = null;
+
+ /**
+ * @private
+ * @type {ol.FeatureLoader}
+ */
+ this.loader_;
+
+ /**
+ * @private
+ * @type {ol.proj.Projection}
+ */
+ this.projection_ = projection;
+
+ /**
+ * @private
+ * @type {ol.TileReplayState}
+ */
+ this.replayState_ = {
+ dirty: false,
+ renderedRenderOrder: null,
+ renderedRevision: -1,
+ replayGroup: null
+ };
+
+ /**
+ * @private
+ * @type {ol.TileLoadFunctionType}
+ */
+ this.tileLoadFunction_ = tileLoadFunction;
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.url_ = src;
+
+};
+goog.inherits(ol.VectorTile, ol.Tile);
+
+
+/**
+ * @inheritDoc
+ */
+ol.VectorTile.prototype.disposeInternal = function() {
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * Get the feature format assigned for reading this tile's features.
+ * @return {ol.format.Feature} Feature format.
+ * @api
+ */
+ol.VectorTile.prototype.getFormat = function() {
+ return this.format_;
+};
+
+
+/**
+ * @return {Array.<ol.Feature>} Features.
+ */
+ol.VectorTile.prototype.getFeatures = function() {
+ return this.features_;
+};
+
+
+/**
+ * @return {ol.TileReplayState}
+ */
+ol.VectorTile.prototype.getReplayState = function() {
+ return this.replayState_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.VectorTile.prototype.getKey = function() {
+ return this.url_;
+};
+
+
+/**
+ * @return {ol.proj.Projection} Feature projection.
+ */
+ol.VectorTile.prototype.getProjection = function() {
+ return this.projection_;
+};
+
+
+/**
+ * Load the tile.
+ */
+ol.VectorTile.prototype.load = function() {
+ if (this.state == ol.TileState.IDLE) {
+ this.setState(ol.TileState.LOADING);
+ this.tileLoadFunction_(this, this.url_);
+ this.loader_(null, NaN, this.projection_);
+ }
+};
+
+
+/**
+ * @param {Array.<ol.Feature>} features Features.
+ */
+ol.VectorTile.prototype.setFeatures = function(features) {
+ this.features_ = features;
+ this.setState(ol.TileState.LOADED);
+};
+
+
+/**
+ * @param {ol.proj.Projection} projection Feature projection.
+ */
+ol.VectorTile.prototype.setProjection = function(projection) {
+ this.projection_ = projection;
+};
+
+
+/**
+ * @param {ol.TileState} tileState Tile state.
+ */
+ol.VectorTile.prototype.setState = function(tileState) {
+ this.state = tileState;
+ this.changed();
+};
+
+
+/**
+ * Set the feature loader for reading this tile's features.
+ * @param {ol.FeatureLoader} loader Feature loader.
+ * @api
+ */
+ol.VectorTile.prototype.setLoader = function(loader) {
+ this.loader_ = loader;
+};
+
+goog.provide('ol.format.FormatType');
+
+
+/**
+ * @enum {string}
+ */
+ol.format.FormatType = {
+ ARRAY_BUFFER: 'arraybuffer',
+ 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.
+
+/**
+ * @fileoverview
+ * XML utilities.
+ *
+ */
+
+goog.provide('goog.dom.xml');
+
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.userAgent');
+
+
+/**
+ * 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}
+ */
+goog.dom.xml.MAX_ELEMENT_DEPTH = 256; // Same default as MSXML6.
+
+
+/**
+ * Check for ActiveXObject support by the browser.
+ * @return {boolean} true if browser has ActiveXObject support.
+ * @private
+ */
+goog.dom.xml.hasActiveXObjectSupport_ = function() {
+ if (!goog.userAgent.IE) {
+ // Avoid raising useless exception in case code is not compiled
+ // and browser is not MSIE.
+ return false;
+ }
+ try {
+ // Due to lot of changes in IE 9, 10 & 11 behaviour and ActiveX being
+ // totally disableable using MSIE's security level, trying to create the
+ // ActiveXOjbect is a lot more reliable than testing for the existance of
+ // window.ActiveXObject
+ new ActiveXObject('MSXML2.DOMDocument');
+ return true;
+ } catch (e) {
+ return false;
+ }
+};
+
+
+/**
+ * True if browser has ActiveXObject support.
+ * Possible override if this test become wrong in coming IE versions.
+ * @type {boolean}
+ */
+goog.dom.xml.ACTIVEX_SUPPORT =
+ goog.userAgent.IE && goog.dom.xml.hasActiveXObjectSupport_();
+
+
+/**
+ * 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.
+ * @param {boolean=} opt_preferActiveX Whether to default to ActiveXObject to
+ * create Document in IE. Use this if you need xpath support in IE (e.g.,
+ * selectSingleNode or selectNodes), but be aware that the ActiveXObject does
+ * not support various DOM-specific Document methods and attributes.
+ * @return {Document} The new document.
+ * @throws {Error} if browser does not support creating new documents or
+ * namespace is provided without a root tag name.
+ */
+goog.dom.xml.createDocument = function(opt_rootTagName, opt_namespaceUri,
+ opt_preferActiveX) {
+ if (opt_namespaceUri && !opt_rootTagName) {
+ throw Error("Can't create document with namespace and no root tag");
+ }
+ // If document.implementation.createDocument is available and they haven't
+ // explicitly opted to use ActiveXObject when possible.
+ if (document.implementation && document.implementation.createDocument &&
+ !(goog.dom.xml.ACTIVEX_SUPPORT && opt_preferActiveX)) {
+ return document.implementation.createDocument(opt_namespaceUri || '',
+ opt_rootTagName || '', null);
+ } else if (goog.dom.xml.ACTIVEX_SUPPORT) {
+ 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');
+};
+
+
+/**
+ * Creates an XML document from a string
+ * @param {string} xml The text.
+ * @param {boolean=} opt_preferActiveX Whether to default to ActiveXObject to
+ * create Document in IE. Use this if you need xpath support in IE (e.g.,
+ * selectSingleNode or selectNodes), but be aware that the ActiveXObject does
+ * not support various DOM-specific Document methods and attributes.
+ * @return {Document} XML document from the text.
+ * @throws {Error} if browser does not support loading XML documents.
+ */
+goog.dom.xml.loadXml = function(xml, opt_preferActiveX) {
+ if (typeof DOMParser != 'undefined' &&
+ !(goog.dom.xml.ACTIVEX_SUPPORT && opt_preferActiveX)) {
+ return new DOMParser().parseFromString(xml, 'application/xml');
+ } else if (goog.dom.xml.ACTIVEX_SUPPORT) {
+ var doc = goog.dom.xml.createMsXmlDocument_();
+ doc.loadXML(xml);
+ return doc;
+ }
+ throw Error('Your browser does not support loading xml documents');
+};
+
+
+/**
+ * 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.
+ * @throws {Error} if browser does not support XML serialization.
+ */
+goog.dom.xml.serialize = function(xml) {
+ // Compatible with IE/ActiveXObject.
+ var text = xml.xml;
+ if (text) {
+ return text;
+ }
+ // Compatible with Firefox, Opera and WebKit.
+ if (typeof XMLSerializer != 'undefined') {
+ return new XMLSerializer().serializeToString(xml);
+ }
+ throw Error('Your browser does not support serializing XML documents');
+};
+
+
+/**
+ * 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.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;
+ }
+ // This browser does not support xpath for the given node. If IE, ensure XML
+ // Document was created using ActiveXObject
+ // TODO(joeltine): This should throw instead of return null.
+ return null;
+};
+
+
+/**
+ * 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.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 {
+ // This browser does not support xpath for the given node. If IE, ensure XML
+ // Document was created using ActiveXObject.
+ // TODO(joeltine): This should throw instead of return empty array.
+ return [];
+ }
+};
+
+
+/**
+ * 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]);
+ }
+ }
+};
+
+
+/**
+ * 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.
+ }
+ }
+ return doc;
+};
+
+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);
+};
+
+
+/**
+ * @param {string} namespaceURI Namespace URI.
+ * @param {string} qualifiedName Qualified name.
+ * @return {Node} Node.
+ * @private
+ */
+ol.xml.createElementNSActiveX_ = function(namespaceURI, qualifiedName) {
+ if (!namespaceURI) {
+ namespaceURI = '';
+ }
+ return ol.xml.DOCUMENT.createNode(1, qualifiedName, namespaceURI);
+};
+
+
+/**
+ * @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
+ */
+ol.xml.getAllTextContent = function(node, normalizeWhitespace) {
+ return ol.xml.getAllTextContent_(node, normalizeWhitespace, []).join('');
+};
+
+
+/**
+ * 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.
+ */
+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; n; n = n.nextSibling) {
+ ol.xml.getAllTextContent_(n, normalizeWhitespace, accumulator);
+ }
+ }
+ return accumulator;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {string} Local name.
+ */
+ol.xml.getLocalName_ = function(node) {
+ return node.localName;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {string} Local name.
+ */
+ol.xml.getLocalNameIE_ = function(node) {
+ var localName = node.localName;
+ if (localName !== undefined) {
+ return localName;
+ }
+ var baseName = node.baseName;
+ goog.asserts.assert(baseName,
+ 'Failed to get localName/baseName of node %s', node);
+ return baseName;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {string} Local name.
+ */
+ol.xml.getLocalName = goog.userAgent.IE ?
+ ol.xml.getLocalNameIE_ : ol.xml.getLocalName_;
+
+
+/**
+ * @param {?} value Value.
+ * @private
+ * @return {boolean} Is document.
+ */
+ol.xml.isDocument_ = function(value) {
+ return value instanceof Document;
+};
+
+
+/**
+ * @param {?} value Value.
+ * @private
+ * @return {boolean} Is document.
+ */
+ol.xml.isDocumentIE_ = function(value) {
+ return goog.isObject(value) && value.nodeType == goog.dom.NodeType.DOCUMENT;
+};
+
+
+/**
+ * @param {?} value Value.
+ * @return {boolean} Is document.
+ */
+ol.xml.isDocument = goog.userAgent.IE ?
+ ol.xml.isDocumentIE_ : ol.xml.isDocument_;
+
+
+/**
+ * @param {?} value Value.
+ * @private
+ * @return {boolean} Is node.
+ */
+ol.xml.isNode_ = function(value) {
+ return value instanceof Node;
+};
+
+
+/**
+ * @param {?} value Value.
+ * @private
+ * @return {boolean} Is node.
+ */
+ol.xml.isNodeIE_ = function(value) {
+ return goog.isObject(value) && value.nodeType !== undefined;
+};
+
+
+/**
+ * @param {?} value Value.
+ * @return {boolean} Is node.
+ */
+ol.xml.isNode = goog.userAgent.IE ? ol.xml.isNodeIE_ : ol.xml.isNode_;
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @return {string} Value
+ * @private
+ */
+ol.xml.getAttributeNS_ = function(node, namespaceURI, name) {
+ return node.getAttributeNS(namespaceURI, name) || '';
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @return {string} Value
+ * @private
+ */
+ol.xml.getAttributeNSActiveX_ = function(node, namespaceURI, name) {
+ var attributeValue = '';
+ var attributeNode = ol.xml.getAttributeNodeNS(node, namespaceURI, name);
+ if (attributeNode !== undefined) {
+ attributeValue = attributeNode.nodeValue;
+ }
+ return attributeValue;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @return {string} Value
+ */
+ol.xml.getAttributeNS =
+ (document.implementation && document.implementation.createDocument) ?
+ ol.xml.getAttributeNS_ : ol.xml.getAttributeNSActiveX_;
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @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.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;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @return {?Node} Attribute node or null if none found.
+ */
+ol.xml.getAttributeNodeNS =
+ (document.implementation && document.implementation.createDocument) ?
+ ol.xml.getAttributeNodeNS_ : ol.xml.getAttributeNodeNSActiveX_;
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @param {string|number} value Value.
+ * @private
+ */
+ol.xml.setAttributeNSActiveX_ = function(node, namespaceURI, name, value) {
+ if (namespaceURI) {
+ var attribute = node.ownerDocument.createNode(2, name, namespaceURI);
+ attribute.nodeValue = value;
+ node.setAttributeNode(attribute);
+ } else {
+ node.setAttribute(name, value);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @param {string|number} value Value.
+ */
+ol.xml.setAttributeNS =
+ (document.implementation && document.implementation.createDocument) ?
+ ol.xml.setAttributeNS_ : ol.xml.setAttributeNSActiveX_;
+
+
+/**
+ * Parse an XML string to an XML Document.
+ * @param {string} xml XML.
+ * @return {Document} Document.
+ * @api
+ */
+ol.xml.parse = function(xml) {
+ return new DOMParser().parseFromString(xml, 'application/xml');
+};
+
+
+/**
+ * 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
+ */
+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 (value !== undefined) {
+ 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);
+ }
+ });
+};
+
+
+/**
+ * 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
+ */
+ol.xml.makeArrayPusher = function(valueReader, opt_this) {
+ return (
+ /**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ */
+ function(node, objectStack) {
+ var value = valueReader.call(opt_this !== undefined ? opt_this : this,
+ node, objectStack);
+ if (value !== undefined) {
+ var array = objectStack[objectStack.length - 1];
+ goog.asserts.assert(goog.isArray(array),
+ 'objectStack is supposed to be an array of arrays');
+ array.push(value);
+ }
+ });
+};
+
+
+/**
+ * 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
+ */
+ol.xml.makeReplacer = function(valueReader, opt_this) {
+ return (
+ /**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ */
+ function(node, objectStack) {
+ var value = valueReader.call(opt_this !== undefined ? opt_this : this,
+ node, objectStack);
+ if (value !== undefined) {
+ objectStack[objectStack.length - 1] = value;
+ }
+ });
+};
+
+
+/**
+ * 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
+ */
+ol.xml.makeObjectPropertyPusher =
+ function(valueReader, opt_property, opt_this) {
+ goog.asserts.assert(valueReader !== undefined,
+ '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(opt_this !== undefined ? opt_this : this,
+ node, objectStack);
+ if (value !== undefined) {
+ var object = /** @type {Object} */
+ (objectStack[objectStack.length - 1]);
+ var property = opt_property !== undefined ?
+ 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);
+ }
+ });
+};
+
+
+/**
+ * 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
+ */
+ol.xml.makeObjectPropertySetter =
+ function(valueReader, opt_property, opt_this) {
+ goog.asserts.assert(valueReader !== undefined,
+ '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(opt_this !== undefined ? opt_this : this,
+ node, objectStack);
+ if (value !== undefined) {
+ var object = /** @type {Object} */
+ (objectStack[objectStack.length - 1]);
+ var property = opt_property !== undefined ?
+ opt_property : node.localName;
+ goog.asserts.assert(goog.isObject(object),
+ 'entity from stack was not an object');
+ object[property] = value;
+ }
+ });
+};
+
+
+/**
+ * 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
+ */
+ol.xml.makeChildAppender = function(nodeWriter, opt_this) {
+ return function(node, value, objectStack) {
+ nodeWriter.call(opt_this !== undefined ? 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);
+ };
+};
+
+
+/**
+ * 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
+ */
+ol.xml.makeArraySerializer = function(nodeWriter, opt_this) {
+ var serializersNS, nodeFactory;
+ return function(node, value, objectStack) {
+ if (serializersNS === undefined) {
+ serializersNS = {};
+ var serializers = {};
+ serializers[node.localName] = nodeWriter;
+ serializersNS[node.namespaceURI] = serializers;
+ nodeFactory = ol.xml.makeSimpleNodeFactory(node.localName);
+ }
+ ol.xml.serialize(serializersNS, nodeFactory, value, objectStack);
+ };
+};
+
+
+/**
+ * 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.
+ */
+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 (nodeName === undefined) {
+ nodeName = opt_nodeName;
+ }
+ var namespaceURI = opt_namespaceURI;
+ if (opt_namespaceURI === undefined) {
+ namespaceURI = node.namespaceURI;
+ }
+ goog.asserts.assert(nodeName !== undefined, '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 {function(*, Array.<*>, string=): (Node|undefined)}
+ */
+ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory();
+
+
+/**
+ * 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
+ */
+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;
+};
+
+
+/**
+ * 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
+ */
+ol.xml.makeStructureNS = function(namespaceURIs, structure, opt_structureNS) {
+ /**
+ * @type {Object.<string, *>}
+ */
+ var structureNS = opt_structureNS !== undefined ? opt_structureNS : {};
+ var i, ii;
+ for (i = 0, ii = namespaceURIs.length; i < ii; ++i) {
+ structureNS[namespaceURIs[i]] = structure;
+ }
+ return structureNS;
+};
+
+
+/**
+ * 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`.
+ */
+ol.xml.parseNode = function(parsersNS, node, objectStack, opt_this) {
+ var n;
+ for (n = node.firstElementChild; n; n = n.nextElementSibling) {
+ var parsers = parsersNS[n.namespaceURI];
+ if (parsers !== undefined) {
+ var parser = parsers[n.localName];
+ if (parser !== undefined) {
+ parser.call(opt_this, n, objectStack);
+ }
+ }
+ }
+};
+
+
+/**
+ * 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
+ */
+ol.xml.pushParseAndPop = function(
+ object, parsersNS, node, objectStack, opt_this) {
+ objectStack.push(object);
+ ol.xml.parseNode(parsersNS, node, objectStack, opt_this);
+ return objectStack.pop();
+};
+
+
+/**
+ * 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
+ */
+ol.xml.serialize = function(
+ serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) {
+ var length = (opt_keys !== undefined ? opt_keys : values).length;
+ var value, node;
+ for (var i = 0; i < length; ++i) {
+ value = values[i];
+ if (value !== undefined) {
+ node = nodeFactory.call(opt_this, value, objectStack,
+ opt_keys !== undefined ? opt_keys[i] : undefined);
+ if (node !== undefined) {
+ serializersNS[node.namespaceURI][node.localName]
+ .call(opt_this, node, value, objectStack);
+ }
+ }
+ }
+};
+
+
+/**
+ * @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
+ */
+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.FeatureUrlFunction');
+goog.provide('ol.featureloader');
+
+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.TileState');
+goog.require('ol.VectorTile');
+goog.require('ol.format.FormatType');
+goog.require('ol.proj');
+goog.require('ol.proj.Projection');
+goog.require('ol.proj.Units');
+goog.require('ol.xml');
+
+
+/**
+ * {@link ol.source.Vector} sources use a function of this type to load
+ * features.
+ *
+ * This function takes an {@link ol.Extent} representing the area to be loaded,
+ * a `{number}` representing the resolution (map units per pixel) and an
+ * {@link ol.proj.Projection} for the projection as arguments. `this` within
+ * the function is bound to the {@link ol.source.Vector} it's called from.
+ *
+ * The function is responsible for loading the features and adding them to the
+ * source.
+ * @api
+ * @typedef {function(this:ol.source.Vector, ol.Extent, number,
+ * ol.proj.Projection)}
+ */
+ol.FeatureLoader;
+
+
+/**
+ * {@link ol.source.Vector} sources use a function of this type to get the url
+ * to load features from.
+ *
+ * This function takes an {@link ol.Extent} representing the area to be loaded,
+ * a `{number}` representing the resolution (map units per pixel) and an
+ * {@link ol.proj.Projection} for the projection as arguments and returns a
+ * `{string}` representing the URL.
+ * @api
+ * @typedef {function(ol.Extent, number, ol.proj.Projection) : string}
+ */
+ol.FeatureUrlFunction;
+
+
+/**
+ * @param {string|ol.FeatureUrlFunction} url Feature URL service.
+ * @param {ol.format.Feature} format Feature format.
+ * @param {function(this:ol.VectorTile, Array.<ol.Feature>, ol.proj.Projection)|function(this:ol.source.Vector, Array.<ol.Feature>)} success
+ * Function called with the loaded features and optionally with the data
+ * projection. Called with the vector tile or source as `this`.
+ * @param {function(this:ol.VectorTile)|function(this:ol.source.Vector)} failure
+ * Function called when loading failed. Called with the vector tile or
+ * source as `this`.
+ * @return {ol.FeatureLoader} The feature loader.
+ */
+ol.featureloader.loadFeaturesXhr = function(url, format, success, failure) {
+ return (
+ /**
+ * @param {ol.Extent} extent Extent.
+ * @param {number} resolution Resolution.
+ * @param {ol.proj.Projection} projection Projection.
+ * @this {ol.source.Vector|ol.VectorTile}
+ */
+ function(extent, resolution, projection) {
+ var xhrIo = new goog.net.XhrIo();
+ xhrIo.setResponseType(
+ format.getType() == ol.format.FormatType.ARRAY_BUFFER ?
+ goog.net.XhrIo.ResponseType.ARRAY_BUFFER :
+ 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 (!source) {
+ source = ol.xml.parse(xhrIo.getResponseText());
+ }
+ } else if (type == ol.format.FormatType.ARRAY_BUFFER) {
+ source = xhrIo.getResponse();
+ } else {
+ goog.asserts.fail('unexpected format type');
+ }
+ if (source) {
+ if (ol.ENABLE_VECTOR_TILE && this instanceof ol.VectorTile) {
+ var dataUnits = format.readProjection(source).getUnits();
+ if (dataUnits === ol.proj.Units.TILE_PIXELS) {
+ projection = new ol.proj.Projection({
+ code: this.getProjection().getCode(),
+ units: dataUnits
+ });
+ this.setProjection(projection);
+ }
+ }
+ success.call(this, format.readFeatures(source,
+ {featureProjection: projection}));
+ } else {
+ goog.asserts.fail('undefined or null source');
+ }
+ } else {
+ failure.call(this);
+ }
+ goog.dispose(xhrIo);
+ }, false, this);
+ if (goog.isFunction(url)) {
+ xhrIo.send(url(extent, resolution, projection));
+ } else {
+ xhrIo.send(url);
+ }
+
+ });
+};
+
+
+/**
+ * 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 tile.
+ * @param {string|ol.FeatureUrlFunction} url Feature URL service.
+ * @param {ol.format.Feature} format Feature format.
+ * @return {ol.FeatureLoader} The feature loader.
+ * @api
+ */
+ol.featureloader.tile = function(url, format) {
+ return ol.featureloader.loadFeaturesXhr(url, format,
+ /**
+ * @param {Array.<ol.Feature>} features The loaded features.
+ * @this {ol.VectorTile}
+ */
+ function(features) {
+ this.setFeatures(features);
+ },
+ /**
+ * @this {ol.VectorTile}
+ */
+ function() {
+ this.setState(ol.TileState.ERROR);
+ });
+};
+
+
+/**
+ * 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|ol.FeatureUrlFunction} url Feature URL service.
+ * @param {ol.format.Feature} format Feature format.
+ * @return {ol.FeatureLoader} The feature loader.
+ * @api
+ */
+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);
+ }, /* FIXME handle error */ ol.nullFunction);
+};
+
+goog.provide('ol.LoadingStrategy');
+goog.provide('ol.loadingstrategy');
+
+goog.require('ol.TileCoord');
+
+
+/**
+ * @typedef {function(ol.Extent, number): Array.<ol.Extent>}
+ * @api
+ */
+ol.LoadingStrategy;
+
+
+/**
+ * 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
+ */
+ol.loadingstrategy.all = function(extent, resolution) {
+ return [[-Infinity, -Infinity, Infinity, Infinity]];
+};
+
+
+/**
+ * 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
+ */
+ol.loadingstrategy.bbox = function(extent, resolution) {
+ return [extent];
+};
+
+
+/**
+ * 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
+ */
+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;
+/**
+ * @fileoverview
+ * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility}
+ */
+/*
+ (c) 2015, Vladimir Agafonkin
+ RBush, a JavaScript library for high-performance 2D spatial indexing of points and rectangles.
+ https://github.com/mourner/rbush
+*/
+
+(function () { 'use strict';
+
+function rbush(maxEntries, format) {
+
+ // 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));
+
+ if (format) {
+ this._initFormat(format);
+ }
+
+ this.clear();
+}
+
+rbush.prototype = {
+
+ all: function () {
+ return this._all(this.data, []);
+ },
+
+ search: function (bbox) {
+
+ var node = this.data,
+ result = [],
+ toBBox = this.toBBox;
+
+ if (!intersects(bbox, node.bbox)) return result;
+
+ var nodesToSearch = [],
+ i, len, child, childBBox;
+
+ while (node) {
+ for (i = 0, len = node.children.length; i < len; i++) {
+
+ 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();
+ }
+
+ return result;
+ },
+
+ collides: function (bbox) {
+
+ var node = this.data,
+ toBBox = this.toBBox;
+
+ if (!intersects(bbox, node.bbox)) return false;
+
+ var nodesToSearch = [],
+ i, len, child, childBBox;
+
+ while (node) {
+ for (i = 0, len = node.children.length; i < len; i++) {
+
+ child = node.children[i];
+ childBBox = node.leaf ? toBBox(child) : child.bbox;
+
+ if (intersects(bbox, childBBox)) {
+ if (node.leaf || contains(bbox, childBBox)) return true;
+ nodesToSearch.push(child);
+ }
+ }
+ node = nodesToSearch.pop();
+ }
+
+ return false;
+ },
+
+ 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;
+ }
+
+ // 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;
+
+ } 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;
+ }
+
+ // insert the small tree into the large tree at appropriate level
+ this._insert(node, this.data.height - node.height - 1, true);
+ }
+
+ return this;
+ },
+
+ 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;
+ },
+
+ remove: function (item) {
+ if (!item) return this;
+
+ var node = this.data,
+ bbox = this.toBBox(item),
+ path = [],
+ indexes = [],
+ i, parent, index, goingUp;
+
+ // 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;
+ }
+
+ 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;
+ }
+ }
+
+ 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;
+
+ } else node = null; // nothing found
+ }
+
+ return this;
+ },
+
+ toBBox: function (item) { return item; },
+
+ compareMinX: function (a, b) { return a[0] - b[0]; },
+ compareMinY: function (a, b) { return a[1] - b[1]; },
+
+ toJSON: function () { return this.data; },
+
+ 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);
+
+ node = nodesToSearch.pop();
+ }
+ return result;
+ },
+
+ _build: function (items, left, right, height) {
+
+ var N = right - left + 1,
+ M = this._maxEntries,
+ node;
+
+ 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));
+
+ // target number of root entries to maximize storage utilization
+ M = Math.ceil(N / Math.pow(M, height - 1));
+ }
+
+ // TODO eliminate recursion?
+
+ node = {
+ children: [],
+ height: height,
+ bbox: null
+ };
+
+ // split the items into M mostly square tiles
+
+ var N2 = Math.ceil(N / M),
+ N1 = N2 * Math.ceil(Math.sqrt(M)),
+ i, j, right2, right3;
+
+ multiSelect(items, left, right, N1, this.compareMinX);
+
+ for (i = left; i <= right; i += N1) {
+
+ right2 = Math.min(i + N1 - 1, right);
+
+ multiSelect(items, i, right2, N2, this.compareMinY);
+
+ for (j = i; j <= right2; j += N2) {
+
+ right3 = Math.min(j + N2 - 1, right2);
+
+ // pack each entry recursively
+ node.children.push(this._build(items, j, right3, height - 1));
+ }
+ }
+
+ calcBBox(node, this.toBBox);
+
+ return node;
+ },
+
+ _chooseSubtree: function (bbox, node, level, path) {
+
+ var i, len, child, targetNode, area, enlargement, minArea, minEnlargement;
+
+ while (true) {
+ path.push(node);
+
+ if (node.leaf || path.length - 1 === level) break;
+
+ 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;
+
+ // 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;
+ }
+
+ return node;
+ },
+
+ _insert: function (item, level, isNode) {
+
+ var toBBox = this.toBBox,
+ bbox = isNode ? item.bbox : toBBox(item),
+ insertPath = [];
+
+ // 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);
+
+ // 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);
+ },
+
+ // split overflowed node into two
+ _split: function (insertPath, level) {
+
+ var node = insertPath[level],
+ M = node.children.length,
+ m = this._minEntries;
+
+ this._chooseSplitAxis(node, m, M);
+
+ var splitIndex = this._chooseSplitIndex(node, m, M);
+
+ var newNode = {
+ children: node.children.splice(splitIndex, node.children.length - splitIndex),
+ height: node.height
+ };
+
+ if (node.leaf) newNode.leaf = true;
+
+ calcBBox(node, this.toBBox);
+ calcBBox(newNode, this.toBBox);
+
+ 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);
+ },
+
+ _chooseSplitIndex: function (node, m, M) {
+
+ var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index;
+
+ minOverlap = minArea = Infinity;
+
+ 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);
+
+ // choose distribution with minimum overlap
+ if (overlap < minOverlap) {
+ minOverlap = overlap;
+ index = i;
+
+ minArea = area < minArea ? area : minArea;
+
+ } else if (overlap === minOverlap) {
+ // otherwise choose distribution with minimum area
+ if (area < minArea) {
+ minArea = area;
+ index = i;
+ }
+ }
+ }
+
+ 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);
+
+ // 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);
+ },
+
+ // 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);
+
+ 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);
+ }
+
+ 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;
+ },
+
+ _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);
+
+ } else this.clear();
+
+ } else calcBBox(path[i], this.toBBox);
+ }
+ },
+
+ _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
+
+ // jshint evil: true
+
+ var compareArr = ['return a', ' - b', ';'];
+
+ 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') + '];');
+ }
+};
+
+
+// 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);
+ }
+
+ return bbox;
+}
+
+function empty() { return [Infinity, Infinity, -Infinity, -Infinity]; }
+
+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]; }
+
+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]));
+}
+
+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);
+}
+
+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];
+}
+
+// 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;
+
+ while (stack.length) {
+ right = stack.pop();
+ left = stack.pop();
+
+ if (right - left <= n) continue;
+
+ mid = left + Math.ceil((right - left) / n / 2) * n;
+ select(arr, left, right, mid, compare);
+
+ stack.push(left, mid, mid, right);
+ }
+}
+
+// Floyd-Rivest selection algorithm:
+// sort an 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);
+ }
+
+ t = arr[k];
+ i = left;
+ j = right;
+
+ swap(arr, left, k);
+ if (compare(arr[right], t) > 0) swap(arr, left, right);
+
+ 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);
+ }
+
+ 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;
+}
+
+
+// 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;
+
+})();
+
+ol.ext.rbush = module.exports;
+})();
+
+goog.provide('ol.structs.RBush');
+
+goog.require('goog.asserts');
+goog.require('goog.object');
+goog.require('ol.ext.rbush');
+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
+ */
+ol.structs.RBush = function(opt_maxEntries) {
+
+ /**
+ * @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_ = {};
+
+ if (goog.DEBUG) {
+ /**
+ * @private
+ * @type {number}
+ */
+ this.readers_ = 0;
+ }
+};
+
+
+/**
+ * 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.asserts.assert(!(goog.getUid(value) in this.items_),
+ 'uid (%s) of value (%s) already exists', goog.getUid(value), value);
+ this.items_[goog.getUid(value)] = item;
+};
+
+
+/**
+ * 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,
+ '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];
+
+ var item = [
+ extent[0],
+ extent[1],
+ extent[2],
+ extent[3],
+ value
+ ];
+ items[i] = item;
+ goog.asserts.assert(!(goog.getUid(value) in this.items_),
+ 'uid (%s) of value (%s) already exists', goog.getUid(value), value);
+ this.items_[goog.getUid(value)] = item;
+ }
+ this.rbush_.load(items);
+};
+
+
+/**
+ * Remove a value from the RBush.
+ * @param {T} value Value.
+ * @return {boolean} Removed.
+ */
+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(uid in this.items_,
+ 'uid (%s) of value (%s) does not exist', uid, value);
+
+ // 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];
+ delete 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) {
+ var uid = goog.getUid(value);
+ goog.asserts.assert(uid in this.items_,
+ 'uid (%s) of value (%s) does not exist', uid, value);
+
+ 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);
+ }
+};
+
+
+/**
+ * Return all values in the RBush.
+ * @return {Array.<T>} All.
+ */
+ol.structs.RBush.prototype.getAll = function() {
+ var items = this.rbush_.all();
+ return items.map(function(item) {
+ return item[4];
+ });
+};
+
+
+/**
+ * Return all values in the given extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {Array.<T>} All in extent.
+ */
+ol.structs.RBush.prototype.getInExtent = function(extent) {
+ var items = this.rbush_.search(extent);
+ return items.map(function(item) {
+ return item[4];
+ });
+};
+
+
+/**
+ * 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
+ */
+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);
+ }
+};
+
+
+/**
+ * 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
+ */
+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);
+ }
+};
+
+
+/**
+ * @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
+ */
+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;
+};
+
+
+/**
+ * @return {boolean} Is empty.
+ */
+ol.structs.RBush.prototype.isEmpty = function() {
+ return goog.object.isEmpty(this.items_);
+};
+
+
+/**
+ * Remove all values from the RBush.
+ */
+ol.structs.RBush.prototype.clear = function() {
+ this.rbush_.clear();
+ this.items_ = {};
+};
+
+
+/**
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {!ol.Extent} Extent.
+ */
+ol.structs.RBush.prototype.getExtent = function(opt_extent) {
+ // FIXME add getExtent() to rbush
+ return this.rbush_.data.bbox;
+};
+
+// 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');
+
+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');
+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');
+
+
+/**
+ * @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'
+};
+
+
+
+/**
+ * @classdesc
+ * Provides a source of features for vector layers. Vector features provided
+ * by this source are suitable for editing. See {@link ol.source.VectorTile} for
+ * vector data that is optimized for rendering.
+ *
+ * @constructor
+ * @extends {ol.source.Source}
+ * @fires ol.source.VectorEvent
+ * @param {olx.source.VectorOptions=} opt_options Vector source options.
+ * @api stable
+ */
+ol.source.Vector = function(opt_options) {
+
+ var options = opt_options || {};
+
+ goog.base(this, {
+ attributions: options.attributions,
+ logo: options.logo,
+ projection: undefined,
+ state: ol.source.State.READY,
+ wrapX: options.wrapX !== undefined ? options.wrapX : true
+ });
+
+ /**
+ * @private
+ * @type {ol.FeatureLoader}
+ */
+ this.loader_ = ol.nullFunction;
+
+ if (options.loader !== undefined) {
+ this.loader_ = options.loader;
+ } else if (options.url !== undefined) {
+ goog.asserts.assert(options.format !== undefined,
+ '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_ = options.strategy !== undefined ? options.strategy :
+ ol.loadingstrategy.all;
+
+ var useSpatialIndex =
+ options.useSpatialIndex !== undefined ? options.useSpatialIndex : true;
+
+ /**
+ * @private
+ * @type {ol.structs.RBush.<ol.Feature>}
+ */
+ this.featuresRtree_ = useSpatialIndex ? new ol.structs.RBush() : null;
+
+ /**
+ * @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_ = {};
+
+ /**
+ * 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_ = {};
+
+ /**
+ * @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 && collection === undefined) {
+ collection = new ol.Collection(features);
+ }
+ if (features !== undefined) {
+ this.addFeaturesInternal(features);
+ }
+ if (collection !== undefined) {
+ this.bindFeaturesCollection_(collection);
+ }
+
+};
+goog.inherits(ol.source.Vector, ol.source.Source);
+
+
+/**
+ * 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
+ */
+ol.source.Vector.prototype.addFeature = function(feature) {
+ this.addFeatureInternal(feature);
+ this.changed();
+};
+
+
+/**
+ * Add a feature without firing a `change` event.
+ * @param {ol.Feature} feature Feature.
+ * @protected
+ */
+ol.source.Vector.prototype.addFeatureInternal = function(feature) {
+ var featureKey = goog.getUid(feature).toString();
+
+ if (!this.addToIndex_(featureKey, feature)) {
+ return;
+ }
+
+ this.setupChangeEvents_(featureKey, feature);
+
+ var geometry = feature.getGeometry();
+ if (geometry) {
+ var extent = geometry.getExtent();
+ if (this.featuresRtree_) {
+ this.featuresRtree_.insert(extent, feature);
+ }
+ } else {
+ this.nullGeometryFeatures_[featureKey] = feature;
+ }
+
+ this.dispatchEvent(
+ new ol.source.VectorEvent(ol.source.VectorEventType.ADDFEATURE, feature));
+};
+
+
+/**
+ * @param {string} featureKey
+ * @param {ol.Feature} feature
+ * @private
+ */
+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)
+ ];
+};
+
+
+/**
+ * @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
+ */
+ol.source.Vector.prototype.addToIndex_ = function(featureKey, feature) {
+ var valid = true;
+ var id = feature.getId();
+ if (id !== undefined) {
+ 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;
+};
+
+
+/**
+ * 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);
+ this.changed();
+};
+
+
+/**
+ * Add features without firing a `change` event.
+ * @param {Array.<ol.Feature>} features Features.
+ * @protected
+ */
+ol.source.Vector.prototype.addFeaturesInternal = function(features) {
+ var featureKey, i, length, feature;
+
+ var extents = [];
+ var newFeatures = [];
+ var geometryFeatures = [];
+
+ 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);
+
+ var geometry = feature.getGeometry();
+ if (geometry) {
+ var extent = geometry.getExtent();
+ extents.push(extent);
+ geometryFeatures.push(feature);
+ } else {
+ this.nullGeometryFeatures_[featureKey] = feature;
+ }
+ }
+ if (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]));
+ }
+};
+
+
+/**
+ * @param {!ol.Collection.<ol.Feature>} collection Collection.
+ * @private
+ */
+ol.source.Vector.prototype.bindFeaturesCollection_ = function(collection) {
+ goog.asserts.assert(!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;
+};
+
+
+/**
+ * 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];
+ keys.forEach(goog.events.unlistenByKey);
+ }
+ if (!this.featuresCollection_) {
+ this.featureChangeKeys_ = {};
+ this.idIndex_ = {};
+ this.undefIdIndex_ = {};
+ }
+ } else {
+ var rmFeatureInternal = this.removeFeatureInternal;
+ if (this.featuresRtree_) {
+ this.featuresRtree_.forEach(rmFeatureInternal, this);
+ goog.object.forEach(this.nullGeometryFeatures_, rmFeatureInternal, this);
+ }
+ }
+ if (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 (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();
+};
+
+
+/**
+ * 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
+ */
+ol.source.Vector.prototype.forEachFeature = function(callback, opt_this) {
+ if (this.featuresRtree_) {
+ return this.featuresRtree_.forEach(callback, opt_this);
+ } else if (this.featuresCollection_) {
+ return this.featuresCollection_.forEach(callback, opt_this);
+ }
+};
+
+
+/**
+ * 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
+ */
+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(geometry, 'feature geometry is defined and not null');
+ if (geometry.containsCoordinate(coordinate)) {
+ return callback.call(opt_this, feature);
+ } else {
+ return undefined;
+ }
+ });
+};
+
+
+/**
+ * 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
+ */
+ol.source.Vector.prototype.forEachFeatureInExtent =
+ function(extent, callback, opt_this) {
+ if (this.featuresRtree_) {
+ return this.featuresRtree_.forEachInExtent(extent, callback, opt_this);
+ } else if (this.featuresCollection_) {
+ return this.featuresCollection_.forEach(callback, opt_this);
+ }
+};
+
+
+/**
+ * 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
+ */
+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(geometry,
+ 'feature geometry is defined and not null');
+ if (geometry.intersectsExtent(extent)) {
+ var result = callback.call(opt_this, feature);
+ if (result) {
+ return result;
+ }
+ }
+ });
+};
+
+
+/**
+ * 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
+ */
+ol.source.Vector.prototype.getFeaturesCollection = function() {
+ return this.featuresCollection_;
+};
+
+
+/**
+ * Get all features on the source.
+ * @return {Array.<ol.Feature>} Features.
+ * @api stable
+ */
+ol.source.Vector.prototype.getFeatures = function() {
+ var features;
+ if (this.featuresCollection_) {
+ features = this.featuresCollection_.getArray();
+ } else if (this.featuresRtree_) {
+ features = this.featuresRtree_.getAll();
+ if (!goog.object.isEmpty(this.nullGeometryFeatures_)) {
+ goog.array.extend(
+ features, goog.object.getValues(this.nullGeometryFeatures_));
+ }
+ }
+ goog.asserts.assert(features !== undefined,
+ 'Neither featuresRtree_ nor featuresCollection_ are available');
+ return features;
+};
+
+
+/**
+ * Get all features whose geometry intersects the provided coordinate.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {Array.<ol.Feature>} Features.
+ * @api stable
+ */
+ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) {
+ var features = [];
+ this.forEachFeatureAtCoordinateDirect(coordinate, function(feature) {
+ features.push(feature);
+ });
+ return features;
+};
+
+
+/**
+ * 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
+ */
+ol.source.Vector.prototype.getFeaturesInExtent = function(extent) {
+ goog.asserts.assert(this.featuresRtree_,
+ 'getFeaturesInExtent does not work when useSpatialIndex is set to false');
+ return this.featuresRtree_.getInExtent(extent);
+};
+
+
+/**
+ * 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
+ */
+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(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(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;
+};
+
+
+/**
+ * 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
+ */
+ol.source.Vector.prototype.getExtent = function() {
+ goog.asserts.assert(this.featuresRtree_,
+ 'getExtent does not work when useSpatialIndex is set to false');
+ return this.featuresRtree_.getExtent();
+};
+
+
+/**
+ * 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
+ */
+ol.source.Vector.prototype.getFeatureById = function(id) {
+ var feature = this.idIndex_[id.toString()];
+ return feature !== undefined ? feature : null;
+};
+
+
+/**
+ * @param {goog.events.Event} event Event.
+ * @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 (!geometry) {
+ if (!(featureKey in this.nullGeometryFeatures_)) {
+ if (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 (this.featuresRtree_) {
+ this.featuresRtree_.insert(extent, feature);
+ }
+ } else {
+ if (this.featuresRtree_) {
+ this.featuresRtree_.update(extent, feature);
+ }
+ }
+ }
+ var id = feature.getId();
+ var removed;
+ if (id !== undefined) {
+ 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));
+};
+
+
+/**
+ * @return {boolean} Is empty.
+ */
+ol.source.Vector.prototype.isEmpty = function() {
+ return this.featuresRtree_.isEmpty() &&
+ goog.object.isEmpty(this.nullGeometryFeatures_);
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {number} resolution Resolution.
+ * @param {ol.proj.Projection} projection Projection.
+ */
+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()});
+ }
+ }
+};
+
+
+/**
+ * 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
+ */
+ol.source.Vector.prototype.removeFeature = function(feature) {
+ var featureKey = goog.getUid(feature).toString();
+ if (featureKey in this.nullGeometryFeatures_) {
+ delete this.nullGeometryFeatures_[featureKey];
+ } else {
+ if (this.featuresRtree_) {
+ this.featuresRtree_.remove(feature);
+ }
+ }
+ this.removeFeatureInternal(feature);
+ this.changed();
+};
+
+
+/**
+ * Remove feature without firing a `change` event.
+ * @param {ol.Feature} feature Feature.
+ * @protected
+ */
+ol.source.Vector.prototype.removeFeatureInternal = function(feature) {
+ var featureKey = goog.getUid(feature).toString();
+ goog.asserts.assert(featureKey in this.featureChangeKeys_,
+ 'featureKey exists in featureChangeKeys');
+ this.featureChangeKeys_[featureKey].forEach(goog.events.unlistenByKey);
+ delete this.featureChangeKeys_[featureKey];
+ var id = feature.getId();
+ if (id !== undefined) {
+ delete this.idIndex_[id.toString()];
+ } else {
+ delete this.undefIdIndex_[featureKey];
+ }
+ this.dispatchEvent(new ol.source.VectorEvent(
+ ol.source.VectorEventType.REMOVEFEATURE, feature));
+};
+
+
+/**
+ * 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
+ */
+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;
+};
+
+
+
+/**
+ * @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.
+ */
+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.inherits(ol.source.VectorEvent, goog.events.Event);
+
+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_.forEachFeatureInExtent(extent,
+ /**
+ * @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.forEachFeatureAtCoordinate = function(
+ coordinate, resolution, rotation, skippedFeatureUids, callback) {
+ if (!this.replayGroup_) {
+ return undefined;
+ } else {
+ /** @type {Object.<string, boolean>} */
+ var features = {};
+ return this.replayGroup_.forEachFeatureAtCoordinate(
+ coordinate, resolution, 0, skippedFeatureUids,
+ /**
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @return {?} Callback result.
+ */
+ function(feature) {
+ goog.asserts.assert(feature !== undefined, 'passed a feature');
+ var key = goog.getUid(feature).toString();
+ if (!(key in features)) {
+ features[key] = true;
+ return callback(feature);
+ }
+ });
+ }
+};
+
+
+/**
+ * Get a reference to the wrapped source.
+ * @return {ol.source.Vector} Source.
+ * @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]);
+};
+
+
+/**
+ * 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
+ */
+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());
+};
+
+
+/**
+ * @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;
+ var styleFunction = feature.getStyleFunction();
+ if (styleFunction) {
+ styles = styleFunction.call(feature, resolution);
+ } else if (this.styleFunction_) {
+ styles = this.styleFunction_(feature, resolution);
+ }
+ if (!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;
+};
+
+
+/**
+ * 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.source.ImageVector.prototype.setStyle = function(style) {
+ this.style_ = style !== undefined ? style : ol.style.defaultStyleFunction;
+ this.styleFunction_ = !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');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.canvas.Layer}
+ * @param {ol.layer.Image} imageLayer Single image layer.
+ */
+ol.renderer.canvas.ImageLayer = function(imageLayer) {
+
+ goog.base(this, imageLayer);
+
+ /**
+ * @private
+ * @type {?ol.ImageBase}
+ */
+ this.image_ = null;
+
+ /**
+ * @private
+ * @type {!goog.vec.Mat4.Number}
+ */
+ this.imageTransform_ = goog.vec.Mat4.createNumber();
+
+ /**
+ * @private
+ * @type {?goog.vec.Mat4.Number}
+ */
+ this.imageTransformInv_ = null;
+
+ /**
+ * @private
+ * @type {CanvasRenderingContext2D}
+ */
+ this.hitCanvasContext_ = null;
+
+};
+goog.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer);
+
+
+/**
+ * @inheritDoc
+ */
+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|ol.render.Feature} feature Feature.
+ * @return {?} Callback result.
+ */
+ function(feature) {
+ return callback.call(thisArg, feature, layer);
+ });
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.ImageLayer.prototype.forEachLayerAtPixel =
+ function(pixel, frameState, callback, thisArg) {
+ if (!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);
+
+ if (hasFeature) {
+ return callback.call(thisArg, this.getLayer());
+ } else {
+ return undefined;
+ }
+ } else {
+ // for all other image sources directly check the image
+ if (!this.imageTransformInv_) {
+ this.imageTransformInv_ = goog.vec.Mat4.createNumber();
+ goog.vec.Mat4.invert(this.imageTransform_, this.imageTransformInv_);
+ }
+
+ var pixelOnCanvas =
+ this.getPixelOnCanvas(pixel, this.imageTransformInv_);
+
+ if (!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);
+
+ var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data;
+ if (imageData[3] > 0) {
+ return callback.call(thisArg, this.getLayer());
+ } else {
+ return undefined;
+ }
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.ImageLayer.prototype.getImage = function() {
+ return !this.image_ ? null : this.image_.getImage();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.ImageLayer.prototype.getImageTransform = function() {
+ return this.imageTransform_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.ImageLayer.prototype.prepareFrame =
+ function(frameState, layerState) {
+
+ 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();
+
+ var hints = frameState.viewHints;
+
+ var renderedExtent = frameState.extent;
+ if (layerState.extent !== undefined) {
+ renderedExtent = ol.extent.getIntersection(
+ renderedExtent, layerState.extent);
+ }
+
+ if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] &&
+ !ol.extent.isEmpty(renderedExtent)) {
+ var projection = viewState.projection;
+ if (!ol.ENABLE_RASTER_REPROJECTION) {
+ var sourceProjection = imageSource.getProjection();
+ if (sourceProjection) {
+ goog.asserts.assert(ol.proj.equivalent(projection, sourceProjection),
+ 'projection and sourceProjection are equivalent');
+ projection = sourceProjection;
+ }
+ }
+ image = imageSource.getImage(
+ renderedExtent, viewResolution, pixelRatio, projection);
+ if (image) {
+ var loaded = this.loadImage(image);
+ if (loaded) {
+ this.image_ = image;
+ }
+ }
+ }
+
+ if (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);
+ }
+
+ return true;
+};
+
+// FIXME find correct globalCompositeOperation
+// FIXME optimize :-)
+
+goog.provide('ol.renderer.canvas.TileLayer');
+
+goog.require('goog.asserts');
+goog.require('goog.vec.Mat4');
+goog.require('ol.Size');
+goog.require('ol.TileRange');
+goog.require('ol.TileState');
+goog.require('ol.array');
+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');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.canvas.Layer}
+ * @param {ol.layer.Tile} tileLayer Tile layer.
+ */
+ol.renderer.canvas.TileLayer = function(tileLayer) {
+
+ goog.base(this, tileLayer);
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement}
+ */
+ this.canvas_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.canvasSize_ = null;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.canvasTooBig_ = false;
+
+ /**
+ * @private
+ * @type {CanvasRenderingContext2D}
+ */
+ this.context_ = null;
+
+ /**
+ * @private
+ * @type {!goog.vec.Mat4.Number}
+ */
+ this.imageTransform_ = goog.vec.Mat4.createNumber();
+
+ /**
+ * @private
+ * @type {?goog.vec.Mat4.Number}
+ */
+ this.imageTransformInv_ = null;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedCanvasZ_ = NaN;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedTileWidth_ = NaN;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedTileHeight_ = NaN;
+
+ /**
+ * @private
+ * @type {ol.TileRange}
+ */
+ this.renderedCanvasTileRange_ = null;
+
+ /**
+ * @private
+ * @type {Array.<ol.Tile|undefined>}
+ */
+ this.renderedTiles_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.tmpSize_ = [0, 0];
+
+};
+goog.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.Layer);
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.TileLayer.prototype.getImage = function() {
+ return this.canvas_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.TileLayer.prototype.getImageTransform = function() {
+ return this.imageTransform_;
+};
+
+
+/**
+ * @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,
+ '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 (layerState.extent !== undefined) {
+ 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[0] * tileRange.getWidth();
+ var canvasHeight = tilePixelSize[1] * tileRange.getHeight();
+
+ var canvas, context;
+ if (!this.canvas_) {
+ goog.asserts.assert(!this.canvasSize_,
+ 'canvasSize is null (because canvas is null)');
+ goog.asserts.assert(!this.context_,
+ 'context is null (because canvas is null)');
+ goog.asserts.assert(!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(this.canvasSize_,
+ 'non-null canvasSize (because canvas is not null)');
+ goog.asserts.assert(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 (!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');
+
+ /**
+ * @type {Object.<number, Object.<string, ol.Tile>>}
+ */
+ var tilesToDrawByZ = {};
+ tilesToDrawByZ[z] = {};
+ /** @type {Array.<ol.Tile>} */
+ var tilesToClear = [];
+
+ var findLoadedTiles = this.createLoadedTileFinder(
+ tileSource, projection, tilesToDrawByZ);
+
+ var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
+
+ var tmpExtent = ol.extent.createEmpty();
+ var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
+ var childTileRange, fullyLoaded, tile, x, y;
+ var drawableTile = (
+ /**
+ * @param {!ol.Tile} tile Tile.
+ * @return {boolean} Tile is selected.
+ */
+ function(tile) {
+ var tileState = tile.getState();
+ return tileState == ol.TileState.LOADED ||
+ tileState == ol.TileState.EMPTY ||
+ tileState == ol.TileState.ERROR && !useInterimTilesOnError;
+ });
+ 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 (!drawableTile(tile) && tile.interimTile) {
+ tile = tile.interimTile;
+ }
+ goog.asserts.assert(tile);
+ if (drawableTile(tile)) {
+ 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 (childTileRange) {
+ findLoadedTiles(z + 1, childTileRange);
+ }
+ }
+
+ }
+ }
+
+ 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 = Object.keys(tilesToDrawByZ).map(Number);
+ zs.sort(ol.array.numberSafeCompareFunction);
+ var opaque = tileSource.getOpaque();
+ var origin = ol.extent.getTopLeft(tileGrid.getTileCoordExtent(
+ [z, canvasTileRange.minX, canvasTileRange.maxY],
+ tmpExtent));
+ var currentZ, index, scale, tileCoordKey, tileExtent, tileState, tilesToDraw;
+ var ix, iy, 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;
+ }
+ }
+ }
+ }
+ }
+
+ 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;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.TileLayer.prototype.forEachLayerAtPixel =
+ function(pixel, frameState, callback, thisArg) {
+ if (!this.context_) {
+ return undefined;
+ }
+
+ if (!this.imageTransformInv_) {
+ this.imageTransformInv_ = goog.vec.Mat4.createNumber();
+ goog.vec.Mat4.invert(this.imageTransform_, this.imageTransformInv_);
+ }
+
+ var pixelOnCanvas =
+ this.getPixelOnCanvas(pixel, this.imageTransformInv_);
+
+ var imageData = this.context_.getImageData(
+ pixelOnCanvas[0], pixelOnCanvas[1], 1, 1).data;
+
+ if (imageData[3] > 0) {
+ return callback.call(thisArg, this.getLayer());
+ } else {
+ return undefined;
+ }
+};
+
+goog.provide('ol.renderer.canvas.VectorLayer');
+
+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
+ * @extends {ol.renderer.canvas.Layer}
+ * @param {ol.layer.Vector} vectorLayer Vector layer.
+ */
+ol.renderer.canvas.VectorLayer = function(vectorLayer) {
+
+ goog.base(this, vectorLayer);
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.dirty_ = false;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedRevision_ = -1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedResolution_ = NaN;
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.renderedExtent_ = ol.extent.createEmpty();
+
+ /**
+ * @private
+ * @type {function(ol.Feature, ol.Feature): number|null}
+ */
+ this.renderedRenderOrder_ = null;
+
+ /**
+ * @private
+ * @type {ol.render.canvas.ReplayGroup}
+ */
+ this.replayGroup_ = null;
+
+ /**
+ * @private
+ * @type {CanvasRenderingContext2D}
+ */
+ this.context_ = ol.dom.createCanvasContext2D();
+
+};
+goog.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer);
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.VectorLayer.prototype.composeFrame =
+ function(frameState, layerState, context) {
+
+ 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);
+
+ this.dispatchPreComposeEvent(context, frameState, transform);
+
+ var replayGroup = this.replayGroup_;
+ if (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);
+ }
+
+ if (replayContext != context) {
+ this.dispatchRenderEvent(replayContext, frameState, transform);
+ context.drawImage(replayContext.canvas, 0, 0);
+ }
+ replayContext.globalAlpha = alpha;
+ }
+
+ this.dispatchPostComposeEvent(context, frameState, transform);
+
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate =
+ function(coordinate, frameState, callback, thisArg) {
+ if (!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|ol.render.Feature} feature Feature.
+ * @return {?} Callback result.
+ */
+ function(feature) {
+ goog.asserts.assert(feature !== undefined, 'received a feature');
+ var key = goog.getUid(feature).toString();
+ if (!(key in features)) {
+ features[key] = true;
+ return callback.call(thisArg, feature, layer);
+ }
+ });
+ }
+};
+
+
+/**
+ * Handle changes in image style state.
+ * @param {goog.events.Event} event Image style change event.
+ * @private
+ */
+ol.renderer.canvas.VectorLayer.prototype.handleStyleImageChange_ =
+ function(event) {
+ this.renderIfReadyAndVisible();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.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);
+
+ 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();
+
+ if (vectorLayerRenderOrder === undefined) {
+ vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
+ }
+
+ var extent = ol.extent.buffer(frameStateExtent,
+ vectorLayerRenderBuffer * resolution);
+ var projectionExtent = viewState.projection.getExtent();
+
+ 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;
+
+ 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.canvas.VectorLayer}
+ */
+ function(feature) {
+ var styles;
+ var styleFunction = feature.getStyleFunction();
+ if (styleFunction) {
+ styles = styleFunction.call(feature, resolution);
+ } else {
+ styleFunction = vectorLayer.getStyleFunction();
+ if (styleFunction) {
+ styles = styleFunction(feature, resolution);
+ }
+ }
+ if (styles) {
+ var dirty = this.renderFeature(
+ feature, resolution, pixelRatio, styles, replayGroup);
+ this.dirty_ = this.dirty_ || dirty;
+ }
+ };
+ if (vectorLayerRenderOrder) {
+ /** @type {Array.<ol.Feature>} */
+ var features = [];
+ vectorSource.forEachFeatureInExtent(extent,
+ /**
+ * @param {ol.Feature} feature Feature.
+ */
+ function(feature) {
+ features.push(feature);
+ }, this);
+ features.sort(vectorLayerRenderOrder);
+ features.forEach(renderFeature, this);
+ } else {
+ vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
+ }
+ replayGroup.finish();
+
+ this.renderedResolution_ = resolution;
+ this.renderedRevision_ = vectorLayerRevision;
+ this.renderedRenderOrder_ = vectorLayerRenderOrder;
+ this.renderedExtent_ = extent;
+ this.replayGroup_ = replayGroup;
+
+ return true;
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
+ * styles.
+ * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
+ * @return {boolean} `true` if an image is loading.
+ */
+ol.renderer.canvas.VectorLayer.prototype.renderFeature =
+ function(feature, resolution, pixelRatio, styles, replayGroup) {
+ if (!styles) {
+ return false;
+ }
+ var loading = false;
+ if (goog.isArray(styles)) {
+ for (var 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;
+ }
+ } else {
+ loading = ol.renderer.vector.renderFeature(
+ replayGroup, feature, styles,
+ ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
+ this.handleStyleImageChange_, this) || loading;
+ }
+ return loading;
+};
+
+goog.provide('ol.TileUrlFunction');
+goog.provide('ol.TileUrlFunctionType');
+
+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;
+
+
+/**
+ * @typedef {function(ol.TileCoord, ol.proj.Projection, ol.TileCoord=):
+ * ol.TileCoord}
+ */
+ol.TileCoordTransformType;
+
+
+/**
+ * @param {string} template Template.
+ * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
+ * @return {ol.TileUrlFunctionType} Tile URL function.
+ */
+ol.TileUrlFunction.createFromTemplate = function(template, tileGrid) {
+ 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 (!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 z = tileCoord[0];
+ var range = tileGrid.getFullTileRange(z);
+ goog.asserts.assert(range,
+ 'The {-y} template requires a tile grid with extent');
+ var y = range.getHeight() + tileCoord[2];
+ return y.toString();
+ });
+ }
+ });
+};
+
+
+/**
+ * @param {Array.<string>} templates Templates.
+ * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
+ * @return {ol.TileUrlFunctionType} Tile URL function.
+ */
+ol.TileUrlFunction.createFromTemplates = function(templates, tileGrid) {
+ var len = templates.length;
+ var tileUrlFunctions = new Array(len);
+ for (var i = 0; i < len; ++i) {
+ tileUrlFunctions[i] = ol.TileUrlFunction.createFromTemplate(
+ templates[i], tileGrid);
+ }
+ return ol.TileUrlFunction.createFromTileUrlFunctions(tileUrlFunctions);
+};
+
+
+/**
+ * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions.
+ * @return {ol.TileUrlFunctionType} Tile URL function.
+ */
+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 (!tileCoord) {
+ return undefined;
+ } else {
+ var h = ol.tilecoord.hash(tileCoord);
+ var index = goog.math.modulo(h, tileUrlFunctions.length);
+ return tileUrlFunctions[index](tileCoord, pixelRatio, projection);
+ }
+ });
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * @param {string} url URL.
+ * @return {Array.<string>} Array of urls.
+ */
+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;
+};
+
+goog.provide('ol.source.UrlTile');
+
+goog.require('goog.events');
+goog.require('ol.TileLoadFunctionType');
+goog.require('ol.TileState');
+goog.require('ol.TileUrlFunction');
+goog.require('ol.TileUrlFunctionType');
+goog.require('ol.proj');
+goog.require('ol.source.Tile');
+goog.require('ol.source.TileEvent');
+
+
+/**
+ * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
+ * extent: (ol.Extent|undefined),
+ * logo: (string|olx.LogoOptions|undefined),
+ * opaque: (boolean|undefined),
+ * projection: ol.proj.ProjectionLike,
+ * state: (ol.source.State|string|undefined),
+ * tileGrid: (ol.tilegrid.TileGrid|undefined),
+ * tileLoadFunction: ol.TileLoadFunctionType,
+ * tilePixelRatio: (number|undefined),
+ * tileUrlFunction: (ol.TileUrlFunctionType|undefined),
+ * url: (string|undefined),
+ * urls: (Array.<string>|undefined),
+ * wrapX: (boolean|undefined)}}
+ */
+ol.source.UrlTileOptions;
+
+
+
+/**
+ * @classdesc
+ * Base class for sources providing tiles divided into a tile grid over http.
+ *
+ * @constructor
+ * @fires ol.source.TileEvent
+ * @extends {ol.source.Tile}
+ * @param {ol.source.UrlTileOptions} options Image tile options.
+ */
+ol.source.UrlTile = function(options) {
+
+ goog.base(this, {
+ attributions: options.attributions,
+ cacheSize: options.cacheSize,
+ extent: options.extent,
+ logo: options.logo,
+ opaque: options.opaque,
+ projection: options.projection,
+ state: options.state ?
+ /** @type {ol.source.State} */ (options.state) : undefined,
+ tileGrid: options.tileGrid,
+ tilePixelRatio: options.tilePixelRatio,
+ wrapX: options.wrapX
+ });
+
+ /**
+ * @protected
+ * @type {ol.TileLoadFunctionType}
+ */
+ this.tileLoadFunction = options.tileLoadFunction;
+
+ /**
+ * @protected
+ * @type {ol.TileUrlFunctionType}
+ */
+ this.tileUrlFunction = options.tileUrlFunction ?
+ options.tileUrlFunction :
+ ol.TileUrlFunction.nullTileUrlFunction;
+
+ /**
+ * @protected
+ * @type {!Array.<string>|null}
+ */
+ this.urls = null;
+
+ if (options.urls) {
+ if (options.tileUrlFunction) {
+ this.urls = options.urls;
+ } else {
+ this.setUrls(options.urls);
+ }
+ } else if (options.url) {
+ this.setUrl(options.url);
+ }
+ if (options.tileUrlFunction) {
+ this.setTileUrlFunction(options.tileUrlFunction);
+ }
+
+};
+goog.inherits(ol.source.UrlTile, ol.source.Tile);
+
+
+/**
+ * Return the tile load function of the source.
+ * @return {ol.TileLoadFunctionType} TileLoadFunction
+ * @api
+ */
+ol.source.UrlTile.prototype.getTileLoadFunction = function() {
+ return this.tileLoadFunction;
+};
+
+
+/**
+ * Return the tile URL function of the source.
+ * @return {ol.TileUrlFunctionType} TileUrlFunction
+ * @api
+ */
+ol.source.UrlTile.prototype.getTileUrlFunction = function() {
+ return this.tileUrlFunction;
+};
+
+
+/**
+ * Return the URLs used for this source.
+ * When a tileUrlFunction is used instead of url or urls,
+ * null will be returned.
+ * @return {!Array.<string>|null} URLs.
+ * @api
+ */
+ol.source.UrlTile.prototype.getUrls = function() {
+ return this.urls;
+};
+
+
+/**
+ * Handle tile change events.
+ * @param {goog.events.Event} event Event.
+ * @protected
+ */
+ol.source.UrlTile.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;
+ }
+};
+
+
+/**
+ * Set the tile load function of the source.
+ * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
+ * @api
+ */
+ol.source.UrlTile.prototype.setTileLoadFunction = function(tileLoadFunction) {
+ this.tileCache.clear();
+ this.tileLoadFunction = tileLoadFunction;
+ this.changed();
+};
+
+
+/**
+ * Set the tile URL function of the source.
+ * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
+ * @api
+ */
+ol.source.UrlTile.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();
+};
+
+
+/**
+ * Set the URL to use for requests.
+ * @param {string} url URL.
+ * @api stable
+ */
+ol.source.UrlTile.prototype.setUrl = function(url) {
+ this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates(
+ ol.TileUrlFunction.expandUrl(url), this.tileGrid));
+ this.urls = [url];
+};
+
+
+/**
+ * Set the URLs to use for requests.
+ * @param {Array.<string>} urls URLs.
+ * @api stable
+ */
+ol.source.UrlTile.prototype.setUrls = function(urls) {
+ this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates(
+ urls, this.tileGrid));
+ this.urls = urls;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.UrlTile.prototype.useTile = function(z, x, y) {
+ var tileCoordKey = this.getKeyZXY(z, x, y);
+ if (this.tileCache.containsKey(tileCoordKey)) {
+ this.tileCache.get(tileCoordKey);
+ }
+};
+
+goog.provide('ol.source.VectorTile');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('ol.TileState');
+goog.require('ol.VectorTile');
+goog.require('ol.featureloader');
+goog.require('ol.source.UrlTile');
+
+
+
+/**
+ * @classdesc
+ * Class for layer sources providing vector data divided into a tile grid, to be
+ * used with {@link ol.layer.VectorTile}. Although this source receives tiles
+ * with vector features from the server, it is not meant for feature editing.
+ * Features are optimized for rendering, their geometries are clipped at or near
+ * tile boundaries and simplified for a view resolution. See
+ * {@link ol.source.Vector} for vector sources that are suitable for feature
+ * editing.
+ *
+ * @constructor
+ * @fires ol.source.TileEvent
+ * @extends {ol.source.UrlTile}
+ * @param {olx.source.VectorTileOptions} options Vector tile options.
+ * @api
+ */
+ol.source.VectorTile = function(options) {
+
+ goog.base(this, {
+ attributions: options.attributions,
+ cacheSize: ol.DEFAULT_TILE_CACHE_HIGH_WATER_MARK / 16,
+ extent: options.extent,
+ logo: options.logo,
+ opaque: options.opaque,
+ projection: options.projection,
+ state: options.state ?
+ /** @type {ol.source.State} */ (options.state) : undefined,
+ tileGrid: options.tileGrid,
+ tileLoadFunction: options.tileLoadFunction ?
+ options.tileLoadFunction : ol.source.VectorTile.defaultTileLoadFunction,
+ tileUrlFunction: options.tileUrlFunction,
+ tilePixelRatio: options.tilePixelRatio,
+ url: options.url,
+ urls: options.urls,
+ wrapX: options.wrapX === undefined ? true : options.wrapX
+ });
+
+ /**
+ * @private
+ * @type {ol.format.Feature}
+ */
+ this.format_ = options.format ? options.format : null;
+
+ /**
+ * @protected
+ * @type {function(new: ol.VectorTile, ol.TileCoord, ol.TileState, string,
+ * ol.format.Feature, ol.TileLoadFunctionType, ol.proj.Projection)}
+ */
+ this.tileClass = options.tileClass ? options.tileClass : ol.VectorTile;
+
+};
+goog.inherits(ol.source.VectorTile, ol.source.UrlTile);
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.VectorTile.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 = urlTileCoord ?
+ this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined;
+ var tile = new this.tileClass(
+ tileCoord,
+ tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY,
+ tileUrl !== undefined ? tileUrl : '',
+ this.format_, this.tileLoadFunction, projection);
+ goog.events.listen(tile, goog.events.EventType.CHANGE,
+ this.handleTileChange, false, this);
+
+ this.tileCache.set(tileCoordKey, tile);
+ return tile;
+ }
+};
+
+
+/**
+ * @param {ol.VectorTile} vectorTile Vector tile.
+ * @param {string} url URL.
+ */
+ol.source.VectorTile.defaultTileLoadFunction = function(vectorTile, url) {
+ vectorTile.setLoader(ol.featureloader.tile(url, vectorTile.getFormat()));
+};
+
+goog.provide('ol.renderer.canvas.VectorTileLayer');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.vec.Mat4');
+goog.require('ol.Feature');
+goog.require('ol.TileRange');
+goog.require('ol.TileState');
+goog.require('ol.VectorTile');
+goog.require('ol.ViewHint');
+goog.require('ol.array');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.layer.VectorTile');
+goog.require('ol.proj.Units');
+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.size');
+goog.require('ol.source.VectorTile');
+goog.require('ol.tilecoord');
+goog.require('ol.vec.Mat4');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.canvas.Layer}
+ * @param {ol.layer.VectorTile} layer VectorTile layer.
+ */
+ol.renderer.canvas.VectorTileLayer = function(layer) {
+
+ goog.base(this, layer);
+
+ /**
+ * @private
+ * @type {CanvasRenderingContext2D}
+ */
+ this.context_ = ol.dom.createCanvasContext2D();
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.dirty_ = false;
+
+ /**
+ * @private
+ * @type {Array.<ol.VectorTile>}
+ */
+ this.renderedTiles_ = [];
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.tmpExtent_ = ol.extent.createEmpty();
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.tmpSize_ = [NaN, NaN];
+
+ /**
+ * @private
+ * @type {!goog.vec.Mat4.Number}
+ */
+ this.tmpTransform_ = goog.vec.Mat4.createNumber();
+
+};
+goog.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.Layer);
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.composeFrame =
+ function(frameState, layerState, context) {
+
+ var pixelRatio = frameState.pixelRatio;
+ var skippedFeatureUids = layerState.managed ?
+ frameState.skippedFeatureUids : {};
+ var viewState = frameState.viewState;
+ var center = viewState.center;
+ var projection = viewState.projection;
+ var resolution = viewState.resolution;
+ var rotation = viewState.rotation;
+ var layer = this.getLayer();
+ var source = layer.getSource();
+ goog.asserts.assertInstanceof(source, ol.source.VectorTile,
+ 'Source is an ol.source.VectorTile');
+
+ var transform = this.getTransform(frameState, 0);
+
+ this.dispatchPreComposeEvent(context, frameState, transform);
+
+ 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;
+
+ var tilesToDraw = this.renderedTiles_;
+ var tileGrid = source.getTileGrid();
+
+ var currentZ, i, ii, origin, tile, tileSize;
+ var tilePixelRatio, tilePixelResolution, tilePixelSize, tileResolution;
+ for (i = 0, ii = tilesToDraw.length; i < ii; ++i) {
+ tile = tilesToDraw[i];
+ currentZ = tile.getTileCoord()[0];
+ tileSize = tileGrid.getTileSize(currentZ);
+ tilePixelSize = source.getTilePixelSize(currentZ, pixelRatio, projection);
+ tilePixelRatio = tilePixelSize[0] /
+ ol.size.toSize(tileSize, this.tmpSize_)[0];
+ tileResolution = tileGrid.getResolution(currentZ);
+ tilePixelResolution = tileResolution / tilePixelRatio;
+ if (tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) {
+ origin = ol.extent.getTopLeft(tileGrid.getTileCoordExtent(
+ tile.getTileCoord(), this.tmpExtent_));
+ transform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_,
+ pixelRatio * frameState.size[0] / 2,
+ pixelRatio * frameState.size[1] / 2,
+ pixelRatio * tilePixelResolution / resolution,
+ pixelRatio * tilePixelResolution / resolution,
+ viewState.rotation,
+ (origin[0] - center[0]) / tilePixelResolution,
+ (center[1] - origin[1]) / tilePixelResolution);
+ }
+ tile.getReplayState().replayGroup.replay(replayContext, pixelRatio,
+ transform, rotation, skippedFeatureUids);
+ }
+
+ transform = this.getTransform(frameState, 0);
+
+ if (replayContext != context) {
+ this.dispatchRenderEvent(replayContext, frameState, transform);
+ context.drawImage(replayContext.canvas, 0, 0);
+ }
+ replayContext.globalAlpha = alpha;
+
+ this.dispatchPostComposeEvent(context, frameState, transform);
+};
+
+
+/**
+ * @param {ol.VectorTile} tile Tile.
+ * @param {ol.layer.VectorTile} layer Vector tile layer.
+ * @param {number} pixelRatio Pixel ratio.
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile,
+ layer, pixelRatio) {
+ var revision = layer.getRevision();
+ var renderOrder = layer.getRenderOrder() || null;
+
+ var replayState = tile.getReplayState();
+ if (!replayState.dirty && replayState.renderedRevision == revision &&
+ replayState.renderedRenderOrder == renderOrder) {
+ return;
+ }
+
+ // FIXME dispose of old replayGroup in post render
+ goog.dispose(replayState.replayGroup);
+ replayState.replayGroup = null;
+ replayState.dirty = false;
+
+ var source = layer.getSource();
+ goog.asserts.assertInstanceof(source, ol.source.VectorTile,
+ 'Source is an ol.source.VectorTile');
+ var tileGrid = source.getTileGrid();
+ var tileCoord = tile.getTileCoord();
+ var pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS;
+ var extent;
+ if (pixelSpace) {
+ var tilePixelSize = source.getTilePixelSize(tileCoord[0], pixelRatio,
+ tile.getProjection());
+ extent = [0, 0, tilePixelSize[0], tilePixelSize[1]];
+ } else {
+ extent = tileGrid.getTileCoordExtent(tileCoord);
+ }
+ var resolution = tileGrid.getResolution(tileCoord[0]);
+ var tileResolution = pixelSpace ? source.getTilePixelRatio() : resolution;
+ replayState.dirty = false;
+ var replayGroup = new ol.render.canvas.ReplayGroup(0, extent,
+ tileResolution, layer.getRenderBuffer());
+ var squaredTolerance = ol.renderer.vector.getSquaredTolerance(
+ tileResolution, pixelRatio);
+
+ /**
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @this {ol.renderer.canvas.VectorTileLayer}
+ */
+ function renderFeature(feature) {
+ var styles;
+ var styleFunction = feature.getStyleFunction();
+ if (styleFunction) {
+ goog.asserts.assertInstanceof(feature, ol.Feature, 'Got an ol.Feature');
+ styles = styleFunction.call(feature, resolution);
+ } else {
+ styleFunction = layer.getStyleFunction();
+ if (styleFunction) {
+ styles = styleFunction(feature, resolution);
+ }
+ }
+ if (styles) {
+ if (!goog.isArray(styles)) {
+ styles = [styles];
+ }
+ var dirty = this.renderFeature(feature, squaredTolerance, styles,
+ replayGroup);
+ this.dirty_ = this.dirty_ || dirty;
+ replayState.dirty = replayState.dirty || dirty;
+ }
+ }
+
+ var features = tile.getFeatures();
+ if (renderOrder && renderOrder !== replayState.renderedRenderOrder) {
+ features.sort(renderOrder);
+ }
+ features.forEach(renderFeature, this);
+
+ replayGroup.finish();
+
+ replayState.renderedRevision = revision;
+ replayState.renderedRenderOrder = renderOrder;
+ replayState.replayGroup = replayGroup;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate =
+ function(coordinate, frameState, callback, thisArg) {
+ 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 = {};
+
+ var replayables = this.renderedTiles_;
+ var source = layer.getSource();
+ goog.asserts.assertInstanceof(source, ol.source.VectorTile,
+ 'Source is an ol.source.VectorTile');
+ var tileGrid = source.getTileGrid();
+ var found, tileSpaceCoordinate;
+ var i, ii, origin, replayGroup;
+ var tile, tileCoord, tileExtent, tilePixelRatio, tileResolution;
+ for (i = 0, ii = replayables.length; i < ii; ++i) {
+ tile = replayables[i];
+ tileCoord = tile.getTileCoord();
+ tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord,
+ this.tmpExtent_);
+ if (!ol.extent.containsCoordinate(tileExtent, coordinate)) {
+ continue;
+ }
+ if (tile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) {
+ origin = ol.extent.getTopLeft(tileExtent);
+ tilePixelRatio = source.getTilePixelRatio();
+ tileResolution = tileGrid.getResolution(tileCoord[0]) / tilePixelRatio;
+ tileSpaceCoordinate = [
+ (coordinate[0] - origin[0]) / tileResolution,
+ (origin[1] - coordinate[1]) / tileResolution
+ ];
+ resolution = tilePixelRatio;
+ } else {
+ tileSpaceCoordinate = coordinate;
+ }
+ replayGroup = tile.getReplayState().replayGroup;
+ found = found || replayGroup.forEachFeatureAtCoordinate(
+ tileSpaceCoordinate, resolution, rotation,
+ layerState.managed ? frameState.skippedFeatureUids : {},
+ /**
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @return {?} Callback result.
+ */
+ function(feature) {
+ goog.asserts.assert(feature, 'received a feature');
+ var key = goog.getUid(feature).toString();
+ if (!(key in features)) {
+ features[key] = true;
+ return callback.call(thisArg, feature, layer);
+ }
+ });
+ }
+ return found;
+};
+
+
+/**
+ * Handle changes in image style state.
+ * @param {goog.events.Event} event Image style change event.
+ * @private
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ =
+ function(event) {
+ this.renderIfReadyAndVisible();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame =
+ function(frameState, layerState) {
+ var layer = /** @type {ol.layer.Vector} */ (this.getLayer());
+ goog.asserts.assertInstanceof(layer, ol.layer.VectorTile,
+ 'layer is an instance of ol.layer.VectorTile');
+ var source = layer.getSource();
+ goog.asserts.assertInstanceof(source, ol.source.VectorTile,
+ 'Source is an ol.source.VectorTile');
+
+ this.updateAttributions(
+ frameState.attributions, source.getAttributions());
+ this.updateLogos(frameState, source);
+
+ var animating = frameState.viewHints[ol.ViewHint.ANIMATING];
+ var interacting = frameState.viewHints[ol.ViewHint.INTERACTING];
+ var updateWhileAnimating = layer.getUpdateWhileAnimating();
+ var updateWhileInteracting = layer.getUpdateWhileInteracting();
+
+ if (!this.dirty_ && (!updateWhileAnimating && animating) ||
+ (!updateWhileInteracting && interacting)) {
+ return true;
+ }
+
+ var extent = frameState.extent;
+ if (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 viewState = frameState.viewState;
+ var projection = viewState.projection;
+ var resolution = viewState.resolution;
+ var pixelRatio = frameState.pixelRatio;
+
+ var tileGrid = source.getTileGrid();
+ var resolutions = tileGrid.getResolutions();
+ var z = resolutions.length - 1;
+ while (z > 0 && resolutions[z] < resolution) {
+ --z;
+ }
+ var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
+ this.updateUsedTiles(frameState.usedTiles, source, z, tileRange);
+ this.manageTilePyramid(frameState, source, tileGrid, pixelRatio,
+ projection, extent, z, layer.getPreload());
+ this.scheduleExpireCache(frameState, source);
+
+ /**
+ * @type {Object.<number, Object.<string, ol.VectorTile>>}
+ */
+ var tilesToDrawByZ = {};
+ tilesToDrawByZ[z] = {};
+
+ var findLoadedTiles = this.createLoadedTileFinder(source, projection,
+ tilesToDrawByZ);
+
+ var useInterimTilesOnError = layer.getUseInterimTilesOnError();
+
+ var tmpExtent = this.tmpExtent_;
+ 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 = source.getTile(z, x, y, pixelRatio, projection);
+ goog.asserts.assertInstanceof(tile, ol.VectorTile,
+ 'Tile is an ol.VectorTile');
+ 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) {
+ childTileRange = tileGrid.getTileCoordChildTileRange(
+ tile.tileCoord, tmpTileRange, tmpExtent);
+ if (childTileRange) {
+ findLoadedTiles(z + 1, childTileRange);
+ }
+ }
+
+ }
+ }
+
+ this.dirty_ = false;
+
+ /** @type {Array.<number>} */
+ var zs = Object.keys(tilesToDrawByZ).map(Number);
+ zs.sort(ol.array.numberSafeCompareFunction);
+ var replayables = [];
+ var i, ii, currentZ, tileCoordKey, tilesToDraw;
+ for (i = 0, ii = zs.length; i < ii; ++i) {
+ currentZ = zs[i];
+ tilesToDraw = tilesToDrawByZ[currentZ];
+ for (tileCoordKey in tilesToDraw) {
+ tile = tilesToDraw[tileCoordKey];
+ if (tile.getState() == ol.TileState.LOADED) {
+ replayables.push(tile);
+ this.createReplayGroup(tile, layer, pixelRatio);
+ }
+ }
+ }
+
+ this.renderedTiles_ = replayables;
+
+ return true;
+};
+
+
+/**
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
+ * styles.
+ * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
+ * @return {boolean} `true` if an image is loading.
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.renderFeature =
+ function(feature, squaredTolerance, styles, replayGroup) {
+ if (!styles) {
+ return false;
+ }
+ var loading = false;
+ if (goog.isArray(styles)) {
+ for (var i = 0, ii = styles.length; i < ii; ++i) {
+ loading = ol.renderer.vector.renderFeature(
+ replayGroup, feature, styles[i], squaredTolerance,
+ this.handleStyleImageChange_, this) || loading;
+ }
+ } else {
+ loading = ol.renderer.vector.renderFeature(
+ replayGroup, feature, styles, squaredTolerance,
+ this.handleStyleImageChange_, this) || loading;
+ }
+ return loading;
+};
+
+// FIXME offset panning
+
+goog.provide('ol.renderer.canvas.Map');
+
+goog.require('goog.array');
+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.layer.VectorTile');
+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.renderer.canvas.VectorTileLayer');
+goog.require('ol.source.State');
+goog.require('ol.vec.Mat4');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.Map}
+ * @param {Element} container Container.
+ * @param {ol.Map} map Map.
+ */
+ol.renderer.canvas.Map = function(container, map) {
+
+ goog.base(this, container, map);
+
+ /**
+ * @private
+ * @type {CanvasRenderingContext2D}
+ */
+ this.context_ = ol.dom.createCanvasContext2D();
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement}
+ */
+ this.canvas_ = this.context_.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 {boolean}
+ */
+ this.renderedVisible_ = true;
+
+ /**
+ * @private
+ * @type {!goog.vec.Mat4.Number}
+ */
+ this.transform_ = goog.vec.Mat4.createNumber();
+
+};
+goog.inherits(ol.renderer.canvas.Map, ol.renderer.Map);
+
+
+/**
+ * @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_TILE && layer instanceof ol.layer.VectorTile) {
+ return new ol.renderer.canvas.VectorTileLayer(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;
+ }
+};
+
+
+/**
+ * @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;
+
+ var transform = this.getTransform(frameState);
+
+ 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 {olx.FrameState} frameState Frame state.
+ * @protected
+ * @return {!goog.vec.Mat4.Number} Transform.
+ */
+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.renderer.canvas.Map.prototype.getType = function() {
+ return ol.RendererType.CANVAS;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
+
+ if (!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);
+ }
+
+ this.calculateMatrices2D(frameState);
+
+ this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
+
+ var layerStatesArray = frameState.layerStatesArray;
+ goog.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);
+
+ 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);
+ }
+ }
+
+ this.dispatchComposeEvent_(
+ ol.render.EventType.POSTCOMPOSE, frameState);
+
+ if (!this.renderedVisible_) {
+ goog.style.setElementShown(this.canvas_, true);
+ this.renderedVisible_ = true;
+ }
+
+ this.scheduleRemoveUnusedLayerRenderers(frameState);
+ this.scheduleExpireIconCache(frameState);
+};
+
+goog.provide('ol.renderer.dom.Layer');
+
+goog.require('ol');
+goog.require('ol.layer.Layer');
+goog.require('ol.renderer.Layer');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.Layer}
+ * @param {ol.layer.Layer} layer Layer.
+ * @param {!Element} target Target.
+ */
+ol.renderer.dom.Layer = function(layer, target) {
+
+ goog.base(this, layer);
+
+ /**
+ * @type {!Element}
+ * @protected
+ */
+ this.target = target;
+
+};
+goog.inherits(ol.renderer.dom.Layer, ol.renderer.Layer);
+
+
+/**
+ * Clear rendered elements.
+ */
+ol.renderer.dom.Layer.prototype.clearFrame = ol.nullFunction;
+
+
+/**
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.layer.LayerState} layerState Layer state.
+ */
+ol.renderer.dom.Layer.prototype.composeFrame = ol.nullFunction;
+
+
+/**
+ * @return {!Element} Target.
+ */
+ol.renderer.dom.Layer.prototype.getTarget = function() {
+ return this.target;
+};
+
+
+/**
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.layer.LayerState} layerState Layer state.
+ * @return {boolean} whether composeFrame should be called.
+ */
+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.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');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.dom.Layer}
+ * @param {ol.layer.Image} imageLayer Image layer.
+ */
+ol.renderer.dom.ImageLayer = function(imageLayer) {
+ var target = document.createElement('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.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|ol.render.Feature} feature Feature.
+ * @return {?} Callback result.
+ */
+ function(feature) {
+ return callback.call(thisArg, feature, layer);
+ });
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.dom.ImageLayer.prototype.clearFrame = function() {
+ goog.dom.removeChildren(this.target);
+ this.image_ = null;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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,
+ 'layer is an instance of ol.layer.Image');
+ var imageSource = imageLayer.getSource();
+
+ var hints = frameState.viewHints;
+
+ var renderedExtent = frameState.extent;
+ if (layerState.extent !== undefined) {
+ renderedExtent = ol.extent.getIntersection(
+ renderedExtent, layerState.extent);
+ }
+
+ if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] &&
+ !ol.extent.isEmpty(renderedExtent)) {
+ var projection = viewState.projection;
+ if (!ol.ENABLE_RASTER_REPROJECTION) {
+ var sourceProjection = imageSource.getProjection();
+ if (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 (image_) {
+ var loaded = this.loadImage(image_);
+ if (loaded) {
+ image = image_;
+ }
+ }
+ }
+
+ if (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);
+ this.target.appendChild(imageElement);
+ this.image_ = image;
+ }
+ this.setTransform_(transform);
+ this.updateAttributions(frameState.attributions, image.getAttributions());
+ this.updateLogos(frameState, imageSource);
+ }
+
+ return true;
+};
+
+
+/**
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @private
+ */
+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);
+ }
+};
+
+// 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');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+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.array');
+goog.require('ol.dom');
+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');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.dom.Layer}
+ * @param {ol.layer.Tile} tileLayer Tile layer.
+ */
+ol.renderer.dom.TileLayer = function(tileLayer) {
+
+ var target = document.createElement('DIV');
+ target.style.position = 'absolute';
+
+ goog.base(this, tileLayer, target);
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.renderedVisible_ = true;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedOpacity_ = 1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedRevision_ = 0;
+
+ /**
+ * @private
+ * @type {!Object.<number, ol.renderer.dom.TileLayerZ_>}
+ */
+ this.tileLayerZs_ = {};
+
+};
+goog.inherits(ol.renderer.dom.TileLayer, ol.renderer.dom.Layer);
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.dom.TileLayer.prototype.clearFrame = function() {
+ goog.dom.removeChildren(this.target);
+ this.renderedRevision_ = 0;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.dom.TileLayer.prototype.prepareFrame =
+ function(frameState, layerState) {
+
+ 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,
+ '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 (layerState.extent !== undefined) {
+ extent = ol.extent.getIntersection(extent, layerState.extent);
+ }
+
+ var tileRange = tileGrid.getTileRangeForExtentAndResolution(
+ extent, tileResolution);
+
+ /** @type {Object.<number, Object.<string, ol.Tile>>} */
+ var tilesToDrawByZ = {};
+ tilesToDrawByZ[z] = {};
+
+ var findLoadedTiles = this.createLoadedTileFinder(
+ tileSource, projection, tilesToDrawByZ);
+
+ var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
+
+ var tmpExtent = ol.extent.createEmpty();
+ var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
+ var childTileRange, drawable, 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();
+ drawable = tileState == ol.TileState.LOADED ||
+ tileState == ol.TileState.EMPTY ||
+ tileState == ol.TileState.ERROR && !useInterimTilesOnError;
+ if (!drawable && tile.interimTile) {
+ tile = tile.interimTile;
+ }
+ goog.asserts.assert(tile);
+ 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 (childTileRange) {
+ findLoadedTiles(z + 1, childTileRange);
+ }
+ }
+
+ }
+
+ }
+
+ // 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 = Object.keys(tilesToDrawByZ).map(Number);
+ zs.sort(ol.array.numberSafeCompareFunction);
+
+ /** @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 = Object.keys(this.tileLayerZs_).map(Number);
+ tileLayerZKeys.sort(ol.array.numberSafeCompareFunction);
+
+ 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);
+ }
+ }
+ }
+
+ if (layerState.opacity != this.renderedOpacity_) {
+ this.target.style.opacity = layerState.opacity;
+ this.renderedOpacity_ = layerState.opacity;
+ }
+
+ if (layerState.visible && !this.renderedVisible_) {
+ goog.style.setElementShown(this.target, true);
+ this.renderedVisible_ = true;
+ }
+
+ 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;
+};
+
+
+
+/**
+ * @constructor
+ * @private
+ * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
+ * @param {ol.TileCoord} tileCoordOrigin Tile coord origin.
+ */
+ol.renderer.dom.TileLayerZ_ = function(tileGrid, tileCoordOrigin) {
+
+ /**
+ * @type {!Element}
+ */
+ this.target = document.createElement('DIV');
+ this.target.style.position = 'absolute';
+ this.target.style.width = '100%';
+ this.target.style.height = '100%';
+
+ /**
+ * @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));
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.resolution_ = tileGrid.getResolution(tileCoordOrigin[0]);
+
+ /**
+ * @private
+ * @type {Object.<string, ol.Tile>}
+ */
+ this.tiles_ = {};
+
+ /**
+ * @private
+ * @type {DocumentFragment}
+ */
+ this.documentFragment_ = null;
+
+ /**
+ * @private
+ * @type {goog.vec.Mat4.Number}
+ */
+ this.transform_ = goog.vec.Mat4.createNumberIdentity();
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.tmpSize_ = [0, 0];
+
+};
+
+
+/**
+ * @param {ol.Tile} tile Tile.
+ * @param {number} tileGutter Tile gutter.
+ */
+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;
+ }
+ 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 = document.createElement('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';
+ tileElement.appendChild(image);
+ } else {
+ imageStyle.width = tileSize[0] + 'px';
+ imageStyle.height = tileSize[1] + 'px';
+ tileElement = image;
+ tileElementStyle = imageStyle;
+ }
+ tileElementStyle.position = 'absolute';
+ tileElementStyle.left =
+ ((tileCoordX - this.tileCoordOrigin_[1]) * tileSize[0]) + 'px';
+ tileElementStyle.top =
+ ((this.tileCoordOrigin_[2] - tileCoordY) * tileSize[1]) + 'px';
+ if (!this.documentFragment_) {
+ this.documentFragment_ = document.createDocumentFragment();
+ }
+ this.documentFragment_.appendChild(tileElement);
+ this.tiles_[tileCoordKey] = tile;
+};
+
+
+/**
+ * FIXME empty description for jsdoc
+ */
+ol.renderer.dom.TileLayerZ_.prototype.finalizeAddTiles = function() {
+ if (this.documentFragment_) {
+ this.target.appendChild(this.documentFragment_);
+ this.documentFragment_ = null;
+ }
+};
+
+
+/**
+ * @return {ol.Coordinate} Origin.
+ */
+ol.renderer.dom.TileLayerZ_.prototype.getOrigin = function() {
+ return this.origin_;
+};
+
+
+/**
+ * @return {number} Resolution.
+ */
+ol.renderer.dom.TileLayerZ_.prototype.getResolution = function() {
+ return this.resolution_;
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
+ */
+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];
+ }
+};
+
+
+/**
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ */
+ol.renderer.dom.TileLayerZ_.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.provide('ol.renderer.dom.VectorLayer');
+
+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');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.dom.Layer}
+ * @param {ol.layer.Vector} vectorLayer Vector layer.
+ */
+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';
+
+ goog.base(this, vectorLayer, target);
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.dirty_ = false;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedRevision_ = -1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedResolution_ = NaN;
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.renderedExtent_ = ol.extent.createEmpty();
+
+ /**
+ * @private
+ * @type {function(ol.Feature, ol.Feature): number|null}
+ */
+ this.renderedRenderOrder_ = null;
+
+ /**
+ * @private
+ * @type {ol.render.canvas.ReplayGroup}
+ */
+ this.replayGroup_ = null;
+
+ /**
+ * @private
+ * @type {goog.vec.Mat4.Number}
+ */
+ this.transform_ = goog.vec.Mat4.createNumber();
+
+ /**
+ * @private
+ * @type {goog.vec.Mat4.Number}
+ */
+ this.elementTransform_ = goog.vec.Mat4.createNumber();
+
+};
+goog.inherits(ol.renderer.dom.VectorLayer, ol.renderer.dom.Layer);
+
+
+/**
+ * @inheritDoc
+ */
+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');
+
+ 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]);
+
+ var context = this.context_;
+
+ // Clear the canvas and set the correct size
+ context.canvas.width = imageWidth;
+ context.canvas.height = imageHeight;
+
+ 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 (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);
+};
+
+
+/**
+ * @param {ol.render.EventType} type Event type.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @private
+ */
+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();
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtCoordinate =
+ function(coordinate, frameState, callback, thisArg) {
+ if (!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|ol.render.Feature} feature Feature.
+ * @return {?} Callback result.
+ */
+ function(feature) {
+ goog.asserts.assert(feature !== undefined, 'received a feature');
+ var key = goog.getUid(feature).toString();
+ if (!(key in features)) {
+ features[key] = true;
+ return callback.call(thisArg, feature, layer);
+ }
+ });
+ }
+};
+
+
+/**
+ * Handle changes in image style state.
+ * @param {goog.events.Event} event Image style change event.
+ * @private
+ */
+ol.renderer.dom.VectorLayer.prototype.handleStyleImageChange_ =
+ function(event) {
+ this.renderIfReadyAndVisible();
+};
+
+
+/**
+ * @inheritDoc
+ */
+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);
+
+ 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();
+
+ if (vectorLayerRenderOrder === undefined) {
+ vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
+ }
+
+ 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;
+ }
+
+ // 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;
+ var styleFunction = feature.getStyleFunction();
+ if (styleFunction) {
+ styles = styleFunction.call(feature, resolution);
+ } else {
+ styleFunction = vectorLayer.getStyleFunction();
+ if (styleFunction) {
+ styles = styleFunction(feature, resolution);
+ }
+ }
+ if (styles) {
+ var dirty = this.renderFeature(
+ feature, resolution, pixelRatio, styles, replayGroup);
+ this.dirty_ = this.dirty_ || dirty;
+ }
+ };
+ if (vectorLayerRenderOrder) {
+ /** @type {Array.<ol.Feature>} */
+ var features = [];
+ vectorSource.forEachFeatureInExtent(extent,
+ /**
+ * @param {ol.Feature} feature Feature.
+ */
+ function(feature) {
+ features.push(feature);
+ }, this);
+ features.sort(vectorLayerRenderOrder);
+ features.forEach(renderFeature, this);
+ } else {
+ vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
+ }
+ replayGroup.finish();
+
+ this.renderedResolution_ = resolution;
+ this.renderedRevision_ = vectorLayerRevision;
+ this.renderedRenderOrder_ = vectorLayerRenderOrder;
+ this.renderedExtent_ = extent;
+ this.replayGroup_ = replayGroup;
+
+ return true;
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or 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 (!styles) {
+ return false;
+ }
+ var loading = false;
+ if (goog.isArray(styles)) {
+ for (var 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;
+ }
+ } else {
+ loading = ol.renderer.vector.renderFeature(
+ replayGroup, feature, styles,
+ ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
+ this.handleStyleImageChange_, this) || loading;
+ }
+ return loading;
+};
+
+goog.provide('ol.renderer.dom.Map');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventType');
+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.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.Map}
+ * @param {Element} container Container.
+ * @param {ol.Map} map Map.
+ */
+ol.renderer.dom.Map = function(container, map) {
+
+ goog.base(this, container, map);
+
+ /**
+ * @private
+ * @type {CanvasRenderingContext2D}
+ */
+ 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);
+
+ /**
+ * @private
+ * @type {!goog.vec.Mat4.Number}
+ */
+ this.transform_ = goog.vec.Mat4.createNumber();
+
+ /**
+ * @type {!Element}
+ * @private
+ */
+ this.layersPane_ = document.createElement('DIV');
+ this.layersPane_.className = ol.css.CLASS_UNSELECTABLE;
+ var style = this.layersPane_.style;
+ style.position = 'absolute';
+ style.width = '100%';
+ style.height = '100%';
+
+ // 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);
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.renderedVisible_ = true;
+
+};
+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.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 (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();
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.dom.Map.prototype.getType = function() {
+ return ol.RendererType.DOM;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.dom.Map.prototype.renderFrame = function(frameState) {
+
+ if (!frameState) {
+ if (this.renderedVisible_) {
+ goog.style.setElementShown(this.layersPane_, false);
+ this.renderedVisible_ = false;
+ }
+ return;
+ }
+
+ var map = this.getMap();
+ if (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;
+ }
+
+ this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
+
+ var layerStatesArray = frameState.layerStatesArray;
+ goog.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);
+
+ 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');
+ goog.dom.insertChildAt(this.layersPane_, 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();
+ }
+ }
+
+ 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.
+
+
+/**
+ * @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
+ */
+
+goog.provide('goog.webgl');
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_BUFFER_BIT = 0x00000100;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BUFFER_BIT = 0x00000400;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.COLOR_BUFFER_BIT = 0x00004000;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.POINTS = 0x0000;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LINES = 0x0001;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LINE_LOOP = 0x0002;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LINE_STRIP = 0x0003;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TRIANGLES = 0x0004;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TRIANGLE_STRIP = 0x0005;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TRIANGLE_FAN = 0x0006;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ZERO = 0;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ONE = 1;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SRC_COLOR = 0x0300;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ONE_MINUS_SRC_COLOR = 0x0301;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SRC_ALPHA = 0x0302;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ONE_MINUS_SRC_ALPHA = 0x0303;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DST_ALPHA = 0x0304;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ONE_MINUS_DST_ALPHA = 0x0305;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DST_COLOR = 0x0306;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ONE_MINUS_DST_COLOR = 0x0307;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SRC_ALPHA_SATURATE = 0x0308;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FUNC_ADD = 0x8006;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND_EQUATION = 0x8009;
+
+
+/**
+ * Same as BLEND_EQUATION
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND_EQUATION_RGB = 0x8009;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND_EQUATION_ALPHA = 0x883D;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FUNC_SUBTRACT = 0x800A;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FUNC_REVERSE_SUBTRACT = 0x800B;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND_DST_RGB = 0x80C8;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND_SRC_RGB = 0x80C9;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND_DST_ALPHA = 0x80CA;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND_SRC_ALPHA = 0x80CB;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CONSTANT_COLOR = 0x8001;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ONE_MINUS_CONSTANT_COLOR = 0x8002;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CONSTANT_ALPHA = 0x8003;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ONE_MINUS_CONSTANT_ALPHA = 0x8004;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND_COLOR = 0x8005;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ARRAY_BUFFER = 0x8892;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ELEMENT_ARRAY_BUFFER = 0x8893;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ARRAY_BUFFER_BINDING = 0x8894;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ELEMENT_ARRAY_BUFFER_BINDING = 0x8895;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STREAM_DRAW = 0x88E0;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STATIC_DRAW = 0x88E4;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DYNAMIC_DRAW = 0x88E8;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BUFFER_SIZE = 0x8764;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BUFFER_USAGE = 0x8765;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CURRENT_VERTEX_ATTRIB = 0x8626;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRONT = 0x0404;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BACK = 0x0405;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRONT_AND_BACK = 0x0408;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CULL_FACE = 0x0B44;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND = 0x0BE2;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DITHER = 0x0BD0;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_TEST = 0x0B90;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_TEST = 0x0B71;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SCISSOR_TEST = 0x0C11;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.POLYGON_OFFSET_FILL = 0x8037;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SAMPLE_ALPHA_TO_COVERAGE = 0x809E;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SAMPLE_COVERAGE = 0x80A0;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.NO_ERROR = 0;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INVALID_ENUM = 0x0500;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INVALID_VALUE = 0x0501;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INVALID_OPERATION = 0x0502;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.OUT_OF_MEMORY = 0x0505;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CW = 0x0900;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CCW = 0x0901;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LINE_WIDTH = 0x0B21;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ALIASED_POINT_SIZE_RANGE = 0x846D;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ALIASED_LINE_WIDTH_RANGE = 0x846E;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CULL_FACE_MODE = 0x0B45;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRONT_FACE = 0x0B46;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_RANGE = 0x0B70;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_WRITEMASK = 0x0B72;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_CLEAR_VALUE = 0x0B73;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_FUNC = 0x0B74;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_CLEAR_VALUE = 0x0B91;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_FUNC = 0x0B92;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_FAIL = 0x0B94;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_PASS_DEPTH_FAIL = 0x0B95;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_PASS_DEPTH_PASS = 0x0B96;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_REF = 0x0B97;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_VALUE_MASK = 0x0B93;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_WRITEMASK = 0x0B98;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BACK_FUNC = 0x8800;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BACK_FAIL = 0x8801;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BACK_PASS_DEPTH_FAIL = 0x8802;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BACK_PASS_DEPTH_PASS = 0x8803;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BACK_REF = 0x8CA3;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BACK_VALUE_MASK = 0x8CA4;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BACK_WRITEMASK = 0x8CA5;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VIEWPORT = 0x0BA2;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SCISSOR_BOX = 0x0C10;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.COLOR_CLEAR_VALUE = 0x0C22;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.COLOR_WRITEMASK = 0x0C23;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.UNPACK_ALIGNMENT = 0x0CF5;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.PACK_ALIGNMENT = 0x0D05;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_TEXTURE_SIZE = 0x0D33;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_VIEWPORT_DIMS = 0x0D3A;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SUBPIXEL_BITS = 0x0D50;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RED_BITS = 0x0D52;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.GREEN_BITS = 0x0D53;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLUE_BITS = 0x0D54;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ALPHA_BITS = 0x0D55;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_BITS = 0x0D56;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BITS = 0x0D57;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.POLYGON_OFFSET_UNITS = 0x2A00;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.POLYGON_OFFSET_FACTOR = 0x8038;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_BINDING_2D = 0x8069;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SAMPLE_BUFFERS = 0x80A8;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SAMPLES = 0x80A9;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SAMPLE_COVERAGE_VALUE = 0x80AA;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SAMPLE_COVERAGE_INVERT = 0x80AB;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.COMPRESSED_TEXTURE_FORMATS = 0x86A3;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DONT_CARE = 0x1100;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FASTEST = 0x1101;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.NICEST = 0x1102;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.GENERATE_MIPMAP_HINT = 0x8192;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BYTE = 0x1400;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.UNSIGNED_BYTE = 0x1401;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SHORT = 0x1402;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.UNSIGNED_SHORT = 0x1403;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INT = 0x1404;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.UNSIGNED_INT = 0x1405;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FLOAT = 0x1406;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_COMPONENT = 0x1902;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ALPHA = 0x1906;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RGB = 0x1907;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RGBA = 0x1908;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LUMINANCE = 0x1909;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LUMINANCE_ALPHA = 0x190A;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.UNSIGNED_SHORT_4_4_4_4 = 0x8033;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.UNSIGNED_SHORT_5_5_5_1 = 0x8034;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.UNSIGNED_SHORT_5_6_5 = 0x8363;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAGMENT_SHADER = 0x8B30;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERTEX_SHADER = 0x8B31;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_VERTEX_ATTRIBS = 0x8869;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_VARYING_VECTORS = 0x8DFC;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_TEXTURE_IMAGE_UNITS = 0x8872;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_FRAGMENT_UNIFORM_VECTORS = 0x8DFD;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SHADER_TYPE = 0x8B4F;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DELETE_STATUS = 0x8B80;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LINK_STATUS = 0x8B82;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VALIDATE_STATUS = 0x8B83;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ATTACHED_SHADERS = 0x8B85;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ACTIVE_UNIFORMS = 0x8B86;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ACTIVE_ATTRIBUTES = 0x8B89;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SHADING_LANGUAGE_VERSION = 0x8B8C;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CURRENT_PROGRAM = 0x8B8D;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.NEVER = 0x0200;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LESS = 0x0201;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.EQUAL = 0x0202;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LEQUAL = 0x0203;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.GREATER = 0x0204;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.NOTEQUAL = 0x0205;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.GEQUAL = 0x0206;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ALWAYS = 0x0207;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.KEEP = 0x1E00;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.REPLACE = 0x1E01;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INCR = 0x1E02;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DECR = 0x1E03;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INVERT = 0x150A;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INCR_WRAP = 0x8507;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DECR_WRAP = 0x8508;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VENDOR = 0x1F00;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERER = 0x1F01;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERSION = 0x1F02;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.NEAREST = 0x2600;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LINEAR = 0x2601;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.NEAREST_MIPMAP_NEAREST = 0x2700;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LINEAR_MIPMAP_NEAREST = 0x2701;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.NEAREST_MIPMAP_LINEAR = 0x2702;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LINEAR_MIPMAP_LINEAR = 0x2703;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_MAG_FILTER = 0x2800;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_MIN_FILTER = 0x2801;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_WRAP_S = 0x2802;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_WRAP_T = 0x2803;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_2D = 0x0DE1;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE = 0x1702;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_CUBE_MAP = 0x8513;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_BINDING_CUBE_MAP = 0x8514;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE0 = 0x84C0;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE1 = 0x84C1;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE2 = 0x84C2;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE3 = 0x84C3;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE4 = 0x84C4;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE5 = 0x84C5;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE6 = 0x84C6;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE7 = 0x84C7;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE8 = 0x84C8;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE9 = 0x84C9;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE10 = 0x84CA;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE11 = 0x84CB;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE12 = 0x84CC;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE13 = 0x84CD;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE14 = 0x84CE;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE15 = 0x84CF;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE16 = 0x84D0;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE17 = 0x84D1;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE18 = 0x84D2;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE19 = 0x84D3;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE20 = 0x84D4;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE21 = 0x84D5;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE22 = 0x84D6;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE23 = 0x84D7;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE24 = 0x84D8;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE25 = 0x84D9;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE26 = 0x84DA;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE27 = 0x84DB;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE28 = 0x84DC;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE29 = 0x84DD;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE30 = 0x84DE;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE31 = 0x84DF;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ACTIVE_TEXTURE = 0x84E0;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.REPEAT = 0x2901;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CLAMP_TO_EDGE = 0x812F;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MIRRORED_REPEAT = 0x8370;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FLOAT_VEC2 = 0x8B50;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FLOAT_VEC3 = 0x8B51;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FLOAT_VEC4 = 0x8B52;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INT_VEC2 = 0x8B53;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INT_VEC3 = 0x8B54;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INT_VEC4 = 0x8B55;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BOOL = 0x8B56;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BOOL_VEC2 = 0x8B57;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BOOL_VEC3 = 0x8B58;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BOOL_VEC4 = 0x8B59;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FLOAT_MAT2 = 0x8B5A;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FLOAT_MAT3 = 0x8B5B;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FLOAT_MAT4 = 0x8B5C;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SAMPLER_2D = 0x8B5E;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SAMPLER_CUBE = 0x8B60;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERTEX_ATTRIB_ARRAY_SIZE = 0x8623;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERTEX_ATTRIB_ARRAY_TYPE = 0x8625;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERTEX_ATTRIB_ARRAY_POINTER = 0x8645;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.COMPILE_STATUS = 0x8B81;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LOW_FLOAT = 0x8DF0;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MEDIUM_FLOAT = 0x8DF1;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.HIGH_FLOAT = 0x8DF2;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LOW_INT = 0x8DF3;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MEDIUM_INT = 0x8DF4;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.HIGH_INT = 0x8DF5;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER = 0x8D40;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER = 0x8D41;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RGBA4 = 0x8056;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RGB5_A1 = 0x8057;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RGB565 = 0x8D62;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_COMPONENT16 = 0x81A5;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_INDEX = 0x1901;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_INDEX8 = 0x8D48;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_STENCIL = 0x84F9;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_WIDTH = 0x8D42;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_HEIGHT = 0x8D43;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_INTERNAL_FORMAT = 0x8D44;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_RED_SIZE = 0x8D50;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_GREEN_SIZE = 0x8D51;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_BLUE_SIZE = 0x8D52;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_ALPHA_SIZE = 0x8D53;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_DEPTH_SIZE = 0x8D54;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_STENCIL_SIZE = 0x8D55;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x8CD0;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x8CD1;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = 0x8CD2;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 0x8CD3;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.COLOR_ATTACHMENT0 = 0x8CE0;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_ATTACHMENT = 0x8D00;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_ATTACHMENT = 0x8D20;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_STENCIL_ATTACHMENT = 0x821A;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.NONE = 0;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_COMPLETE = 0x8CD5;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_UNSUPPORTED = 0x8CDD;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_BINDING = 0x8CA6;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_BINDING = 0x8CA7;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_RENDERBUFFER_SIZE = 0x84E8;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INVALID_FRAMEBUFFER_OPERATION = 0x0506;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.UNPACK_FLIP_Y_WEBGL = 0x9240;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CONTEXT_LOST_WEBGL = 0x9242;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BROWSER_DEFAULT_WEBGL = 0x9244;
+
+
+/**
+ * From the OES_texture_half_float extension.
+ * http://www.khronos.org/registry/webgl/extensions/OES_texture_half_float/
+ * @const
+ * @type {number}
+ */
+goog.webgl.HALF_FLOAT_OES = 0x8D61;
+
+
+/**
+ * From the OES_standard_derivatives extension.
+ * http://www.khronos.org/registry/webgl/extensions/OES_standard_derivatives/
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAGMENT_SHADER_DERIVATIVE_HINT_OES = 0x8B8B;
+
+
+/**
+ * From the OES_vertex_array_object extension.
+ * http://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERTEX_ARRAY_BINDING_OES = 0x85B5;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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}
+ */
+goog.webgl.TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE;
+
+
+/**
+ * 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;
+
+goog.provide('ol.webgl.Fragment');
+goog.provide('ol.webgl.Shader');
+goog.provide('ol.webgl.Vertex');
+goog.provide('ol.webgl.shader');
+
+goog.require('goog.functions');
+goog.require('goog.webgl');
+goog.require('ol.webgl');
+
+
+
+/**
+ * @constructor
+ * @param {string} source Source.
+ * @struct
+ */
+ol.webgl.Shader = function(source) {
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.source_ = source;
+
+};
+
+
+/**
+ * @return {number} Type.
+ */
+ol.webgl.Shader.prototype.getType = goog.abstractMethod;
+
+
+/**
+ * @return {string} Source.
+ */
+ol.webgl.Shader.prototype.getSource = function() {
+ return this.source_;
+};
+
+
+/**
+ * @return {boolean} Is animated?
+ */
+ol.webgl.Shader.prototype.isAnimated = goog.functions.FALSE;
+
+
+
+/**
+ * @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);
+
+
+/**
+ * @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.webgl.shader.Vertex, ol.webgl.Shader);
+
+
+/**
+ * @inheritDoc
+ */
+ol.webgl.shader.Vertex.prototype.getType = function() {
+ return goog.webgl.VERTEX_SHADER;
+};
+
+// This file is automatically generated, do not edit
+goog.provide('ol.render.webgl.imagereplay.shader.Default');
+goog.provide('ol.render.webgl.imagereplay.shader.Default.Locations');
+goog.provide('ol.render.webgl.imagereplay.shader.DefaultFragment');
+goog.provide('ol.render.webgl.imagereplay.shader.DefaultVertex');
+
+goog.require('ol.webgl.shader');
+
+
+
+/**
+ * @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);
+
+
+/**
+ * @const
+ * @type {string}
+ */
+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';
+
+
+/**
+ * @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;}';
+
+
+/**
+ * @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;
+
+
+
+/**
+ * @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);
+
+
+/**
+ * @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}
+ */
+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;}';
+
+
+/**
+ * @const
+ * @type {string}
+ */
+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;
+
+
+
+/**
+ * @constructor
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
+ */
+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');
+
+ /**
+ * @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.webgl');
+goog.require('ol');
+
+
+/**
+ * @enum {number}
+ */
+ol.webgl.BufferUsage = {
+ STATIC_DRAW: goog.webgl.STATIC_DRAW,
+ STREAM_DRAW: goog.webgl.STREAM_DRAW,
+ DYNAMIC_DRAW: goog.webgl.DYNAMIC_DRAW
+};
+
+
+
+/**
+ * @constructor
+ * @param {Array.<number>=} opt_arr Array.
+ * @param {number=} opt_usage Usage.
+ * @struct
+ */
+ol.webgl.Buffer = function(opt_arr, opt_usage) {
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.arr_ = opt_arr !== undefined ? opt_arr : [];
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.usage_ = opt_usage !== undefined ?
+ opt_usage : ol.webgl.BufferUsage.STATIC_DRAW;
+
+};
+
+
+/**
+ * @return {Array.<number>} Array.
+ */
+ol.webgl.Buffer.prototype.getArray = function() {
+ return this.arr_;
+};
+
+
+/**
+ * @return {number} Usage.
+ */
+ol.webgl.Buffer.prototype.getUsage = function() {
+ return this.usage_;
+};
+
+goog.provide('ol.webgl.Context');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.log');
+goog.require('goog.object');
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.webgl.Buffer');
+goog.require('ol.webgl.WebGLContextEventType');
+
+
+/**
+ * @typedef {{buf: ol.webgl.Buffer,
+ * buffer: WebGLBuffer}}
+ */
+ol.webgl.BufferCacheEntry;
+
+
+
+/**
+ * @classdesc
+ * A WebGL context for accessing low-level WebGL capabilities.
+ *
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @param {HTMLCanvasElement} canvas Canvas.
+ * @param {WebGLRenderingContext} gl GL.
+ */
+ol.webgl.Context = function(canvas, gl) {
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement}
+ */
+ this.canvas_ = canvas;
+
+ /**
+ * @private
+ * @type {WebGLRenderingContext}
+ */
+ this.gl_ = gl;
+
+ /**
+ * @private
+ * @type {Object.<number, ol.webgl.BufferCacheEntry>}
+ */
+ this.bufferCache_ = {};
+
+ /**
+ * @private
+ * @type {Object.<number, WebGLShader>}
+ */
+ this.shaderCache_ = {};
+
+ /**
+ * @private
+ * @type {Object.<string, WebGLProgram>}
+ */
+ this.programCache_ = {};
+
+ /**
+ * @private
+ * @type {WebGLProgram}
+ */
+ this.currentProgram_ = null;
+
+ /**
+ * @private
+ * @type {WebGLFramebuffer}
+ */
+ this.hitDetectionFramebuffer_ = null;
+
+ /**
+ * @private
+ * @type {WebGLTexture}
+ */
+ this.hitDetectionTexture_ = null;
+
+ /**
+ * @private
+ * @type {WebGLRenderbuffer}
+ */
+ this.hitDetectionRenderbuffer_ = null;
+
+ /**
+ * @type {boolean}
+ */
+ this.hasOESElementIndexUint = ol.array.includes(
+ 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(ext,
+ 'Failed to get extension "OES_element_index_uint"');
+ }
+
+ 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);
+
+};
+
+
+/**
+ * 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
+ };
+ }
+};
+
+
+/**
+ * @param {ol.webgl.Buffer} buf Buffer.
+ */
+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];
+};
+
+
+/**
+ * @inheritDoc
+ */
+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_);
+ }
+};
+
+
+/**
+ * @return {HTMLCanvasElement} Canvas.
+ */
+ol.webgl.Context.prototype.getCanvas = function() {
+ return this.canvas_;
+};
+
+
+/**
+ * Get the WebGL rendering context
+ * @return {WebGLRenderingContext} The rendering context.
+ * @api
+ */
+ol.webgl.Context.prototype.getGL = function() {
+ return this.gl_;
+};
+
+
+/**
+ * Get the frame buffer for hit detection.
+ * @return {WebGLFramebuffer} The hit detection frame buffer.
+ */
+ol.webgl.Context.prototype.getHitDetectionFramebuffer = function() {
+ if (!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 {
+ 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;
+ }
+};
+
+
+/**
+ * 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.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;
+ }
+};
+
+
+/**
+ * FIXME empy description for jsdoc
+ */
+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;
+};
+
+
+/**
+ * FIXME empy description for jsdoc
+ */
+ol.webgl.Context.prototype.handleWebGLContextRestored = function() {
+};
+
+
+/**
+ * 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 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);
+
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+
+ this.hitDetectionFramebuffer_ = framebuffer;
+ this.hitDetectionTexture_ = texture;
+ this.hitDetectionRenderbuffer_ = renderbuffer;
+};
+
+
+/**
+ * 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 {
+ var gl = this.getGL();
+ gl.useProgram(program);
+ this.currentProgram_ = program;
+ return true;
+ }
+};
+
+
+/**
+ * @private
+ * @type {goog.log.Logger}
+ */
+ol.webgl.Context.prototype.logger_ = goog.log.getLogger('ol.webgl.Context');
+
+
+/**
+ * @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 (opt_wrapS !== undefined) {
+ gl.texParameteri(
+ goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S, opt_wrapS);
+ }
+ if (opt_wrapT !== undefined) {
+ 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.asserts');
+goog.require('goog.functions');
+goog.require('goog.object');
+goog.require('goog.vec.Mat4');
+goog.require('ol.extent');
+goog.require('ol.render.IReplayGroup');
+goog.require('ol.render.VectorContext');
+goog.require('ol.render.webgl.imagereplay.shader.Default');
+goog.require('ol.render.webgl.imagereplay.shader.Default.Locations');
+goog.require('ol.render.webgl.imagereplay.shader.DefaultFragment');
+goog.require('ol.render.webgl.imagereplay.shader.DefaultVertex');
+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;
+
+ /**
+ * 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;
+
+ /**
+ * @type {Array.<number>}
+ * @private
+ */
+ this.indices_ = [];
+
+ /**
+ * @type {ol.webgl.Buffer}
+ * @private
+ */
+ this.indicesBuffer_ = null;
+
+ /**
+ * @private
+ * @type {ol.render.webgl.imagereplay.shader.Default.Locations}
+ */
+ this.defaultLocations_ = null;
+
+ /**
+ * @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();
+
+ /**
+ * @type {number|undefined}
+ * @private
+ */
+ this.originX_ = undefined;
+
+ /**
+ * @type {number|undefined}
+ * @private
+ */
+ this.originY_ = undefined;
+
+ /**
+ * @type {!goog.vec.Mat4.Number}
+ * @private
+ */
+ this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity();
+
+ /**
+ * @private
+ * @type {boolean|undefined}
+ */
+ this.rotateWithView_ = undefined;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.rotation_ = undefined;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.scale_ = undefined;
+
+ /**
+ * @type {Array.<WebGLTexture>}
+ * @private
+ */
+ this.textures_ = [];
+
+ /**
+ * @type {Array.<WebGLTexture>}
+ * @private
+ */
+ this.hitDetectionTextures_ = [];
+
+ /**
+ * @type {Array.<number>}
+ * @private
+ */
+ this.vertices_ = [];
+
+ /**
+ * @type {ol.webgl.Buffer}
+ * @private
+ */
+ this.verticesBuffer_ = null;
+
+ /**
+ * Start index per feature (the index).
+ * @type {Array.<number>}
+ * @private
+ */
+ this.startIndices_ = [];
+
+ /**
+ * Start index per feature (the feature).
+ * @type {Array.<ol.Feature>}
+ * @private
+ */
+ this.startIndicesFeature_ = [];
+
+ /**
+ * @type {number|undefined}
+ * @private
+ */
+ this.width_ = undefined;
+};
+goog.inherits(ol.render.webgl.ImageReplay, ol.render.VectorContext);
+
+
+/**
+ * @param {ol.webgl.Context} context WebGL context.
+ * @return {function()} Delete resources function.
+ */
+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(this.verticesBuffer_,
+ 'verticesBuffer must not be null');
+ goog.asserts.assert(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);
+ };
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.webgl.ImageReplay.prototype.drawAsync = goog.abstractMethod;
+
+
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} My end.
+ * @private
+ */
+ol.render.webgl.ImageReplay.prototype.drawCoordinates_ =
+ function(flatCoordinates, offset, end, stride) {
+ goog.asserts.assert(this.anchorX_ !== undefined, 'anchorX is defined');
+ goog.asserts.assert(this.anchorY_ !== undefined, 'anchorY is defined');
+ goog.asserts.assert(this.height_ !== undefined, 'height is defined');
+ goog.asserts.assert(this.imageHeight_ !== undefined,
+ 'imageHeight is defined');
+ goog.asserts.assert(this.imageWidth_ !== undefined, 'imageWidth is defined');
+ goog.asserts.assert(this.opacity_ !== undefined, 'opacity is defined');
+ goog.asserts.assert(this.originX_ !== undefined, 'originX is defined');
+ goog.asserts.assert(this.originY_ !== undefined, 'originY is defined');
+ goog.asserts.assert(this.rotateWithView_ !== undefined,
+ 'rotateWithView is defined');
+ goog.asserts.assert(this.rotation_ !== undefined, 'rotation is defined');
+ goog.asserts.assert(this.scale_ !== undefined, 'scale is defined');
+ goog.asserts.assert(this.width_ !== undefined, '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];
+
+ // 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 numVertices;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+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);
+};
+
+
+/**
+ * @param {ol.webgl.Context} context Context.
+ */
+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;
+};
+
+
+/**
+ * @private
+ * @param {Array.<WebGLTexture>} textures Textures.
+ * @param {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} images
+ * Images.
+ * @param {Object.<string, WebGLTexture>} texturePerImage Texture cache.
+ * @param {WebGLRenderingContext} gl Gl.
+ */
+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 (uid in texturePerImage) {
+ 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;
+ }
+};
+
+
+/**
+ * @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 {Object.<string, boolean>} 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.replay = function(context,
+ center, resolution, rotation, size, pixelRatio,
+ opacity, skippedFeaturesHash,
+ featureCallback, oneByOne, opt_hitExtent) {
+ var gl = context.getGL();
+
+ // bind the vertices buffer
+ goog.asserts.assert(this.verticesBuffer_,
+ 'verticesBuffer must not be null');
+ context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_);
+
+ // bind the indices buffer
+ goog.asserts.assert(this.indicesBuffer_,
+ 'indecesBuffer must not be null');
+ context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
+
+ // get the program
+ var fragmentShader =
+ ol.render.webgl.imagereplay.shader.DefaultFragment.getInstance();
+ var vertexShader =
+ ol.render.webgl.imagereplay.shader.DefaultVertex.getInstance();
+ var program = context.getProgram(fragmentShader, vertexShader);
+
+ // get the locations
+ var locations;
+ if (!this.defaultLocations_) {
+ locations =
+ new ol.render.webgl.imagereplay.shader.Default.Locations(gl, program);
+ this.defaultLocations_ = locations;
+ } else {
+ locations = this.defaultLocations_;
+ }
+
+ // 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);
+
+ 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);
+
+ gl.enableVertexAttribArray(locations.a_rotateWithView);
+ gl.vertexAttribPointer(locations.a_rotateWithView, 1, goog.webgl.FLOAT,
+ false, 32, 28);
+
+ // 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);
+
+ var offsetRotateMatrix = this.offsetRotateMatrix_;
+ goog.vec.Mat4.makeIdentity(offsetRotateMatrix);
+ if (rotation !== 0) {
+ goog.vec.Mat4.rotateZ(offsetRotateMatrix, -rotation);
+ }
+
+ 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);
+
+ // draw!
+ var result;
+ if (featureCallback === undefined) {
+ 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);
+ }
+
+ // 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);
+
+ return result;
+};
+
+
+/**
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.webgl.Context} context Context.
+ * @param {Object.<string, boolean>} 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;
+ }
+ }
+};
+
+
+/**
+ * Draw the replay while paying attention to skipped features.
+ *
+ * This functions creates groups of features that can be drawn to together,
+ * so that the number of `drawElements` calls is minimized.
+ *
+ * For example given the following texture groups:
+ *
+ * Group 1: A B C
+ * Group 2: D [E] F G
+ *
+ * If feature E should be skipped, the following `drawElements` calls will be
+ * made:
+ *
+ * drawElements with feature A, B and C
+ * drawElements with feature D
+ * drawElements with feature F and G
+ *
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {Object.<string, boolean>} 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.
+ */
+ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ =
+ function(gl, skippedFeaturesHash, textures, groupIndices,
+ elementType, elementSize) {
+ var featureIndex = 0;
+
+ 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 (skippedFeaturesHash[featureUid] !== undefined) {
+ // 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 (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);
+ }
+ }
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.webgl.Context} context Context.
+ * @param {Object.<string, boolean>} 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);
+ }
+};
+
+
+/**
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.webgl.Context} context Context.
+ * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+ * to skip.
+ * @param {function(ol.Feature): T|undefined} featureCallback Feature callback.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+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 {
+ return undefined;
+ }
+};
+
+
+/**
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.webgl.Context} context Context.
+ * @param {Object.<string, boolean>} 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
+ */
+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 (skippedFeaturesHash[featureUid] === undefined &&
+ feature.getGeometry() &&
+ (opt_hitExtent === undefined || ol.extent.intersects(
+ /** @type {Array<number>} */ (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--;
+ }
+ }
+ 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(anchor, 'imageStyle anchor is not null');
+ goog.asserts.assert(image, 'imageStyle image is not null');
+ goog.asserts.assert(imageSize,
+ 'imageStyle imageSize is not null');
+ goog.asserts.assert(hitDetectionImage,
+ 'imageStyle hitDetectionImage is not null');
+ goog.asserts.assert(hitDetectionImageSize,
+ 'imageStyle hitDetectionImageSize is not null');
+ goog.asserts.assert(opacity !== undefined, 'imageStyle opacity is defined');
+ goog.asserts.assert(origin, 'imageStyle origin is not null');
+ goog.asserts.assert(rotateWithView !== undefined,
+ 'imageStyle rotateWithView is defined');
+ goog.asserts.assert(rotation !== undefined, 'imageStyle rotation is defined');
+ goog.asserts.assert(size, 'imageStyle size is not null');
+ goog.asserts.assert(scale !== undefined, 'imageStyle scale is defined');
+
+ var currentImage;
+ if (this.images_.length === 0) {
+ this.images_.push(image);
+ } else {
+ 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];
+};
+
+
+
+/**
+ * @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;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.tolerance_ = tolerance;
+
+ /**
+ * @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_ = {};
+
+};
+
+
+/**
+ * @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));
+ }
+ 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);
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.webgl.ReplayGroup.prototype.getReplay =
+ function(zIndex, replayType) {
+ var replay = this.replays_[replayType];
+ if (replay === undefined) {
+ var constructor = ol.render.webgl.BATCH_CONSTRUCTORS_[replayType];
+ goog.asserts.assert(constructor !== undefined,
+ replayType +
+ ' constructor missing from ol.render.webgl.BATCH_CONSTRUCTORS_');
+ replay = new constructor(this.tolerance_, this.maxExtent_);
+ this.replays_[replayType] = replay;
+ }
+ return replay;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.webgl.ReplayGroup.prototype.isEmpty = function() {
+ return goog.object.isEmpty(this.replays_);
+};
+
+
+/**
+ * @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 {Object.<string, boolean>} skippedFeaturesHash Ids of features
+ * to skip.
+ */
+ol.render.webgl.ReplayGroup.prototype.replay = function(context,
+ center, resolution, rotation, size, pixelRatio,
+ opacity, skippedFeaturesHash) {
+ var i, ii, replay;
+ for (i = 0, ii = ol.render.REPLAY_ORDER.length; i < ii; ++i) {
+ replay = this.replays_[ol.render.REPLAY_ORDER[i]];
+ if (replay !== undefined) {
+ replay.replay(context,
+ center, resolution, rotation, size, pixelRatio,
+ opacity, skippedFeaturesHash,
+ undefined, false);
+ }
+ }
+};
+
+
+/**
+ * @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 {Object.<string, boolean>} 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.ReplayGroup.prototype.replayHitDetection_ = function(context,
+ center, resolution, rotation, size, pixelRatio, opacity,
+ 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 (replay !== undefined) {
+ result = replay.replay(context,
+ center, resolution, rotation, size, pixelRatio, opacity,
+ skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent);
+ if (result) {
+ return result;
+ }
+ }
+ }
+ return undefined;
+};
+
+
+/**
+ * @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 {Object.<string, boolean>} skippedFeaturesHash Ids of features
+ * to skip.
+ * @param {function(ol.Feature): T|undefined} callback Feature callback.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
+ coordinate, context, center, resolution, rotation, size, pixelRatio,
+ opacity, skippedFeaturesHash,
+ callback) {
+ var gl = context.getGL();
+ gl.bindFramebuffer(
+ gl.FRAMEBUFFER, context.getHitDetectionFramebuffer());
+
+
+ /**
+ * @type {ol.Extent}
+ */
+ var hitExtent;
+ if (this.renderBuffer_ !== undefined) {
+ // 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, 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);
+
+ if (imageData[3] > 0) {
+ var result = callback(feature);
+ if (result) {
+ return result;
+ }
+ }
+ }, true, hitExtent);
+};
+
+
+/**
+ * @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 {Object.<string, boolean>} skippedFeaturesHash Ids of features
+ * to skip.
+ * @return {boolean} Is there a feature at the given coordinate?
+ */
+ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function(
+ coordinate, context, center, resolution, rotation, size, pixelRatio,
+ opacity, 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, 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);
+
+ return hasFeature !== undefined;
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Object.<ol.render.ReplayType,
+ * function(new: ol.render.webgl.ImageReplay, number,
+ * ol.Extent)>}
+ */
+ol.render.webgl.BATCH_CONSTRUCTORS_ = {
+ 'Image': ol.render.webgl.ImageReplay
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Array.<number>}
+ */
+ol.render.webgl.HIT_DETECTION_SIZE_ = [1, 1];
+
+goog.provide('ol.render.webgl.Immediate');
+goog.require('ol.array');
+goog.require('ol.extent');
+goog.require('ol.render.VectorContext');
+goog.require('ol.render.webgl.ImageReplay');
+goog.require('ol.render.webgl.ReplayGroup');
+
+
+
+/**
+ * @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
+ */
+ol.render.webgl.Immediate = function(context,
+ center, resolution, rotation, size, extent, pixelRatio) {
+ goog.base(this);
+
+ /**
+ * @private
+ */
+ this.context_ = context;
+
+ /**
+ * @private
+ */
+ this.center_ = center;
+
+ /**
+ * @private
+ */
+ this.extent_ = extent;
+
+ /**
+ * @private
+ */
+ this.pixelRatio_ = pixelRatio;
+
+ /**
+ * @private
+ */
+ this.size_ = size;
+
+ /**
+ * @private
+ */
+ this.rotation_ = rotation;
+
+ /**
+ * @private
+ */
+ this.resolution_ = resolution;
+
+ /**
+ * @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);
+
+
+/**
+ * FIXME: empty description for jsdoc
+ */
+ol.render.webgl.Immediate.prototype.flush = function() {
+ /** @type {Array.<number>} */
+ var zs = Object.keys(this.callbacksByZIndex_).map(Number);
+ zs.sort(ol.array.numberSafeCompareFunction);
+ 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);
+ }
+ }
+};
+
+
+/**
+ * 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.webgl.Immediate)} callback Callback.
+ * @api
+ */
+ol.render.webgl.Immediate.prototype.drawAsync = function(zIndex, callback) {
+ var zIndexKey = zIndex.toString();
+ var callbacks = this.callbacksByZIndex_[zIndexKey];
+ if (callbacks !== undefined) {
+ callbacks.push(callback);
+ } 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 (!geometry ||
+ !ol.extent.intersects(this.extent_, geometry.getExtent())) {
+ return;
+ }
+ var zIndex = style.getZIndex();
+ if (zIndex === undefined) {
+ 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);
+ }
+ });
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+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);
+ }
+ }
+};
+
+
+/**
+ * @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 = /** @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 skippedFeatures = {};
+ var featureCallback;
+ var oneByOne = false;
+ replay.replay(this.context_, this.center_, this.resolution_, this.rotation_,
+ this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback,
+ oneByOne);
+ 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 = /** @type {ol.render.webgl.ImageReplay} */ (
+ replayGroup.getReplay(0, ol.render.ReplayType.IMAGE));
+ replay.setImageStyle(this.imageStyle_);
+ replay.drawMultiPointGeometry(multiPointGeometry, data);
+ replay.finish(context);
+ var opacity = 1;
+ var skippedFeatures = {};
+ var featureCallback;
+ var oneByOne = false;
+ replay.replay(this.context_, this.center_, this.resolution_, this.rotation_,
+ this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback,
+ oneByOne);
+ 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
+ */
+ol.render.webgl.Immediate.prototype.setTextStyle = function(textStyle) {
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Object.<ol.geom.GeometryType,
+ * function(this: ol.render.webgl.Immediate,
+ * (ol.geom.Geometry|ol.render.Feature), Object)>}
+ */
+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.Default');
+goog.provide('ol.renderer.webgl.map.shader.Default.Locations');
+goog.provide('ol.renderer.webgl.map.shader.DefaultFragment');
+goog.provide('ol.renderer.webgl.map.shader.DefaultVertex');
+
+goog.require('ol.webgl.shader');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.webgl.shader.Fragment}
+ * @struct
+ */
+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);
+
+
+/**
+ * @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';
+
+
+/**
+ * @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;}';
+
+
+/**
+ * @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;
+
+
+
+/**
+ * @constructor
+ * @extends {ol.webgl.shader.Vertex}
+ * @struct
+ */
+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);
+
+
+/**
+ * @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}
+ */
+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;}';
+
+
+/**
+ * @const
+ * @type {string}
+ */
+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;
+
+
+
+/**
+ * @constructor
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
+ */
+ol.renderer.webgl.map.shader.Default.Locations = function(gl, program) {
+
+ /**
+ * @type {WebGLUniformLocation}
+ */
+ this.u_opacity = gl.getUniformLocation(
+ program, goog.DEBUG ? 'u_opacity' : 'f');
+
+ /**
+ * @type {WebGLUniformLocation}
+ */
+ this.u_projectionMatrix = gl.getUniformLocation(
+ program, goog.DEBUG ? 'u_projectionMatrix' : 'e');
+
+ /**
+ * @type {WebGLUniformLocation}
+ */
+ this.u_texCoordMatrix = gl.getUniformLocation(
+ program, goog.DEBUG ? 'u_texCoordMatrix' : 'd');
+
+ /**
+ * @type {WebGLUniformLocation}
+ */
+ this.u_texture = gl.getUniformLocation(
+ program, goog.DEBUG ? 'u_texture' : 'g');
+
+ /**
+ * @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');
+};
+
+goog.provide('ol.renderer.webgl.Layer');
+
+goog.require('goog.vec.Mat4');
+goog.require('goog.webgl');
+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.Default');
+goog.require('ol.renderer.webgl.map.shader.Default.Locations');
+goog.require('ol.renderer.webgl.map.shader.DefaultFragment');
+goog.require('ol.renderer.webgl.map.shader.DefaultVertex');
+goog.require('ol.webgl.Buffer');
+goog.require('ol.webgl.Context');
+
+
+
+/**
+ * @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.base(this, layer);
+
+ /**
+ * @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;
+
+ /**
+ * @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();
+
+ /**
+ * @protected
+ * @type {!goog.vec.Mat4.Number}
+ */
+ this.projectionMatrix = goog.vec.Mat4.createNumberIdentity();
+
+ /**
+ * @private
+ * @type {ol.renderer.webgl.map.shader.Default.Locations}
+ */
+ this.defaultLocations_ = null;
+
+};
+goog.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer);
+
+
+/**
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {number} framebufferDimension Framebuffer dimension.
+ * @protected
+ */
+ol.renderer.webgl.Layer.prototype.bindFramebuffer =
+ function(frameState, framebufferDimension) {
+
+ var gl = this.mapRenderer.getGL();
+
+ if (this.framebufferDimension === undefined ||
+ 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));
+
+ 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);
+
+ this.texture = texture;
+ this.framebuffer = framebuffer;
+ this.framebufferDimension = framebufferDimension;
+
+ } else {
+ gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, this.framebuffer);
+ }
+
+};
+
+
+/**
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.layer.LayerState} layerState Layer state.
+ * @param {ol.webgl.Context} context Context.
+ */
+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 fragmentShader =
+ ol.renderer.webgl.map.shader.DefaultFragment.getInstance();
+ var vertexShader = ol.renderer.webgl.map.shader.DefaultVertex.getInstance();
+
+ var program = context.getProgram(fragmentShader, vertexShader);
+
+ var locations;
+ if (!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());
+ 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);
+
+};
+
+
+/**
+ * @param {ol.render.EventType} type Event type.
+ * @param {ol.webgl.Context} context WebGL context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
+ */
+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);
+ }
+};
+
+
+/**
+ * @return {!goog.vec.Mat4.Number} Matrix.
+ */
+ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() {
+ return this.texCoordMatrix;
+};
+
+
+/**
+ * @return {WebGLTexture} Texture.
+ */
+ol.renderer.webgl.Layer.prototype.getTexture = function() {
+ return this.texture;
+};
+
+
+/**
+ * @return {!goog.vec.Mat4.Number} Matrix.
+ */
+ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() {
+ return this.projectionMatrix;
+};
+
+
+/**
+ * Handle webglcontextlost.
+ */
+ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() {
+ this.texture = null;
+ this.framebuffer = null;
+ this.framebufferDimension = undefined;
+};
+
+
+/**
+ * @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.
+ */
+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');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.webgl.Layer}
+ * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Image} imageLayer Tile layer.
+ */
+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);
+
+
+/**
+ * @param {ol.ImageBase} image Image.
+ * @private
+ * @return {WebGLTexture} Texture.
+ */
+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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+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|ol.render.Feature} feature Feature.
+ * @return {?} Callback result.
+ */
+ function(feature) {
+ return callback.call(thisArg, feature, layer);
+ });
+};
+
+
+/**
+ * @inheritDoc
+ */
+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 (layerState.extent !== undefined) {
+ renderedExtent = ol.extent.getIntersection(
+ renderedExtent, layerState.extent);
+ }
+ if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] &&
+ !ol.extent.isEmpty(renderedExtent)) {
+ var projection = viewState.projection;
+ if (!ol.ENABLE_RASTER_REPROJECTION) {
+ var sourceProjection = imageSource.getProjection();
+ if (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 (image_) {
+ var loaded = this.loadImage(image_);
+ if (loaded) {
+ image = image_;
+ texture = this.createTexture_(image_);
+ if (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 (image) {
+ goog.asserts.assert(texture, 'texture is truthy');
+
+ var canvas = this.mapRenderer.getContext().getCanvas();
+
+ this.updateProjectionMatrix_(canvas.width, canvas.height,
+ pixelRatio, viewCenter, viewResolution, viewRotation,
+ image.getExtent());
+ this.hitTransformationMatrix_ = null;
+
+ // 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;
+
+ this.updateAttributions(frameState.attributions, image.getAttributions());
+ this.updateLogos(frameState, imageSource);
+ }
+
+ return true;
+};
+
+
+/**
+ * @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
+ */
+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);
+
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate =
+ function(coordinate, frameState) {
+ var hasFeature = this.forEachFeatureAtCoordinate(
+ coordinate, frameState, goog.functions.TRUE, this);
+ return hasFeature !== undefined;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel =
+ function(pixel, frameState, callback, thisArg) {
+ if (!this.image_ || !this.image_.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);
+
+ if (hasFeature) {
+ return callback.call(thisArg, this.getLayer());
+ } else {
+ return undefined;
+ }
+ } else {
+ var imageSize =
+ [this.image_.getImage().width, this.image_.getImage().height];
+
+ if (!this.hitTransformationMatrix_) {
+ this.hitTransformationMatrix_ = this.getHitTransformationMatrix_(
+ frameState.size, imageSize);
+ }
+
+ 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;
+ }
+
+ if (!this.hitCanvasContext_) {
+ this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1);
+ }
+
+ this.hitCanvasContext_.clearRect(0, 0, 1, 1);
+ this.hitCanvasContext_.drawImage(this.image_.getImage(),
+ pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, 0, 0, 1, 1);
+
+ var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data;
+ if (imageData[3] > 0) {
+ return callback.call(thisArg, this.getLayer());
+ } else {
+ return undefined;
+ }
+ }
+};
+
+
+/**
+ * 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
+ */
+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);
+
+ // 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);
+
+ // 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);
+
+ var transformMatrix = goog.vec.Mat4.createNumber();
+ goog.vec.Mat4.multMat(
+ imageCoordMatrix, projectionMatrixInv, transformMatrix);
+ goog.vec.Mat4.multMat(
+ transformMatrix, mapCoordMatrix, transformMatrix);
+
+ return transformMatrix;
+};
+
+// This file is automatically generated, do not edit
+goog.provide('ol.renderer.webgl.tilelayer.shader');
+goog.provide('ol.renderer.webgl.tilelayer.shader.Locations');
+goog.provide('ol.renderer.webgl.tilelayer.shader.Fragment');
+goog.provide('ol.renderer.webgl.tilelayer.shader.Vertex');
+
+goog.require('ol.webgl.shader');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.webgl.shader.Fragment}
+ * @struct
+ */
+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);
+
+
+/**
+ * @const
+ * @type {string}
+ */
+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';
+
+
+/**
+ * @const
+ * @type {string}
+ */
+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);}';
+
+
+/**
+ * @const
+ * @type {string}
+ */
+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;
+
+
+
+/**
+ * @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);
+
+
+/**
+ * @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';
+
+
+/**
+ * @const
+ * @type {string}
+ */
+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;}';
+
+
+/**
+ * @const
+ * @type {string}
+ */
+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;
+
+
+
+/**
+ * @constructor
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
+ */
+ol.renderer.webgl.tilelayer.shader.Locations = function(gl, program) {
+
+ /**
+ * @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');
+
+ /**
+ * @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');
+};
+
+// FIXME large resolutions lead to too large framebuffers :-(
+// FIXME animated shaders! check in redraw
+
+goog.provide('ol.renderer.webgl.TileLayer');
+
+goog.require('goog.asserts');
+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.array');
+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.Fragment');
+goog.require('ol.renderer.webgl.tilelayer.shader.Locations');
+goog.require('ol.renderer.webgl.tilelayer.shader.Vertex');
+goog.require('ol.size');
+goog.require('ol.tilecoord');
+goog.require('ol.vec.Mat4');
+goog.require('ol.webgl.Buffer');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.webgl.Layer}
+ * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Tile} tileLayer Tile layer.
+ */
+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();
+
+ /**
+ * @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;
+
+ /**
+ * @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;
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.renderedFramebufferExtent_ = null;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedRevision_ = -1;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.tmpSize_ = [0, 0];
+
+};
+goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
+ var context = this.mapRenderer.getContext();
+ context.deleteBuffer(this.renderArrayBuffer_);
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * Create a function that adds loaded tiles to the tile lookup.
+ * @param {ol.source.Tile} source Tile source.
+ * @param {ol.proj.Projection} projection Projection of the tiles.
+ * @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.renderer.webgl.TileLayer.prototype.createLoadedTileFinder =
+ function(source, projection, tiles) {
+ var mapRenderer = this.mapRenderer;
+
+ 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(projection, zoom,
+ tileRange, function(tile) {
+ var loaded = mapRenderer.isTileTextureLoaded(tile);
+ if (loaded) {
+ if (!tiles[zoom]) {
+ tiles[zoom] = {};
+ }
+ tiles[zoom][tile.tileCoord.toString()] = tile;
+ }
+ return loaded;
+ });
+ });
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
+ goog.base(this, 'handleWebGLContextLost');
+ this.locations_ = null;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.TileLayer.prototype.prepareFrame =
+ function(frameState, layerState, context) {
+
+ var mapRenderer = this.mapRenderer;
+ var gl = context.getGL();
+
+ 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();
+
+ 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 (this.renderedTileRange_ &&
+ this.renderedTileRange_.equals(tileRange) &&
+ this.renderedRevision_ == tileSource.getRevision()) {
+ framebufferExtent = this.renderedFramebufferExtent_;
+ } else {
+
+ var tileRangeSize = tileRange.getSize();
+
+ 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
+ ];
+
+ 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);
+
+ var program = context.getProgram(this.fragmentShader_, this.vertexShader_);
+ context.useProgram(program);
+ if (!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);
+
+ /**
+ * @type {Object.<number, Object.<string, ol.Tile>>}
+ */
+ var tilesToDrawByZ = {};
+ tilesToDrawByZ[z] = {};
+
+ var findLoadedTiles = this.createLoadedTileFinder(
+ tileSource, projection, tilesToDrawByZ);
+
+ var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
+ var allTilesLoaded = true;
+ var tmpExtent = ol.extent.createEmpty();
+ var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
+ var childTileRange, drawable, fullyLoaded, tile, tileState;
+ var 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 (layerState.extent !== undefined) {
+ // ignore tiles outside layer extent
+ tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
+ if (!ol.extent.intersects(tileExtent, layerState.extent)) {
+ continue;
+ }
+ }
+ tileState = tile.getState();
+ drawable = tileState == ol.TileState.LOADED ||
+ tileState == ol.TileState.EMPTY ||
+ tileState == ol.TileState.ERROR && !useInterimTilesOnError;
+ if (!drawable && tile.interimTile) {
+ tile = tile.interimTile;
+ }
+ goog.asserts.assert(tile);
+ 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 (childTileRange) {
+ findLoadedTiles(z + 1, childTileRange);
+ }
+ }
+
+ }
+
+ }
+
+ /** @type {Array.<number>} */
+ var zs = Object.keys(tilesToDrawByZ).map(Number);
+ zs.sort(ol.array.numberSafeCompareFunction);
+ 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;
+ }
+
+ }
+
+ 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);
+
+ 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;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel =
+ function(pixel, frameState, callback, thisArg) {
+ if (!this.framebuffer) {
+ return undefined;
+ }
+
+ var pixelOnMapScaled = [
+ pixel[0] / frameState.size[0],
+ (frameState.size[1] - pixel[1]) / frameState.size[1]];
+
+ 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);
+
+ if (imageData[3] > 0) {
+ return callback.call(thisArg, this.getLayer());
+ } else {
+ return undefined;
+ }
+};
+
+goog.provide('ol.renderer.webgl.VectorLayer');
+
+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');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.webgl.Layer}
+ * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Vector} vectorLayer Vector layer.
+ */
+ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) {
+
+ goog.base(this, mapRenderer, vectorLayer);
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.dirty_ = false;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedRevision_ = -1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedResolution_ = NaN;
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.renderedExtent_ = ol.extent.createEmpty();
+
+ /**
+ * @private
+ * @type {function(ol.Feature, ol.Feature): number|null}
+ */
+ this.renderedRenderOrder_ = null;
+
+ /**
+ * @private
+ * @type {ol.render.webgl.ReplayGroup}
+ */
+ this.replayGroup_ = null;
+
+ /**
+ * The last layer state.
+ * @private
+ * @type {?ol.layer.LayerState}
+ */
+ this.layerState_ = null;
+
+};
+goog.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer);
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.VectorLayer.prototype.composeFrame =
+ function(frameState, layerState, context) {
+ this.layerState_ = layerState;
+ var viewState = frameState.viewState;
+ var replayGroup = this.replayGroup_;
+ if (replayGroup && !replayGroup.isEmpty()) {
+ replayGroup.replay(context,
+ viewState.center, viewState.resolution, viewState.rotation,
+ frameState.size, frameState.pixelRatio, layerState.opacity,
+ layerState.managed ? frameState.skippedFeatureUids : {});
+ }
+
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() {
+ var replayGroup = this.replayGroup_;
+ if (replayGroup) {
+ var context = this.mapRenderer.getContext();
+ replayGroup.getDeleteResourcesFunction(context)();
+ this.replayGroup_ = null;
+ }
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate =
+ function(coordinate, frameState, callback, thisArg) {
+ if (!this.replayGroup_ || !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.managed ? frameState.skippedFeatureUids : {},
+ /**
+ * @param {ol.Feature} feature Feature.
+ * @return {?} Callback result.
+ */
+ function(feature) {
+ goog.asserts.assert(feature !== undefined, 'received a feature');
+ var key = goog.getUid(feature).toString();
+ if (!(key in features)) {
+ features[key] = true;
+ return callback.call(thisArg, feature, layer);
+ }
+ });
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtCoordinate =
+ function(coordinate, frameState) {
+ if (!this.replayGroup_ || !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,
+ frameState.skippedFeatureUids);
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+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;
+ }
+};
+
+
+/**
+ * Handle changes in image style state.
+ * @param {goog.events.Event} event Image style change event.
+ * @private
+ */
+ol.renderer.webgl.VectorLayer.prototype.handleStyleImageChange_ =
+ function(event) {
+ this.renderIfReadyAndVisible();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.VectorLayer.prototype.prepareFrame =
+ function(frameState, layerState, context) {
+
+ 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();
+
+ if (vectorLayerRenderOrder === undefined) {
+ vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
+ }
+
+ 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;
+ }
+
+ if (this.replayGroup_) {
+ frameState.postRenderFunctions.push(
+ this.replayGroup_.getDeleteResourcesFunction(context));
+ }
+
+ 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;
+ var styleFunction = feature.getStyleFunction();
+ if (styleFunction) {
+ styles = styleFunction.call(feature, resolution);
+ } else {
+ styleFunction = vectorLayer.getStyleFunction();
+ if (styleFunction) {
+ styles = styleFunction(feature, resolution);
+ }
+ }
+ if (styles) {
+ var dirty = this.renderFeature(
+ feature, resolution, pixelRatio, styles, replayGroup);
+ this.dirty_ = this.dirty_ || dirty;
+ }
+ };
+ if (vectorLayerRenderOrder) {
+ /** @type {Array.<ol.Feature>} */
+ var features = [];
+ vectorSource.forEachFeatureInExtent(extent,
+ /**
+ * @param {ol.Feature} feature Feature.
+ */
+ function(feature) {
+ features.push(feature);
+ }, this);
+ features.sort(vectorLayerRenderOrder);
+ features.forEach(renderFeature, this);
+ } else {
+ vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
+ }
+ replayGroup.finish(context);
+
+ this.renderedResolution_ = resolution;
+ this.renderedRevision_ = vectorLayerRevision;
+ this.renderedRenderOrder_ = vectorLayerRenderOrder;
+ this.renderedExtent_ = extent;
+ this.replayGroup_ = replayGroup;
+
+ return true;
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
+ * styles.
+ * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group.
+ * @return {boolean} `true` if an image is loading.
+ */
+ol.renderer.webgl.VectorLayer.prototype.renderFeature =
+ function(feature, resolution, pixelRatio, styles, replayGroup) {
+ if (!styles) {
+ return false;
+ }
+ var loading = false;
+ if (goog.isArray(styles)) {
+ for (var 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;
+ }
+ } else {
+ loading = ol.renderer.vector.renderFeature(
+ replayGroup, feature, styles,
+ 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.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');
+
+
+/**
+ * @typedef {{magFilter: number, minFilter: number, texture: WebGLTexture}}
+ */
+ol.renderer.webgl.TextureCacheEntry;
+
+
+
+/**
+ * @constructor
+ * @extends {ol.renderer.Map}
+ * @param {Element} container Container.
+ * @param {ol.Map} map Map.
+ */
+ol.renderer.webgl.Map = function(container, map) {
+
+ goog.base(this, container, map);
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement}
+ */
+ this.canvas_ = /** @type {HTMLCanvasElement} */
+ (document.createElement('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.clipTileCanvasWidth_ = 0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.clipTileCanvasHeight_ = 0;
+
+ /**
+ * @private
+ * @type {CanvasRenderingContext2D}
+ */
+ this.clipTileContext_ = ol.dom.createCanvasContext2D();
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.renderedVisible_ = true;
+
+ /**
+ * @private
+ * @type {WebGLRenderingContext}
+ */
+ this.gl_ = ol.webgl.getContext(this.canvas_, {
+ antialias: true,
+ depth: false,
+ failIfMajorPerformanceCaveat: true,
+ preserveDrawingBuffer: false,
+ stencil: true
+ });
+ goog.asserts.assert(this.gl_, 'got a WebGLRenderingContext');
+
+ /**
+ * @private
+ * @type {ol.webgl.Context}
+ */
+ this.context_ = new ol.webgl.Context(this.canvas_, this.gl_);
+
+ 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();
+
+ /**
+ * @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();
+ });
+
+ /**
+ * @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 {ol.Size} */ (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;
+
+ this.initializeGL_();
+
+};
+goog.inherits(ol.renderer.webgl.Map, ol.renderer.Map);
+
+
+/**
+ * @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.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(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.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;
+ }
+};
+
+
+/**
+ * @param {ol.render.EventType} type Event type.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
+ */
+ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ =
+ function(type, frameState) {
+ var map = this.getMap();
+ if (map.hasListener(type)) {
+ var context = this.context_;
+
+ 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 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();
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+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 (textureCacheEntry) {
+ gl.deleteTexture(textureCacheEntry.texture);
+ }
+ });
+ }
+ goog.dispose(this.context_);
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @param {ol.Map} map Map.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
+ */
+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 (!textureCacheEntry) {
+ if (+this.textureCache_.peekLastKey() == frameState.index) {
+ break;
+ } else {
+ --this.textureCacheFrameMarkerCount_;
+ }
+ } else {
+ gl.deleteTexture(textureCacheEntry.texture);
+ }
+ this.textureCache_.pop();
+ }
+};
+
+
+/**
+ * @return {ol.webgl.Context}
+ */
+ol.renderer.webgl.Map.prototype.getContext = function() {
+ return this.context_;
+};
+
+
+/**
+ * @return {WebGLRenderingContext} GL.
+ */
+ol.renderer.webgl.Map.prototype.getGL = function() {
+ return this.gl_;
+};
+
+
+/**
+ * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue.
+ */
+ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() {
+ return this.tileTextureQueue_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.Map.prototype.getType = function() {
+ return ol.RendererType.WEBGL;
+};
+
+
+/**
+ * @param {goog.events.Event} event Event.
+ * @protected
+ */
+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');
+ layerRenderer.handleWebGLContextLost();
+ });
+};
+
+
+/**
+ * @protected
+ */
+ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() {
+ this.initializeGL_();
+ this.getMap().render();
+};
+
+
+/**
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @param {ol.Tile} tile Tile.
+ * @return {boolean} Is tile texture loaded.
+ */
+ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) {
+ return this.textureCache_.containsKey(tile.getKey());
+};
+
+
+/**
+ * @private
+ * @type {goog.log.Logger}
+ */
+ol.renderer.webgl.Map.prototype.logger_ =
+ goog.log.getLogger('ol.renderer.webgl.Map');
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
+
+ var context = this.getContext();
+ var gl = this.getGL();
+
+ if (gl.isContextLost()) {
+ return false;
+ }
+
+ if (!frameState) {
+ if (this.renderedVisible_) {
+ goog.style.setElementShown(this.canvas_, false);
+ this.renderedVisible_ = false;
+ }
+ return false;
+ }
+
+ this.focus_ = frameState.focus;
+
+ this.textureCache_.set((-frameState.index).toString(), null);
+ ++this.textureCacheFrameMarkerCount_;
+
+ this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
+
+ /** @type {Array.<ol.layer.LayerState>} */
+ var layerStatesToDraw = [];
+ var layerStatesArray = frameState.layerStatesArray;
+ goog.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);
+
+ 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);
+ }
+ }
+ }
+
+ 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);
+
+ 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);
+
+ 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);
+ }
+
+ 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);
+
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate =
+ function(coordinate, frameState, callback, thisArg,
+ layerFilter, thisArg2) {
+ var result;
+
+ if (this.getGL().isContextLost()) {
+ return false;
+ }
+
+ 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;
+ }
+ }
+ }
+ return undefined;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate =
+ function(coordinate, frameState, layerFilter, thisArg) {
+ var hasFeature = false;
+
+ if (this.getGL().isContextLost()) {
+ return false;
+ }
+
+ 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;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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;
+
+ 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;
+};
+
+// 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.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');
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.OL3_URL = 'http://openlayers.org/';
+
+
+/**
+ * @const
+ * @type {string}
+ */
+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';
+
+
+/**
+ * @type {Array.<ol.RendererType>}
+ * @const
+ */
+ol.DEFAULT_RENDERER_TYPES = [
+ ol.RendererType.CANVAS,
+ ol.RendererType.WEBGL,
+ ol.RendererType.DOM
+];
+
+
+/**
+ * @enum {string}
+ */
+ol.MapProperty = {
+ LAYERGROUP: 'layergroup',
+ SIZE: 'size',
+ TARGET: 'target',
+ VIEW: 'view'
+};
+
+
+
+/**
+ * @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.
+ *
+ * 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 {olx.MapOptions} options Map options.
+ * @fires ol.MapBrowserEvent
+ * @fires ol.MapEvent
+ * @fires ol.render.Event#postcompose
+ * @fires ol.render.Event#precompose
+ * @api stable
+ */
+ol.Map = function(options) {
+
+ goog.base(this);
+
+ var optionsInternal = ol.Map.createOptionsInternal(options);
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.loadTilesWhileAnimating_ =
+ options.loadTilesWhileAnimating !== undefined ?
+ options.loadTilesWhileAnimating : false;
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.loadTilesWhileInteracting_ =
+ options.loadTilesWhileInteracting !== undefined ?
+ options.loadTilesWhileInteracting : false;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.pixelRatio_ = options.pixelRatio !== undefined ?
+ options.pixelRatio : ol.has.DEVICE_PIXEL_RATIO;
+
+ /**
+ * @private
+ * @type {Object.<string, string>}
+ */
+ this.logos_ = optionsInternal.logos;
+
+ /**
+ * @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();
+
+ /**
+ * @private
+ * @type {goog.vec.Mat4.Number}
+ */
+ this.pixelToCoordinateMatrix_ = goog.vec.Mat4.createNumber();
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.frameIndex_ = 0;
+
+ /**
+ * @private
+ * @type {?olx.FrameState}
+ */
+ this.frameState_ = null;
+
+ /**
+ * The extent at the previous 'moveend' event.
+ * @private
+ * @type {ol.Extent}
+ */
+ this.previousExtent_ = ol.extent.createEmpty();
+
+ /**
+ * @private
+ * @type {goog.events.Key}
+ */
+ this.viewPropertyListenerKey_ = null;
+
+ /**
+ * @private
+ * @type {Array.<goog.events.Key>}
+ */
+ this.layerGroupPropertyListenerKeys_ = null;
+
+ /**
+ * @private
+ * @type {Element}
+ */
+ this.viewport_ = document.createElement('DIV');
+ this.viewport_.className = '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';
+ this.viewport_.style.touchAction = 'none';
+ if (ol.has.TOUCH) {
+ goog.dom.classlist.add(this.viewport_, 'ol-touch');
+ }
+
+ /**
+ * @private
+ * @type {Element}
+ */
+ this.overlayContainer_ = document.createElement('DIV');
+ this.overlayContainer_.className = 'ol-overlaycontainer';
+ this.viewport_.appendChild(this.overlayContainer_);
+
+ /**
+ * @private
+ * @type {Element}
+ */
+ this.overlayContainerStopEvent_ = document.createElement('DIV');
+ this.overlayContainerStopEvent_.className = '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);
+ this.viewport_.appendChild(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);
+
+ /**
+ * @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_);
+
+ var mouseWheelHandler = new goog.events.MouseWheelHandler(this.viewport_);
+ goog.events.listen(mouseWheelHandler,
+ goog.events.MouseWheelHandler.EventType.MOUSEWHEEL,
+ this.handleBrowserEvent, false, this);
+ this.registerDisposable(mouseWheelHandler);
+
+ /**
+ * @type {ol.Collection.<ol.control.Control>}
+ * @private
+ */
+ this.controls_ = optionsInternal.controls;
+
+ /**
+ * @type {ol.Collection.<ol.interaction.Interaction>}
+ * @private
+ */
+ this.interactions_ = optionsInternal.interactions;
+
+ /**
+ * @type {ol.Collection.<ol.Overlay>}
+ * @private
+ */
+ this.overlays_ = optionsInternal.overlays;
+
+ /**
+ * A lookup of overlays by id.
+ * @private
+ * @type {Object.<string, ol.Overlay>}
+ */
+ this.overlayIdIndex_ = {};
+
+ /**
+ * @type {ol.renderer.Map}
+ * @private
+ */
+ this.renderer_ =
+ new optionsInternal.rendererConstructor(this.viewport_, this);
+ this.registerDisposable(this.renderer_);
+
+ /**
+ * @type {goog.dom.ViewportSizeMonitor}
+ * @private
+ */
+ this.viewportSizeMonitor_ = new goog.dom.ViewportSizeMonitor();
+ this.registerDisposable(this.viewportSizeMonitor_);
+
+ /**
+ * @type {goog.events.Key}
+ * @private
+ */
+ this.viewportResizeListenerKey_ = null;
+
+ /**
+ * @private
+ * @type {ol.Coordinate}
+ */
+ this.focus_ = null;
+
+ /**
+ * @private
+ * @type {Array.<ol.PreRenderFunction>}
+ */
+ this.preRenderFunctions_ = [];
+
+ /**
+ * @private
+ * @type {Array.<ol.PostRenderFunction>}
+ */
+ this.postRenderFunctions_ = [];
+
+ /**
+ * @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_ = {};
+
+ 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(this.addOverlayInternal_, this);
+
+ goog.events.listen(this.overlays_, ol.CollectionEventType.ADD,
+ /**
+ * @param {ol.CollectionEvent} event Collection event.
+ */
+ function(event) {
+ this.addOverlayInternal_(/** @type {ol.Overlay} */ (event.element));
+ }, false, this);
+
+ goog.events.listen(this.overlays_, ol.CollectionEventType.REMOVE,
+ /**
+ * @param {ol.CollectionEvent} event Collection event.
+ */
+ function(event) {
+ var id = event.element.getId();
+ if (id !== undefined) {
+ delete this.overlayIdIndex_[id.toString()];
+ }
+ event.element.setMap(null);
+ }, false, this);
+
+};
+goog.inherits(ol.Map, ol.Object);
+
+
+/**
+ * Add the given control to the map.
+ * @param {ol.control.Control} control Control.
+ * @api stable
+ */
+ol.Map.prototype.addControl = function(control) {
+ var controls = this.getControls();
+ goog.asserts.assert(controls !== undefined, 'controls should be defined');
+ controls.push(control);
+};
+
+
+/**
+ * Add the given interaction to the map.
+ * @param {ol.interaction.Interaction} interaction Interaction to add.
+ * @api stable
+ */
+ol.Map.prototype.addInteraction = function(interaction) {
+ var interactions = this.getInteractions();
+ goog.asserts.assert(interactions !== undefined,
+ 'interactions should be defined');
+ interactions.push(interaction);
+};
+
+
+/**
+ * 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.Map.prototype.addLayer = function(layer) {
+ var layers = this.getLayerGroup().getLayers();
+ layers.push(layer);
+};
+
+
+/**
+ * Add the given overlay to the map.
+ * @param {ol.Overlay} overlay Overlay.
+ * @api stable
+ */
+ol.Map.prototype.addOverlay = function(overlay) {
+ var overlays = this.getOverlays();
+ goog.asserts.assert(overlays !== undefined, 'overlays should be defined');
+ overlays.push(overlay);
+};
+
+
+/**
+ * This deals with map's overlay collection changes.
+ * @param {ol.Overlay} overlay Overlay.
+ * @private
+ */
+ol.Map.prototype.addOverlayInternal_ = function(overlay) {
+ var id = overlay.getId();
+ if (id !== undefined) {
+ this.overlayIdIndex_[id.toString()] = overlay;
+ }
+ overlay.setMap(this);
+};
+
+
+/**
+ * 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.Map.prototype.beforeRender = function(var_args) {
+ this.render();
+ Array.prototype.push.apply(this.preRenderFunctions_, arguments);
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ *
+ * @inheritDoc
+ */
+ol.Map.prototype.disposeInternal = function() {
+ goog.dom.removeNode(this.viewport_);
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * 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.render.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} or
+ * {@link ol.render.Feature render feature} at the pixel, the second is
+ * the {@link ol.layer.Layer layer} of the feature and will be null for
+ * unmanaged layers. 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.Map.prototype.forEachFeatureAtPixel =
+ function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
+ if (!this.frameState_) {
+ return;
+ }
+ var coordinate = this.getCoordinateFromPixel(pixel);
+ var thisArg = opt_this !== undefined ? opt_this : null;
+ var layerFilter = opt_layerFilter !== undefined ?
+ opt_layerFilter : goog.functions.TRUE;
+ var thisArg2 = opt_this2 !== undefined ? opt_this2 : null;
+ return this.renderer_.forEachFeatureAtCoordinate(
+ coordinate, this.frameState_, callback, thisArg,
+ layerFilter, thisArg2);
+};
+
+
+/**
+ * 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
+ */
+ol.Map.prototype.forEachLayerAtPixel =
+ function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
+ if (!this.frameState_) {
+ return;
+ }
+ var thisArg = opt_this !== undefined ? opt_this : null;
+ var layerFilter = opt_layerFilter !== undefined ?
+ opt_layerFilter : goog.functions.TRUE;
+ var thisArg2 = opt_this2 !== undefined ? opt_this2 : null;
+ return this.renderer_.forEachLayerAtPixel(
+ pixel, this.frameState_, callback, thisArg,
+ layerFilter, thisArg2);
+};
+
+
+/**
+ * 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.Map.prototype.hasFeatureAtPixel =
+ function(pixel, opt_layerFilter, opt_this) {
+ if (!this.frameState_) {
+ return false;
+ }
+ var coordinate = this.getCoordinateFromPixel(pixel);
+ var layerFilter = opt_layerFilter !== undefined ?
+ opt_layerFilter : goog.functions.TRUE;
+ var thisArg = opt_this !== undefined ? opt_this : null;
+ return this.renderer_.hasFeatureAtCoordinate(
+ coordinate, this.frameState_, layerFilter, thisArg);
+};
+
+
+/**
+ * Returns the geographical coordinate for a browser event.
+ * @param {Event} event Event.
+ * @return {ol.Coordinate} Coordinate.
+ * @api stable
+ */
+ol.Map.prototype.getEventCoordinate = function(event) {
+ return this.getCoordinateFromPixel(this.getEventPixel(event));
+};
+
+
+/**
+ * Returns the map pixel position for a browser event relative to the viewport.
+ * @param {Event} event Event.
+ * @return {ol.Pixel} Pixel.
+ * @api stable
+ */
+ol.Map.prototype.getEventPixel = function(event) {
+ var eventPosition = goog.style.getRelativePosition(event, this.viewport_);
+ return [eventPosition.x, eventPosition.y];
+};
+
+
+/**
+ * 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.Map.prototype.getTarget = function() {
+ return /** @type {Element|string|undefined} */ (
+ this.get(ol.MapProperty.TARGET));
+};
+
+
+/**
+ * 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.Map.prototype.getTargetElement = function() {
+ var target = this.getTarget();
+ return target !== undefined ? goog.dom.getElement(target) : null;
+};
+
+
+/**
+ * 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.Map.prototype.getCoordinateFromPixel = function(pixel) {
+ var frameState = this.frameState_;
+ if (!frameState) {
+ return null;
+ } else {
+ var vec2 = pixel.slice();
+ return ol.vec.Mat4.multVec2(frameState.pixelToCoordinateMatrix, vec2, vec2);
+ }
+};
+
+
+/**
+ * Get the map controls. Modifying this collection changes the controls
+ * associated with the map.
+ * @return {ol.Collection.<ol.control.Control>} Controls.
+ * @api stable
+ */
+ol.Map.prototype.getControls = function() {
+ return this.controls_;
+};
+
+
+/**
+ * Get the map overlays. Modifying this collection changes the overlays
+ * associated with the map.
+ * @return {ol.Collection.<ol.Overlay>} Overlays.
+ * @api stable
+ */
+ol.Map.prototype.getOverlays = function() {
+ return this.overlays_;
+};
+
+
+/**
+ * Get an overlay by its identifier (the value returned by overlay.getId()).
+ * Note that the index treats string and numeric identifiers as the same. So
+ * `map.getOverlayById(2)` will return an overlay with id `'2'` or `2`.
+ * @param {string|number} id Overlay identifier.
+ * @return {ol.Overlay} Overlay.
+ * @api
+ */
+ol.Map.prototype.getOverlayById = function(id) {
+ var overlay = this.overlayIdIndex_[id.toString()];
+ return overlay !== undefined ? overlay : null;
+};
+
+
+/**
+ * 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.Map.prototype.getInteractions = function() {
+ return this.interactions_;
+};
+
+
+/**
+ * Get the layergroup associated with this map.
+ * @return {ol.layer.Group} A layer group containing the layers in this map.
+ * @observable
+ * @api stable
+ */
+ol.Map.prototype.getLayerGroup = function() {
+ return /** @type {ol.layer.Group} */ (this.get(ol.MapProperty.LAYERGROUP));
+};
+
+
+/**
+ * Get the collection of layers associated with this map.
+ * @return {!ol.Collection.<ol.layer.Base>} Layers.
+ * @api stable
+ */
+ol.Map.prototype.getLayers = function() {
+ var layers = this.getLayerGroup().getLayers();
+ return layers;
+};
+
+
+/**
+ * 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.Map.prototype.getPixelFromCoordinate = function(coordinate) {
+ var frameState = this.frameState_;
+ if (!frameState) {
+ return null;
+ } else {
+ var vec2 = coordinate.slice(0, 2);
+ return ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, vec2, vec2);
+ }
+};
+
+
+/**
+ * Get the map renderer.
+ * @return {ol.renderer.Map} Renderer
+ */
+ol.Map.prototype.getRenderer = function() {
+ return this.renderer_;
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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.Map.prototype.getView = function() {
+ return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW));
+};
+
+
+/**
+ * Get the element that serves as the map viewport.
+ * @return {Element} Viewport.
+ * @api stable
+ */
+ol.Map.prototype.getViewport = function() {
+ return this.viewport_;
+};
+
+
+/**
+ * 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.Map.prototype.getOverlayContainer = function() {
+ return this.overlayContainer_;
+};
+
+
+/**
+ * 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.Map.prototype.getOverlayContainerStopEvent = function() {
+ return this.overlayContainerStopEvent_;
+};
+
+
+/**
+ * @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.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 (!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;
+};
+
+
+/**
+ * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @param {string=} opt_type Type.
+ */
+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.MapBrowserEvent} mapBrowserEvent The event to handle.
+ */
+ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) {
+ if (!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(interactions !== undefined,
+ '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;
+ }
+ }
+ }
+};
+
+
+/**
+ * @protected
+ */
+ol.Map.prototype.handlePostRender = function() {
+
+ var frameState = this.frameState_;
+
+ // 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 (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;
+};
+
+
+/**
+ * @private
+ */
+ol.Map.prototype.handleSizeChanged_ = function() {
+ this.render();
+};
+
+
+/**
+ * @private
+ */
+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 (!targetElement) {
+ goog.dom.removeNode(this.viewport_);
+ if (this.viewportResizeListenerKey_) {
+ goog.events.unlistenByKey(this.viewportResizeListenerKey_);
+ this.viewportResizeListenerKey_ = null;
+ }
+ } else {
+ targetElement.appendChild(this.viewport_);
+
+ var keyboardEventTarget = !this.keyboardEventTarget_ ?
+ targetElement : this.keyboardEventTarget_;
+ this.keyHandler_.attach(keyboardEventTarget);
+
+ if (!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.
+};
+
+
+/**
+ * @private
+ */
+ol.Map.prototype.handleTileChange_ = function() {
+ this.render();
+};
+
+
+/**
+ * @private
+ */
+ol.Map.prototype.handleViewPropertyChanged_ = function() {
+ this.render();
+};
+
+
+/**
+ * @private
+ */
+ol.Map.prototype.handleViewChanged_ = function() {
+ if (this.viewPropertyListenerKey_) {
+ goog.events.unlistenByKey(this.viewPropertyListenerKey_);
+ this.viewPropertyListenerKey_ = null;
+ }
+ var view = this.getView();
+ if (view) {
+ this.viewPropertyListenerKey_ = goog.events.listen(
+ view, ol.ObjectEventType.PROPERTYCHANGE,
+ this.handleViewPropertyChanged_, false, this);
+ }
+ this.render();
+};
+
+
+/**
+ * @param {goog.events.Event} event Event.
+ * @private
+ */
+ol.Map.prototype.handleLayerGroupMemberChanged_ = function(event) {
+ goog.asserts.assertInstanceof(event, goog.events.Event,
+ 'event should be an Event');
+ this.render();
+};
+
+
+/**
+ * @param {ol.ObjectEvent} event Event.
+ * @private
+ */
+ol.Map.prototype.handleLayerGroupPropertyChanged_ = function(event) {
+ goog.asserts.assertInstanceof(event, ol.ObjectEvent,
+ 'event should be an ol.ObjectEvent');
+ this.render();
+};
+
+
+/**
+ * @private
+ */
+ol.Map.prototype.handleLayerGroupChanged_ = function() {
+ if (this.layerGroupPropertyListenerKeys_) {
+ this.layerGroupPropertyListenerKeys_.forEach(goog.events.unlistenByKey);
+ this.layerGroupPropertyListenerKeys_ = null;
+ }
+ var layerGroup = this.getLayerGroup();
+ if (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();
+};
+
+
+/**
+ * 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.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 (!size || size[0] <= 0 || size[1] <= 0) {
+ return false;
+ }
+ var view = this.getView();
+ if (!view || !view.isDef()) {
+ return false;
+ }
+ return true;
+};
+
+
+/**
+ * @return {boolean} Is rendered.
+ */
+ol.Map.prototype.isRendered = function() {
+ return !!this.frameState_;
+};
+
+
+/**
+ * Requests an immediate render in a synchronous manner.
+ * @api stable
+ */
+ol.Map.prototype.renderSync = function() {
+ this.animationDelay_.fire();
+};
+
+
+/**
+ * Request a map rendering (at the next animation frame).
+ * @api stable
+ */
+ol.Map.prototype.render = function() {
+ if (!this.animationDelay_.isActive()) {
+ this.animationDelay_.start();
+ }
+};
+
+
+/**
+ * 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.Map.prototype.removeControl = function(control) {
+ var controls = this.getControls();
+ goog.asserts.assert(controls !== undefined, 'controls should be defined');
+ return controls.remove(control);
+};
+
+
+/**
+ * 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.Map.prototype.removeInteraction = function(interaction) {
+ var interactions = this.getInteractions();
+ goog.asserts.assert(interactions !== undefined,
+ 'interactions should be defined');
+ return interactions.remove(interaction);
+};
+
+
+/**
+ * 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.Map.prototype.removeLayer = function(layer) {
+ var layers = this.getLayerGroup().getLayers();
+ return layers.remove(layer);
+};
+
+
+/**
+ * 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.Map.prototype.removeOverlay = function(overlay) {
+ var overlays = this.getOverlays();
+ goog.asserts.assert(overlays !== undefined, 'overlays should be defined');
+ return overlays.remove(overlay);
+};
+
+
+/**
+ * @param {number} time Time.
+ * @private
+ */
+ol.Map.prototype.renderFrame_ = function(time) {
+
+ var i, ii, viewState;
+
+ var size = this.getSize();
+ var view = this.getView();
+ /** @type {?olx.FrameState} */
+ var frameState = null;
+ if (size !== undefined && ol.size.hasArea(size) &&
+ 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: !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 (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);
+ }
+
+ this.frameState_ = frameState;
+ this.renderer_.renderFrame(frameState);
+
+ if (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_);
+
+ 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);
+
+};
+
+
+/**
+ * Sets the layergroup of this map.
+ * @param {ol.layer.Group} layerGroup A layer group containing the layers in
+ * this map.
+ * @observable
+ * @api stable
+ */
+ol.Map.prototype.setLayerGroup = function(layerGroup) {
+ this.set(ol.MapProperty.LAYERGROUP, layerGroup);
+};
+
+
+/**
+ * Set the size of this map.
+ * @param {ol.Size|undefined} size The size in pixels of the map in the DOM.
+ * @observable
+ * @api
+ */
+ol.Map.prototype.setSize = function(size) {
+ this.set(ol.MapProperty.SIZE, size);
+};
+
+
+/**
+ * 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.Map.prototype.setTarget = function(target) {
+ this.set(ol.MapProperty.TARGET, target);
+};
+
+
+/**
+ * Set the view for this map.
+ * @param {ol.View} view The view that controls this map.
+ * @observable
+ * @api stable
+ */
+ol.Map.prototype.setView = function(view) {
+ this.set(ol.MapProperty.VIEW, view);
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ */
+ol.Map.prototype.skipFeature = function(feature) {
+ var featureUid = goog.getUid(feature).toString();
+ this.skippedFeatureUids_[featureUid] = true;
+ this.render();
+};
+
+
+/**
+ * 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.Map.prototype.updateSize = function() {
+ var targetElement = this.getTargetElement();
+
+ if (!targetElement) {
+ this.setSize(undefined);
+ } else {
+ var size = goog.style.getContentBoxSize(targetElement);
+ this.setSize([size.width, size.height]);
+ }
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ */
+ol.Map.prototype.unskipFeature = function(feature) {
+ var featureUid = goog.getUid(feature).toString();
+ delete this.skippedFeatureUids_[featureUid];
+ this.render();
+};
+
+
+/**
+ * @typedef {{controls: ol.Collection.<ol.control.Control>,
+ * interactions: ol.Collection.<ol.interaction.Interaction>,
+ * keyboardEventTarget: (Element|Document),
+ * logos: Object.<string, string>,
+ * overlays: ol.Collection.<ol.Overlay>,
+ * rendererConstructor:
+ * function(new: ol.renderer.Map, Element, ol.Map),
+ * values: Object.<string, *>}}
+ */
+ol.MapOptionsInternal;
+
+
+/**
+ * @param {olx.MapOptions} options Map options.
+ * @return {ol.MapOptionsInternal} Internal map options.
+ */
+ol.Map.createOptionsInternal = function(options) {
+
+ /**
+ * @type {Element|Document}
+ */
+ var keyboardEventTarget = null;
+ if (options.keyboardEventTarget !== undefined) {
+ // 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 = {};
+
+ var logos = {};
+ if (options.logo === undefined ||
+ (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;
+ }
+ }
+
+ 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;
+
+ values[ol.MapProperty.VIEW] = options.view !== undefined ?
+ options.view : new ol.View();
+
+ /**
+ * @type {function(new: ol.renderer.Map, Element, ol.Map)}
+ */
+ var rendererConstructor = ol.renderer.Map;
+
+ /**
+ * @type {Array.<ol.RendererType>}
+ */
+ var rendererTypes;
+ if (options.renderer !== undefined) {
+ 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 (options.controls !== undefined) {
+ 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();
+ }
+
+ var interactions;
+ if (options.interactions !== undefined) {
+ 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();
+ }
+
+ var overlays;
+ if (options.overlays !== undefined) {
+ 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();
+
+
+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');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+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');
+
+
+/**
+ * @enum {string}
+ */
+ol.OverlayProperty = {
+ ELEMENT: 'element',
+ MAP: 'map',
+ OFFSET: 'offset',
+ POSITION: 'position',
+ POSITIONING: 'positioning'
+};
+
+
+/**
+ * Overlay position: `'bottom-left'`, `'bottom-center'`, `'bottom-right'`,
+ * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`,
+ * `'top-center'`, `'top-right'`
+ * @enum {string}
+ * @api stable
+ */
+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'
+};
+
+
+
+/**
+ * @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.
+ *
+ * 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
+ */
+ol.Overlay = function(options) {
+
+ goog.base(this);
+
+ /**
+ * @private
+ * @type {number|string|undefined}
+ */
+ this.id_ = options.id;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.insertFirst_ = options.insertFirst !== undefined ?
+ options.insertFirst : true;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.stopEvent_ = options.stopEvent !== undefined ? options.stopEvent : true;
+
+ /**
+ * @private
+ * @type {Element}
+ */
+ this.element_ = document.createElement('DIV');
+ this.element_.className = 'ol-overlay-container';
+ this.element_.style.position = 'absolute';
+
+ /**
+ * @protected
+ * @type {boolean}
+ */
+ this.autoPan = options.autoPan !== undefined ? options.autoPan : false;
+
+ /**
+ * @private
+ * @type {olx.animation.PanOptions}
+ */
+ this.autoPanAnimation_ = options.autoPanAnimation !== undefined ?
+ options.autoPanAnimation : /** @type {olx.animation.PanOptions} */ ({});
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.autoPanMargin_ = options.autoPanMargin !== undefined ?
+ options.autoPanMargin : 20;
+
+ /**
+ * @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;
+
+ 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);
+
+ 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);
+
+ goog.events.listen(
+ this, ol.Object.getChangeEventType(ol.OverlayProperty.POSITIONING),
+ this.handlePositioningChanged, false, this);
+
+ if (options.element !== undefined) {
+ this.setElement(options.element);
+ }
+
+ this.setOffset(options.offset !== undefined ? options.offset : [0, 0]);
+
+ this.setPositioning(options.positioning !== undefined ?
+ /** @type {ol.OverlayPositioning} */ (options.positioning) :
+ ol.OverlayPositioning.TOP_LEFT);
+
+ if (options.position !== undefined) {
+ this.setPosition(options.position);
+ }
+
+};
+goog.inherits(ol.Overlay, ol.Object);
+
+
+/**
+ * Get the DOM element of this overlay.
+ * @return {Element|undefined} The Element containing the overlay.
+ * @observable
+ * @api stable
+ */
+ol.Overlay.prototype.getElement = function() {
+ return /** @type {Element|undefined} */ (
+ this.get(ol.OverlayProperty.ELEMENT));
+};
+
+
+/**
+ * Get the overlay identifier which is set on constructor.
+ * @return {number|string|undefined} Id.
+ * @api
+ */
+ol.Overlay.prototype.getId = function() {
+ return this.id_;
+};
+
+
+/**
+ * Get the map associated with this overlay.
+ * @return {ol.Map|undefined} The map that the overlay is part of.
+ * @observable
+ * @api stable
+ */
+ol.Overlay.prototype.getMap = function() {
+ return /** @type {ol.Map|undefined} */ (
+ this.get(ol.OverlayProperty.MAP));
+};
+
+
+/**
+ * Get the offset of this overlay.
+ * @return {Array.<number>} The offset.
+ * @observable
+ * @api stable
+ */
+ol.Overlay.prototype.getOffset = function() {
+ return /** @type {Array.<number>} */ (
+ this.get(ol.OverlayProperty.OFFSET));
+};
+
+
+/**
+ * Get the current position of this overlay.
+ * @return {ol.Coordinate|undefined} The spatial point that the overlay is
+ * anchored at.
+ * @observable
+ * @api stable
+ */
+ol.Overlay.prototype.getPosition = function() {
+ return /** @type {ol.Coordinate|undefined} */ (
+ this.get(ol.OverlayProperty.POSITION));
+};
+
+
+/**
+ * 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.Overlay.prototype.getPositioning = function() {
+ return /** @type {ol.OverlayPositioning} */ (
+ this.get(ol.OverlayProperty.POSITIONING));
+};
+
+
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handleElementChanged = function() {
+ goog.dom.removeChildren(this.element_);
+ var element = this.getElement();
+ if (element) {
+ this.element_.appendChild(element);
+ }
+};
+
+
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handleMapChanged = function() {
+ if (this.mapPostrenderListenerKey_) {
+ goog.dom.removeNode(this.element_);
+ goog.events.unlistenByKey(this.mapPostrenderListenerKey_);
+ this.mapPostrenderListenerKey_ = null;
+ }
+ var map = this.getMap();
+ if (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 {
+ container.appendChild(this.element_);
+ }
+ }
+};
+
+
+/**
+ * @protected
+ */
+ol.Overlay.prototype.render = function() {
+ this.updatePixelPosition();
+};
+
+
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handleOffsetChanged = function() {
+ this.updatePixelPosition();
+};
+
+
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handlePositionChanged = function() {
+ this.updatePixelPosition();
+ if (this.get(ol.OverlayProperty.POSITION) !== undefined && this.autoPan) {
+ this.panIntoView_();
+ }
+};
+
+
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handlePositioningChanged = function() {
+ this.updatePixelPosition();
+};
+
+
+/**
+ * Set the DOM element to be associated with this overlay.
+ * @param {Element|undefined} element The Element containing the overlay.
+ * @observable
+ * @api stable
+ */
+ol.Overlay.prototype.setElement = function(element) {
+ this.set(ol.OverlayProperty.ELEMENT, element);
+};
+
+
+/**
+ * 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.Overlay.prototype.setMap = function(map) {
+ this.set(ol.OverlayProperty.MAP, map);
+};
+
+
+/**
+ * Set the offset for this overlay.
+ * @param {Array.<number>} offset Offset.
+ * @observable
+ * @api stable
+ */
+ol.Overlay.prototype.setOffset = function(offset) {
+ this.set(ol.OverlayProperty.OFFSET, offset);
+};
+
+
+/**
+ * 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.Overlay.prototype.setPosition = function(position) {
+ this.set(ol.OverlayProperty.POSITION, position);
+};
+
+
+/**
+ * Pan the map so that the overlay is entirely visible in the current viewport
+ * (if necessary).
+ * @private
+ */
+ol.Overlay.prototype.panIntoView_ = function() {
+ goog.asserts.assert(this.autoPan, 'this.autoPan should be true');
+ var map = this.getMap();
+
+ if (map === undefined || !map.getTargetElement()) {
+ return;
+ }
+
+ var mapRect = this.getRect_(map.getTargetElement(), map.getSize());
+ var element = this.getElement();
+ goog.asserts.assert(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(center !== undefined, 'center should be defined');
+ var centerPx = map.getPixelFromCoordinate(center);
+ var newCenterPx = [
+ centerPx[0] + delta[0],
+ centerPx[1] + delta[1]
+ ];
+
+ if (this.autoPanAnimation_) {
+ this.autoPanAnimation_.source = center;
+ map.beforeRender(ol.animation.pan(this.autoPanAnimation_));
+ }
+ map.getView().setCenter(map.getCoordinateFromPixel(newCenterPx));
+ }
+ }
+};
+
+
+/**
+ * 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.Overlay.prototype.getRect_ = function(element, size) {
+ goog.asserts.assert(element, 'element should be defined');
+ goog.asserts.assert(size !== undefined, 'size should be defined');
+
+ var offset = goog.style.getPageOffset(element);
+ return [
+ offset.x,
+ offset.y,
+ offset.x + size[0],
+ offset.y + size[1]
+ ];
+};
+
+
+/**
+ * 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.Overlay.prototype.setPositioning = function(positioning) {
+ this.set(ol.OverlayProperty.POSITIONING, positioning);
+};
+
+
+/**
+ * Modify the visibility of the element.
+ * @param {boolean} visible
+ * @protected
+ */
+ol.Overlay.prototype.setVisible = function(visible) {
+ if (this.rendered_.visible !== visible) {
+ goog.style.setElementShown(this.element_, visible);
+ this.rendered_.visible = visible;
+ }
+};
+
+
+/**
+ * Update pixel position.
+ * @protected
+ */
+ol.Overlay.prototype.updatePixelPosition = function() {
+ var map = this.getMap();
+ var position = this.getPosition();
+ if (map === undefined || !map.isRendered() || position === undefined) {
+ this.setVisible(false);
+ return;
+ }
+
+ var pixel = map.getPixelFromCoordinate(position);
+ var mapSize = map.getSize();
+ this.updateRenderedPosition(pixel, mapSize);
+};
+
+
+/**
+ * @param {ol.Pixel} pixel
+ * @param {ol.Size|undefined} mapSize
+ * @protected
+ */
+ol.Overlay.prototype.updateRenderedPosition = function(pixel, mapSize) {
+ goog.asserts.assert(pixel, 'pixel should not be null');
+ goog.asserts.assert(mapSize !== undefined, '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(positioning !== undefined,
+ '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;
+ }
+ }
+
+ this.setVisible(true);
+};
+
+goog.provide('ol.control.OverviewMap');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+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');
+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');
+
+
+
+/**
+ * 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
+ */
+ol.control.OverviewMap = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.collapsible_ = options.collapsible !== undefined ?
+ options.collapsible : true;
+
+ if (!this.collapsible_) {
+ this.collapsed_ = false;
+ }
+
+ var className = options.className ? options.className : 'ol-overviewmap';
+
+ var tipLabel = options.tipLabel ? options.tipLabel : 'Overview map';
+
+ var collapseLabel = options.collapseLabel ? options.collapseLabel : '\u00AB';
+
+ /**
+ * @private
+ * @type {Node}
+ */
+ this.collapseLabel_ = goog.isString(collapseLabel) ?
+ goog.dom.createDom('SPAN', {}, collapseLabel) :
+ collapseLabel;
+
+ var label = options.label ? options.label : '\u00BB';
+
+ /**
+ * @private
+ * @type {Node}
+ */
+ this.label_ = goog.isString(label) ?
+ goog.dom.createDom('SPAN', {}, label) :
+ label;
+
+ var activeLabel = (this.collapsible_ && !this.collapsed_) ?
+ this.collapseLabel_ : this.label_;
+ var button = goog.dom.createDom('BUTTON', {
+ 'type': 'button',
+ 'title': tipLabel
+ }, activeLabel);
+
+ goog.events.listen(button, goog.events.EventType.CLICK,
+ this.handleClick_, false, this);
+
+ var ovmapDiv = goog.dom.createDom('DIV', 'ol-overviewmap-map');
+
+ /**
+ * @type {ol.Map}
+ * @private
+ */
+ this.ovmap_ = new ol.Map({
+ controls: new ol.Collection(),
+ interactions: new ol.Collection(),
+ target: ovmapDiv,
+ view: options.view
+ });
+ var ovmap = this.ovmap_;
+
+ if (options.layers) {
+ options.layers.forEach(
+ /**
+ * @param {ol.layer.Layer} layer Layer.
+ */
+ function(layer) {
+ ovmap.addLayer(layer);
+ }, this);
+ }
+
+ var box = goog.dom.createDom('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('DIV',
+ cssClasses, ovmapDiv, button);
+
+ var render = options.render ? options.render : ol.control.OverviewMap.render;
+
+ goog.base(this, {
+ element: element,
+ render: render,
+ target: options.target
+ });
+};
+goog.inherits(ol.control.OverviewMap, ol.control.Control);
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+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_();
+ }
+ }
+ }
+};
+
+
+/**
+ * Handle map property changes. This only deals with changes to the map's view.
+ * @param {ol.ObjectEvent} event The propertychange event.
+ * @private
+ */
+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);
+ }
+};
+
+
+/**
+ * Register listeners for view property changes.
+ * @param {ol.View} view The view.
+ * @private
+ */
+ol.control.OverviewMap.prototype.bindView_ = function(view) {
+ goog.events.listen(view,
+ ol.Object.getChangeEventType(ol.ViewProperty.ROTATION),
+ this.handleRotationChanged_, false, this);
+};
+
+
+/**
+ * Unregister listeners for view property changes.
+ * @param {ol.View} view The view.
+ * @private
+ */
+ol.control.OverviewMap.prototype.unbindView_ = function(view) {
+ goog.events.unlisten(view,
+ ol.Object.getChangeEventType(ol.ViewProperty.ROTATION),
+ this.handleRotationChanged_, false, this);
+};
+
+
+/**
+ * Handle rotation changes to the main map.
+ * TODO: This should rotate the extent rectrangle instead of the
+ * overview map's view.
+ * @private
+ */
+ol.control.OverviewMap.prototype.handleRotationChanged_ = function() {
+ this.ovmap_.getView().setRotation(this.getMap().getView().getRotation());
+};
+
+
+/**
+ * Update the overview map element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.OverviewMap}
+ * @api
+ */
+ol.control.OverviewMap.render = function(mapEvent) {
+ this.validateExtent_();
+ this.updateBox_();
+};
+
+
+/**
+ * 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
+ */
+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(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(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_();
+ }
+};
+
+
+/**
+ * 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;
+ }
+
+ 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(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(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);
+};
+
+
+/**
+ * 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_;
+
+ var view = map.getView();
+ goog.asserts.assert(view, 'view should be defined');
+
+ var ovview = ovmap.getView();
+ goog.asserts.assert(ovview, 'ovview should be defined');
+
+ ovview.setCenter(view.getCenter());
+};
+
+
+/**
+ * Update the box using the main map extent
+ * @private
+ */
+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(view, 'view should be defined');
+
+ var ovview = ovmap.getView();
+ goog.asserts.assert(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(rotation !== undefined, '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 (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 {number} rotation Target rotation.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {ol.Coordinate|undefined} Coordinate for rotation and center anchor.
+ * @private
+ */
+ol.control.OverviewMap.prototype.calculateCoordinateRotate_ = function(
+ rotation, coordinate) {
+ var coordinateRotate;
+
+ var map = this.getMap();
+ var view = map.getView();
+ goog.asserts.assert(view, 'view should be defined');
+
+ var currentCenter = view.getCenter();
+
+ if (currentCenter) {
+ coordinateRotate = [
+ coordinate[0] - currentCenter[0],
+ coordinate[1] - currentCenter[1]
+ ];
+ ol.coordinate.rotate(coordinateRotate, rotation);
+ ol.coordinate.add(coordinateRotate, currentCenter);
+ }
+ return coordinateRotate;
+};
+
+
+/**
+ * @param {goog.events.BrowserEvent} event The event to handle
+ * @private
+ */
+ol.control.OverviewMap.prototype.handleClick_ = function(event) {
+ event.preventDefault();
+ this.handleToggle_();
+};
+
+
+/**
+ * @private
+ */
+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);
+ }
+};
+
+
+/**
+ * Return `true` if the overview map is collapsible, `false` otherwise.
+ * @return {boolean} True if the widget is collapsible.
+ * @api stable
+ */
+ol.control.OverviewMap.prototype.getCollapsible = function() {
+ return this.collapsible_;
+};
+
+
+/**
+ * Set whether the overview map should be collapsible.
+ * @param {boolean} collapsible True if the widget is collapsible.
+ * @api stable
+ */
+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_();
+ }
+};
+
+
+/**
+ * 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.control.OverviewMap.prototype.setCollapsed = function(collapsed) {
+ if (!this.collapsible_ || this.collapsed_ === collapsed) {
+ return;
+ }
+ this.handleToggle_();
+};
+
+
+/**
+ * Determine if the overview map is collapsed.
+ * @return {boolean} The overview map is collapsed.
+ * @api stable
+ */
+ol.control.OverviewMap.prototype.getCollapsed = function() {
+ return this.collapsed_;
+};
+
+
+/**
+ * Return the overview map.
+ * @return {ol.Map} Overview map.
+ * @api
+ */
+ol.control.OverviewMap.prototype.getOverviewMap = function() {
+ return this.ovmap_;
+};
+
+goog.provide('ol.control.ScaleLine');
+goog.provide('ol.control.ScaleLineProperty');
+goog.provide('ol.control.ScaleLineUnits');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('goog.style');
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.TransformFunction');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.math');
+goog.require('ol.proj');
+goog.require('ol.proj.METERS_PER_UNIT');
+goog.require('ol.proj.Units');
+goog.require('ol.sphere.NORMAL');
+
+
+/**
+ * @enum {string}
+ */
+ol.control.ScaleLineProperty = {
+ UNITS: 'units'
+};
+
+
+/**
+ * Units for the scale line. Supported values are `'degrees'`, `'imperial'`,
+ * `'nautical'`, `'metric'`, `'us'`.
+ * @enum {string}
+ * @api stable
+ */
+ol.control.ScaleLineUnits = {
+ DEGREES: 'degrees',
+ IMPERIAL: 'imperial',
+ NAUTICAL: 'nautical',
+ METRIC: 'metric',
+ US: 'us'
+};
+
+
+
+/**
+ * @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`.
+ *
+ * @constructor
+ * @extends {ol.control.Control}
+ * @param {olx.control.ScaleLineOptions=} opt_options Scale line options.
+ * @api stable
+ */
+ol.control.ScaleLine = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ var className = options.className ? options.className : 'ol-scale-line';
+
+ /**
+ * @private
+ * @type {Element}
+ */
+ this.innerElement_ = goog.dom.createDom('DIV',
+ className + '-inner');
+
+ /**
+ * @private
+ * @type {Element}
+ */
+ this.element_ = goog.dom.createDom('DIV',
+ className + ' ' + ol.css.CLASS_UNSELECTABLE, this.innerElement_);
+
+ /**
+ * @private
+ * @type {?olx.ViewState}
+ */
+ this.viewState_ = null;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.minWidth_ = options.minWidth !== undefined ? 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 = 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);
+
+
+/**
+ * @const
+ * @type {Array.<number>}
+ */
+ol.control.ScaleLine.LEADING_DIGITS = [1, 2, 5];
+
+
+/**
+ * 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.control.ScaleLine.prototype.getUnits = function() {
+ return /** @type {ol.control.ScaleLineUnits|undefined} */ (
+ this.get(ol.control.ScaleLineProperty.UNITS));
+};
+
+
+/**
+ * Update the scale line element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.ScaleLine}
+ * @api
+ */
+ol.control.ScaleLine.render = function(mapEvent) {
+ var frameState = mapEvent.frameState;
+ if (!frameState) {
+ this.viewState_ = null;
+ } else {
+ this.viewState_ = frameState.viewState;
+ }
+ this.updateElement_();
+};
+
+
+/**
+ * @private
+ */
+ol.control.ScaleLine.prototype.handleUnitsChanged_ = function() {
+ this.updateElement_();
+};
+
+
+/**
+ * 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.control.ScaleLine.prototype.setUnits = function(units) {
+ this.set(ol.control.ScaleLineProperty.UNITS, units);
+};
+
+
+/**
+ * @private
+ */
+ol.control.ScaleLine.prototype.updateElement_ = function() {
+ var viewState = this.viewState_;
+
+ if (!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();
+
+ 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(ol.math.toRadians(center[1]));
+ pointResolution *= Math.PI * cosLatitude * ol.sphere.NORMAL.radius / 180;
+ projectionUnits = ol.proj.Units.METERS;
+
+ } else if (projectionUnits != ol.proj.Units.DEGREES &&
+ units == ol.control.ScaleLineUnits.DEGREES) {
+
+ // Convert pointResolution from other units to degrees
+ if (!this.toEPSG4326_) {
+ this.toEPSG4326_ = ol.proj.getTransformFromProjections(
+ projection, ol.proj.get('EPSG:4326'));
+ }
+ cosLatitude = Math.cos(ol.math.toRadians(this.toEPSG4326_(center)[1]));
+ var radius = ol.sphere.NORMAL.radius;
+ goog.asserts.assert(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;
+ }
+
+ 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');
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+};
+
+// 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.
+
+/**
+ * @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.events.EventHandler');
+
+goog.require('goog.Disposable');
+goog.require('goog.events');
+goog.require('goog.object');
+
+goog.forwardDeclare('goog.events.EventWrapper');
+
+
+
+/**
+ * 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;
+
+ /**
+ * Keys for events that are being listened to.
+ * @type {!Object<!goog.events.Key>}
+ * @private
+ */
+ this.keys_ = {};
+};
+goog.inherits(goog.events.EventHandler, goog.Disposable);
+
+
+/**
+ * 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_ = [];
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+
+ 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;
+ }
+
+ var key = listenerObj.key;
+ this.keys_[key] = listenerObj;
+ }
+
+ return this;
+};
+
+
+/**
+ * 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
+ */
+goog.events.EventHandler.prototype.listenOnce = function(
+ src, type, opt_fn, opt_capture) {
+ return this.listenOnce_(src, type, opt_fn, opt_capture);
+};
+
+
+/**
+ * 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
+ */
+goog.events.EventHandler.prototype.listenOnceWithScope = function(
+ src, type, fn, capture, scope) {
+ // TODO(mknichel): Deprecate this function.
+ return this.listenOnce_(src, type, fn, capture, scope);
+};
+
+
+/**
+ * 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
+ */
+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;
+ }
+
+ var key = listenerObj.key;
+ this.keys_[key] = listenerObj;
+ }
+
+ return this;
+};
+
+
+/**
+ * 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.
+ */
+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);
+};
+
+
+/**
+ * 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
+ */
+goog.events.EventHandler.prototype.listenWithWrapperAndScope = function(
+ src, wrapper, listener, capture, scope) {
+ // TODO(mknichel): Deprecate this function.
+ return this.listenWithWrapper_(src, wrapper, listener, capture, scope);
+};
+
+
+/**
+ * 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
+ */
+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;
+};
+
+
+/**
+ * @return {number} Number of listeners registered by this handler.
+ */
+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;
+};
+
+
+/**
+ * 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
+ */
+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];
+ }
+ }
+
+ return this;
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * Unlistens to all events.
+ */
+goog.events.EventHandler.prototype.removeAll = function() {
+ goog.object.forEach(this.keys_, function(listenerObj, key) {
+ if (this.keys_.hasOwnProperty(key)) {
+ goog.events.unlistenByKey(listenerObj);
+ }
+ }, this);
+
+ this.keys_ = {};
+};
+
+
+/**
+ * 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();
+};
+
+
+/**
+ * Default event handler
+ * @param {goog.events.Event} e Event object.
+ */
+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.
+
+/**
+ * @fileoverview Bidi utility functions.
+ *
+ */
+
+goog.provide('goog.style.bidi');
+
+goog.require('goog.dom');
+goog.require('goog.style');
+goog.require('goog.userAgent');
+
+
+/**
+ * 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;
+ }
+ }
+ // 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.
+ *
+ * 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.
+ */
+goog.style.bidi.getOffsetStart = function(element) {
+ element = /** @type {!HTMLElement} */ (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;
+ }
+
+ // 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;
+};
+
+
+/**
+ * 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.
+ */
+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 {
+ element.scrollLeft = offsetStart;
+ }
+};
+
+
+/**
+ * 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.
+ */
+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.
+
+/**
+ * @fileoverview Drag Utilities.
+ *
+ * Provides extensible functionality for drag & drop behaviour.
+ *
+ * @see ../demos/drag.html
+ * @see ../demos/dragger.html
+ */
+
+
+goog.provide('goog.fx.DragEvent');
+goog.provide('goog.fx.Dragger');
+goog.provide('goog.fx.Dragger.EventType');
+
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+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');
+
+
+
+/**
+ * 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
+ * @struct
+ */
+goog.fx.Dragger = function(target, opt_handle, opt_limits) {
+ goog.fx.Dragger.base(this, 'constructor');
+
+ /**
+ * Reference to drag target element.
+ * @type {?Element}
+ */
+ this.target = target;
+
+ /**
+ * Reference to the handler that initiates the drag.
+ * @type {?Element}
+ */
+ this.handle = opt_handle || target;
+
+ /**
+ * Object representing the limits of the drag region.
+ * @type {goog.math.Rect}
+ */
+ this.limits = opt_limits || new goog.math.Rect(NaN, NaN, NaN, NaN);
+
+ /**
+ * Reference to a document object to use for the events.
+ * @private {Document}
+ */
+ this.document_ = goog.dom.getOwnerDocument(target);
+
+ /** @private {!goog.events.EventHandler} */
+ this.eventHandler_ = new goog.events.EventHandler(this);
+ this.registerDisposable(this.eventHandler_);
+
+ /**
+ * Whether the element is rendered right-to-left. We initialize this lazily.
+ * @private {boolean|undefined}}
+ */
+ this.rightToLeft_;
+
+ /**
+ * Current x position of mouse or touch relative to viewport.
+ * @type {number}
+ */
+ this.clientX = 0;
+
+ /**
+ * Current y position of mouse or touch relative to viewport.
+ * @type {number}
+ */
+ this.clientY = 0;
+
+ /**
+ * 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.
+ */
+ this.screenX = 0;
+
+ /**
+ * 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.
+ */
+ this.screenY = 0;
+
+ /**
+ * The x position where the first mousedown or touchstart occurred.
+ * @type {number}
+ */
+ this.startX = 0;
+
+ /**
+ * The y position where the first mousedown or touchstart occurred.
+ * @type {number}
+ */
+ this.startY = 0;
+
+ /**
+ * Current x position of drag relative to target's parent.
+ * @type {number}
+ */
+ this.deltaX = 0;
+
+ /**
+ * Current y position of drag relative to target's parent.
+ * @type {number}
+ */
+ this.deltaY = 0;
+
+ /**
+ * The current page scroll value.
+ * @type {?goog.math.Coordinate}
+ */
+ this.pageScroll;
+
+ /**
+ * Whether dragging is currently enabled.
+ * @private {boolean}
+ */
+ this.enabled_ = true;
+
+ /**
+ * Whether object is currently being dragged.
+ * @private {boolean}
+ */
+ this.dragging_ = false;
+
+ /**
+ * Whether mousedown should be default prevented.
+ * @private {boolean}
+ **/
+ this.preventMouseDown_ = true;
+
+ /**
+ * The amount of distance, in pixels, after which a mousedown or touchstart is
+ * considered a drag.
+ * @private {number}
+ */
+ this.hysteresisDistanceSquared_ = 0;
+
+ /**
+ * The SCROLL event target used to make drag element follow scrolling.
+ * @private {?EventTarget}
+ */
+ this.scrollTarget_;
+
+ /**
+ * Whether IE drag events cancelling is on.
+ * @private {boolean}
+ */
+ this.ieDragStartCancellingOn_ = false;
+
+ /**
+ * 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.
+ * @private {boolean}
+ */
+ this.useRightPositioningForRtl_ = false;
+
+ // 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);
+
+
+/**
+ * Whether setCapture is supported by the browser.
+ * @type {boolean}
+ * @private
+ */
+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');
+
+
+/**
+ * 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}.
+ */
+goog.fx.Dragger.cloneNode = function(sourceEl) {
+ var clonedEl = /** @type {Element} */ (sourceEl.cloneNode(true)),
+ origTexts = sourceEl.getElementsByTagName(goog.dom.TagName.TEXTAREA),
+ dragTexts = clonedEl.getElementsByTagName(goog.dom.TagName.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) {
+ case goog.dom.TagName.TR:
+ return goog.dom.createDom(goog.dom.TagName.TABLE, null,
+ goog.dom.createDom(goog.dom.TagName.TBODY,
+ null, clonedEl));
+ case goog.dom.TagName.TD:
+ case goog.dom.TagName.TH:
+ return goog.dom.createDom(
+ goog.dom.TagName.TABLE, null, goog.dom.createDom(
+ goog.dom.TagName.TBODY, null, goog.dom.createDom(
+ goog.dom.TagName.TR, null, clonedEl)));
+ case goog.dom.TagName.TEXTAREA:
+ clonedEl.value = sourceEl.value;
+ default:
+ return clonedEl;
+ }
+};
+
+
+/**
+ * Constants for event names.
+ * @enum {string}
+ */
+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'
+};
+
+
+/**
+ * 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.
+ */
+goog.fx.Dragger.prototype.enableRightPositioningForRtl =
+ function(useRightPositioningForRtl) {
+ this.useRightPositioningForRtl_ = useRightPositioningForRtl;
+};
+
+
+/**
+ * Returns the event handler, intended for subclass use.
+ * @return {!goog.events.EventHandler<T>} The event handler.
+ * @this {T}
+ * @template T
+ */
+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_;
+};
+
+
+/**
+ * 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.
+ */
+goog.fx.Dragger.prototype.setLimits = function(limits) {
+ this.limits = limits || new goog.math.Rect(NaN, NaN, NaN, NaN);
+};
+
+
+/**
+ * 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.
+ */
+goog.fx.Dragger.prototype.setHysteresis = function(distance) {
+ this.hysteresisDistanceSquared_ = Math.pow(distance, 2);
+};
+
+
+/**
+ * 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.
+ */
+goog.fx.Dragger.prototype.getHysteresis = function() {
+ return Math.sqrt(this.hysteresisDistanceSquared_);
+};
+
+
+/**
+ * Sets the SCROLL event target to make drag element follow scrolling.
+ *
+ * @param {EventTarget} scrollTarget The event target that dispatches SCROLL
+ * events.
+ */
+goog.fx.Dragger.prototype.setScrollTarget = function(scrollTarget) {
+ this.scrollTarget_ = scrollTarget;
+};
+
+
+/**
+ * Enables cancelling of built-in IE drag events.
+ * @param {boolean} cancelIeDragStart Whether to enable cancelling of IE
+ * dragstart event.
+ */
+goog.fx.Dragger.prototype.setCancelIeDragStart = function(cancelIeDragStart) {
+ this.ieDragStartCancellingOn_ = cancelIeDragStart;
+};
+
+
+/**
+ * @return {boolean} Whether the dragger is enabled.
+ */
+goog.fx.Dragger.prototype.getEnabled = function() {
+ return this.enabled_;
+};
+
+
+/**
+ * Set whether dragger is enabled
+ * @param {boolean} enabled Whether dragger is enabled.
+ */
+goog.fx.Dragger.prototype.setEnabled = function(enabled) {
+ this.enabled_ = enabled;
+};
+
+
+/**
+ * Set whether mousedown should be default prevented.
+ * @param {boolean} preventMouseDown Whether mousedown should be default
+ * prevented.
+ */
+goog.fx.Dragger.prototype.setPreventMouseDown = function(preventMouseDown) {
+ this.preventMouseDown_ = preventMouseDown;
+};
+
+
+/** @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;
+};
+
+
+/**
+ * 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
+ */
+goog.fx.Dragger.prototype.isRightToLeft_ = function() {
+ if (!goog.isDef(this.rightToLeft_)) {
+ this.rightToLeft_ = goog.style.isRightToLeft(this.target);
+ }
+ return this.rightToLeft_;
+};
+
+
+/**
+ * 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.
+
+ if (this.enabled_ && !this.dragging_ &&
+ (!isMouseDown || e.isMouseActionButton())) {
+ if (this.hysteresisDistanceSquared_ == 0) {
+ if (this.fireDragStart_(e)) {
+ this.dragging_ = true;
+ if (this.preventMouseDown_) {
+ e.preventDefault();
+ }
+ } else {
+ // If the start drag is cancelled, don't setup for a drag.
+ return;
+ }
+ } else if (this.preventMouseDown_) {
+ // 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();
+ } else {
+ this.dispatchEvent(goog.fx.Dragger.EventType.EARLY_CANCEL);
+ }
+};
+
+
+/**
+ * Sets up event handlers when dragging starts.
+ * @protected
+ */
+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.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);
+ }
+};
+
+
+/**
+ * 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
+ */
+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();
+ }
+};
+
+
+/**
+ * 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.
+ */
+goog.fx.Dragger.prototype.endDrag = function(e, opt_dragCanceled) {
+ this.cleanUpAfterDragging_();
+
+ if (this.dragging_) {
+ 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);
+ }
+};
+
+
+/**
+ * Event handler that is used to end the drag by cancelling it.
+ * @param {goog.events.BrowserEvent} e Event object.
+ */
+goog.fx.Dragger.prototype.endDragCancel = function(e) {
+ this.endDrag(e, true);
+};
+
+
+/**
+ * Event handler that is used on mouse / touch move to update the drag
+ * @param {goog.events.BrowserEvent} e Event object.
+ * @private
+ */
+goog.fx.Dragger.prototype.handleMove_ = function(e) {
+ if (this.enabled_) {
+ // 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();
+ }
+ }
+ }
+};
+
+
+/**
+ * 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;
+
+ this.deltaX += dx;
+ this.deltaY += dy;
+
+ 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
+ */
+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 {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
+ */
+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));
+};
+
+
+/**
+ * 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.
+ */
+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));
+};
+
+
+/**
+ * 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.
+ */
+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));
+};
+
+
+/**
+ * Overridable function for computing the initial position of the target
+ * before dragging begins.
+ * @protected
+ */
+goog.fx.Dragger.prototype.computeInitialPosition = function() {
+ this.deltaX = this.useRightPositioningForRtl_ ?
+ goog.style.bidi.getOffsetStart(this.target) :
+ /** @type {!HTMLElement} */ (this.target).offsetLeft;
+ this.deltaY = /** @type {!HTMLElement} */ (this.target).offsetTop;
+};
+
+
+/**
+ * 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.
+ */
+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';
+};
+
+
+/**
+ * @return {boolean} Whether the dragger is currently in the midst of a drag.
+ */
+goog.fx.Dragger.prototype.isDragging = function() {
+ return this.dragging_;
+};
+
+
+
+/**
+ * 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
+ * @struct
+ * @extends {goog.events.Event}
+ */
+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?
+
+goog.provide('ol.control.ZoomSlider');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+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.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');
+goog.require('ol.math');
+
+
+
+/**
+ * @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) {
+
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * 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;
+
+ /**
+ * 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;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.duration_ = options.duration !== undefined ? options.duration : 200;
+
+ var className = options.className ? options.className : 'ol-zoomslider';
+ var thumbElement = goog.dom.createDom('BUTTON', {
+ 'type': 'button',
+ 'class': className + '-thumb ' + ol.css.CLASS_UNSELECTABLE
+ });
+ var containerElement = goog.dom.createDom('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 = options.render ? options.render : ol.control.ZoomSlider.render;
+
+ goog.base(this, {
+ element: containerElement,
+ render: render
+ });
+};
+goog.inherits(ol.control.ZoomSlider, ol.control.Control);
+
+
+/**
+ * The enum for available directions.
+ *
+ * @enum {number}
+ */
+ol.control.ZoomSlider.direction = {
+ VERTICAL: 0,
+ HORIZONTAL: 1
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.control.ZoomSlider.prototype.setMap = function(map) {
+ goog.base(this, 'setMap', map);
+ if (map) {
+ map.render();
+ }
+};
+
+
+/**
+ * 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.
+ *
+ * @private
+ */
+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];
+
+ 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);
+ }
+ this.dragger_.setLimits(limits);
+ this.sliderInitialized_ = true;
+};
+
+
+/**
+ * Update the zoomslider element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.ZoomSlider}
+ * @api
+ */
+ol.control.ZoomSlider.render = function(mapEvent) {
+ if (!mapEvent.frameState) {
+ return;
+ }
+ goog.asserts.assert(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);
+ }
+};
+
+
+/**
+ * @param {goog.events.BrowserEvent} browserEvent The browser event to handle.
+ * @private
+ */
+ol.control.ZoomSlider.prototype.handleContainerClick_ = function(browserEvent) {
+ var map = this.getMap();
+ var view = map.getView();
+ var currentResolution = view.getResolution();
+ goog.asserts.assert(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));
+};
+
+
+/**
+ * Handle dragger start events.
+ * @param {goog.fx.DragEvent} event The drag event.
+ * @private
+ */
+ol.control.ZoomSlider.prototype.handleDraggerStart_ = function(event) {
+ this.getMap().getView().setHint(ol.ViewHint.INTERACTING, 1);
+};
+
+
+/**
+ * Handle dragger drag events.
+ *
+ * @param {goog.fx.DragEvent} event The drag event.
+ * @private
+ */
+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_);
+};
+
+
+/**
+ * Handle dragger end events.
+ * @param {goog.fx.DragEvent} event The drag event.
+ * @private
+ */
+ol.control.ZoomSlider.prototype.handleDraggerEnd_ = function(event) {
+ var map = this.getMap();
+ var view = map.getView();
+ view.setHint(ol.ViewHint.INTERACTING, -1);
+ goog.asserts.assert(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);
+};
+
+
+/**
+ * Positions the thumb inside its container according to the given resolution.
+ *
+ * @param {number} res The res.
+ * @private
+ */
+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);
+ }
+};
+
+
+/**
+ * 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.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 ol.math.clamp(amount, 0, 1);
+};
+
+
+/**
+ * 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.control.ZoomSlider.prototype.getResolutionForPosition_ = function(position) {
+ var fn = this.getMap().getView().getResolutionForValueFunction();
+ return fn(1 - position);
+};
+
+
+/**
+ * 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);
+};
+
+goog.provide('ol.control.ZoomToExtent');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+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
+ */
+ol.control.ZoomToExtent = function(opt_options) {
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * @type {ol.Extent}
+ * @private
+ */
+ this.extent_ = options.extent ? options.extent : null;
+
+ var className = options.className ? options.className :
+ 'ol-zoom-extent';
+
+ var label = options.label ? options.label : 'E';
+ var tipLabel = options.tipLabel ?
+ options.tipLabel : 'Fit to extent';
+ var button = goog.dom.createDom('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('DIV', cssClasses, button);
+
+ goog.base(this, {
+ element: element,
+ target: options.target
+ });
+};
+goog.inherits(ol.control.ZoomToExtent, ol.control.Control);
+
+
+/**
+ * @param {goog.events.BrowserEvent} event The event to handle
+ * @private
+ */
+ol.control.ZoomToExtent.prototype.handleClick_ = function(event) {
+ event.preventDefault();
+ this.handleZoomToExtent_();
+};
+
+
+/**
+ * @private
+ */
+ol.control.ZoomToExtent.prototype.handleZoomToExtent_ = function() {
+ var map = this.getMap();
+ var view = map.getView();
+ var extent = !this.extent_ ?
+ view.getProjection().getExtent() : this.extent_;
+ var size = map.getSize();
+ goog.asserts.assert(size, 'size should be defined');
+ view.fit(extent, size);
+};
+
+goog.provide('ol.DeviceOrientation');
+goog.provide('ol.DeviceOrientationProperty');
+
+goog.require('goog.events');
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.has');
+goog.require('ol.math');
+
+
+/**
+ * @enum {string}
+ */
+ol.DeviceOrientationProperty = {
+ ALPHA: 'alpha',
+ BETA: 'beta',
+ GAMMA: 'gamma',
+ HEADING: 'heading',
+ TRACKING: 'tracking'
+};
+
+
+
+/**
+ * @classdesc
+ * 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
+ * @extends {ol.Object}
+ * @param {olx.DeviceOrientationOptions=} opt_options Options.
+ * @api
+ */
+ol.DeviceOrientation = function(opt_options) {
+
+ goog.base(this);
+
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * @private
+ * @type {goog.events.Key}
+ */
+ this.listenerKey_ = null;
+
+ goog.events.listen(this,
+ ol.Object.getChangeEventType(ol.DeviceOrientationProperty.TRACKING),
+ this.handleTrackingChanged_, false, this);
+
+ this.setTracking(options.tracking !== undefined ? options.tracking : false);
+
+};
+goog.inherits(ol.DeviceOrientation, ol.Object);
+
+
+/**
+ * @inheritDoc
+ */
+ol.DeviceOrientation.prototype.disposeInternal = function() {
+ this.setTracking(false);
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @private
+ * @param {goog.events.BrowserEvent} browserEvent Event.
+ */
+ol.DeviceOrientation.prototype.orientationChange_ = function(browserEvent) {
+ var event = /** @type {DeviceOrientationEvent} */
+ (browserEvent.getBrowserEvent());
+ if (event.alpha !== null) {
+ var alpha = ol.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.isNumber(event.webkitCompassHeading) &&
+ event.webkitCompassAccuracy != -1) {
+ var heading = ol.math.toRadians(event.webkitCompassHeading);
+ this.set(ol.DeviceOrientationProperty.HEADING, heading);
+ }
+ }
+ if (event.beta !== null) {
+ this.set(ol.DeviceOrientationProperty.BETA,
+ ol.math.toRadians(event.beta));
+ }
+ if (event.gamma !== null) {
+ this.set(ol.DeviceOrientationProperty.GAMMA,
+ ol.math.toRadians(event.gamma));
+ }
+ this.changed();
+};
+
+
+/**
+ * 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.DeviceOrientation.prototype.getAlpha = function() {
+ return /** @type {number|undefined} */ (
+ this.get(ol.DeviceOrientationProperty.ALPHA));
+};
+
+
+/**
+ * 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.DeviceOrientation.prototype.getBeta = function() {
+ return /** @type {number|undefined} */ (
+ this.get(ol.DeviceOrientationProperty.BETA));
+};
+
+
+/**
+ * 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.DeviceOrientation.prototype.getGamma = function() {
+ return /** @type {number|undefined} */ (
+ this.get(ol.DeviceOrientationProperty.GAMMA));
+};
+
+
+/**
+ * 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.DeviceOrientation.prototype.getHeading = function() {
+ return /** @type {number|undefined} */ (
+ this.get(ol.DeviceOrientationProperty.HEADING));
+};
+
+
+/**
+ * Determine if orientation is being tracked.
+ * @return {boolean} Changes in device orientation are being tracked.
+ * @observable
+ * @api
+ */
+ol.DeviceOrientation.prototype.getTracking = function() {
+ return /** @type {boolean} */ (
+ this.get(ol.DeviceOrientationProperty.TRACKING));
+};
+
+
+/**
+ * @private
+ */
+ol.DeviceOrientation.prototype.handleTrackingChanged_ = function() {
+ if (ol.has.DEVICE_ORIENTATION) {
+ var tracking = this.getTracking();
+ if (tracking && !this.listenerKey_) {
+ this.listenerKey_ = goog.events.listen(goog.global, 'deviceorientation',
+ this.orientationChange_, false, this);
+ } else if (!tracking && this.listenerKey_) {
+ goog.events.unlistenByKey(this.listenerKey_);
+ this.listenerKey_ = null;
+ }
+ }
+};
+
+
+/**
+ * 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.DeviceOrientation.prototype.setTracking = function(tracking) {
+ this.set(ol.DeviceOrientationProperty.TRACKING, tracking);
+};
+
+goog.provide('ol.format.Feature');
+
+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
+ */
+ol.format.Feature = function() {
+
+ /**
+ * @protected
+ * @type {ol.proj.Projection}
+ */
+ this.defaultDataProjection = null;
+
+};
+
+
+/**
+ * @return {Array.<string>} Extensions.
+ */
+ol.format.Feature.prototype.getExtensions = goog.abstractMethod;
+
+
+/**
+ * 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.Feature.prototype.getReadOptions = function(source, opt_options) {
+ var options;
+ if (opt_options) {
+ options = {
+ dataProjection: opt_options.dataProjection ?
+ opt_options.dataProjection : this.readProjection(source),
+ featureProjection: opt_options.featureProjection
+ };
+ }
+ return this.adaptOptions(options);
+};
+
+
+/**
+ * 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.Feature.prototype.adaptOptions = function(options) {
+ var updatedOptions;
+ if (options) {
+ updatedOptions = {
+ featureProjection: options.featureProjection,
+ dataProjection: options.dataProjection ?
+ options.dataProjection : this.defaultDataProjection,
+ rightHanded: options.rightHanded
+ };
+ }
+ return updatedOptions;
+};
+
+
+/**
+ * @return {ol.format.FormatType} Format.
+ */
+ol.format.Feature.prototype.getType = goog.abstractMethod;
+
+
+/**
+ * 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.Feature.prototype.readFeature = goog.abstractMethod;
+
+
+/**
+ * Read all features from a source.
+ *
+ * @param {Document|Node|ArrayBuffer|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
+ */
+ol.format.Feature.prototype.readFeatures = goog.abstractMethod;
+
+
+/**
+ * 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.Feature.prototype.readGeometry = goog.abstractMethod;
+
+
+/**
+ * Read the projection from a source.
+ *
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.Feature.prototype.readProjection = goog.abstractMethod;
+
+
+/**
+ * Encode a feature in this format.
+ *
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} Result.
+ */
+ol.format.Feature.prototype.writeFeature = goog.abstractMethod;
+
+
+/**
+ * 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.Feature.prototype.writeFeatures = goog.abstractMethod;
+
+
+/**
+ * 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.Feature.prototype.writeGeometry = goog.abstractMethod;
+
+
+/**
+ * @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 = opt_options ?
+ ol.proj.get(opt_options.featureProjection) : null;
+ var dataProjection = opt_options ?
+ ol.proj.get(opt_options.dataProjection) : null;
+ if (featureProjection && 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');
+
+
+
+/**
+ * @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.JSONFeature = function() {
+ goog.base(this);
+};
+goog.inherits(ol.format.JSONFeature, ol.format.Feature);
+
+
+/**
+ * @param {Document|Node|Object|string} source Source.
+ * @private
+ * @return {Object} Object.
+ */
+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 object ? object : null;
+ } else {
+ goog.asserts.fail();
+ return null;
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.getType = function() {
+ return ol.format.FormatType.JSON;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.readFeature = function(source, opt_options) {
+ return this.readFeatureFromObject(
+ this.getObject_(source), this.getReadOptions(source, opt_options));
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.readFeatures = function(source, opt_options) {
+ return this.readFeaturesFromObject(
+ this.getObject_(source), this.getReadOptions(source, opt_options));
+};
+
+
+/**
+ * @param {Object} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {ol.Feature} Feature.
+ */
+ol.format.JSONFeature.prototype.readFeatureFromObject = goog.abstractMethod;
+
+
+/**
+ * @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;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.readGeometry = function(source, opt_options) {
+ return this.readGeometryFromObject(
+ this.getObject_(source), this.getReadOptions(source, opt_options));
+};
+
+
+/**
+ * @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;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.readProjection = function(source) {
+ return this.readProjectionFromObject(this.getObject_(source));
+};
+
+
+/**
+ * @param {Object} object Object.
+ * @protected
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.JSONFeature.prototype.readProjectionFromObject = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.writeFeature = function(feature, opt_options) {
+ return goog.json.serialize(this.writeFeatureObject(feature, opt_options));
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {Object} Object.
+ */
+ol.format.JSONFeature.prototype.writeFeatureObject = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.writeFeatures = function(
+ features, opt_options) {
+ return goog.json.serialize(this.writeFeaturesObject(features, opt_options));
+};
+
+
+/**
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {Object} Object.
+ */
+ol.format.JSONFeature.prototype.writeFeaturesObject = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.writeGeometry = function(
+ geometry, opt_options) {
+ return goog.json.serialize(this.writeGeometryObject(geometry, opt_options));
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {Object} Object.
+ */
+ol.format.JSONFeature.prototype.writeGeometryObject = goog.abstractMethod;
+
+goog.provide('ol.geom.flat.interpolate');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.math');
+
+
+/**
+ * @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.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 (opt_dest) {
+ opt_dest[0] = pointX;
+ opt_dest[1] = pointY;
+ return opt_dest;
+ } else {
+ return [pointX, pointY];
+ }
+};
+
+
+/**
+ * @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.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;
+};
+
+
+/**
+ * @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.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');
+
+
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} Length.
+ */
+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;
+};
+
+
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} Perimeter.
+ */
+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');
+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.geom.SimpleGeometry}
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
+ */
+ol.geom.LineString = function(coordinates, opt_layout) {
+
+ goog.base(this);
+
+ /**
+ * @private
+ * @type {ol.Coordinate}
+ */
+ this.flatMidpoint_ = null;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.flatMidpointRevision_ = -1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.maxDelta_ = -1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.maxDeltaRevision_ = -1;
+
+ this.setCoordinates(coordinates, opt_layout);
+
+};
+goog.inherits(ol.geom.LineString, ol.geom.SimpleGeometry);
+
+
+/**
+ * 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 (!this.flatCoordinates) {
+ this.flatCoordinates = coordinate.slice();
+ } else {
+ goog.array.extend(this.flatCoordinates, coordinate);
+ }
+ this.changed();
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * @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 = opt_extrapolate !== undefined ? opt_extrapolate : false;
+ return ol.geom.flat.lineStringCoordinateAtM(this.flatCoordinates, 0,
+ this.flatCoordinates.length, this.stride, m, extrapolate);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * Return the length of the linestring on projected plane.
+ * @return {number} Length (on projected plane).
+ * @api stable
+ */
+ol.geom.LineString.prototype.getLength = function() {
+ return ol.geom.flat.length.lineString(
+ this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
+};
+
+
+/**
+ * @return {Array.<number>} Flat midpoint.
+ */
+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_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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.geom.LineString.prototype.getType = function() {
+ return ol.geom.GeometryType.LINE_STRING;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+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 (!coordinates) {
+ this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
+ } else {
+ this.setLayout(opt_layout, coordinates, 1);
+ if (!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');
+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.geom.SimpleGeometry}
+ * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
+ */
+ol.geom.MultiLineString = function(coordinates, opt_layout) {
+
+ goog.base(this);
+
+ /**
+ * @type {Array.<number>}
+ * @private
+ */
+ this.ends_ = [];
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.maxDelta_ = -1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.maxDeltaRevision_ = -1;
+
+ this.setCoordinates(coordinates, opt_layout);
+
+};
+goog.inherits(ol.geom.MultiLineString, ol.geom.SimpleGeometry);
+
+
+/**
+ * Append the passed linestring to the multilinestring.
+ * @param {ol.geom.LineString} lineString LineString.
+ * @api stable
+ */
+ol.geom.MultiLineString.prototype.appendLineString = function(lineString) {
+ goog.asserts.assert(lineString.getLayout() == this.layout,
+ 'layout of lineString should match the layout');
+ if (!this.flatCoordinates) {
+ this.flatCoordinates = lineString.getFlatCoordinates().slice();
+ } else {
+ goog.array.extend(
+ this.flatCoordinates, lineString.getFlatCoordinates().slice());
+ }
+ this.ends_.push(this.flatCoordinates.length);
+ this.changed();
+};
+
+
+/**
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.MultiLineString} Clone.
+ * @api stable
+ */
+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.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();
+ }
+ 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.
+ *
+ * @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.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;
+ }
+ var extrapolate = opt_extrapolate !== undefined ? opt_extrapolate : false;
+ var interpolate = opt_interpolate !== undefined ? opt_interpolate : false;
+ return ol.geom.flat.lineStringsCoordinateAtM(this.flatCoordinates, 0,
+ this.ends_, this.stride, m, extrapolate, interpolate);
+};
+
+
+/**
+ * Return the coordinates of the multilinestring.
+ * @return {Array.<Array.<ol.Coordinate>>} Coordinates.
+ * @api stable
+ */
+ol.geom.MultiLineString.prototype.getCoordinates = function() {
+ return ol.geom.flat.inflate.coordinatess(
+ this.flatCoordinates, 0, this.ends_, this.stride);
+};
+
+
+/**
+ * @return {Array.<number>} Ends.
+ */
+ol.geom.MultiLineString.prototype.getEnds = function() {
+ return this.ends_;
+};
+
+
+/**
+ * Return the linestring at the specified index.
+ * @param {number} index Index.
+ * @return {ol.geom.LineString} LineString.
+ * @api stable
+ */
+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;
+};
+
+
+/**
+ * Return the linestrings of this multilinestring.
+ * @return {Array.<ol.geom.LineString>} LineStrings.
+ * @api stable
+ */
+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;
+};
+
+
+/**
+ * @return {Array.<number>} Flat midpoints.
+ */
+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;
+ }
+ return midpoints;
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.geom.MultiLineString.prototype.getType = function() {
+ return ol.geom.GeometryType.MULTI_LINE_STRING;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.geom.MultiLineString.prototype.intersectsExtent = function(extent) {
+ return ol.geom.flat.intersectsextent.lineStrings(
+ this.flatCoordinates, 0, this.ends_, this.stride, extent);
+};
+
+
+/**
+ * Set the coordinates of the multilinestring.
+ * @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 (!coordinates) {
+ this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
+ } else {
+ this.setLayout(opt_layout, coordinates, 2);
+ if (!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();
+ }
+};
+
+
+/**
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Array.<number>} ends Ends.
+ */
+ol.geom.MultiLineString.prototype.setFlatCoordinates =
+ function(layout, flatCoordinates, ends) {
+ if (!flatCoordinates) {
+ goog.asserts.assert(ends && ends.length === 0,
+ 'ends must be truthy and ends.length should be 0');
+ } 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],
+ 'length of flatCoordinates array should match the last value of ends');
+ }
+ this.setFlatCoordinatesInternal(layout, flatCoordinates);
+ this.ends_ = ends;
+ this.changed();
+};
+
+
+/**
+ * @param {Array.<ol.geom.LineString>} lineStrings LineStrings.
+ */
+ol.geom.MultiLineString.prototype.setLineStrings = function(lineStrings) {
+ var layout = this.getLayout();
+ 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.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, opt_layout);
+};
+goog.inherits(ol.geom.MultiPoint, ol.geom.SimpleGeometry);
+
+
+/**
+ * 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 (!this.flatCoordinates) {
+ this.flatCoordinates = point.getFlatCoordinates().slice();
+ } else {
+ goog.array.extend(this.flatCoordinates, point.getFlatCoordinates());
+ }
+ this.changed();
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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 = !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;
+};
+
+
+/**
+ * Return the points of this multipoint.
+ * @return {Array.<ol.geom.Point>} Points.
+ * @api stable
+ */
+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);
+ }
+ return points;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.geom.MultiPoint.prototype.getType = function() {
+ return ol.geom.GeometryType.MULTI_POINT;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+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;
+};
+
+
+/**
+ * Set the coordinates of the multipoint.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
+ */
+ol.geom.MultiPoint.prototype.setCoordinates =
+ function(coordinates, opt_layout) {
+ if (!coordinates) {
+ this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
+ } else {
+ this.setLayout(opt_layout, coordinates, 1);
+ if (!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.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');
+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
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
+ */
+ol.geom.MultiPolygon = function(coordinates, opt_layout) {
+
+ goog.base(this);
+
+ /**
+ * @type {Array.<Array.<number>>}
+ * @private
+ */
+ this.endss_ = [];
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.flatInteriorPointsRevision_ = -1;
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.flatInteriorPoints_ = null;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.maxDelta_ = -1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.maxDeltaRevision_ = -1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.orientedRevision_ = -1;
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.orientedFlatCoordinates_ = null;
+
+ this.setCoordinates(coordinates, opt_layout);
+
+};
+goog.inherits(ol.geom.MultiPolygon, ol.geom.SimpleGeometry);
+
+
+/**
+ * Append the passed polygon to this multipolygon.
+ * @param {ol.geom.Polygon} polygon Polygon.
+ * @api stable
+ */
+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 (!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();
+};
+
+
+/**
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.MultiPolygon} Clone.
+ * @api stable
+ */
+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.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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.MultiPolygon.prototype.containsXY = function(x, y) {
+ return ol.geom.flat.contains.linearRingssContainsXY(
+ this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, x, y);
+};
+
+
+/**
+ * Return the area of the multipolygon on projected plane.
+ * @return {number} Area (on projected plane).
+ * @api stable
+ */
+ol.geom.MultiPolygon.prototype.getArea = function() {
+ return ol.geom.flat.area.linearRingss(
+ this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride);
+};
+
+
+/**
+ * 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 (opt_right !== undefined) {
+ 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);
+};
+
+
+/**
+ * @return {Array.<Array.<number>>} Endss.
+ */
+ol.geom.MultiPolygon.prototype.getEndss = function() {
+ return this.endss_;
+};
+
+
+/**
+ * @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_;
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * @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 {
+ 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_;
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * Return the polygon at the specified index.
+ * @param {number} index Index.
+ * @return {ol.geom.Polygon} Polygon.
+ * @api stable
+ */
+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;
+};
+
+
+/**
+ * Return the polygons of this multipolygon.
+ * @return {Array.<ol.geom.Polygon>} Polygons.
+ * @api stable
+ */
+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;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.geom.MultiPolygon.prototype.getType = function() {
+ return ol.geom.GeometryType.MULTI_POLYGON;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.geom.MultiPolygon.prototype.intersectsExtent = function(extent) {
+ return ol.geom.flat.intersectsextent.linearRingss(
+ this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, extent);
+};
+
+
+/**
+ * Set the coordinates of the multipolygon.
+ * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
+ */
+ol.geom.MultiPolygon.prototype.setCoordinates =
+ function(coordinates, opt_layout) {
+ if (!coordinates) {
+ this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.endss_);
+ } else {
+ this.setLayout(opt_layout, coordinates, 3);
+ if (!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();
+ }
+};
+
+
+/**
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Array.<Array.<number>>} endss Endss.
+ */
+ol.geom.MultiPolygon.prototype.setFlatCoordinates =
+ function(layout, flatCoordinates, endss) {
+ goog.asserts.assert(endss, 'endss must be truthy');
+ if (!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 {Array.<ol.geom.Polygon>} polygons Polygons.
+ */
+ol.geom.MultiPolygon.prototype.setPolygons = function(polygons) {
+ var layout = this.getLayout();
+ 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.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');
+
+
+
+/**
+ * @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.EsriJSON = function(opt_options) {
+
+ var options = 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 {EsriJSONGeometry} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @private
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.EsriJSON.readGeometry_ = function(object, opt_options) {
+ if (!object) {
+ return null;
+ }
+ var type;
+ if (goog.isNumber(object.x) && goog.isNumber(object.y)) {
+ type = ol.geom.GeometryType.POINT;
+ } else if (object.points) {
+ type = ol.geom.GeometryType.MULTI_POINT;
+ } else if (object.paths) {
+ if (object.paths.length === 1) {
+ type = ol.geom.GeometryType.LINE_STRING;
+ } else {
+ type = ol.geom.GeometryType.MULTI_LINE_STRING;
+ }
+ } else if (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(type, 'geometry type should be defined');
+ var geometryReader = ol.format.EsriJSON.GEOMETRY_READERS_[type];
+ goog.asserts.assert(geometryReader,
+ 'geometryReader should be defined');
+ return /** @type {ol.geom.Geometry} */ (
+ ol.format.Feature.transformWithOptions(
+ geometryReader(object), false, opt_options));
+};
+
+
+/**
+ * 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.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 {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Geometry} Point.
+ */
+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 (object.m !== undefined && object.z !== undefined) {
+ point = new ol.geom.Point([object.x, object.y, object.z, object.m],
+ ol.geom.GeometryLayout.XYZM);
+ } else if (object.z !== undefined) {
+ point = new ol.geom.Point([object.x, object.y, object.z],
+ ol.geom.GeometryLayout.XYZ);
+ } else if (object.m !== undefined) {
+ point = new ol.geom.Point([object.x, object.y, object.m],
+ ol.geom.GeometryLayout.XYM);
+ } else {
+ point = new ol.geom.Point([object.x, object.y]);
+ }
+ return point;
+};
+
+
+/**
+ * @param {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Geometry} LineString.
+ */
+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 {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Geometry} MultiLineString.
+ */
+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);
+};
+
+
+/**
+ * @param {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.GeometryLayout} The geometry layout to use.
+ */
+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;
+};
+
+
+/**
+ * @param {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Geometry} MultiPoint.
+ */
+ol.format.EsriJSON.readMultiPointGeometry_ = function(object) {
+ goog.asserts.assert(object.points, 'object.points should be defined');
+ var layout = ol.format.EsriJSON.getGeometryLayout_(object);
+ return new ol.geom.MultiPoint(object.points, layout);
+};
+
+
+/**
+ * @param {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Geometry} MultiPolygon.
+ */
+ol.format.EsriJSON.readMultiPolygonGeometry_ = function(object) {
+ goog.asserts.assert(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);
+};
+
+
+/**
+ * @param {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Geometry} Polygon.
+ */
+ol.format.EsriJSON.readPolygonGeometry_ = function(object) {
+ goog.asserts.assert(object.rings);
+ var layout = ol.format.EsriJSON.getGeometryLayout_(object);
+ return new ol.geom.Polygon(object.rings, layout);
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {EsriJSONGeometry} EsriJSON geometry.
+ */
+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');
+ }
+};
+
+
+/**
+ * @param {ol.geom.SimpleGeometry} geometry Geometry.
+ * @private
+ * @return {Object} Object with boolean hasZ and hasM keys.
+ */
+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)
+ };
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {EsriJSONPolyline} EsriJSON geometry.
+ */
+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()]
+ });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @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)
+ });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {EsriJSONPolyline} EsriJSON geometry.
+ */
+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()
+ });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {EsriJSONMultipoint} EsriJSON geometry.
+ */
+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()
+ });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {EsriJSONPolygon} EsriJSON geometry.
+ */
+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_;
+
+
+/**
+ * @const
+ * @private
+ * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (EsriJSONGeometry)>}
+ */
+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_;
+
+
+/**
+ * Read a feature from a EsriJSON Feature source. Only works for Feature,
+ * use `readFeatures` to read FeatureCollection source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ * @api
+ */
+ol.format.EsriJSON.prototype.readFeature;
+
+
+/**
+ * Read all features from a EsriJSON 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
+ */
+ol.format.EsriJSON.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.EsriJSON.prototype.readFeatureFromObject = function(
+ object, opt_options) {
+ var esriJSONFeature = /** @type {EsriJSONFeature} */ (object);
+ goog.asserts.assert(esriJSONFeature.geometry ||
+ esriJSONFeature.attributes,
+ 'geometry or attributes should be defined');
+ var geometry = ol.format.EsriJSON.readGeometry_(esriJSONFeature.geometry,
+ opt_options);
+ var feature = new ol.Feature();
+ if (this.geometryName_) {
+ feature.setGeometryName(this.geometryName_);
+ }
+ feature.setGeometry(geometry);
+ if (opt_options && opt_options.idField &&
+ 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 (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 = opt_options ? opt_options : {};
+ if (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)];
+ }
+};
+
+
+/**
+ * Read a geometry from a EsriJSON source.
+ *
+ * @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.EsriJSON.prototype.readGeometry;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.EsriJSON.prototype.readGeometryFromObject = function(
+ object, opt_options) {
+ return ol.format.EsriJSON.readGeometry_(
+ /** @type {EsriJSONGeometry} */ (object), opt_options);
+};
+
+
+/**
+ * Read the projection from a EsriJSON source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
+ */
+ol.format.EsriJSON.prototype.readProjection;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.EsriJSON.prototype.readProjectionFromObject = function(object) {
+ var esriJSONObject = /** @type {EsriJSONObject} */ (object);
+ if (esriJSONObject.spatialReference && esriJSONObject.spatialReference.wkid) {
+ var crs = esriJSONObject.spatialReference.wkid;
+ return ol.proj.get('EPSG:' + crs);
+ } else {
+ return null;
+ }
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {EsriJSONGeometry} EsriJSON geometry.
+ */
+ol.format.EsriJSON.writeGeometry_ = function(geometry, opt_options) {
+ var geometryWriter = ol.format.EsriJSON.GEOMETRY_WRITERS_[geometry.getType()];
+ goog.asserts.assert(geometryWriter, 'geometryWriter should be defined');
+ return geometryWriter(/** @type {ol.geom.Geometry} */ (
+ ol.format.Feature.transformWithOptions(geometry, true, opt_options)),
+ opt_options);
+};
+
+
+/**
+ * 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.EsriJSON.prototype.writeGeometry;
+
+
+/**
+ * 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.EsriJSON.prototype.writeGeometryObject = function(geometry,
+ opt_options) {
+ return ol.format.EsriJSON.writeGeometry_(geometry,
+ this.adaptOptions(opt_options));
+};
+
+
+/**
+ * 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.EsriJSON.prototype.writeFeature;
+
+
+/**
+ * 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.EsriJSON.prototype.writeFeatureObject = function(
+ feature, opt_options) {
+ opt_options = this.adaptOptions(opt_options);
+ var object = {};
+ var geometry = feature.getGeometry();
+ if (geometry) {
+ object['geometry'] =
+ ol.format.EsriJSON.writeGeometry_(geometry, opt_options);
+ }
+ var properties = feature.getProperties();
+ delete properties[feature.getGeometryName()];
+ if (!goog.object.isEmpty(properties)) {
+ object['attributes'] = properties;
+ } else {
+ object['attributes'] = {};
+ }
+ if (opt_options && opt_options.featureProjection) {
+ object['spatialReference'] = /** @type {EsriJSONCRS} */({
+ wkid: ol.proj.get(
+ opt_options.featureProjection).getCode().split(':').pop()
+ });
+ }
+ return object;
+};
+
+
+/**
+ * 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.EsriJSON.prototype.writeFeatures;
+
+
+/**
+ * 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.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.geom.GeometryCollection');
+
+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');
+
+
+
+/**
+ * @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) {
+
+ goog.base(this);
+
+ /**
+ * @private
+ * @type {Array.<ol.geom.Geometry>}
+ */
+ this.geometries_ = opt_geometries ? opt_geometries : null;
+
+ this.listenGeometriesChange_();
+};
+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;
+};
+
+
+/**
+ * @private
+ */
+ol.geom.GeometryCollection.prototype.unlistenGeometriesChange_ = function() {
+ var i, ii;
+ if (!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
+ */
+ol.geom.GeometryCollection.prototype.listenGeometriesChange_ = function() {
+ var i, ii;
+ if (!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);
+ }
+};
+
+
+/**
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.GeometryCollection} Clone.
+ * @api stable
+ */
+ol.geom.GeometryCollection.prototype.clone = function() {
+ var geometryCollection = new ol.geom.GeometryCollection(null);
+ geometryCollection.setGeometries(this.geometries_);
+ return geometryCollection;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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.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;
+};
+
+
+/**
+ * Return the geometries that make up this geometry collection.
+ * @return {Array.<ol.geom.Geometry>} Geometries.
+ * @api stable
+ */
+ol.geom.GeometryCollection.prototype.getGeometries = function() {
+ return ol.geom.GeometryCollection.cloneGeometries_(this.geometries_);
+};
+
+
+/**
+ * @return {Array.<ol.geom.Geometry>} Geometries.
+ */
+ol.geom.GeometryCollection.prototype.getGeometriesArray = function() {
+ return this.geometries_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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.geom.GeometryCollection.prototype.getType = function() {
+ return ol.geom.GeometryType.GEOMETRY_COLLECTION;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+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;
+};
+
+
+/**
+ * @return {boolean} Is empty.
+ */
+ol.geom.GeometryCollection.prototype.isEmpty = function() {
+ return this.geometries_.length === 0;
+};
+
+
+/**
+ * Set the geometries that make up this geometry collection.
+ * @param {Array.<ol.geom.Geometry>} geometries Geometries.
+ * @api stable
+ */
+ol.geom.GeometryCollection.prototype.setGeometries = function(geometries) {
+ this.setGeometriesArray(
+ ol.geom.GeometryCollection.cloneGeometries_(geometries));
+};
+
+
+/**
+ * @param {Array.<ol.geom.Geometry>} geometries Geometries.
+ */
+ol.geom.GeometryCollection.prototype.setGeometriesArray = function(geometries) {
+ this.unlistenGeometriesChange_();
+ this.geometries_ = geometries;
+ this.listenGeometriesChange_();
+ this.changed();
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+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();
+};
+
+
+/**
+ * Translate the geometry.
+ * @param {number} deltaX Delta X.
+ * @param {number} deltaY Delta Y.
+ * @api
+ */
+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();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.GeometryCollection.prototype.disposeInternal = function() {
+ this.unlistenGeometriesChange_();
+ goog.base(this, 'disposeInternal');
+};
+
+// TODO: serialize dataProjection as crs member when writing
+// see https://github.com/openlayers/ol3/issues/2078
+
+goog.provide('ol.format.GeoJSON');
+
+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.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 GeoJSON format.
+ *
+ * @constructor
+ * @extends {ol.format.JSONFeature}
+ * @param {olx.format.GeoJSONOptions=} opt_options Options.
+ * @api stable
+ */
+ol.format.GeoJSON = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ goog.base(this);
+
+ /**
+ * @inheritDoc
+ */
+ this.defaultDataProjection = ol.proj.get(
+ options.defaultDataProjection ?
+ options.defaultDataProjection : 'EPSG:4326');
+
+
+ /**
+ * Name of the geometry attribute for features.
+ * @type {string|undefined}
+ * @private
+ */
+ this.geometryName_ = options.geometryName;
+
+};
+goog.inherits(ol.format.GeoJSON, ol.format.JSONFeature);
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.GeoJSON.EXTENSIONS_ = ['.geojson'];
+
+
+/**
+ * @param {GeoJSONGeometry|GeoJSONGeometryCollection} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @private
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.GeoJSON.readGeometry_ = function(object, opt_options) {
+ if (!object) {
+ return null;
+ }
+ var geometryReader = ol.format.GeoJSON.GEOMETRY_READERS_[object.type];
+ goog.asserts.assert(geometryReader, 'geometryReader should be defined');
+ 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.
+ * @private
+ * @return {ol.geom.GeometryCollection} Geometry collection.
+ */
+ol.format.GeoJSON.readGeometryCollectionGeometry_ = function(
+ object, opt_options) {
+ goog.asserts.assert(object.type == 'GeometryCollection',
+ 'object.type should be GeometryCollection');
+ var geometries = object.geometries.map(
+ /**
+ * @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 {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Point} Point.
+ */
+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 {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.LineString} LineString.
+ */
+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 {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.MultiLineString} MultiLineString.
+ */
+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 {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.MultiPoint} MultiPoint.
+ */
+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 {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.MultiPolygon} MultiPolygon.
+ */
+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 {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Polygon} Polygon.
+ */
+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 {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @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(geometryWriter, 'geometryWriter should be defined');
+ return geometryWriter(/** @type {ol.geom.Geometry} */ (
+ ol.format.Feature.transformWithOptions(geometry, true, opt_options)),
+ opt_options);
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @private
+ * @return {GeoJSONGeometryCollection} Empty GeoJSON geometry collection.
+ */
+ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ = function(geometry) {
+ return /** @type {GeoJSONGeometryCollection} */ ({
+ type: 'GeometryCollection',
+ geometries: []
+ });
+};
+
+
+/**
+ * @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,
+ 'geometry should be an ol.geom.GeometryCollection');
+ var geometries = geometry.getGeometriesArray().map(function(geometry) {
+ return ol.format.GeoJSON.writeGeometry_(geometry, opt_options);
+ });
+ return /** @type {GeoJSONGeometryCollection} */ ({
+ type: 'GeometryCollection',
+ geometries: geometries
+ });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
+ */
+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()
+ });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
+ */
+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()
+ });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
+ */
+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()
+ });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
+ */
+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 (opt_options) {
+ right = opt_options.rightHanded;
+ }
+ return /** @type {GeoJSONGeometry} */ ({
+ type: 'MultiPolygon',
+ coordinates: geometry.getCoordinates(right)
+ });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
+ */
+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 (opt_options) {
+ right = opt_options.rightHanded;
+ }
+ return /** @type {GeoJSONGeometry} */ ({
+ type: 'Polygon',
+ coordinates: geometry.getCoordinates(right)
+ });
+};
+
+
+/**
+ * @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_
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (GeoJSONGeometry|GeoJSONGeometryCollection)>}
+ */
+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 (this.geometryName_) {
+ feature.setGeometryName(this.geometryName_);
+ }
+ feature.setGeometry(geometry);
+ if (geoJSONFeature.id !== undefined) {
+ feature.setId(geoJSONFeature.id);
+ }
+ if (geoJSONFeature.properties) {
+ feature.setProperties(geoJSONFeature.properties);
+ }
+ return feature;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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;
+ } else {
+ goog.asserts.fail('Unknown geoJSONObject.type: ' + geoJSONObject.type);
+ return [];
+ }
+};
+
+
+/**
+ * Read a geometry from a GeoJSON source.
+ *
+ * @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.GeoJSON.prototype.readGeometry;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GeoJSON.prototype.readGeometryFromObject = function(
+ object, opt_options) {
+ return ol.format.GeoJSON.readGeometry_(
+ /** @type {GeoJSONGeometry} */ (object), opt_options);
+};
+
+
+/**
+ * Read the projection from a GeoJSON source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.GeoJSON.prototype.readProjection;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GeoJSON.prototype.readProjectionFromObject = function(object) {
+ var geoJSONObject = /** @type {GeoJSONObject} */ (object);
+ var crs = geoJSONObject.crs;
+ if (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('Unknown crs.type: ' + crs.type);
+ return null;
+ }
+ } else {
+ return this.defaultDataProjection;
+ }
+};
+
+
+/**
+ * Encode a feature as a GeoJSON Feature string.
+ *
+ * @function
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} GeoJSON.
+ * @api stable
+ */
+ol.format.GeoJSON.prototype.writeFeature;
+
+
+/**
+ * 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.GeoJSON.prototype.writeFeatureObject = function(
+ feature, opt_options) {
+ opt_options = this.adaptOptions(opt_options);
+ var object = {
+ 'type': 'Feature'
+ };
+ var id = feature.getId();
+ if (id !== undefined) {
+ object['id'] = id;
+ }
+ var geometry = feature.getGeometry();
+ if (geometry) {
+ object['geometry'] =
+ ol.format.GeoJSON.writeGeometry_(geometry, opt_options);
+ } else {
+ object['geometry'] = null;
+ }
+ var properties = feature.getProperties();
+ delete properties[feature.getGeometryName()];
+ if (!goog.object.isEmpty(properties)) {
+ object['properties'] = properties;
+ } else {
+ object['properties'] = null;
+ }
+ return object;
+};
+
+
+/**
+ * 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.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 stable
+ */
+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));
+ }
+ return /** @type {GeoJSONFeatureCollection} */ ({
+ type: 'FeatureCollection',
+ features: objects
+ });
+};
+
+
+/**
+ * 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.GeoJSON.prototype.writeGeometry;
+
+
+/**
+ * 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.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');
+
+
+
+/**
+ * @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.XMLFeature = function() {
+ goog.base(this);
+};
+goog.inherits(ol.format.XMLFeature, ol.format.Feature);
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.XMLFeature.prototype.getType = function() {
+ return ol.format.FormatType.XML;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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;
+ }
+};
+
+
+/**
+ * @param {Document} doc Document.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @return {ol.Feature} Feature.
+ */
+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;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @return {ol.Feature} Feature.
+ */
+ol.format.XMLFeature.prototype.readFeatureFromNode = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+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 [];
+ }
+};
+
+
+/**
+ * @param {Document} doc Document.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @protected
+ * @return {Array.<ol.Feature>} Features.
+ */
+ol.format.XMLFeature.prototype.readFeaturesFromDocument = function(
+ doc, opt_options) {
+ /** @type {Array.<ol.Feature>} */
+ var features = [];
+ var n;
+ for (n = doc.firstChild; n; n = n.nextSibling) {
+ if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+ goog.array.extend(features, this.readFeaturesFromNode(n, opt_options));
+ }
+ }
+ return features;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @protected
+ * @return {Array.<ol.Feature>} Features.
+ */
+ol.format.XMLFeature.prototype.readFeaturesFromNode = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+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;
+ }
+};
+
+
+/**
+ * @param {Document} doc Document.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @protected
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.XMLFeature.prototype.readGeometryFromDocument = goog.abstractMethod;
+
+
+/**
+ * @param {Node} node Node.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @protected
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.XMLFeature.prototype.readGeometryFromNode = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+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;
+ }
+};
+
+
+/**
+ * @param {Document} doc Document.
+ * @protected
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.XMLFeature.prototype.readProjectionFromDocument = function(doc) {
+ return this.defaultDataProjection;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @protected
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.XMLFeature.prototype.readProjectionFromNode = function(node) {
+ return this.defaultDataProjection;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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));
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @protected
+ * @return {Node} Node.
+ */
+ol.format.XMLFeature.prototype.writeFeatureNode = goog.abstractMethod;
+
+
+/**
+ * @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));
+};
+
+
+/**
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ */
+ol.format.XMLFeature.prototype.writeFeaturesNode = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+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));
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ */
+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');
+
+
+
+/**
+ * @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.
+ *
+ * @constructor
+ * @param {olx.format.GMLOptions=} opt_options
+ * Optional configuration object.
+ * @extends {ol.format.XMLFeature}
+ */
+ol.format.GMLBase = function(opt_options) {
+ var options = /** @type {olx.format.GMLOptions} */
+ (opt_options ? opt_options : {});
+
+ /**
+ * @protected
+ * @type {Array.<string>|string|undefined}
+ */
+ this.featureType = options.featureType;
+
+ /**
+ * @protected
+ * @type {Object.<string, string>|string|undefined}
+ */
+ this.featureNS = options.featureNS;
+
+ /**
+ * @protected
+ * @type {string}
+ */
+ this.srsName = options.srsName;
+
+ /**
+ * @protected
+ * @type {string}
+ */
+ this.schemaLocation = '';
+
+ /**
+ * @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);
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.GMLBase.GMLNS = 'http://www.opengis.net/gml';
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<ol.Feature>} Features.
+ */
+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 (!featureType && 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 (featureType.indexOf(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 (!features) {
+ features = [];
+ }
+ return features;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.Geometry|undefined} Geometry.
+ */
+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 (geometry) {
+ return /** @type {ol.geom.Geometry} */ (
+ ol.format.Feature.transformWithOptions(geometry, false, context));
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.Feature} Feature.
+ */
+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; 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 (geometryName) {
+ feature.setGeometryName(geometryName);
+ }
+ if (fid) {
+ feature.setId(fid);
+ }
+ return feature;
+};
+
+
+/**
+ * @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 (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;
+ }
+};
+
+
+/**
+ * @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 (coordinates) {
+ return new ol.geom.MultiPoint(coordinates);
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @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 (lineStrings) {
+ var multiLineString = new ol.geom.MultiLineString(null);
+ multiLineString.setLineStrings(lineStrings);
+ return multiLineString;
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @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 (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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (flatCoordinates) {
+ var lineString = new ol.geom.LineString(null);
+ lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
+ return lineString;
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} LinearRing flat coordinates.
+ */
+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 (ring) {
+ return ring;
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.LinearRing|undefined} LinearRing.
+ */
+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 (flatCoordinates) {
+ var ring = new ol.geom.LinearRing(null);
+ ring.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
+ return ring;
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.Polygon|undefined} Polygon.
+ */
+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 (flatLinearRings && 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;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>} Flat coordinates.
+ */
+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));
+};
+
+
+/**
+ * @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_)
+ }
+});
+
+
+/**
+ * @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
+ */
+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_)
+ }
+});
+
+
+/**
+ * @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_)
+ }
+});
+
+
+/**
+ * @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)
+ }
+});
+
+
+/**
+ * @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)
+ }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @protected
+ */
+ol.format.GMLBase.prototype.RING_PARSERS = Object({
+ 'http://www.opengis.net/gml' : {
+ 'LinearRing': ol.xml.makeReplacer(
+ ol.format.GMLBase.prototype.readFlatLinearRing_)
+ }
+});
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GMLBase.prototype.readGeometryFromNode =
+ function(node, opt_options) {
+ var geometry = this.readGeometryElement(node,
+ [this.getReadOptions(node, opt_options ? opt_options : {})]);
+ return geometry ? geometry : null;
+};
+
+
+/**
+ * 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.GMLBase.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GMLBase.prototype.readFeaturesFromNode =
+ function(node, opt_options) {
+ var options = {
+ featureType: this.featureType,
+ featureNS: this.featureNS
+ };
+ if (opt_options) {
+ goog.object.extend(options, this.getReadOptions(node, opt_options));
+ }
+ return this.readFeaturesInternal(node, [options]);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GMLBase.prototype.readProjectionFromNode = function(node) {
+ return ol.proj.get(this.srsName_ ? this.srsName_ :
+ node.firstElementChild.getAttribute('srsName'));
+};
+
+goog.provide('ol.format.XSD');
+
+goog.require('goog.asserts');
+goog.require('goog.string');
+goog.require('ol');
+goog.require('ol.xml');
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.XSD.NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema';
+
+
+/**
+ * @param {Node} node Node.
+ * @return {boolean|undefined} Boolean.
+ */
+ol.format.XSD.readBoolean = function(node) {
+ var s = ol.xml.getAllTextContent(node, false);
+ return ol.format.XSD.readBooleanString(s);
+};
+
+
+/**
+ * @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 m[1] !== undefined || false;
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {number|undefined} DateTime in seconds.
+ */
+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 (m[10] !== undefined) {
+ dateTime += sign * 60 * 60 * parseInt(m[10], 10);
+ }
+ }
+ return dateTime;
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {number|undefined} Decimal.
+ */
+ol.format.XSD.readDecimal = function(node) {
+ var s = ol.xml.getAllTextContent(node, false);
+ return ol.format.XSD.readDecimalString(s);
+};
+
+
+/**
+ * @param {string} string String.
+ * @return {number|undefined} Decimal.
+ */
+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;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {number|undefined} Non negative integer.
+ */
+ol.format.XSD.readNonNegativeInteger = function(node) {
+ var s = ol.xml.getAllTextContent(node, false);
+ return ol.format.XSD.readNonNegativeIntegerString(s);
+};
+
+
+/**
+ * @param {string} string String.
+ * @return {number|undefined} Non negative integer.
+ */
+ol.format.XSD.readNonNegativeIntegerString = function(string) {
+ var m = /^\s*(\d+)\s*$/.exec(string);
+ if (m) {
+ return parseInt(m[1], 10);
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {string|undefined} String.
+ */
+ol.format.XSD.readString = function(node) {
+ return ol.xml.getAllTextContent(node, false).trim();
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the boolean to.
+ * @param {boolean} bool Boolean.
+ */
+ol.format.XSD.writeBooleanTextNode = function(node, bool) {
+ ol.format.XSD.writeStringTextNode(node, (bool) ? '1' : '0');
+};
+
+
+/**
+ * @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));
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the decimal to.
+ * @param {number} decimal Decimal.
+ */
+ol.format.XSD.writeDecimalTextNode = function(node, decimal) {
+ var string = decimal.toPrecision();
+ node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the decimal to.
+ * @param {number} nonNegativeInteger Non negative integer.
+ */
+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));
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the string to.
+ * @param {string} string String.
+ */
+ol.format.XSD.writeStringTextNode = function(node, string) {
+ node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
+};
+
+goog.provide('ol.format.GML2');
+
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('ol.extent');
+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} */
+ (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 = options.schemaLocation ?
+ options.schemaLocation : ol.format.GML2.schemaLocation_;
+
+};
+goog.inherits(ol.format.GML2, ol.format.GMLBase);
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+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.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 (containerSrs) {
+ var proj = ol.proj.get(containerSrs);
+ if (proj) {
+ axisOrientation = proj.getAxisOrientation();
+ }
+ }
+ var coords = s.split(/[\s,]+/);
+ // The "dimension" attribute is from the GML 3.0.1 spec.
+ var dim = 2;
+ if (node.getAttribute('srsDimension')) {
+ dim = ol.format.XSD.readNonNegativeIntegerString(
+ node.getAttribute('srsDimension'));
+ } else if (node.getAttribute('dimension')) {
+ dim = ol.format.XSD.readNonNegativeIntegerString(
+ node.getAttribute('dimension'));
+ } else if (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;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Extent|undefined} Envelope.
+ */
+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]);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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),
+ this.RING_PARSERS, node, objectStack, this);
+ if (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 larger than 0');
+ flatLinearRings.push(flatLinearRing);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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),
+ this.RING_PARSERS, node, objectStack, this);
+ if (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 larger than 0');
+ flatLinearRings[0] = flatLinearRing;
+ }
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML2.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = Object({
+ 'http://www.opengis.net/gml' : {
+ 'coordinates': ol.xml.makeReplacer(
+ ol.format.GML2.prototype.readFlatCoordinates_)
+ }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+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_
+ }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML2.prototype.BOX_PARSERS_ = Object({
+ 'http://www.opengis.net/gml' : {
+ 'coordinates': ol.xml.makeArrayPusher(
+ ol.format.GML2.prototype.readFlatCoordinates_)
+ }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+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.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');
+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
+ * 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
+ */
+ol.format.GML3 = function(opt_options) {
+ var options = /** @type {olx.format.GMLOptions} */
+ (opt_options ? opt_options : {});
+
+ goog.base(this, options);
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.surface_ = options.surface !== undefined ? options.surface : false;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.curve_ = options.curve !== undefined ? options.curve : false;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.multiCurve_ = options.multiCurve !== undefined ?
+ options.multiCurve : true;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.multiSurface_ = options.multiSurface !== undefined ?
+ options.multiSurface : true;
+
+ /**
+ * @inheritDoc
+ */
+ this.schemaLocation = options.schemaLocation ?
+ options.schemaLocation : ol.format.GML3.schemaLocation_;
+
+};
+goog.inherits(ol.format.GML3, ol.format.GMLBase);
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+ol.format.GML3.schemaLocation_ = ol.format.GMLBase.GMLNS +
+ ' http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' +
+ '1.0.0/gmlsf.xsd';
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.MultiLineString|undefined} MultiLineString.
+ */
+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 (lineStrings) {
+ var multiLineString = new ol.geom.MultiLineString(null);
+ multiLineString.setLineStrings(lineStrings);
+ return multiLineString;
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.MultiPolygon|undefined} MultiPolygon.
+ */
+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 (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.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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<(Array.<number>)>|undefined} flat coordinates.
+ */
+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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} flat coordinates.
+ */
+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);
+};
+
+
+/**
+ * @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,
+ '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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} flat coordinates.
+ */
+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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (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);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (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;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Polygon|undefined} Polygon.
+ */
+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 (flatLinearRings && 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;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.LineString|undefined} LineString.
+ */
+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 (flatCoordinates) {
+ var lineString = new ol.geom.LineString(null);
+ lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
+ return lineString;
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Extent|undefined} Envelope.
+ */
+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]);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} Flat coordinates.
+ */
+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 (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;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} Flat coordinates.
+ */
+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 (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 (node.getAttribute('srsDimension')) {
+ dim = ol.format.XSD.readNonNegativeIntegerString(
+ node.getAttribute('srsDimension'));
+ } else if (node.getAttribute('dimension')) {
+ dim = ol.format.XSD.readNonNegativeIntegerString(
+ node.getAttribute('dimension'));
+ } else if (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;
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+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 {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+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 {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+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 {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+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 {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+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 {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+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 {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+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 {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.SURFACE_PARSERS_ = Object({
+ 'http://www.opengis.net/gml' : {
+ 'patches': ol.xml.makeReplacer(ol.format.GML3.prototype.readPatch_)
+ }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.CURVE_PARSERS_ = Object({
+ 'http://www.opengis.net/gml' : {
+ 'segments': ol.xml.makeReplacer(ol.format.GML3.prototype.readSegment_)
+ }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+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 {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.PATCHES_PARSERS_ = Object({
+ 'http://www.opengis.net/gml' : {
+ 'PolygonPatch': ol.xml.makeReplacer(
+ ol.format.GML3.prototype.readPolygonPatch_)
+ }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.SEGMENTS_PARSERS_ = Object({
+ 'http://www.opengis.net/gml' : {
+ 'LineStringSegment': ol.xml.makeReplacer(
+ ol.format.GML3.prototype.readLineStringSegment_)
+ }
+});
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Point} value Point geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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 (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);
+};
+
+
+/**
+ * @param {Array.<number>} point Point geometry.
+ * @param {string=} opt_srsName Optional srsName
+ * @return {string}
+ * @private
+ */
+ol.format.GML3.prototype.getCoords_ = function(point, opt_srsName) {
+ var axisOrientation = 'enu';
+ if (opt_srsName) {
+ axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation();
+ }
+ return ((axisOrientation.substr(0, 2) === 'en') ?
+ point[0] + ' ' + point[1] :
+ point[1] + ' ' + point[0]);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString|ol.geom.LinearRing} value Geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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(' '));
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Point} geometry Point geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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 (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ var pos = ol.xml.createElementNS(node.namespaceURI, 'pos');
+ node.appendChild(pos);
+ this.writePos_(pos, geometry, objectStack);
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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.
+ * @param {ol.Extent} extent Extent.
+ * @param {Array.<*>} objectStack Node stack.
+ */
+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 (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
+ */
+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 (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ var posList = ol.xml.createElementNS(node.namespaceURI, 'posList');
+ node.appendChild(posList);
+ this.writePosList_(posList, geometry, objectStack);
+};
+
+
+/**
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node} Node.
+ * @private
+ */
+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 (exteriorWritten === undefined) {
+ context['exteriorWritten'] = true;
+ }
+ return ol.xml.createElementNS(parentNode.namespaceURI,
+ exteriorWritten !== undefined ? 'interior' : 'exterior');
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Polygon} geometry Polygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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' && 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
+ */
+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' && 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
+ */
+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 (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 {ol.geom.MultiPoint} geometry MultiPoint geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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 (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 {ol.geom.MultiLineString} geometry MultiLineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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 (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);
+};
+
+
+/**
+ * @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 {ol.geom.Polygon} polygon Polygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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 (child) {
+ node.appendChild(child);
+ this.writeSurfaceOrPolygon_(child, polygon, objectStack);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Point} point Point geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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 {ol.geom.LineString} line LineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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 (child) {
+ node.appendChild(child);
+ this.writeCurveOrLineString_(child, line, objectStack);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Polygon} polygon Polygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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 {ol.geom.LineString} line LineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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 {ol.geom.Geometry|ol.Extent} geometry Geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ */
+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 (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);
+ }
+ 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 {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
+ */
+ol.format.GML3.prototype.writeFeatureElement =
+ function(node, feature, objectStack) {
+ var fid = feature.getId();
+ if (fid) {
+ node.setAttribute('fid', fid);
+ }
+ 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 (!context.serializers) {
+ context.serializers = {};
+ context.serializers[featureNS] = {};
+ }
+ var properties = feature.getProperties();
+ var keys = [], values = [];
+ for (var key in properties) {
+ var value = properties[key];
+ if (value !== null) {
+ keys.push(key);
+ values.push(value);
+ if (key == geometryName || value instanceof ol.geom.Geometry) {
+ 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.<ol.Feature>} features Features.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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_)
+ }
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GML3.POINTMEMBER_SERIALIZERS_ = {
+ 'http://www.opengis.net/gml': {
+ 'pointMember': ol.xml.makeChildAppender(
+ ol.format.GML3.prototype.writePointMember_)
+ }
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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_)
+ }
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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_)
+ }
+};
+
+
+/**
+ * @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)
+ }
+};
+
+
+/**
+ * @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
+ */
+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]);
+};
+
+
+/**
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @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), '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';
+ }
+ } else {
+ nodeName = 'Envelope';
+ }
+ return ol.xml.createElementNS('http://www.opengis.net/gml',
+ nodeName);
+};
+
+
+/**
+ * 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.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 (opt_options) {
+ goog.object.extend(context, opt_options);
+ }
+ this.writeGeometryElement(geom, geometry, [context]);
+ return geom;
+};
+
+
+/**
+ * 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.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 (opt_options) {
+ goog.object.extend(context, opt_options);
+ }
+ this.writeFeatureMembers_(node, features, [context]);
+ return node;
+};
+
+
+
+/**
+ * @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.GML = ol.format.GML3;
+
+
+/**
+ * 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.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.GPX');
+
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('ol.Feature');
+goog.require('ol.array');
+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 = 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
+ * @private
+ * @type {Array.<string>}
+ */
+ol.format.GPX.NAMESPACE_URIS_ = [
+ null,
+ 'http://www.topografix.com/GPX/1/0',
+ 'http://www.topografix.com/GPX/1/1'
+];
+
+
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Node} node Node.
+ * @param {Object} values Values.
+ * @private
+ * @return {Array.<number>} Flat coordinates.
+ */
+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 ('ele' in values) {
+ flatCoordinates.push(/** @type {number} */ (values['ele']));
+ delete values['ele'];
+ } else {
+ flatCoordinates.push(0);
+ }
+ if ('time' in values) {
+ flatCoordinates.push(/** @type {number} */ (values['time']));
+ delete values['time'];
+ } else {
+ flatCoordinates.push(0);
+ }
+ return flatCoordinates;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (href !== null) {
+ values['link'] = href;
+ }
+ ol.xml.parseNode(ol.format.GPX.LINK_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (values) {
+ var rteValues = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+ var flatCoordinates = /** @type {Array.<number>} */
+ (rteValues['flatCoordinates']);
+ ol.format.GPX.appendCoordinate_(flatCoordinates, node, values);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (values) {
+ var trkValues = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+ var flatCoordinates = /** @type {Array.<number>} */
+ (trkValues['flatCoordinates']);
+ ol.format.GPX.appendCoordinate_(flatCoordinates, node, values);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (!values) {
+ return undefined;
+ }
+ var flatCoordinates = /** @type {Array.<number>} */
+ (values['flatCoordinates']);
+ delete 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 (!values) {
+ return undefined;
+ }
+ var flatCoordinates = /** @type {Array.<number>} */
+ (values['flatCoordinates']);
+ delete values['flatCoordinates'];
+ var ends = /** @type {Array.<number>} */ (values['ends']);
+ delete 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;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Waypoint.
+ */
+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 (!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, function(Node, Array.<*>): (ol.Feature|undefined)>}
+ * @private
+ */
+ol.format.GPX.FEATURE_READER_ = {
+ 'rte': ol.format.GPX.readRte_,
+ 'trk': ol.format.GPX.readTrk_,
+ 'wpt': ol.format.GPX.readWpt_
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.GPX_PARSERS_ = ol.xml.makeStructureNS(
+ 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_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.LINK_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.GPX.NAMESPACE_URIS_, {
+ 'text':
+ ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkText'),
+ 'type':
+ ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkType')
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.RTE_PARSERS_ = ol.xml.makeStructureNS(
+ 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_
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.RTEPT_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.GPX.NAMESPACE_URIS_, {
+ 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+ 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.TRK_PARSERS_ = ol.xml.makeStructureNS(
+ 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_
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.TRKSEG_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.GPX.NAMESPACE_URIS_, {
+ 'trkpt': ol.format.GPX.parseTrkPt_
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.TRKPT_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.GPX.NAMESPACE_URIS_, {
+ 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+ 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.WPT_PARSERS_ = ol.xml.makeStructureNS(
+ 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_
+ });
+
+
+/**
+ * @param {Array.<ol.Feature>} features
+ * @private
+ */
+ol.format.GPX.prototype.handleReadExtensions_ = function(features) {
+ if (!features) {
+ features = [];
+ }
+ for (var i = 0, ii = features.length; i < ii; ++i) {
+ var feature = features[i];
+ if (this.readExtensions_) {
+ var extensionsNode = feature.get('extensionsNode_') || null;
+ this.readExtensions_(feature, extensionsNode);
+ }
+ feature.set('extensionsNode_', undefined);
+ }
+};
+
+
+/**
+ * Read the first feature from a GPX 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.GPX.prototype.readFeature;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GPX.prototype.readFeatureFromNode = function(node, opt_options) {
+ goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT,
+ 'node.nodeType should be ELEMENT');
+ if (!ol.array.includes(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) {
+ return null;
+ }
+ var featureReader = ol.format.GPX.FEATURE_READER_[node.localName];
+ if (!featureReader) {
+ return null;
+ }
+ var feature = featureReader(node, [this.getReadOptions(node, opt_options)]);
+ if (!feature) {
+ return null;
+ }
+ this.handleReadExtensions_([feature]);
+ return feature;
+};
+
+
+/**
+ * Read all features from a GPX 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.GPX.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GPX.prototype.readFeaturesFromNode = function(node, opt_options) {
+ goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT,
+ 'node.nodeType should be ELEMENT');
+ if (!ol.array.includes(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 (features) {
+ this.handleReadExtensions_(features);
+ return features;
+ } else {
+ return [];
+ }
+ }
+ return [];
+};
+
+
+/**
+ * Read the projection from a GPX source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.GPX.prototype.readProjection;
+
+
+/**
+ * @param {Node} node Node.
+ * @param {string} value Value for the link's `href` attribute.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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.Coordinate} coordinate Coordinate.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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];
+ }
+ }
+ /* 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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (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.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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (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();
+ }
+ 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.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
+ */
+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 (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);
+ }
+};
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type'];
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS(
+ ol.format.GPX.NAMESPACE_URIS_, [
+ 'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'rtept'
+ ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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_))
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS(
+ ol.format.GPX.NAMESPACE_URIS_, [
+ 'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'trkseg'
+ ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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 {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt');
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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.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)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, string>}
+ * @private
+ */
+ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = {
+ 'Point': 'wpt',
+ 'LineString': 'rte',
+ 'MultiLineString': 'trk'
+};
+
+
+/**
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
+ */
+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 (geometry) {
+ var nodeName = ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_[geometry.getType()];
+ if (nodeName) {
+ 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, nodeName);
+ }
+ }
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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.
+
+/**
+ * @fileoverview Utilities for string newlines.
+ * @author nnaze@google.com (Nathan Naze)
+ */
+
+
+/**
+ * Namespace for string utilities
+ */
+goog.provide('goog.string.newlines');
+goog.provide('goog.string.newlines.Line');
+
+goog.require('goog.array');
+
+
+/**
+ * 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.
+ */
+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();
+ });
+};
+
+
+
+/**
+ * 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
+ */
+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;
+
+ /**
+ * 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}
+ */
+
+ this.endLineIndex = endLineIndex;
+};
+
+
+/**
+ * @return {string} The content of the line, excluding any newline characters.
+ */
+goog.string.newlines.Line.prototype.getContent = function() {
+ return this.string.substring(this.startLineIndex, this.endContentIndex);
+};
+
+
+/**
+ * @return {string} The full line, including any newline characters.
+ */
+goog.string.newlines.Line.prototype.getFullLine = function() {
+ return this.string.substring(this.startLineIndex, this.endLineIndex);
+};
+
+
+/**
+ * @return {string} The newline characters, if any ('\n', \r', '\r\n', '', etc).
+ */
+goog.string.newlines.Line.prototype.getNewline = function() {
+ return this.string.substring(this.endContentIndex, this.endLineIndex);
+};
+
+
+/**
+ * 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.
+ */
+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);
+
+ // 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');
+
+
+
+/**
+ * @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.TextFeature = function() {
+ goog.base(this);
+};
+goog.inherits(ol.format.TextFeature, ol.format.Feature);
+
+
+/**
+ * @param {Document|Node|Object|string} source Source.
+ * @private
+ * @return {string} Text.
+ */
+ol.format.TextFeature.prototype.getText_ = function(source) {
+ if (goog.isString(source)) {
+ return source;
+ } else {
+ goog.asserts.fail();
+ return '';
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.getType = function() {
+ return ol.format.FormatType.TEXT;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.readFeature = function(source, opt_options) {
+ return this.readFeatureFromText(
+ this.getText_(source), this.adaptOptions(opt_options));
+};
+
+
+/**
+ * @param {string} text Text.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {ol.Feature} Feature.
+ */
+ol.format.TextFeature.prototype.readFeatureFromText = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.readFeatures = function(source, opt_options) {
+ return this.readFeaturesFromText(
+ this.getText_(source), this.adaptOptions(opt_options));
+};
+
+
+/**
+ * @param {string} text Text.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {Array.<ol.Feature>} Features.
+ */
+ol.format.TextFeature.prototype.readFeaturesFromText = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.readGeometry = function(source, opt_options) {
+ return this.readGeometryFromText(
+ this.getText_(source), this.adaptOptions(opt_options));
+};
+
+
+/**
+ * @param {string} text Text.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.TextFeature.prototype.readGeometryFromText = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.readProjection = function(source) {
+ return this.readProjectionFromText(this.getText_(source));
+};
+
+
+/**
+ * @param {string} text Text.
+ * @protected
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.TextFeature.prototype.readProjectionFromText = function(text) {
+ return this.defaultDataProjection;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.writeFeature = function(feature, opt_options) {
+ return this.writeFeatureText(feature, this.adaptOptions(opt_options));
+};
+
+
+/**
+ * @param {ol.Feature} feature Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @protected
+ * @return {string} Text.
+ */
+ol.format.TextFeature.prototype.writeFeatureText = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.writeFeatures = function(
+ features, opt_options) {
+ return this.writeFeaturesText(features, this.adaptOptions(opt_options));
+};
+
+
+/**
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @protected
+ * @return {string} Text.
+ */
+ol.format.TextFeature.prototype.writeFeaturesText = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.writeGeometry = function(
+ geometry, opt_options) {
+ return this.writeGeometryText(geometry, this.adaptOptions(opt_options));
+};
+
+
+/**
+ * @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.string.newlines');
+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.proj');
+
+
+/**
+ * IGC altitude/z. One of 'barometric', 'gps', 'none'.
+ * @enum {string}
+ * @api
+ */
+ol.format.IGCZ = {
+ BAROMETRIC: 'barometric',
+ GPS: 'gps',
+ NONE: 'none'
+};
+
+
+
+/**
+ * @classdesc
+ * Feature format for `*.igc` flight recording files.
+ *
+ * @constructor
+ * @extends {ol.format.TextFeature}
+ * @param {olx.format.IGCOptions=} opt_options Options.
+ * @api
+ */
+ol.format.IGC = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ goog.base(this);
+
+ /**
+ * @inheritDoc
+ */
+ this.defaultDataProjection = ol.proj.get('EPSG:4326');
+
+ /**
+ * @private
+ * @type {ol.format.IGCZ}
+ */
+ this.altitudeMode_ = options.altitudeMode ?
+ options.altitudeMode : ol.format.IGCZ.NONE;
+
+};
+goog.inherits(ol.format.IGC, ol.format.TextFeature);
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.IGC.EXTENSIONS_ = ['.igc'];
+
+
+/**
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+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})/;
+
+
+/**
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+ol.format.IGC.H_RECORD_RE_ = /^H.([A-Z]{3}).*?:(.*)/;
+
+
+/**
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+ol.format.IGC.HFDTE_RECORD_RE_ = /^HFDTE(\d{2})(\d{2})(\d{2})/;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.IGC.prototype.getExtensions = function() {
+ return ol.format.IGC.EXTENSIONS_;
+};
+
+
+/**
+ * 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.IGC.prototype.readFeature;
+
+
+/**
+ * @inheritDoc
+ */
+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]] = m[2].trim();
+ 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 the feature from the source. As IGC sources contain a single
+ * feature, this will return the feature in an array.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
+ * @api
+ */
+ol.format.IGC.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.IGC.prototype.readFeaturesFromText = function(text, opt_options) {
+ var feature = this.readFeatureFromText(text, opt_options);
+ if (feature) {
+ return [feature];
+ } else {
+ return [];
+ }
+};
+
+
+/**
+ * Read the projection from the IGC source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
+ */
+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.
+
+/**
+ * @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.
+ *
+ */
+
+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');
+
+
+
+/**
+ * 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.
+ *
+ * @throws URIError If opt_uri is provided and URI is malformed (that is,
+ * if decodeURIComponent fails on any of the URI components).
+ * @constructor
+ * @struct
+ */
+goog.Uri = function(opt_uri, opt_ignoreCase) {
+ /**
+ * Scheme such as "http".
+ * @private {string}
+ */
+ this.scheme_ = '';
+
+ /**
+ * User credentials in the form "username:password".
+ * @private {string}
+ */
+ this.userInfo_ = '';
+
+ /**
+ * Domain part, e.g. "www.google.com".
+ * @private {string}
+ */
+ this.domain_ = '';
+
+ /**
+ * Port, e.g. 8080.
+ * @private {?number}
+ */
+ this.port_ = null;
+
+ /**
+ * Path, e.g. "/tests/img.png".
+ * @private {string}
+ */
+ this.path_ = '';
+
+ /**
+ * The fragment without the #.
+ * @private {string}
+ */
+ this.fragment_ = '';
+
+ /**
+ * Whether or not this Uri should be treated as Read Only.
+ * @private {boolean}
+ */
+ this.isReadOnly_ = false;
+
+ /**
+ * Whether or not to ignore case when comparing query params.
+ * @private {boolean}
+ */
+ this.ignoreCase_ = false;
+
+ /**
+ * Object representing query data.
+ * @private {!goog.Uri.QueryData}
+ */
+ this.queryData_;
+
+ // 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_);
+ }
+};
+
+
+/**
+ * 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;
+
+
+/**
+ * Parameter name added to stop caching.
+ * @type {string}
+ */
+goog.Uri.RANDOM_PARAM = goog.uri.utils.StandardQueryParam.RANDOM;
+
+
+/**
+ * @return {string} The string form of the url.
+ * @override
+ */
+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 || scheme == 'file') {
+ 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));
+ }
+
+ 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('');
+};
+
+
+/**
+ * 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.
+ */
+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();
+ }
+
+ if (overridden) {
+ absoluteUri.setUserInfo(relativeUri.getUserInfo());
+ } else {
+ overridden = relativeUri.hasDomain();
+ }
+
+ if (overridden) {
+ absoluteUri.setDomain(relativeUri.getDomain());
+ } else {
+ overridden = relativeUri.hasPort();
+ }
+
+ 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();
+ }
+
+ if (overridden) {
+ absoluteUri.setQueryData(relativeUri.getDecodedQuery());
+ } else {
+ overridden = relativeUri.hasFragment();
+ }
+
+ if (overridden) {
+ absoluteUri.setFragment(relativeUri.getFragment());
+ }
+
+ return absoluteUri;
+};
+
+
+/**
+ * Clones the URI instance.
+ * @return {!goog.Uri} New instance of the URI object.
+ */
+goog.Uri.prototype.clone = function() {
+ return new goog.Uri(this);
+};
+
+
+/**
+ * @return {string} The encoded scheme/protocol for the URI.
+ */
+goog.Uri.prototype.getScheme = function() {
+ return this.scheme_;
+};
+
+
+/**
+ * Sets the scheme/protocol.
+ * @throws URIError If opt_decode is true and newScheme is malformed (that is,
+ * if decodeURIComponent fails).
+ * @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;
+
+ // 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.
+ */
+goog.Uri.prototype.hasScheme = function() {
+ return !!this.scheme_;
+};
+
+
+/**
+ * @return {string} The decoded user info.
+ */
+goog.Uri.prototype.getUserInfo = function() {
+ return this.userInfo_;
+};
+
+
+/**
+ * Sets the userInfo.
+ * @throws URIError If opt_decode is true and newUserInfo is malformed (that is,
+ * if decodeURIComponent fails).
+ * @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;
+};
+
+
+/**
+ * @return {boolean} Whether the user info has been set.
+ */
+goog.Uri.prototype.hasUserInfo = function() {
+ return !!this.userInfo_;
+};
+
+
+/**
+ * @return {string} The decoded domain.
+ */
+goog.Uri.prototype.getDomain = function() {
+ return this.domain_;
+};
+
+
+/**
+ * Sets the domain.
+ * @throws URIError If opt_decode is true and newDomain is malformed (that is,
+ * if decodeURIComponent fails).
+ * @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;
+};
+
+
+/**
+ * @return {boolean} Whether the domain has been set.
+ */
+goog.Uri.prototype.hasDomain = function() {
+ return !!this.domain_;
+};
+
+
+/**
+ * @return {?number} The port number.
+ */
+goog.Uri.prototype.getPort = function() {
+ return this.port_;
+};
+
+
+/**
+ * Sets the port number.
+ * @param {*} newPort Port number. Will be explicitly casted to a number.
+ * @return {!goog.Uri} Reference to this URI object.
+ */
+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;
+ }
+
+ return this;
+};
+
+
+/**
+ * @return {boolean} Whether the port has been set.
+ */
+goog.Uri.prototype.hasPort = function() {
+ return this.port_ != null;
+};
+
+
+/**
+ * @return {string} The decoded path.
+ */
+goog.Uri.prototype.getPath = function() {
+ return this.path_;
+};
+
+
+/**
+ * Sets the path.
+ * @throws URIError If opt_decode is true and newPath is malformed (that is,
+ * if decodeURIComponent fails).
+ * @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;
+};
+
+
+/**
+ * @return {boolean} Whether the path has been set.
+ */
+goog.Uri.prototype.hasPath = function() {
+ return !!this.path_;
+};
+
+
+/**
+ * @return {boolean} Whether the query string has been set.
+ */
+goog.Uri.prototype.hasQuery = function() {
+ return this.queryData_.toString() !== '';
+};
+
+
+/**
+ * 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();
+
+ 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_);
+ }
+
+ return this;
+};
+
+
+/**
+ * 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.
+ */
+goog.Uri.prototype.setQuery = function(newQuery, opt_decode) {
+ return this.setQueryData(newQuery, opt_decode);
+};
+
+
+/**
+ * @return {string} The encoded URI query, not including the ?.
+ */
+goog.Uri.prototype.getEncodedQuery = function() {
+ return this.queryData_.toString();
+};
+
+
+/**
+ * @return {string} The decoded URI query, not including the ?.
+ */
+goog.Uri.prototype.getDecodedQuery = function() {
+ return this.queryData_.toDecodedString();
+};
+
+
+/**
+ * Returns the query data.
+ * @return {!goog.Uri.QueryData} QueryData object.
+ */
+goog.Uri.prototype.getQueryData = function() {
+ return this.queryData_;
+};
+
+
+/**
+ * @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();
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * 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.
+ */
+goog.Uri.prototype.setParameterValues = function(key, values) {
+ this.enforceReadOnly();
+
+ if (!goog.isArray(values)) {
+ values = [String(values)];
+ }
+
+ 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.
+ */
+goog.Uri.prototype.getParameterValues = function(name) {
+ return this.queryData_.getValues(name);
+};
+
+
+/**
+ * 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.
+ */
+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));
+};
+
+
+/**
+ * @return {string} The URI fragment, not including the #.
+ */
+goog.Uri.prototype.getFragment = function() {
+ return this.fragment_;
+};
+
+
+/**
+ * Sets the URI fragment.
+ * @throws URIError If opt_decode is true and newFragment is malformed (that is,
+ * if decodeURIComponent fails).
+ * @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;
+};
+
+
+/**
+ * @return {boolean} Whether the URI has a fragment set.
+ */
+goog.Uri.prototype.hasFragment = function() {
+ return !!this.fragment_;
+};
+
+
+/**
+ * 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());
+};
+
+
+/**
+ * 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 this;
+};
+
+
+/**
+ * Removes the named query parameter.
+ *
+ * @param {string} key The parameter to remove.
+ * @return {!goog.Uri} Reference to this URI object.
+ */
+goog.Uri.prototype.removeParameter = function(key) {
+ this.enforceReadOnly();
+ this.queryData_.remove(key);
+ return this;
+};
+
+
+/**
+ * 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.
+ */
+goog.Uri.prototype.setReadOnly = function(isReadOnly) {
+ this.isReadOnly_ = isReadOnly;
+ return this;
+};
+
+
+/**
+ * @return {boolean} Whether the URI is read only.
+ */
+goog.Uri.prototype.isReadOnly = function() {
+ return this.isReadOnly_;
+};
+
+
+/**
+ * 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.
+ */
+goog.Uri.prototype.enforceReadOnly = function() {
+ if (this.isReadOnly_) {
+ throw Error('Tried to modify a read-only Uri');
+ }
+};
+
+
+/**
+ * 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.
+ */
+goog.Uri.prototype.setIgnoreCase = function(ignoreCase) {
+ this.ignoreCase_ = ignoreCase;
+ if (this.queryData_) {
+ this.queryData_.setIgnoreCase(ignoreCase);
+ }
+ return this;
+};
+
+
+/**
+ * @return {boolean} Whether to ignore case.
+ */
+goog.Uri.prototype.getIgnoreCase = function() {
+ return this.ignoreCase_;
+};
+
+
+//==============================================================================
+// Static members
+//==============================================================================
+
+
+/**
+ * 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.
+ *
+ * @throws URIError If parsing the URI is malformed. The passed URI components
+ * should all be parseable by decodeURIComponent.
+ * @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.
+ */
+goog.Uri.parse = function(uri, opt_ignoreCase) {
+ return uri instanceof goog.Uri ?
+ uri.clone() : new goog.Uri(uri, opt_ignoreCase);
+};
+
+
+/**
+ * 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.
+ */
+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;
+};
+
+
+/**
+ * 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.
+ */
+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);
+};
+
+
+/**
+ * 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.
+ */
+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('/');
+ }
+};
+
+
+/**
+ * Decodes a value or returns the empty string if it isn't defined or empty.
+ * @throws URIError If decodeURIComponent fails to decode val.
+ * @param {string|undefined} val Value to decode.
+ * @param {boolean=} opt_preserveReserved If true, restricted characters will
+ * not be decoded.
+ * @return {string} Decoded value.
+ * @private
+ */
+goog.Uri.decodeOrEmpty_ = function(val, opt_preserveReserved) {
+ // Don't use UrlDecode() here because val is not a query parameter.
+ if (!val) {
+ return '';
+ }
+
+ // decodeURI has the same output for '%2f' and '%252f'. We double encode %25
+ // so that we can distinguish between the 2 inputs. This is later undone by
+ // removeDoubleEncoding_.
+ return opt_preserveReserved ?
+ decodeURI(val.replace(/%25/g, '%2525')) : 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;
+ }
+ return null;
+};
+
+
+/**
+ * Converts a character in [\01-\177] to its unicode character equivalent.
+ * @param {string} ch One character string.
+ * @return {string} Encoded string.
+ * @private
+ */
+goog.Uri.encodeChar_ = function(ch) {
+ var n = ch.charCodeAt(0);
+ return '%' + ((n >> 4) & 0xf).toString(16) + (n & 0xf).toString(16);
+};
+
+
+/**
+ * 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');
+};
+
+
+/**
+ * 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}
+ * @private
+ */
+goog.Uri.reDisallowedInAbsolutePath_ = /[\#\?]/g;
+
+
+/**
+ * Regular expression for characters that are disallowed in the query.
+ * @type {RegExp}
+ * @private
+ */
+goog.Uri.reDisallowedInQuery_ = /[\#\?@]/g;
+
+
+/**
+ * Regular expression for characters that are disallowed in the fragment.
+ * @type {RegExp}
+ * @private
+ */
+goog.Uri.reDisallowedInFragment_ = /#/g;
+
+
+/**
+ * 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];
+};
+
+
+
+/**
+ * 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
+ * @struct
+ * @final
+ */
+goog.Uri.QueryData = function(opt_query, opt_uri, opt_ignoreCase) {
+ /**
+ * 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.
+ *
+ * @private {goog.structs.Map<string, !Array<*>>}
+ */
+ this.keyMap_ = null;
+
+ /**
+ * The number of params, or null if it requires computing.
+ * @private {?number}
+ */
+ this.count_ = null;
+
+ /**
+ * Encoded query string, or null if it requires computing from the key map.
+ * @private {?string}
+ */
+ this.encodedQuery_ = opt_query || null;
+
+ /**
+ * If true, ignore the case of the parameter name in #get.
+ * @private {boolean}
+ */
+ this.ignoreCase_ = !!opt_ignoreCase;
+};
+
+
+/**
+ * If the underlying key map is not yet initialized, it parses the
+ * query string and fills the map with parsed data.
+ * @private
+ */
+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);
+ });
+ }
+ }
+};
+
+
+/**
+ * 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.
+ */
+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;
+};
+
+
+/**
+ * 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 {!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.
+ */
+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]);
+ }
+ return queryData;
+};
+
+
+/**
+ * @return {?number} The number of parameters.
+ */
+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_();
+
+ key = this.getKeyName_(key);
+ var values = this.keyMap_.get(key);
+ if (!values) {
+ this.keyMap_.set(key, (values = []));
+ }
+ values.push(value);
+ this.count_++;
+ return this;
+};
+
+
+/**
+ * Removes all the params with the given key.
+ * @param {string} key Name.
+ * @return {boolean} Whether any parameter was removed.
+ */
+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;
+};
+
+
+/**
+ * Clears the parameters.
+ */
+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]);
+ }
+ }
+ return rv;
+};
+
+
+/**
+ * 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.
+ */
+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_();
+
+ // 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;
+};
+
+
+/**
+ * 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;
+ }
+};
+
+
+/**
+ * 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 {string} Encoded query string.
+ * @override
+ */
+goog.Uri.QueryData.prototype.toString = function() {
+ if (this.encodedQuery_) {
+ return this.encodedQuery_;
+ }
+
+ if (!this.keyMap_) {
+ return '';
+ }
+
+ 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 this.encodedQuery_ = sb.join('&');
+};
+
+
+/**
+ * @throws URIError If URI is malformed (that is, if decodeURIComponent fails on
+ * any of the URI components).
+ * @return {string} Decoded query string.
+ */
+goog.Uri.QueryData.prototype.toDecodedString = function() {
+ return goog.Uri.decodeOrEmpty_(this.toString());
+};
+
+
+/**
+ * Invalidate the cache.
+ * @private
+ */
+goog.Uri.QueryData.prototype.invalidateCache_ = function() {
+ this.encodedQuery_ = null;
+};
+
+
+/**
+ * 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.
+ */
+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;
+};
+
+
+/**
+ * Clone the query data instance.
+ * @return {!goog.Uri.QueryData} New instance of the QueryData object.
+ */
+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;
+};
+
+
+/**
+ * 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_.
+ */
+goog.Uri.QueryData.prototype.getKeyName_ = function(arg) {
+ var keyName = String(arg);
+ if (this.ignoreCase_) {
+ keyName = keyName.toLowerCase();
+ }
+ return keyName;
+};
+
+
+/**
+ * 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');
+
+
+goog.require('ol.style.Fill');
+
+
+
+/**
+ * @classdesc
+ * Set text style for vector features.
+ *
+ * @constructor
+ * @param {olx.style.TextOptions=} opt_options Options.
+ * @api
+ */
+ol.style.Text = function(opt_options) {
+
+ var 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;
+
+ /**
+ * @private
+ * @type {string|undefined}
+ */
+ this.textAlign_ = options.textAlign;
+
+ /**
+ * @private
+ * @type {string|undefined}
+ */
+ this.textBaseline_ = options.textBaseline;
+
+ /**
+ * @private
+ * @type {ol.style.Fill}
+ */
+ this.fill_ = options.fill !== undefined ? options.fill :
+ new ol.style.Fill({color: ol.style.Text.DEFAULT_FILL_COLOR_});
+
+ /**
+ * @private
+ * @type {ol.style.Stroke}
+ */
+ this.stroke_ = options.stroke !== undefined ? options.stroke : null;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.offsetX_ = options.offsetX !== undefined ? options.offsetX : 0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.offsetY_ = options.offsetY !== undefined ? options.offsetY : 0;
+};
+
+
+/**
+ * The default fill color to use if no fill was set at construction time; a
+ * blackish `#333`.
+ *
+ * @const {string}
+ * @private
+ */
+ol.style.Text.DEFAULT_FILL_COLOR_ = '#333';
+
+
+/**
+ * Get the font name.
+ * @return {string|undefined} Font.
+ * @api
+ */
+ol.style.Text.prototype.getFont = function() {
+ return this.font_;
+};
+
+
+/**
+ * Get the x-offset for the text.
+ * @return {number} Horizontal text offset.
+ * @api
+ */
+ol.style.Text.prototype.getOffsetX = function() {
+ return this.offsetX_;
+};
+
+
+/**
+ * Get the y-offset for the text.
+ * @return {number} Vertical text offset.
+ * @api
+ */
+ol.style.Text.prototype.getOffsetY = function() {
+ return this.offsetY_;
+};
+
+
+/**
+ * Get the fill style for the text.
+ * @return {ol.style.Fill} Fill style.
+ * @api
+ */
+ol.style.Text.prototype.getFill = function() {
+ return this.fill_;
+};
+
+
+/**
+ * Get the text rotation.
+ * @return {number|undefined} Rotation.
+ * @api
+ */
+ol.style.Text.prototype.getRotation = function() {
+ return this.rotation_;
+};
+
+
+/**
+ * Get the text scale.
+ * @return {number|undefined} Scale.
+ * @api
+ */
+ol.style.Text.prototype.getScale = function() {
+ return this.scale_;
+};
+
+
+/**
+ * Get the stroke style for the text.
+ * @return {ol.style.Stroke} Stroke style.
+ * @api
+ */
+ol.style.Text.prototype.getStroke = function() {
+ return this.stroke_;
+};
+
+
+/**
+ * Get the text to be rendered.
+ * @return {string|undefined} Text.
+ * @api
+ */
+ol.style.Text.prototype.getText = function() {
+ return this.text_;
+};
+
+
+/**
+ * Get the text alignment.
+ * @return {string|undefined} Text align.
+ * @api
+ */
+ol.style.Text.prototype.getTextAlign = function() {
+ return this.textAlign_;
+};
+
+
+/**
+ * Get the text baseline.
+ * @return {string|undefined} Text baseline.
+ * @api
+ */
+ol.style.Text.prototype.getTextBaseline = function() {
+ return this.textBaseline_;
+};
+
+
+/**
+ * Set the font.
+ *
+ * @param {string|undefined} font Font.
+ * @api
+ */
+ol.style.Text.prototype.setFont = function(font) {
+ this.font_ = font;
+};
+
+
+/**
+ * Set the x offset.
+ *
+ * @param {number} offsetX Horizontal text offset.
+ * @api
+ */
+ol.style.Text.prototype.setOffsetX = function(offsetX) {
+ this.offsetX_ = offsetX;
+};
+
+
+/**
+ * Set the y offset.
+ *
+ * @param {number} offsetY Vertical text offset.
+ * @api
+ */
+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;
+};
+
+
+/**
+ * Set the stroke.
+ *
+ * @param {ol.style.Stroke} stroke Stroke style.
+ * @api
+ */
+ol.style.Text.prototype.setStroke = function(stroke) {
+ this.stroke_ = stroke;
+};
+
+
+/**
+ * Set the text.
+ *
+ * @param {string|undefined} text Text.
+ * @api
+ */
+ol.style.Text.prototype.setText = function(text) {
+ this.text_ = text;
+};
+
+
+/**
+ * Set the text alignment.
+ *
+ * @param {string|undefined} textAlign Text align.
+ * @api
+ */
+ol.style.Text.prototype.setTextAlign = function(textAlign) {
+ this.textAlign_ = textAlign;
+};
+
+
+/**
+ * Set the text baseline.
+ *
+ * @param {string|undefined} textBaseline Text baseline.
+ * @api
+ */
+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.object');
+goog.require('ol');
+goog.require('ol.Feature');
+goog.require('ol.FeatureStyleFunction');
+goog.require('ol.array');
+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.math');
+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
+ */
+ol.format.KML = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ goog.base(this);
+
+ /**
+ * @inheritDoc
+ */
+ this.defaultDataProjection = ol.proj.get('EPSG:4326');
+
+ /**
+ * @private
+ * @type {Array.<ol.style.Style>}
+ */
+ this.defaultStyle_ = options.defaultStyle ?
+ options.defaultStyle : ol.format.KML.DEFAULT_STYLE_ARRAY_;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.extractStyles_ = options.extractStyles !== undefined ?
+ options.extractStyles : true;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.writeStyles_ = options.writeStyles !== undefined ?
+ options.writeStyles : true;
+
+ /**
+ * @private
+ * @type {Object.<string, (Array.<ol.style.Style>|string)>}
+ */
+ this.sharedStyles_ = {};
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.showPointNames_ = options.showPointNames !== undefined ?
+ options.showPointNames : true;
+
+};
+goog.inherits(ol.format.KML, ol.format.XMLFeature);
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.KML.EXTENSIONS_ = ['.kml'];
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.KML.GX_NAMESPACE_URIS_ = [
+ 'http://www.google.com/kml/ext/2.2'
+];
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @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'
+];
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+ol.format.KML.SCHEMA_LOCATION_ = 'http://www.opengis.net/kml/2.2 ' +
+ 'https://developers.google.com/kml/schema/kml22gx.xsd';
+
+
+/**
+ * @const
+ * @type {ol.Color}
+ * @private
+ */
+ol.format.KML.DEFAULT_COLOR_ = [255, 255, 255, 1];
+
+
+/**
+ * @const
+ * @type {ol.style.Fill}
+ * @private
+ */
+ol.format.KML.DEFAULT_FILL_STYLE_ = new ol.style.Fill({
+ color: ol.format.KML.DEFAULT_COLOR_
+});
+
+
+/**
+ * @const
+ * @type {ol.Size}
+ * @private
+ */
+ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_ = [20, 2]; // FIXME maybe [8, 32] ?
+
+
+/**
+ * @const
+ * @type {ol.style.IconAnchorUnits}
+ * @private
+ */
+ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_ =
+ ol.style.IconAnchorUnits.PIXELS;
+
+
+/**
+ * @const
+ * @type {ol.style.IconAnchorUnits}
+ * @private
+ */
+ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_ =
+ ol.style.IconAnchorUnits.PIXELS;
+
+
+/**
+ * @const
+ * @type {ol.Size}
+ * @private
+ */
+ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_ = [64, 64];
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ =
+ 'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png';
+
+
+/**
+ * @const
+ * @type {number}
+ * @private
+ */
+ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_ = 0.5;
+
+
+/**
+ * @const
+ * @type {ol.style.Image}
+ * @private
+ */
+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: ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_,
+ size: ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_,
+ src: ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_
+});
+
+
+/**
+ * @const
+ * @type {ol.style.Stroke}
+ * @private
+ */
+ol.format.KML.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
+ color: ol.format.KML.DEFAULT_COLOR_,
+ width: 1
+});
+
+
+/**
+ * @const
+ * @type {ol.style.Stroke}
+ * @private
+ */
+ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_ = new ol.style.Stroke({
+ color: [51, 51, 51, 1],
+ width: 2
+});
+
+
+/**
+ * @const
+ * @type {ol.style.Text}
+ * @private
+ */
+ol.format.KML.DEFAULT_TEXT_STYLE_ = new ol.style.Text({
+ font: 'bold 16px Helvetica',
+ fill: ol.format.KML.DEFAULT_FILL_STYLE_,
+ stroke: ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_,
+ scale: 0.8
+});
+
+
+/**
+ * @const
+ * @type {ol.style.Style}
+ * @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
+});
+
+
+/**
+ * @const
+ * @type {Array.<ol.style.Style>}
+ * @private
+ */
+ol.format.KML.DEFAULT_STYLE_ARRAY_ = [ol.format.KML.DEFAULT_STYLE_];
+
+
+/**
+ * @const
+ * @type {Object.<string, ol.style.IconAnchorUnits>}
+ * @private
+ */
+ol.format.KML.ICON_ANCHOR_UNITS_MAP_ = {
+ 'fraction': ol.style.IconAnchorUnits.FRACTION,
+ 'pixels': ol.style.IconAnchorUnits.PIXELS
+};
+
+
+/**
+ * @param {ol.style.Style|undefined} foundStyle Style.
+ * @param {string} name Name.
+ * @return {ol.style.Style} style Style.
+ * @private
+ */
+ol.format.KML.createNameStyleFunction_ = function(foundStyle, name) {
+ /** @type {?ol.style.Text} */
+ var textStyle = null;
+ var textOffset = [0, 0];
+ var textAlign = 'start';
+ if (foundStyle.getImage()) {
+ var imageSize = foundStyle.getImage().getImageSize();
+ if (imageSize && imageSize.length == 2) {
+ // Offset the label to be centered to the right of the icon, if there is
+ // one.
+ textOffset[0] = foundStyle.getImage().getScale() * imageSize[0] / 2;
+ textOffset[1] = -foundStyle.getImage().getScale() * imageSize[1] / 2;
+ textAlign = 'left';
+ }
+ }
+ if (!goog.object.isEmpty(foundStyle.getText())) {
+ textStyle = /** @type {ol.style.Text} */
+ (goog.object.clone(foundStyle.getText()));
+ textStyle.setText(name);
+ textStyle.setTextAlign(textAlign);
+ textStyle.setOffsetX(textOffset[0]);
+ textStyle.setOffsetY(textOffset[1]);
+ } else {
+ textStyle = new ol.style.Text({
+ text: name,
+ offsetX: textOffset[0],
+ offsetY: textOffset[1],
+ textAlign: textAlign
+ });
+ }
+ var nameStyle = new ol.style.Style({
+ text: textStyle
+ });
+ return nameStyle;
+};
+
+
+/**
+ * @param {Array.<ol.style.Style>|undefined} style Style.
+ * @param {string} styleUrl Style URL.
+ * @param {Array.<ol.style.Style>} defaultStyle Default style.
+ * @param {Object.<string, (Array.<ol.style.Style>|string)>} sharedStyles Shared
+ * styles.
+ * @param {boolean|undefined} showPointNames true to show names for point
+ * placemarks.
+ * @return {ol.FeatureStyleFunction} Feature style function.
+ * @private
+ */
+ol.format.KML.createFeatureStyleFunction_ = function(style, styleUrl,
+ defaultStyle, sharedStyles, showPointNames) {
+
+ return (
+ /**
+ * @param {number} resolution Resolution.
+ * @return {Array.<ol.style.Style>} Style.
+ * @this {ol.Feature}
+ */
+ function(resolution) {
+ var drawName = showPointNames;
+ /** @type {ol.style.Style|undefined} */
+ var nameStyle;
+ /** @type {string} */
+ var name = '';
+ if (drawName) {
+ if (this.getGeometry()) {
+ drawName = (this.getGeometry().getType() ===
+ ol.geom.GeometryType.POINT);
+ }
+ }
+
+ if (drawName) {
+ name = /** @type {string} */ (this.getProperties()['name']);
+ drawName = drawName && name;
+ }
+
+ if (style) {
+ if (drawName) {
+ nameStyle = ol.format.KML.createNameStyleFunction_(style[0],
+ name);
+ return style.concat(nameStyle);
+ }
+ return style;
+ }
+ if (styleUrl) {
+ var foundStyle = ol.format.KML.findStyle_(styleUrl, defaultStyle,
+ sharedStyles);
+ if (drawName) {
+ nameStyle = ol.format.KML.createNameStyleFunction_(foundStyle[0],
+ name);
+ return foundStyle.concat(nameStyle);
+ }
+ return foundStyle;
+ }
+ if (drawName) {
+ nameStyle = ol.format.KML.createNameStyleFunction_(defaultStyle[0],
+ name);
+ return defaultStyle.concat(nameStyle);
+ }
+ return defaultStyle;
+ });
+};
+
+
+/**
+ * @param {Array.<ol.style.Style>|string|undefined} styleValue Style value.
+ * @param {Array.<ol.style.Style>} defaultStyle Default style.
+ * @param {Object.<string, (Array.<ol.style.Style>|string)>} sharedStyles
+ * Shared styles.
+ * @return {Array.<ol.style.Style>} Style.
+ * @private
+ */
+ol.format.KML.findStyle_ = function(styleValue, defaultStyle, sharedStyles) {
+ 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 ol.format.KML.findStyle_(
+ sharedStyles[styleValue], defaultStyle, sharedStyles);
+ } else {
+ return defaultStyle;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {ol.Color|undefined} Color.
+ */
+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;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @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;
+ }
+ return flatCoordinates;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {string|undefined} Style URL.
+ */
+ol.format.KML.readStyleUrl_ = function(node) {
+ var s = ol.xml.getAllTextContent(node, false).trim();
+ if (node.baseURI) {
+ return goog.Uri.resolve(node.baseURI, s).toString();
+ } else {
+ return s;
+ }
+
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {string} URI.
+ */
+ol.format.KML.readURI_ = function(node) {
+ var s = ol.xml.getAllTextContent(node, false);
+ if (node.baseURI) {
+ return goog.Uri.resolve(node.baseURI, s.trim()).toString();
+ } else {
+ return s.trim();
+ }
+};
+
+
+/**
+ * @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]
+ };
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {number|undefined} Scale.
+ */
+ol.format.KML.readScale_ = function(node) {
+ var number = ol.format.XSD.readDecimal(node);
+ if (number !== undefined) {
+ return Math.sqrt(number);
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object 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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (!object) {
+ return;
+ }
+ var styleObject = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+ goog.asserts.assert(goog.isObject(styleObject),
+ 'styleObject should be an Object');
+ var IconObject = 'Icon' in object ? object['Icon'] : {};
+ var src;
+ var href = /** @type {string|undefined} */
+ (IconObject['href']);
+ if (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 (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 (x !== undefined && y !== undefined) {
+ offset = [x, y];
+ }
+
+ var size;
+ var w = /** @type {number|undefined} */
+ (IconObject['w']);
+ var h = /** @type {number|undefined} */
+ (IconObject['h']);
+ if (w !== undefined && h !== undefined) {
+ size = [w, h];
+ }
+
+ var rotation;
+ var heading = /** @type {number} */
+ (object['heading']);
+ if (heading !== undefined) {
+ rotation = ol.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_;
+ if (scale === undefined) {
+ scale = ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_;
+ }
+ }
+
+ 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 {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (!object) {
+ return;
+ }
+ 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} */
+ ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_)
+ }),
+ scale: /** @type {number|undefined} */
+ (object['scale'])
+ });
+ styleObject['textStyle'] = textStyle;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (!object) {
+ return;
+ }
+ 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} */
+ ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_),
+ width: /** @type {number} */ ('width' in object ? object['width'] : 1)
+ });
+ styleObject['strokeStyle'] = strokeStyle;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (!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} */
+ ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_)
+ });
+ styleObject['fillStyle'] = fillStyle;
+ var fill = /** @type {boolean|undefined} */ (object['fill']);
+ if (fill !== undefined) {
+ styleObject['fill'] = fill;
+ }
+ var outline =
+ /** @type {boolean|undefined} */ (object['outline']);
+ if (outline !== undefined) {
+ styleObject['outline'] = outline;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>} LinearRing flat coordinates.
+ */
+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 {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.gxCoordParser_ = function(node, objectStack) {
+ goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT,
+ 'node.nodeType should be ELEMENT');
+ goog.asserts.assert(ol.array.includes(
+ 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);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.MultiLineString|undefined} MultiLineString.
+ */
+ol.format.KML.readGxMultiTrack_ = function(node, objectStack) {
+ goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT,
+ 'node.nodeType should be ELEMENT');
+ goog.asserts.assert(ol.array.includes(
+ 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 (!lineStrings) {
+ return undefined;
+ }
+ var multiLineString = new ol.geom.MultiLineString(null);
+ multiLineString.setLineStrings(lineStrings);
+ return multiLineString;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.LineString|undefined} LineString.
+ */
+ol.format.KML.readGxTrack_ = function(node, objectStack) {
+ goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT,
+ 'node.nodeType should be ELEMENT');
+ goog.asserts.assert(ol.array.includes(
+ 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 (!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;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object} Icon object.
+ */
+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 (iconObject) {
+ return iconObject;
+ } else {
+ return null;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>} Flat coordinates.
+ */
+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));
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.LineString|undefined} LineString.
+ */
+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 (flatCoordinates) {
+ var lineString = new ol.geom.LineString(null);
+ lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
+ lineString.setProperties(properties);
+ return lineString;
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Polygon|undefined} Polygon.
+ */
+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 (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;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Geometry} 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 (!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;
+ }
+ }
+ 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);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Point|undefined} Point.
+ */
+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 (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;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Polygon|undefined} Polygon.
+ */
+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 (flatLinearRings && 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 undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<ol.style.Style>} Style.
+ */
+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 (!styleObject) {
+ return null;
+ }
+ var fillStyle = /** @type {ol.style.Fill} */
+ ('fillStyle' in styleObject ?
+ styleObject['fillStyle'] : ol.format.KML.DEFAULT_FILL_STYLE_);
+ var fill = /** @type {boolean|undefined} */ (styleObject['fill']);
+ if (fill !== undefined && !fill) {
+ fillStyle = null;
+ }
+ var imageStyle = /** @type {ol.style.Image} */
+ ('imageStyle' in styleObject ?
+ styleObject['imageStyle'] : ol.format.KML.DEFAULT_IMAGE_STYLE_);
+ var textStyle = /** @type {ol.style.Text} */
+ ('textStyle' in styleObject ?
+ styleObject['textStyle'] : ol.format.KML.DEFAULT_TEXT_STYLE_);
+ var strokeStyle = /** @type {ol.style.Stroke} */
+ ('strokeStyle' in styleObject ?
+ styleObject['strokeStyle'] : ol.format.KML.DEFAULT_STROKE_STYLE_);
+ var outline = /** @type {boolean|undefined} */
+ (styleObject['outline']);
+ if (outline !== undefined && !outline) {
+ strokeStyle = null;
+ }
+ return [new ol.style.Style({
+ fill: fillStyle,
+ image: imageStyle,
+ stroke: strokeStyle,
+ text: textStyle,
+ zIndex: undefined // FIXME
+ })];
+};
+
+
+/**
+ * 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.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 || extrudes[i] !== undefined;
+ hasAltitudeMode = hasAltitudeMode || altitudeModes[i];
+ }
+ if (hasExtrude) {
+ multiGeometry.set('extrude', extrudes);
+ }
+ if (hasAltitudeMode) {
+ multiGeometry.set('altitudeMode', altitudeModes);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (name !== null) {
+ var data = ol.xml.pushParseAndPop(
+ undefined, ol.format.KML.DATA_PARSERS_, node, objectStack);
+ if (data) {
+ var featureObject =
+ /** @type {Object} */ (objectStack[objectStack.length - 1]);
+ goog.asserts.assert(goog.isObject(featureObject),
+ 'featureObject should be an Object');
+ featureObject[name] = data;
+ }
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (!pairObject) {
+ return;
+ }
+ var key = /** @type {string|undefined} */
+ (pairObject['key']);
+ if (key && key == 'normal') {
+ var styleUrl = /** @type {string|undefined} */
+ (pairObject['styleUrl']);
+ if (styleUrl) {
+ objectStack[objectStack.length - 1] = styleUrl;
+ }
+ var Style = /** @type {ol.style.Style} */
+ (pairObject['Style']);
+ if (Style) {
+ objectStack[objectStack.length - 1] = Style;
+ }
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (!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');
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (name !== null) {
+ var data = ol.format.XSD.readString(node);
+ var featureObject =
+ /** @type {Object} */ (objectStack[objectStack.length - 1]);
+ featureObject[name] = data;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (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);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 (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 {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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 = m[3] ? parseInt(m[3], 10) - 1 : 0;
+ var day = m[5] ? parseInt(m[5], 10) : 1;
+ var hour = m[7] ? parseInt(m[7], 10) : 0;
+ var minute = m[8] ? parseInt(m[8], 10) : 0;
+ var second = m[9] ? parseInt(m[9], 10) : 0;
+ var when = Date.UTC(year, month, day, hour, minute, second);
+ if (m[10] && m[10] != 'Z') {
+ var sign = m[11] == '-' ? -1 : 1;
+ when += sign * 60 * parseInt(m[12], 10);
+ if (m[13]) {
+ when += sign * 60 * 60 * parseInt(m[13], 10);
+ }
+ }
+ whens.push(when);
+ } else {
+ whens.push(0);
+ }
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.DATA_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'value': ol.xml.makeReplacer(ol.format.XSD.readString)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'Data': ol.format.KML.DataParser_,
+ 'SchemaData': ol.format.KML.SchemaDataParser_
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'extrude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
+ 'altitudeMode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.FLAT_LINEAR_RING_PARSERS_ = ol.xml.makeStructureNS(
+ 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.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'innerBoundaryIs': ol.format.KML.innerBoundaryIsParser_,
+ 'outerBoundaryIs': ol.format.KML.outerBoundaryIsParser_
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.GX_TRACK_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'when': ol.format.KML.whenParser_
+ }, ol.xml.makeStructureNS(
+ ol.format.KML.GX_NAMESPACE_URIS_, {
+ 'coord': ol.format.KML.gxCoordParser_
+ }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = ol.xml.makeStructureNS(
+ 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.ICON_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_)
+ }, ol.xml.makeStructureNS(
+ 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)
+ }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.ICON_STYLE_PARSERS_ = ol.xml.makeStructureNS(
+ 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_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.INNER_BOUNDARY_IS_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.LABEL_STYLE_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
+ 'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.LINE_STYLE_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
+ 'width': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.MULTI_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS(
+ 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_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.GX_NAMESPACE_URIS_, {
+ 'Track': ol.xml.makeArrayPusher(ol.format.KML.readGxTrack_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.NETWORK_LINK_PARSERS_ = ol.xml.makeStructureNS(
+ 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)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.LINK_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.PAIR_PARSERS_ = ol.xml.makeStructureNS(
+ 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_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeStructureNS(
+ 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.makeStructureNS(
+ ol.format.KML.GX_NAMESPACE_URIS_, {
+ 'MultiTrack': ol.xml.makeObjectPropertySetter(
+ ol.format.KML.readGxMultiTrack_, 'geometry'),
+ 'Track': ol.xml.makeObjectPropertySetter(
+ ol.format.KML.readGxTrack_, 'geometry')
+ }
+ ));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.POLY_STYLE_PARSERS_ = ol.xml.makeStructureNS(
+ 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)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.SCHEMA_DATA_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'SimpleData': ol.format.KML.SimpleDataParser_
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.STYLE_PARSERS_ = ol.xml.makeStructureNS(
+ 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_
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.STYLE_MAP_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'Pair': ol.format.KML.PairDataParser_
+ });
+
+
+/**
+ * @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,
+ '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.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'Document': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this),
+ '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 (features) {
+ return features;
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Feature.
+ */
+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 (!object) {
+ return undefined;
+ }
+ var feature = new ol.Feature();
+ var id = node.getAttribute('id');
+ if (id !== null) {
+ feature.setId(id);
+ }
+ var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
+
+ var geometry = object['geometry'];
+ if (geometry) {
+ ol.format.Feature.transformWithOptions(geometry, false, options);
+ }
+ feature.setGeometry(geometry);
+ delete object['geometry'];
+
+ if (this.extractStyles_) {
+ var style = object['Style'];
+ var styleUrl = object['styleUrl'];
+ var styleFunction = ol.format.KML.createFeatureStyleFunction_(
+ style, styleUrl, this.defaultStyle_, this.sharedStyles_,
+ this.showPointNames_);
+ feature.setStyle(styleFunction);
+ }
+ delete object['Style'];
+ // we do not remove the styleUrl property from the object, so it
+ // gets stored on feature when setProperties is called
+
+ feature.setProperties(object);
+
+ 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,
+ 'node.nodeType should be ELEMENT');
+ goog.asserts.assert(node.localName == 'Style', 'localName should be Style');
+ var id = node.getAttribute('id');
+ if (id !== null) {
+ var style = ol.format.KML.readStyle_(node, objectStack);
+ if (style) {
+ var styleUri;
+ if (node.baseURI) {
+ styleUri = goog.Uri.resolve(node.baseURI, '#' + id).toString();
+ } else {
+ styleUri = '#' + id;
+ }
+ this.sharedStyles_[styleUri] = style;
+ }
+ }
+};
+
+
+/**
+ * @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,
+ 'node.nodeType should be ELEMENT');
+ goog.asserts.assert(node.localName == 'StyleMap',
+ 'localName should be StyleMap');
+ var id = node.getAttribute('id');
+ if (id === null) {
+ return;
+ }
+ var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack);
+ if (!styleMapValue) {
+ return;
+ }
+ var styleUri;
+ if (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.
+ *
+ * @function
+ * @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;
+
+
+/**
+ * @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 (!ol.array.includes(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 (feature) {
+ return feature;
+ } else {
+ return null;
+ }
+};
+
+
+/**
+ * Read all features from a KML 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.KML.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.KML.prototype.readFeaturesFromNode = function(node, opt_options) {
+ goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT,
+ 'node.nodeType should be ELEMENT');
+ if (!ol.array.includes(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 (features) {
+ return features;
+ } else {
+ return [];
+ }
+ } else if (localName == 'Placemark') {
+ var feature = this.readPlacemark_(
+ node, [this.getReadOptions(node, opt_options)]);
+ if (feature) {
+ return [feature];
+ } else {
+ return [];
+ }
+ } else if (localName == 'kml') {
+ features = [];
+ var n;
+ for (n = node.firstElementChild; n; n = n.nextElementSibling) {
+ var fs = this.readFeaturesFromNode(n, opt_options);
+ if (fs) {
+ goog.array.extend(features, fs);
+ }
+ }
+ return features;
+ } else {
+ return [];
+ }
+};
+
+
+/**
+ * Read the name of the KML.
+ *
+ * @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('Unknown type for source');
+ return undefined;
+ }
+};
+
+
+/**
+ * @param {Document} doc Document.
+ * @return {string|undefined} Name.
+ */
+ol.format.KML.prototype.readNameFromDocument = function(doc) {
+ var n;
+ for (n = doc.firstChild; n; n = n.nextSibling) {
+ if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+ var name = this.readNameFromNode(n);
+ if (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; n; n = n.nextElementSibling) {
+ if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
+ n.localName == 'name') {
+ return ol.format.XSD.readString(n);
+ }
+ }
+ for (n = node.firstElementChild; n; n = n.nextElementSibling) {
+ var localName = ol.xml.getLocalName(n);
+ if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
+ (localName == 'Document' ||
+ localName == 'Folder' ||
+ localName == 'Placemark' ||
+ localName == 'kml')) {
+ var name = this.readNameFromNode(n);
+ if (name) {
+ return name;
+ }
+ }
+ }
+ return undefined;
+};
+
+
+/**
+ * Read the network links of the KML.
+ *
+ * @param {Document|Node|string} source Source.
+ * @return {Array.<Object>} Network links.
+ * @api
+ */
+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 networkLinks;
+};
+
+
+/**
+ * @param {Document} doc Document.
+ * @return {Array.<Object>} Network links.
+ */
+ol.format.KML.prototype.readNetworkLinksFromDocument = function(doc) {
+ var n, networkLinks = [];
+ for (n = doc.firstChild; n; n = n.nextSibling) {
+ if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+ goog.array.extend(networkLinks, this.readNetworkLinksFromNode(n));
+ }
+ }
+ return networkLinks;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {Array.<Object>} Network links.
+ */
+ol.format.KML.prototype.readNetworkLinksFromNode = function(node) {
+ var n, networkLinks = [];
+ for (n = node.firstElementChild; n; n = n.nextElementSibling) {
+ if (ol.array.includes(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; n; n = n.nextElementSibling) {
+ var localName = ol.xml.getLocalName(n);
+ if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
+ (localName == 'Document' ||
+ localName == 'Folder' ||
+ localName == 'kml')) {
+ goog.array.extend(networkLinks, this.readNetworkLinksFromNode(n));
+ }
+ }
+ return networkLinks;
+};
+
+
+/**
+ * Read the projection from a KML source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.KML.prototype.readProjection;
+
+
+/**
+ * @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), '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.
+ * @this {ol.format.KML}
+ * @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, undefined,
+ this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Object} icon Icon object.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.style.Icon} style Icon style.
+ * @param {Array.<*>} objectStack Object 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 (size) {
+ iconProperties['w'] = size[0];
+ iconProperties['h'] = size[1];
+ var anchor = style.getAnchor(); // top-left
+ var origin = style.getOrigin(); // top-left
+
+ if (origin && iconImageSize && origin[0] !== 0 && origin[1] !== size[1]) {
+ iconProperties['x'] = origin[0];
+ iconProperties['y'] = iconImageSize[1] - (origin[1] + size[1]);
+ }
+
+ if (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;
+ }
+ }
+
+ properties['Icon'] = iconProperties;
+
+ var scale = style.getScale();
+ if (scale !== 1) {
+ properties['scale'] = scale;
+ }
+
+ var rotation = style.getRotation();
+ if (rotation !== 0) {
+ 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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.style.Text} style style.
+ * @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 (fill) {
+ properties['color'] = fill.getColor();
+ }
+ var scale = style.getScale();
+ if (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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.style.Stroke} style style.
+ * @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()
+ };
+ 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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @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),
+ '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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.LinearRing} linearRing Linear ring.
+ * @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);
+};
+
+
+/**
+ * FIXME currently we do serialize arbitrary/custom feature properties
+ * (ExtendedData).
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Object stack.
+ * @this {ol.format.KML}
+ * @private
+ */
+ol.format.KML.writePlacemark_ = function(node, feature, objectStack) {
+ var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+
+ // set id
+ if (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 (styleFunction) {
+ // FIXME the styles returned by the style function are supposed to be
+ // resolution-independent here
+ var styles = styleFunction.call(feature, 0);
+ if (styles && styles.length > 0) {
+ var style = styles[0];
+ if (this.writeStyles_) {
+ properties['Style'] = styles[0];
+ }
+ var textStyle = style.getText();
+ if (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);
+
+ // serialize geometry
+ var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
+ var geometry = feature.getGeometry();
+ if (geometry) {
+ geometry =
+ ol.format.Feature.transformWithOptions(geometry, true, options);
+ }
+ 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.
+ * @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),
+ '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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Polygon} polygon Polygon.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.style.Fill} style Style.
+ * @param {Array.<*>} objectStack Object stack.
+ * @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);
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the scale to.
+ * @param {number|undefined} scale Scale.
+ * @private
+ */
+ol.format.KML.writeScaleTextNode_ = function(node, scale) {
+ ol.format.XSD.writeDecimalTextNode(node, scale * scale);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.style.Style} style Style.
+ * @param {Array.<*>} objectStack Object stack.
+ * @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 (imageStyle instanceof ol.style.Icon) {
+ properties['IconStyle'] = imageStyle;
+ }
+ if (textStyle) {
+ properties['LabelStyle'] = textStyle;
+ }
+ if (strokeStyle) {
+ properties['LineStyle'] = strokeStyle;
+ }
+ if (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);
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the Vec2 to.
+ * @param {ol.format.KMLVec2_} vec2 Vec2.
+ * @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);
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.KML.KML_SEQUENCE_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, [
+ 'Document', 'Placemark'
+ ]);
+
+
+/**
+ * @const
+ * @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_)
+ });
+
+
+/**
+ * @const
+ * @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_)
+ });
+
+
+/**
+ * @const
+ * @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'
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @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'
+ ]));
+
+
+/**
+ * @const
+ * @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)
+ }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+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
+ */
+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, Array.<string>>}
+ * @private
+ */
+ol.format.KML.LABEL_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, [
+ 'color', 'scale'
+ ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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, Array.<string>>}
+ * @private
+ */
+ol.format.KML.LINE_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, [
+ 'color', 'width'
+ ]);
+
+
+/**
+ * @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)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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.Serializer>>}
+ * @private
+ */
+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, Array.<string>>}
+ * @private
+ */
+ol.format.KML.PLACEMARK_SEQUENCE_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, [
+ 'name', 'open', 'visibility', 'address', 'phoneNumber', 'description',
+ 'styleUrl', 'Style'
+ ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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 {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_)
+ });
+
+
+/**
+ * @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
+ */
+ol.format.KML.POLY_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.KML.STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, [
+ 'IconStyle', 'LabelStyle', 'LineStyle', 'PolyStyle'
+ ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+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_)
+ });
+
+
+/**
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
+ */
+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');
+};
+
+
+/**
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
+ */
+ol.format.KML.GEOMETRY_NODE_FACTORY_ = function(value, objectStack,
+ opt_nodeName) {
+ if (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.format.KML.COLOR_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('color');
+
+
+/**
+ * A factory for creating coordinates nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.COORDINATES_NODE_FACTORY_ =
+ ol.xml.makeSimpleNodeFactory('coordinates');
+
+
+/**
+ * A factory for creating innerBoundaryIs nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+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
+ */
+ol.format.KML.POINT_NODE_FACTORY_ =
+ ol.xml.makeSimpleNodeFactory('Point');
+
+
+/**
+ * A factory for creating LineString nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.LINE_STRING_NODE_FACTORY_ =
+ ol.xml.makeSimpleNodeFactory('LineString');
+
+
+/**
+ * A factory for creating LinearRing nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.LINEAR_RING_NODE_FACTORY_ =
+ ol.xml.makeSimpleNodeFactory('LinearRing');
+
+
+/**
+ * A factory for creating Polygon nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.POLYGON_NODE_FACTORY_ =
+ ol.xml.makeSimpleNodeFactory('Polygon');
+
+
+/**
+ * A factory for creating outerBoundaryIs nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_ =
+ ol.xml.makeSimpleNodeFactory('outerBoundaryIs');
+
+
+/**
+ * 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.format.KML.prototype.writeFeatures;
+
+
+/**
+ * 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.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,
+ this);
+ return kml;
+};
+
+goog.provide('ol.ext.pbf');
+/** @typedef {function(*)} */
+ol.ext.pbf;
+(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.pbf = 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(_dereq_,module,exports){
+'use strict';
+
+// lightweight Buffer shim for pbf browser build
+// based on code from github.com/feross/buffer (MIT-licensed)
+
+module.exports = Buffer;
+
+var ieee754 = _dereq_('ieee754');
+
+var BufferMethods;
+
+function Buffer(length) {
+ var arr;
+ if (length && length.length) {
+ arr = length;
+ length = arr.length;
+ }
+ var buf = new Uint8Array(length || 0);
+ if (arr) buf.set(arr);
+
+ buf.readUInt32LE = BufferMethods.readUInt32LE;
+ buf.writeUInt32LE = BufferMethods.writeUInt32LE;
+ buf.readInt32LE = BufferMethods.readInt32LE;
+ buf.writeInt32LE = BufferMethods.writeInt32LE;
+ buf.readFloatLE = BufferMethods.readFloatLE;
+ buf.writeFloatLE = BufferMethods.writeFloatLE;
+ buf.readDoubleLE = BufferMethods.readDoubleLE;
+ buf.writeDoubleLE = BufferMethods.writeDoubleLE;
+ buf.toString = BufferMethods.toString;
+ buf.write = BufferMethods.write;
+ buf.slice = BufferMethods.slice;
+ buf.copy = BufferMethods.copy;
+
+ buf._isBuffer = true;
+ return buf;
+}
+
+var lastStr, lastStrEncoded;
+
+BufferMethods = {
+ readUInt32LE: function(pos) {
+ return ((this[pos]) |
+ (this[pos + 1] << 8) |
+ (this[pos + 2] << 16)) +
+ (this[pos + 3] * 0x1000000);
+ },
+
+ writeUInt32LE: function(val, pos) {
+ this[pos] = val;
+ this[pos + 1] = (val >>> 8);
+ this[pos + 2] = (val >>> 16);
+ this[pos + 3] = (val >>> 24);
+ },
+
+ readInt32LE: function(pos) {
+ return ((this[pos]) |
+ (this[pos + 1] << 8) |
+ (this[pos + 2] << 16)) +
+ (this[pos + 3] << 24);
+ },
+
+ readFloatLE: function(pos) { return ieee754.read(this, pos, true, 23, 4); },
+ readDoubleLE: function(pos) { return ieee754.read(this, pos, true, 52, 8); },
+
+ writeFloatLE: function(val, pos) { return ieee754.write(this, val, pos, true, 23, 4); },
+ writeDoubleLE: function(val, pos) { return ieee754.write(this, val, pos, true, 52, 8); },
+
+ toString: function(encoding, start, end) {
+ var str = '',
+ tmp = '';
+
+ start = start || 0;
+ end = Math.min(this.length, end || this.length);
+
+ for (var i = start; i < end; i++) {
+ var ch = this[i];
+ if (ch <= 0x7F) {
+ str += decodeURIComponent(tmp) + String.fromCharCode(ch);
+ tmp = '';
+ } else {
+ tmp += '%' + ch.toString(16);
+ }
+ }
+
+ str += decodeURIComponent(tmp);
+
+ return str;
+ },
+
+ write: function(str, pos) {
+ var bytes = str === lastStr ? lastStrEncoded : encodeString(str);
+ for (var i = 0; i < bytes.length; i++) {
+ this[pos + i] = bytes[i];
+ }
+ },
+
+ slice: function(start, end) {
+ return this.subarray(start, end);
+ },
+
+ copy: function(buf, pos) {
+ pos = pos || 0;
+ for (var i = 0; i < this.length; i++) {
+ buf[pos + i] = this[i];
+ }
+ }
+};
+
+BufferMethods.writeInt32LE = BufferMethods.writeUInt32LE;
+
+Buffer.byteLength = function(str) {
+ lastStr = str;
+ lastStrEncoded = encodeString(str);
+ return lastStrEncoded.length;
+};
+
+Buffer.isBuffer = function(buf) {
+ return !!(buf && buf._isBuffer);
+};
+
+function encodeString(str) {
+ var length = str.length,
+ bytes = [];
+
+ for (var i = 0, c, lead; i < length; i++) {
+ c = str.charCodeAt(i); // code point
+
+ if (c > 0xD7FF && c < 0xE000) {
+
+ if (lead) {
+ if (c < 0xDC00) {
+ bytes.push(0xEF, 0xBF, 0xBD);
+ lead = c;
+ continue;
+
+ } else {
+ c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000;
+ lead = null;
+ }
+
+ } else {
+ if (c > 0xDBFF || (i + 1 === length)) bytes.push(0xEF, 0xBF, 0xBD);
+ else lead = c;
+
+ continue;
+ }
+
+ } else if (lead) {
+ bytes.push(0xEF, 0xBF, 0xBD);
+ lead = null;
+ }
+
+ if (c < 0x80) bytes.push(c);
+ else if (c < 0x800) bytes.push(c >> 0x6 | 0xC0, c & 0x3F | 0x80);
+ else if (c < 0x10000) bytes.push(c >> 0xC | 0xE0, c >> 0x6 & 0x3F | 0x80, c & 0x3F | 0x80);
+ else bytes.push(c >> 0x12 | 0xF0, c >> 0xC & 0x3F | 0x80, c >> 0x6 & 0x3F | 0x80, c & 0x3F | 0x80);
+ }
+ return bytes;
+}
+
+},{"ieee754":3}],2:[function(_dereq_,module,exports){
+(function (global){
+'use strict';
+
+module.exports = Pbf;
+
+var Buffer = global.Buffer || _dereq_('./buffer');
+
+function Pbf(buf) {
+ this.buf = !Buffer.isBuffer(buf) ? new Buffer(buf || 0) : buf;
+ this.pos = 0;
+ this.length = this.buf.length;
+}
+
+Pbf.Varint = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum
+Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64
+Pbf.Bytes = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields
+Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32
+
+var SHIFT_LEFT_32 = (1 << 16) * (1 << 16),
+ SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32,
+ POW_2_63 = Math.pow(2, 63);
+
+Pbf.prototype = {
+
+ destroy: function() {
+ this.buf = null;
+ },
+
+ // === READING =================================================================
+
+ readFields: function(readField, result, end) {
+ end = end || this.length;
+
+ while (this.pos < end) {
+ var val = this.readVarint(),
+ tag = val >> 3,
+ startPos = this.pos;
+
+ readField(tag, result, this);
+
+ if (this.pos === startPos) this.skip(val);
+ }
+ return result;
+ },
+
+ readMessage: function(readField, result) {
+ return this.readFields(readField, result, this.readVarint() + this.pos);
+ },
+
+ readFixed32: function() {
+ var val = this.buf.readUInt32LE(this.pos);
+ this.pos += 4;
+ return val;
+ },
+
+ readSFixed32: function() {
+ var val = this.buf.readInt32LE(this.pos);
+ this.pos += 4;
+ return val;
+ },
+
+ // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed)
+
+ readFixed64: function() {
+ var val = this.buf.readUInt32LE(this.pos) + this.buf.readUInt32LE(this.pos + 4) * SHIFT_LEFT_32;
+ this.pos += 8;
+ return val;
+ },
+
+ readSFixed64: function() {
+ var val = this.buf.readUInt32LE(this.pos) + this.buf.readInt32LE(this.pos + 4) * SHIFT_LEFT_32;
+ this.pos += 8;
+ return val;
+ },
+
+ readFloat: function() {
+ var val = this.buf.readFloatLE(this.pos);
+ this.pos += 4;
+ return val;
+ },
+
+ readDouble: function() {
+ var val = this.buf.readDoubleLE(this.pos);
+ this.pos += 8;
+ return val;
+ },
+
+ readVarint: function() {
+ var buf = this.buf,
+ val, b, b0, b1, b2, b3;
+
+ b0 = buf[this.pos++]; if (b0 < 0x80) return b0; b0 = b0 & 0x7f;
+ b1 = buf[this.pos++]; if (b1 < 0x80) return b0 | b1 << 7; b1 = (b1 & 0x7f) << 7;
+ b2 = buf[this.pos++]; if (b2 < 0x80) return b0 | b1 | b2 << 14; b2 = (b2 & 0x7f) << 14;
+ b3 = buf[this.pos++]; if (b3 < 0x80) return b0 | b1 | b2 | b3 << 21;
+
+ val = b0 | b1 | b2 | (b3 & 0x7f) << 21;
+
+ b = buf[this.pos++]; val += (b & 0x7f) * 0x10000000; if (b < 0x80) return val;
+ b = buf[this.pos++]; val += (b & 0x7f) * 0x800000000; if (b < 0x80) return val;
+ b = buf[this.pos++]; val += (b & 0x7f) * 0x40000000000; if (b < 0x80) return val;
+ b = buf[this.pos++]; val += (b & 0x7f) * 0x2000000000000; if (b < 0x80) return val;
+ b = buf[this.pos++]; val += (b & 0x7f) * 0x100000000000000; if (b < 0x80) return val;
+ b = buf[this.pos++]; val += (b & 0x7f) * 0x8000000000000000; if (b < 0x80) return val;
+
+ throw new Error('Expected varint not more than 10 bytes');
+ },
+
+ readVarint64: function() {
+ var startPos = this.pos,
+ val = this.readVarint();
+
+ if (val < POW_2_63) return val;
+
+ var pos = this.pos - 2;
+ while (this.buf[pos] === 0xff) pos--;
+ if (pos < startPos) pos = startPos;
+
+ val = 0;
+ for (var i = 0; i < pos - startPos + 1; i++) {
+ var b = ~this.buf[startPos + i] & 0x7f;
+ val += i < 4 ? b << i * 7 : b * Math.pow(2, i * 7);
+ }
+
+ return -val - 1;
+ },
+
+ readSVarint: function() {
+ var num = this.readVarint();
+ return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding
+ },
+
+ readBoolean: function() {
+ return Boolean(this.readVarint());
+ },
+
+ readString: function() {
+ var end = this.readVarint() + this.pos,
+ str = this.buf.toString('utf8', this.pos, end);
+ this.pos = end;
+ return str;
+ },
+
+ readBytes: function() {
+ var end = this.readVarint() + this.pos,
+ buffer = this.buf.slice(this.pos, end);
+ this.pos = end;
+ return buffer;
+ },
+
+ // verbose for performance reasons; doesn't affect gzipped size
+
+ readPackedVarint: function() {
+ var end = this.readVarint() + this.pos, arr = [];
+ while (this.pos < end) arr.push(this.readVarint());
+ return arr;
+ },
+ readPackedSVarint: function() {
+ var end = this.readVarint() + this.pos, arr = [];
+ while (this.pos < end) arr.push(this.readSVarint());
+ return arr;
+ },
+ readPackedBoolean: function() {
+ var end = this.readVarint() + this.pos, arr = [];
+ while (this.pos < end) arr.push(this.readBoolean());
+ return arr;
+ },
+ readPackedFloat: function() {
+ var end = this.readVarint() + this.pos, arr = [];
+ while (this.pos < end) arr.push(this.readFloat());
+ return arr;
+ },
+ readPackedDouble: function() {
+ var end = this.readVarint() + this.pos, arr = [];
+ while (this.pos < end) arr.push(this.readDouble());
+ return arr;
+ },
+ readPackedFixed32: function() {
+ var end = this.readVarint() + this.pos, arr = [];
+ while (this.pos < end) arr.push(this.readFixed32());
+ return arr;
+ },
+ readPackedSFixed32: function() {
+ var end = this.readVarint() + this.pos, arr = [];
+ while (this.pos < end) arr.push(this.readSFixed32());
+ return arr;
+ },
+ readPackedFixed64: function() {
+ var end = this.readVarint() + this.pos, arr = [];
+ while (this.pos < end) arr.push(this.readFixed64());
+ return arr;
+ },
+ readPackedSFixed64: function() {
+ var end = this.readVarint() + this.pos, arr = [];
+ while (this.pos < end) arr.push(this.readSFixed64());
+ return arr;
+ },
+
+ skip: function(val) {
+ var type = val & 0x7;
+ if (type === Pbf.Varint) while (this.buf[this.pos++] > 0x7f) {}
+ else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos;
+ else if (type === Pbf.Fixed32) this.pos += 4;
+ else if (type === Pbf.Fixed64) this.pos += 8;
+ else throw new Error('Unimplemented type: ' + type);
+ },
+
+ // === WRITING =================================================================
+
+ writeTag: function(tag, type) {
+ this.writeVarint((tag << 3) | type);
+ },
+
+ realloc: function(min) {
+ var length = this.length || 16;
+
+ while (length < this.pos + min) length *= 2;
+
+ if (length !== this.length) {
+ var buf = new Buffer(length);
+ this.buf.copy(buf);
+ this.buf = buf;
+ this.length = length;
+ }
+ },
+
+ finish: function() {
+ this.length = this.pos;
+ this.pos = 0;
+ return this.buf.slice(0, this.length);
+ },
+
+ writeFixed32: function(val) {
+ this.realloc(4);
+ this.buf.writeUInt32LE(val, this.pos);
+ this.pos += 4;
+ },
+
+ writeSFixed32: function(val) {
+ this.realloc(4);
+ this.buf.writeInt32LE(val, this.pos);
+ this.pos += 4;
+ },
+
+ writeFixed64: function(val) {
+ this.realloc(8);
+ this.buf.writeInt32LE(val & -1, this.pos);
+ this.buf.writeUInt32LE(Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
+ this.pos += 8;
+ },
+
+ writeSFixed64: function(val) {
+ this.realloc(8);
+ this.buf.writeInt32LE(val & -1, this.pos);
+ this.buf.writeInt32LE(Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
+ this.pos += 8;
+ },
+
+ writeVarint: function(val) {
+ val = +val;
+
+ if (val <= 0x7f) {
+ this.realloc(1);
+ this.buf[this.pos++] = val;
+
+ } else if (val <= 0x3fff) {
+ this.realloc(2);
+ this.buf[this.pos++] = ((val >>> 0) & 0x7f) | 0x80;
+ this.buf[this.pos++] = ((val >>> 7) & 0x7f);
+
+ } else if (val <= 0x1fffff) {
+ this.realloc(3);
+ this.buf[this.pos++] = ((val >>> 0) & 0x7f) | 0x80;
+ this.buf[this.pos++] = ((val >>> 7) & 0x7f) | 0x80;
+ this.buf[this.pos++] = ((val >>> 14) & 0x7f);
+
+ } else if (val <= 0xfffffff) {
+ this.realloc(4);
+ this.buf[this.pos++] = ((val >>> 0) & 0x7f) | 0x80;
+ this.buf[this.pos++] = ((val >>> 7) & 0x7f) | 0x80;
+ this.buf[this.pos++] = ((val >>> 14) & 0x7f) | 0x80;
+ this.buf[this.pos++] = ((val >>> 21) & 0x7f);
+
+ } else {
+ var pos = this.pos;
+ while (val >= 0x80) {
+ this.realloc(1);
+ this.buf[this.pos++] = (val & 0xff) | 0x80;
+ val /= 0x80;
+ }
+ this.realloc(1);
+ this.buf[this.pos++] = val | 0;
+ if (this.pos - pos > 10) throw new Error('Given varint doesn\'t fit into 10 bytes');
+ }
+ },
+
+ writeSVarint: function(val) {
+ this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2);
+ },
+
+ writeBoolean: function(val) {
+ this.writeVarint(Boolean(val));
+ },
+
+ writeString: function(str) {
+ str = String(str);
+ var bytes = Buffer.byteLength(str);
+ this.writeVarint(bytes);
+ this.realloc(bytes);
+ this.buf.write(str, this.pos);
+ this.pos += bytes;
+ },
+
+ writeFloat: function(val) {
+ this.realloc(4);
+ this.buf.writeFloatLE(val, this.pos);
+ this.pos += 4;
+ },
+
+ writeDouble: function(val) {
+ this.realloc(8);
+ this.buf.writeDoubleLE(val, this.pos);
+ this.pos += 8;
+ },
+
+ writeBytes: function(buffer) {
+ var len = buffer.length;
+ this.writeVarint(len);
+ this.realloc(len);
+ for (var i = 0; i < len; i++) this.buf[this.pos++] = buffer[i];
+ },
+
+ writeRawMessage: function(fn, obj) {
+ this.pos++; // reserve 1 byte for short message length
+
+ // write the message directly to the buffer and see how much was written
+ var startPos = this.pos;
+ fn(obj, this);
+ var len = this.pos - startPos;
+
+ var varintLen =
+ len <= 0x7f ? 1 :
+ len <= 0x3fff ? 2 :
+ len <= 0x1fffff ? 3 :
+ len <= 0xfffffff ? 4 : Math.ceil(Math.log(len) / (Math.LN2 * 7));
+
+ // if 1 byte isn't enough for encoding message length, shift the data to the right
+ if (varintLen > 1) {
+ this.realloc(varintLen - 1);
+ for (var i = this.pos - 1; i >= startPos; i--) this.buf[i + varintLen - 1] = this.buf[i];
+ }
+
+ // finally, write the message length in the reserved place and restore the position
+ this.pos = startPos - 1;
+ this.writeVarint(len);
+ this.pos += len;
+ },
+
+ writeMessage: function(tag, fn, obj) {
+ this.writeTag(tag, Pbf.Bytes);
+ this.writeRawMessage(fn, obj);
+ },
+
+ writePackedVarint: function(tag, arr) { this.writeMessage(tag, writePackedVarint, arr); },
+ writePackedSVarint: function(tag, arr) { this.writeMessage(tag, writePackedSVarint, arr); },
+ writePackedBoolean: function(tag, arr) { this.writeMessage(tag, writePackedBoolean, arr); },
+ writePackedFloat: function(tag, arr) { this.writeMessage(tag, writePackedFloat, arr); },
+ writePackedDouble: function(tag, arr) { this.writeMessage(tag, writePackedDouble, arr); },
+ writePackedFixed32: function(tag, arr) { this.writeMessage(tag, writePackedFixed32, arr); },
+ writePackedSFixed32: function(tag, arr) { this.writeMessage(tag, writePackedSFixed32, arr); },
+ writePackedFixed64: function(tag, arr) { this.writeMessage(tag, writePackedFixed64, arr); },
+ writePackedSFixed64: function(tag, arr) { this.writeMessage(tag, writePackedSFixed64, arr); },
+
+ writeBytesField: function(tag, buffer) {
+ this.writeTag(tag, Pbf.Bytes);
+ this.writeBytes(buffer);
+ },
+ writeFixed32Field: function(tag, val) {
+ this.writeTag(tag, Pbf.Fixed32);
+ this.writeFixed32(val);
+ },
+ writeSFixed32Field: function(tag, val) {
+ this.writeTag(tag, Pbf.Fixed32);
+ this.writeSFixed32(val);
+ },
+ writeFixed64Field: function(tag, val) {
+ this.writeTag(tag, Pbf.Fixed64);
+ this.writeFixed64(val);
+ },
+ writeSFixed64Field: function(tag, val) {
+ this.writeTag(tag, Pbf.Fixed64);
+ this.writeSFixed64(val);
+ },
+ writeVarintField: function(tag, val) {
+ this.writeTag(tag, Pbf.Varint);
+ this.writeVarint(val);
+ },
+ writeSVarintField: function(tag, val) {
+ this.writeTag(tag, Pbf.Varint);
+ this.writeSVarint(val);
+ },
+ writeStringField: function(tag, str) {
+ this.writeTag(tag, Pbf.Bytes);
+ this.writeString(str);
+ },
+ writeFloatField: function(tag, val) {
+ this.writeTag(tag, Pbf.Fixed32);
+ this.writeFloat(val);
+ },
+ writeDoubleField: function(tag, val) {
+ this.writeTag(tag, Pbf.Fixed64);
+ this.writeDouble(val);
+ },
+ writeBooleanField: function(tag, val) {
+ this.writeVarintField(tag, Boolean(val));
+ }
+};
+
+function writePackedVarint(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeVarint(arr[i]); }
+function writePackedSVarint(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSVarint(arr[i]); }
+function writePackedFloat(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFloat(arr[i]); }
+function writePackedDouble(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeDouble(arr[i]); }
+function writePackedBoolean(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeBoolean(arr[i]); }
+function writePackedFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFixed32(arr[i]); }
+function writePackedSFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed32(arr[i]); }
+function writePackedFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFixed64(arr[i]); }
+function writePackedSFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed64(arr[i]); }
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./buffer":1}],3:[function(_dereq_,module,exports){
+exports.read = function (buffer, offset, isLE, mLen, nBytes) {
+ var e, m
+ var eLen = nBytes * 8 - mLen - 1
+ var eMax = (1 << eLen) - 1
+ var eBias = eMax >> 1
+ var nBits = -7
+ var i = isLE ? (nBytes - 1) : 0
+ var d = isLE ? -1 : 1
+ var s = buffer[offset + i]
+
+ i += d
+
+ e = s & ((1 << (-nBits)) - 1)
+ s >>= (-nBits)
+ nBits += eLen
+ for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+
+ m = e & ((1 << (-nBits)) - 1)
+ e >>= (-nBits)
+ nBits += mLen
+ for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+
+ if (e === 0) {
+ e = 1 - eBias
+ } else if (e === eMax) {
+ return m ? NaN : ((s ? -1 : 1) * Infinity)
+ } else {
+ m = m + Math.pow(2, mLen)
+ e = e - eBias
+ }
+ return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
+}
+
+exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
+ var e, m, c
+ var eLen = nBytes * 8 - mLen - 1
+ var eMax = (1 << eLen) - 1
+ var eBias = eMax >> 1
+ var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
+ var i = isLE ? 0 : (nBytes - 1)
+ var d = isLE ? 1 : -1
+ var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
+
+ value = Math.abs(value)
+
+ if (isNaN(value) || value === Infinity) {
+ m = isNaN(value) ? 1 : 0
+ e = eMax
+ } else {
+ e = Math.floor(Math.log(value) / Math.LN2)
+ if (value * (c = Math.pow(2, -e)) < 1) {
+ e--
+ c *= 2
+ }
+ if (e + eBias >= 1) {
+ value += rt / c
+ } else {
+ value += rt * Math.pow(2, 1 - eBias)
+ }
+ if (value * c >= 2) {
+ e++
+ c /= 2
+ }
+
+ if (e + eBias >= eMax) {
+ m = 0
+ e = eMax
+ } else if (e + eBias >= 1) {
+ m = (value * c - 1) * Math.pow(2, mLen)
+ e = e + eBias
+ } else {
+ m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
+ e = 0
+ }
+ }
+
+ for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
+
+ e = (e << mLen) | m
+ eLen += mLen
+ for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
+
+ buffer[offset + i - d] |= s * 128
+}
+
+},{}]},{},[2])(2)
+});
+ol.ext.pbf = module.exports;
+})();
+
+goog.provide('ol.ext.vectortile');
+/** @typedef {function(*)} */
+ol.ext.vectortile;
+(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.vectortile = 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(_dereq_,module,exports){
+module.exports.VectorTile = _dereq_('./lib/vectortile.js');
+module.exports.VectorTileFeature = _dereq_('./lib/vectortilefeature.js');
+module.exports.VectorTileLayer = _dereq_('./lib/vectortilelayer.js');
+
+},{"./lib/vectortile.js":2,"./lib/vectortilefeature.js":3,"./lib/vectortilelayer.js":4}],2:[function(_dereq_,module,exports){
+'use strict';
+
+var VectorTileLayer = _dereq_('./vectortilelayer');
+
+module.exports = VectorTile;
+
+function VectorTile(pbf, end) {
+ this.layers = pbf.readFields(readTile, {}, end);
+}
+
+function readTile(tag, layers, pbf) {
+ if (tag === 3) {
+ var layer = new VectorTileLayer(pbf, pbf.readVarint() + pbf.pos);
+ if (layer.length) layers[layer.name] = layer;
+ }
+}
+
+
+},{"./vectortilelayer":4}],3:[function(_dereq_,module,exports){
+'use strict';
+
+var Point = _dereq_('point-geometry');
+
+module.exports = VectorTileFeature;
+
+function VectorTileFeature(pbf, end, extent, keys, values) {
+ // Public
+ this.properties = {};
+ this.extent = extent;
+ this.type = 0;
+
+ // Private
+ this._pbf = pbf;
+ this._geometry = -1;
+ this._keys = keys;
+ this._values = values;
+
+ pbf.readFields(readFeature, this, end);
+}
+
+function readFeature(tag, feature, pbf) {
+ if (tag == 1) feature._id = pbf.readVarint();
+ else if (tag == 2) readTag(pbf, feature);
+ else if (tag == 3) feature.type = pbf.readVarint();
+ else if (tag == 4) feature._geometry = pbf.pos;
+}
+
+function readTag(pbf, feature) {
+ var end = pbf.readVarint() + pbf.pos;
+
+ while (pbf.pos < end) {
+ var key = feature._keys[pbf.readVarint()],
+ value = feature._values[pbf.readVarint()];
+ feature.properties[key] = value;
+ }
+}
+
+VectorTileFeature.types = ['Unknown', 'Point', 'LineString', 'Polygon'];
+
+VectorTileFeature.prototype.loadGeometry = function() {
+ var pbf = this._pbf;
+ pbf.pos = this._geometry;
+
+ var end = pbf.readVarint() + pbf.pos,
+ cmd = 1,
+ length = 0,
+ x = 0,
+ y = 0,
+ lines = [],
+ line;
+
+ while (pbf.pos < end) {
+ if (!length) {
+ var cmdLen = pbf.readVarint();
+ cmd = cmdLen & 0x7;
+ length = cmdLen >> 3;
+ }
+
+ length--;
+
+ if (cmd === 1 || cmd === 2) {
+ x += pbf.readSVarint();
+ y += pbf.readSVarint();
+
+ if (cmd === 1) { // moveTo
+ if (line) lines.push(line);
+ line = [];
+ }
+
+ line.push(new Point(x, y));
+
+ } else if (cmd === 7) {
+
+ // Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90
+ if (line) {
+ line.push(line[0].clone()); // closePolygon
+ }
+
+ } else {
+ throw new Error('unknown command ' + cmd);
+ }
+ }
+
+ if (line) lines.push(line);
+
+ return lines;
+};
+
+VectorTileFeature.prototype.bbox = function() {
+ var pbf = this._pbf;
+ pbf.pos = this._geometry;
+
+ var end = pbf.readVarint() + pbf.pos,
+ cmd = 1,
+ length = 0,
+ x = 0,
+ y = 0,
+ x1 = Infinity,
+ x2 = -Infinity,
+ y1 = Infinity,
+ y2 = -Infinity;
+
+ while (pbf.pos < end) {
+ if (!length) {
+ var cmdLen = pbf.readVarint();
+ cmd = cmdLen & 0x7;
+ length = cmdLen >> 3;
+ }
+
+ length--;
+
+ if (cmd === 1 || cmd === 2) {
+ x += pbf.readSVarint();
+ y += pbf.readSVarint();
+ if (x < x1) x1 = x;
+ if (x > x2) x2 = x;
+ if (y < y1) y1 = y;
+ if (y > y2) y2 = y;
+
+ } else if (cmd !== 7) {
+ throw new Error('unknown command ' + cmd);
+ }
+ }
+
+ return [x1, y1, x2, y2];
+};
+
+VectorTileFeature.prototype.toGeoJSON = function(x, y, z) {
+ var size = this.extent * Math.pow(2, z),
+ x0 = this.extent * x,
+ y0 = this.extent * y,
+ coords = this.loadGeometry(),
+ type = VectorTileFeature.types[this.type];
+
+ for (var i = 0; i < coords.length; i++) {
+ var line = coords[i];
+ for (var j = 0; j < line.length; j++) {
+ var p = line[j], y2 = 180 - (p.y + y0) * 360 / size;
+ line[j] = [
+ (p.x + x0) * 360 / size - 180,
+ 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90
+ ];
+ }
+ }
+
+ if (type === 'Point' && coords.length === 1) {
+ coords = coords[0][0];
+ } else if (type === 'Point') {
+ coords = coords[0];
+ type = 'MultiPoint';
+ } else if (type === 'LineString' && coords.length === 1) {
+ coords = coords[0];
+ } else if (type === 'LineString') {
+ type = 'MultiLineString';
+ }
+
+ var result = {
+ type: "Feature",
+ geometry: {
+ type: type,
+ coordinates: coords
+ },
+ properties: this.properties
+ };
+
+ if ('_id' in this) {
+ result.id = this._id;
+ }
+
+ return result;
+};
+
+},{"point-geometry":5}],4:[function(_dereq_,module,exports){
+'use strict';
+
+var VectorTileFeature = _dereq_('./vectortilefeature.js');
+
+module.exports = VectorTileLayer;
+
+function VectorTileLayer(pbf, end) {
+ // Public
+ this.version = 1;
+ this.name = null;
+ this.extent = 4096;
+ this.length = 0;
+
+ // Private
+ this._pbf = pbf;
+ this._keys = [];
+ this._values = [];
+ this._features = [];
+
+ pbf.readFields(readLayer, this, end);
+
+ this.length = this._features.length;
+}
+
+function readLayer(tag, layer, pbf) {
+ if (tag === 15) layer.version = pbf.readVarint();
+ else if (tag === 1) layer.name = pbf.readString();
+ else if (tag === 5) layer.extent = pbf.readVarint();
+ else if (tag === 2) layer._features.push(pbf.pos);
+ else if (tag === 3) layer._keys.push(pbf.readString());
+ else if (tag === 4) layer._values.push(readValueMessage(pbf));
+}
+
+function readValueMessage(pbf) {
+ var value = null,
+ end = pbf.readVarint() + pbf.pos;
+
+ while (pbf.pos < end) {
+ var tag = pbf.readVarint() >> 3;
+
+ value = tag === 1 ? pbf.readString() :
+ tag === 2 ? pbf.readFloat() :
+ tag === 3 ? pbf.readDouble() :
+ tag === 4 ? pbf.readVarint64() :
+ tag === 5 ? pbf.readVarint() :
+ tag === 6 ? pbf.readSVarint() :
+ tag === 7 ? pbf.readBoolean() : null;
+ }
+
+ return value;
+}
+
+// return feature `i` from this layer as a `VectorTileFeature`
+VectorTileLayer.prototype.feature = function(i) {
+ if (i < 0 || i >= this._features.length) throw new Error('feature index out of bounds');
+
+ this._pbf.pos = this._features[i];
+
+ var end = this._pbf.readVarint() + this._pbf.pos;
+ return new VectorTileFeature(this._pbf, end, this.extent, this._keys, this._values);
+};
+
+},{"./vectortilefeature.js":3}],5:[function(_dereq_,module,exports){
+'use strict';
+
+module.exports = Point;
+
+function Point(x, y) {
+ this.x = x;
+ this.y = y;
+}
+
+Point.prototype = {
+ clone: function() { return new Point(this.x, this.y); },
+
+ add: function(p) { return this.clone()._add(p); },
+ sub: function(p) { return this.clone()._sub(p); },
+ mult: function(k) { return this.clone()._mult(k); },
+ div: function(k) { return this.clone()._div(k); },
+ rotate: function(a) { return this.clone()._rotate(a); },
+ matMult: function(m) { return this.clone()._matMult(m); },
+ unit: function() { return this.clone()._unit(); },
+ perp: function() { return this.clone()._perp(); },
+ round: function() { return this.clone()._round(); },
+
+ mag: function() {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ },
+
+ equals: function(p) {
+ return this.x === p.x &&
+ this.y === p.y;
+ },
+
+ dist: function(p) {
+ return Math.sqrt(this.distSqr(p));
+ },
+
+ distSqr: function(p) {
+ var dx = p.x - this.x,
+ dy = p.y - this.y;
+ return dx * dx + dy * dy;
+ },
+
+ angle: function() {
+ return Math.atan2(this.y, this.x);
+ },
+
+ angleTo: function(b) {
+ return Math.atan2(this.y - b.y, this.x - b.x);
+ },
+
+ angleWith: function(b) {
+ return this.angleWithSep(b.x, b.y);
+ },
+
+ // Find the angle of the two vectors, solving the formula for the cross product a x b = |a||b|sin(θ) for θ.
+ angleWithSep: function(x, y) {
+ return Math.atan2(
+ this.x * y - this.y * x,
+ this.x * x + this.y * y);
+ },
+
+ _matMult: function(m) {
+ var x = m[0] * this.x + m[1] * this.y,
+ y = m[2] * this.x + m[3] * this.y;
+ this.x = x;
+ this.y = y;
+ return this;
+ },
+
+ _add: function(p) {
+ this.x += p.x;
+ this.y += p.y;
+ return this;
+ },
+
+ _sub: function(p) {
+ this.x -= p.x;
+ this.y -= p.y;
+ return this;
+ },
+
+ _mult: function(k) {
+ this.x *= k;
+ this.y *= k;
+ return this;
+ },
+
+ _div: function(k) {
+ this.x /= k;
+ this.y /= k;
+ return this;
+ },
+
+ _unit: function() {
+ this._div(this.mag());
+ return this;
+ },
+
+ _perp: function() {
+ var y = this.y;
+ this.y = this.x;
+ this.x = -y;
+ return this;
+ },
+
+ _rotate: function(angle) {
+ var cos = Math.cos(angle),
+ sin = Math.sin(angle),
+ x = cos * this.x - sin * this.y,
+ y = sin * this.x + cos * this.y;
+ this.x = x;
+ this.y = y;
+ return this;
+ },
+
+ _round: function() {
+ this.x = Math.round(this.x);
+ this.y = Math.round(this.y);
+ return this;
+ }
+};
+
+// constructs Point from an array if necessary
+Point.convert = function (a) {
+ if (a instanceof Point) {
+ return a;
+ }
+ if (Array.isArray(a)) {
+ return new Point(a[0], a[1]);
+ }
+ return a;
+};
+
+},{}]},{},[1])(1)
+});
+ol.ext.vectortile = module.exports;
+})();
+
+//FIXME Implement projection handling
+
+goog.provide('ol.format.MVT');
+
+goog.require('goog.asserts');
+goog.require('ol.Feature');
+goog.require('ol.ext.pbf');
+goog.require('ol.ext.vectortile');
+goog.require('ol.format.Feature');
+goog.require('ol.format.FormatType');
+goog.require('ol.geom.Geometry');
+goog.require('ol.geom.GeometryLayout');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.MultiLineString');
+goog.require('ol.geom.MultiPoint');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.proj');
+goog.require('ol.proj.Projection');
+goog.require('ol.proj.Units');
+goog.require('ol.render.Feature');
+
+
+
+/**
+ * @classdesc
+ * Feature format for reading data in the Mapbox MVT format.
+ *
+ * @constructor
+ * @extends {ol.format.Feature}
+ * @param {olx.format.MVTOptions=} opt_options Options.
+ * @api
+ */
+ol.format.MVT = function(opt_options) {
+
+ goog.base(this);
+
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * @type {ol.proj.Projection}
+ */
+ this.defaultDataProjection = new ol.proj.Projection({
+ code: 'EPSG:3857',
+ units: ol.proj.Units.TILE_PIXELS
+ });
+
+ /**
+ * @private
+ * @type {function((ol.geom.Geometry|Object.<string, *>)=)|
+ * function(ol.geom.GeometryType,Array.<number>,
+ * (Array.<number>|Array.<Array.<number>>),Object.<string, *>)}
+ */
+ this.featureClass_ = options.featureClass ?
+ options.featureClass : ol.render.Feature;
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.geometryName_ = options.geometryName ?
+ options.geometryName : 'geometry';
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.layerName_ = options.layerName ? options.layerName : 'layer';
+
+ /**
+ * @private
+ * @type {Array.<string>}
+ */
+ this.layers_ = options.layers ? options.layers : null;
+
+};
+goog.inherits(ol.format.MVT, ol.format.Feature);
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.MVT.prototype.getType = function() {
+ return ol.format.FormatType.ARRAY_BUFFER;
+};
+
+
+/**
+ * @private
+ * @param {Object} rawFeature Raw Mapbox feature.
+ * @param {string} layer Layer.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ */
+ol.format.MVT.prototype.readFeature_ = function(
+ rawFeature, layer, opt_options) {
+ var feature = new this.featureClass_();
+ var values = rawFeature.properties;
+ values[this.layerName_] = layer;
+ var geometry = ol.format.Feature.transformWithOptions(
+ ol.format.MVT.readGeometry_(rawFeature), false,
+ this.adaptOptions(opt_options));
+ if (geometry) {
+ goog.asserts.assertInstanceof(geometry, ol.geom.Geometry);
+ values[this.geometryName_] = geometry;
+ }
+ feature.setProperties(values);
+ feature.setGeometryName(this.geometryName_);
+ return feature;
+};
+
+
+/**
+ * @private
+ * @param {Object} rawFeature Raw Mapbox feature.
+ * @param {string} layer Layer.
+ * @return {ol.render.Feature} Feature.
+ */
+ol.format.MVT.prototype.readRenderFeature_ = function(rawFeature, layer) {
+ var coords = rawFeature.loadGeometry();
+ var ends = [];
+ var flatCoordinates = [];
+ ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends);
+
+ var type = rawFeature.type;
+ /** @type {ol.geom.GeometryType} */
+ var geometryType;
+ if (type === 1) {
+ geometryType = coords.length === 1 ?
+ ol.geom.GeometryType.POINT : ol.geom.GeometryType.MULTI_POINT;
+ } else if (type === 2) {
+ if (coords.length === 1) {
+ geometryType = ol.geom.GeometryType.LINE_STRING;
+ } else {
+ geometryType = ol.geom.GeometryType.MULTI_LINE_STRING;
+ }
+ } else if (type === 3) {
+ geometryType = ol.geom.GeometryType.POLYGON;
+ }
+
+ var values = rawFeature.properties;
+ values[this.layerName_] = layer;
+
+ return new this.featureClass_(geometryType, flatCoordinates, ends, values);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.MVT.prototype.readFeatures = function(source, opt_options) {
+ goog.asserts.assertInstanceof(source, ArrayBuffer);
+
+ var layers = this.layers_;
+
+ var pbf = new ol.ext.pbf(source);
+ var tile = new ol.ext.vectortile.VectorTile(pbf);
+ var features = [];
+ var featureClass = this.featureClass_;
+ var layer, feature;
+ for (var name in tile.layers) {
+ if (layers && layers.indexOf(name) == -1) {
+ continue;
+ }
+ layer = tile.layers[name];
+
+ for (var i = 0, ii = layer.length; i < ii; ++i) {
+ if (featureClass === ol.render.Feature) {
+ feature = this.readRenderFeature_(layer.feature(i), name);
+ } else {
+ feature = this.readFeature_(layer.feature(i), name, opt_options);
+ }
+ features.push(feature);
+ }
+ }
+
+ return features;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.MVT.prototype.readProjection = function(source) {
+ return this.defaultDataProjection;
+};
+
+
+/**
+ * Sets the layers that features will be read from.
+ * @param {Array.<string>} layers Layers.
+ * @api
+ */
+ol.format.MVT.prototype.setLayers = function(layers) {
+ this.layers_ = layers;
+};
+
+
+/**
+ * @private
+ * @param {Object} coords Raw feature coordinates.
+ * @param {Array.<number>} flatCoordinates Flat coordinates to be populated by
+ * this function.
+ * @param {Array.<number>} ends Ends to be populated by this function.
+ */
+ol.format.MVT.calculateFlatCoordinates_ = function(
+ coords, flatCoordinates, ends) {
+ var end = 0;
+ var line, coord;
+ for (var i = 0, ii = coords.length; i < ii; ++i) {
+ line = coords[i];
+ for (var j = 0, jj = line.length; j < jj; ++j) {
+ coord = line[j];
+ // Non-tilespace coords can be calculated here when a TileGrid and
+ // TileCoord are known.
+ flatCoordinates.push(coord.x, coord.y);
+ }
+ end += 2 * j;
+ ends.push(end);
+ }
+};
+
+
+/**
+ * @private
+ * @param {Object} rawFeature Raw Mapbox feature.
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.MVT.readGeometry_ = function(rawFeature) {
+ var type = rawFeature.type;
+ if (type === 0) {
+ return null;
+ }
+
+ var coords = rawFeature.loadGeometry();
+ var ends = [];
+ var flatCoordinates = [];
+ ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends);
+
+ var geom;
+ if (type === 1) {
+ geom = coords.length === 1 ?
+ new ol.geom.Point(null) : new ol.geom.MultiPoint(null);
+ } else if (type === 2) {
+ if (coords.length === 1) {
+ geom = new ol.geom.LineString(null);
+ } else {
+ geom = new ol.geom.MultiLineString(null);
+ }
+ } else if (type === 3) {
+ geom = new ol.geom.Polygon(null);
+ }
+
+ geom.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates,
+ ends);
+
+ return geom;
+};
+
+// 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');
+
+
+
+/**
+ * @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.format.OSMXML = function() {
+ goog.base(this);
+
+ /**
+ * @inheritDoc
+ */
+ this.defaultDataProjection = ol.proj.get('EPSG:4326');
+};
+goog.inherits(ol.format.OSMXML, ol.format.XMLFeature);
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.OSMXML.EXTENSIONS_ = ['.osm'];
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.OSMXML.prototype.getExtensions = function() {
+ return ol.format.OSMXML.EXTENSIONS_;
+};
+
+
+/**
+ * @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,
+ '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);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Track.
+ */
+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'));
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Track.
+ */
+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');
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Array.<string>}
+ */
+ol.format.OSMXML.NAMESPACE_URIS_ = [
+ null
+];
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.OSMXML.NAMESPACE_URIS_, {
+ 'nd': ol.format.OSMXML.readNd_,
+ 'tag': ol.format.OSMXML.readTag_
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OSMXML.PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.OSMXML.NAMESPACE_URIS_, {
+ 'node': ol.format.OSMXML.readNode_,
+ 'way': ol.format.OSMXML.readWay_
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OSMXML.NODE_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.OSMXML.NAMESPACE_URIS_, {
+ 'tag': ol.format.OSMXML.readTag_
+ });
+
+
+/**
+ * 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;
+
+
+/**
+ * @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 (state.features) {
+ return state.features;
+ }
+ }
+ return [];
+};
+
+
+/**
+ * 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;
+
+goog.provide('ol.format.XLink');
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink';
+
+
+/**
+ * @param {Node} node Node.
+ * @return {boolean|undefined} Boolean.
+ */
+ol.format.XLink.readHref = function(node) {
+ return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href');
+};
+
+goog.provide('ol.format.XML');
+
+goog.require('goog.asserts');
+goog.require('ol.xml');
+
+
+
+/**
+ * @classdesc
+ * Generic format for reading non-feature XML data
+ *
+ * @constructor
+ */
+ol.format.XML = function() {
+};
+
+
+/**
+ * @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;
+ }
+};
+
+
+/**
+ * @param {Document} doc Document.
+ * @return {Object}
+ */
+ol.format.XML.prototype.readFromDocument = goog.abstractMethod;
+
+
+/**
+ * @param {Node} node Node.
+ * @return {Object}
+ */
+ol.format.XML.prototype.readFromNode = goog.abstractMethod;
+
+goog.provide('ol.format.OWS');
+
+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');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.format.XML}
+ */
+ol.format.OWS = function() {
+ goog.base(this);
+};
+goog.inherits(ol.format.OWS, ol.format.XML);
+
+
+/**
+ * @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; n; n = n.nextSibling) {
+ if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+ return this.readFromNode(n);
+ }
+ }
+ return 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 owsObject ? owsObject : null;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+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);
+};
+
+
+/**
+ * @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,
+ '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 {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,
+ 'node.nodeType should be ELEMENT');
+ goog.asserts.assert(node.localName == 'Constraint',
+ 'localName should be Constraint');
+ var name = node.getAttribute('name');
+ if (!name) {
+ return undefined;
+ }
+ return ol.xml.pushParseAndPop({'name': name},
+ ol.format.OWS.CONSTRAINT_PARSERS_, node,
+ objectStack);
+};
+
+
+/**
+ * @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,
+ '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 {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,
+ '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);
+};
+
+
+/**
+ * @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 (!href) {
+ return undefined;
+ }
+ return ol.xml.pushParseAndPop({'href': href},
+ ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @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 (!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;
+
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @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 {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {string|undefined}
+ */
+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);
+};
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.OWS.NAMESPACE_URIS_ = [
+ null,
+ 'http://www.opengis.net/ows/1.1'
+];
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.PARSERS_ = ol.xml.makeStructureNS(
+ 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_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.ADDRESS_PARSERS_ = ol.xml.makeStructureNS(
+ 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)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.OWS.NAMESPACE_URIS_, {
+ 'Value': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readValue_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.OWS.NAMESPACE_URIS_, {
+ 'AllowedValues': ol.xml.makeObjectPropertySetter(
+ ol.format.OWS.readAllowedValues_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.CONTACT_INFO_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.OWS.NAMESPACE_URIS_, {
+ 'Phone': ol.xml.makeObjectPropertySetter(ol.format.OWS.readPhone_),
+ 'Address': ol.xml.makeObjectPropertySetter(ol.format.OWS.readAddress_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.DCP_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.OWS.NAMESPACE_URIS_, {
+ 'HTTP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readHttp_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.OWS.NAMESPACE_URIS_, {
+ 'Get': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readGet_),
+ 'Post': undefined // TODO
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.OWS.NAMESPACE_URIS_, {
+ 'DCP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readDcp_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.OWS.NAMESPACE_URIS_, {
+ 'Operation': ol.format.OWS.readOperation_
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.PHONE_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.OWS.NAMESPACE_URIS_, {
+ 'Voice': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+ 'Facsimile': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.OWS.NAMESPACE_URIS_, {
+ 'Constraint': ol.xml.makeObjectPropertyPusher(
+ ol.format.OWS.readConstraint_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.SERVICE_CONTACT_PARSERS_ =
+ ol.xml.makeStructureNS(
+ 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_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_ =
+ ol.xml.makeStructureNS(
+ 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.makeStructureNS(
+ 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 (opt_dest !== undefined) {
+ dest = opt_dest;
+ destOffset = opt_destOffset !== undefined ? opt_destOffset : 0;
+ } else {
+ goog.asserts.assert(opt_destOffset === undefined,
+ 'opt_destOffSet should be defined');
+ 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;
+};
+
+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.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.format.TextFeature}
+ * @param {olx.format.PolylineOptions=} opt_options
+ * Optional configuration object.
+ * @api stable
+ */
+ol.format.Polyline = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ goog.base(this);
+
+ /**
+ * @inheritDoc
+ */
+ this.defaultDataProjection = ol.proj.get('EPSG:4326');
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.factor_ = options.factor ? options.factor : 1e5;
+
+ /**
+ * @private
+ * @type {ol.geom.GeometryLayout}
+ */
+ this.geometryLayout_ = options.geometryLayout ?
+ options.geometryLayout : ol.geom.GeometryLayout.XY;
+};
+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
+ */
+ol.format.Polyline.encodeDeltas = function(numbers, stride, opt_factor) {
+ var factor = 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;
+ }
+ }
+
+ return ol.format.Polyline.encodeFloats(numbers, factor);
+};
+
+
+/**
+ * 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
+ */
+ol.format.Polyline.decodeDeltas = function(encoded, stride, opt_factor) {
+ var factor = opt_factor ? opt_factor : 1e5;
+ var d;
+
+ /** @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);
+
+ 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];
+ }
+ }
+
+ return numbers;
+};
+
+
+/**
+ * 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.format.Polyline.encodeFloats = function(numbers, opt_factor) {
+ var factor = 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);
+};
+
+
+/**
+ * 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.format.Polyline.decodeFloats = function(encoded, opt_factor) {
+ var factor = 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;
+};
+
+
+/**
+ * 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.
+ */
+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);
+};
+
+
+/**
+ * Decode a list of signed integers from an encoded string
+ *
+ * @param {string} encoded An encoded string.
+ * @return {Array.<number>} A list of signed integers.
+ */
+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;
+};
+
+
+/**
+ * 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.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]);
+ }
+ return encoded;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * Read the feature from the Polyline source. The coordinates are assumed to be
+ * in two dimensions and in latitude, longitude order.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ * @api stable
+ */
+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);
+};
+
+
+/**
+ * Read the feature from the source. As Polyline sources contain a single
+ * feature, this will return the feature in an array.
+ *
+ * @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.Polyline.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.Polyline.prototype.readFeaturesFromText =
+ function(text, opt_options) {
+ var feature = this.readFeatureFromText(text, opt_options);
+ return [feature];
+};
+
+
+/**
+ * Read the geometry from the source.
+ *
+ * @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.Polyline.prototype.readGeometry;
+
+
+/**
+ * @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)));
+};
+
+
+/**
+ * Read the projection from a Polyline source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.Polyline.prototype.readProjection;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.Polyline.prototype.writeFeatureText = function(feature, opt_options) {
+ var geometry = feature.getGeometry();
+ if (geometry) {
+ return this.writeGeometryText(geometry, opt_options);
+ } else {
+ goog.asserts.fail('geometry needs to be defined');
+ return '';
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+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);
+};
+
+
+/**
+ * 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
+ */
+ol.format.Polyline.prototype.writeGeometry;
+
+
+/**
+ * @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');
+
+
+
+/**
+ * @classdesc
+ * Feature format for reading data in the TopoJSON format.
+ *
+ * @constructor
+ * @extends {ol.format.JSONFeature}
+ * @param {olx.format.TopoJSONOptions=} opt_options Options.
+ * @api stable
+ */
+ol.format.TopoJSON = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ goog.base(this);
+
+ /**
+ * @inheritDoc
+ */
+ this.defaultDataProjection = ol.proj.get(
+ options.defaultDataProjection ?
+ options.defaultDataProjection : 'EPSG:4326');
+
+};
+goog.inherits(ol.format.TopoJSON, ol.format.JSONFeature);
+
+
+/**
+ * @const {Array.<string>}
+ * @private
+ */
+ol.format.TopoJSON.EXTENSIONS_ = ['.topojson'];
+
+
+/**
+ * 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
+ */
+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;
+};
+
+
+/**
+ * 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
+ */
+ol.format.TopoJSON.readPointGeometry_ = function(object, scale, translate) {
+ var coordinates = object.coordinates;
+ if (scale && translate) {
+ ol.format.TopoJSON.transformVertex_(coordinates, scale, translate);
+ }
+ return new ol.geom.Point(coordinates);
+};
+
+
+/**
+ * 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
+ */
+ol.format.TopoJSON.readMultiPointGeometry_ = function(object, scale,
+ translate) {
+ var coordinates = object.coordinates;
+ var i, ii;
+ if (scale && translate) {
+ for (i = 0, ii = coordinates.length; i < ii; ++i) {
+ ol.format.TopoJSON.transformVertex_(coordinates[i], scale, translate);
+ }
+ }
+ return new ol.geom.MultiPoint(coordinates);
+};
+
+
+/**
+ * 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
+ */
+ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) {
+ var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs);
+ return new ol.geom.LineString(coordinates);
+};
+
+
+/**
+ * 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
+ */
+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);
+};
+
+
+/**
+ * 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
+ */
+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);
+};
+
+
+/**
+ * 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
+ */
+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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TopoJSON.prototype.getExtensions = function() {
+ return ol.format.TopoJSON.EXTENSIONS_;
+};
+
+
+/**
+ * Create features from a TopoJSON GeometryCollection object.
+ *
+ * @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
+ */
+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;
+};
+
+
+/**
+ * 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
+ */
+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(geometryReader, 'geometryReader should be defined');
+ if ((type === 'Point') || (type === 'MultiPoint')) {
+ geometry = geometryReader(object, scale, translate);
+ } 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 (object.id !== undefined) {
+ feature.setId(object.id);
+ }
+ if (object.properties) {
+ feature.setProperties(object.properties);
+ }
+ return feature;
+};
+
+
+/**
+ * Read all features from a TopoJSON source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {Array.<ol.Feature>} Features.
+ * @api stable
+ */
+ol.format.TopoJSON.prototype.readFeatures;
+
+
+/**
+ * @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 (topoJSONTopology.transform) {
+ transform = topoJSONTopology.transform;
+ scale = transform.scale;
+ translate = transform.translate;
+ }
+ var arcs = topoJSONTopology.arcs;
+ if (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 [];
+ }
+};
+
+
+/**
+ * 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
+ */
+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);
+ }
+};
+
+
+/**
+ * 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
+ */
+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);
+ }
+};
+
+
+/**
+ * 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
+ */
+ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) {
+ vertex[0] = vertex[0] * scale[0] + translate[0];
+ vertex[1] = vertex[1] * scale[1] + translate[1];
+};
+
+
+/**
+ * Read the projection from a TopoJSON source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} object Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.TopoJSON.prototype.readProjection = function(object) {
+ return this.defaultDataProjection;
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Object.<string, function(TopoJSONGeometry, Array, ...Array): ol.geom.Geometry>}
+ */
+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');
+
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('goog.object');
+goog.require('ol');
+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');
+
+
+
+/**
+ * @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 = opt_options ? opt_options : {};
+
+ /**
+ * @private
+ * @type {Array.<string>|string|undefined}
+ */
+ this.featureType_ = options.featureType;
+
+ /**
+ * @private
+ * @type {Object.<string, string>|string|undefined}
+ */
+ this.featureNS_ = options.featureNS;
+
+ /**
+ * @private
+ * @type {ol.format.GMLBase}
+ */
+ this.gmlFormat_ = options.gmlFormat ?
+ options.gmlFormat : new ol.format.GML3();
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.schemaLocation_ = options.schemaLocation ?
+ options.schemaLocation : ol.format.WFS.SCHEMA_LOCATION;
+
+ goog.base(this);
+};
+goog.inherits(ol.format.WFS, ol.format.XMLFeature);
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.WFS.FEATURE_PREFIX = 'feature';
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.WFS.XMLNS = 'http://www.w3.org/2000/xmlns/';
+
+
+/**
+ * Number of features; bounds/extent.
+ * @typedef {{numberOfFeatures: number,
+ * bounds: ol.Extent}}
+ * @api stable
+ */
+ol.format.WFS.FeatureCollectionMetadata;
+
+
+/**
+ * Total deleted; total inserted; total updated; array of insert ids.
+ * @typedef {{totalDeleted: number,
+ * totalInserted: number,
+ * totalUpdated: number,
+ * insertIds: Array.<string>}}
+ * @api stable
+ */
+ol.format.WFS.TransactionResponse;
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.WFS.SCHEMA_LOCATION = 'http://www.opengis.net/wfs ' +
+ 'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd';
+
+
+/**
+ * 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;
+
+
+/**
+ * @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,
+ 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 (!features) {
+ features = [];
+ }
+ return features;
+};
+
+
+/**
+ * Read transaction response of the source.
+ *
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response.
+ * @api stable
+ */
+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;
+ }
+};
+
+
+/**
+ * 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;
+ }
+};
+
+
+/**
+ * @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; n; n = n.nextSibling) {
+ if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+ return this.readFeatureCollectionMetadataFromNode(n);
+ }
+ }
+ return undefined;
+};
+
+
+/**
+ * @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')
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {ol.format.WFS.FeatureCollectionMetadata|undefined}
+ * FeatureCollection metadata.
+ */
+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_);
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+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)
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Transaction Summary.
+ * @private
+ */
+ol.format.WFS.readTransactionSummary_ = function(node, objectStack) {
+ return ol.xml.pushParseAndPop(
+ {}, ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @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');
+ })
+ }
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WFS.INSERT_RESULTS_PARSERS_ = {
+ 'http://www.opengis.net/wfs': {
+ 'Feature': ol.format.WFS.fidParser_
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<string>|undefined} Insert results.
+ * @private
+ */
+ol.format.WFS.readInsertResults_ = function(node, objectStack) {
+ return ol.xml.pushParseAndPop(
+ [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+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 {Document} doc Document.
+ * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response.
+ */
+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; n; n = n.nextSibling) {
+ if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+ return this.readTransactionResponseFromNode(n);
+ }
+ }
+ return undefined;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response.
+ */
+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, []);
+};
+
+
+/**
+ * @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)
+ }
+};
+
+
+/**
+ * @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 {Node} node Node.
+ * @param {number|string} fid Feature identifier.
+ * @param {Array.<*>} objectStack Node stack.
+ * @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);
+};
+
+
+/**
+ * @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 = 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 (fid) {
+ ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack);
+ }
+};
+
+
+/**
+ * @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 = 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 (fid) {
+ var keys = feature.getKeys();
+ var values = [];
+ for (var i = 0, ii = keys.length; i < ii; i++) {
+ var value = feature.get(keys[i]);
+ if (value !== undefined) {
+ 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);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Object} pair Property name and value.
+ * @param {Array.<*>} objectStack Node stack.
+ * @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 (pair.value !== undefined && pair.value !== null) {
+ 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);
+ }
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {{vendorId: string, safeToIgnore: boolean, value: string}}
+ * nativeElement The native element.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.WFS.writeNative_ = function(node, nativeElement, objectStack) {
+ if (nativeElement.vendorId) {
+ node.setAttribute('vendorId', nativeElement.vendorId);
+ }
+ if (nativeElement.safeToIgnore !== undefined) {
+ node.setAttribute('safeToIgnore', nativeElement.safeToIgnore);
+ }
+ if (nativeElement.value !== undefined) {
+ ol.format.XSD.writeStringTextNode(node, nativeElement.value);
+ }
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @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_)
+ }
+};
+
+
+/**
+ * @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 = featurePrefix ? featurePrefix + ':' : '';
+ node.setAttribute('typeName', prefix + featureType);
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ if (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 (bbox) {
+ var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter');
+ ol.format.WFS.writeOgcBBOX_(child, bbox, objectStack);
+ node.appendChild(child);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {string} value PropertyName value.
+ * @param {Array.<*>} objectStack Node stack.
+ * @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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.Extent} bbox Bounding box.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.WFS.GETFEATURE_SERIALIZERS_ = {
+ 'http://www.opengis.net/wfs': {
+ 'Query': ol.xml.makeChildAppender(
+ ol.format.WFS.writeQuery_)
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<{string}>} featureTypes Feature types.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * 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 (options) {
+ if (options.handle) {
+ node.setAttribute('handle', options.handle);
+ }
+ if (options.outputFormat) {
+ node.setAttribute('outputFormat', options.outputFormat);
+ }
+ if (options.maxFeatures !== undefined) {
+ node.setAttribute('maxFeatures', options.maxFeatures);
+ }
+ if (options.resultType) {
+ node.setAttribute('resultType', options.resultType);
+ }
+ if (options.startIndex !== undefined) {
+ node.setAttribute('startIndex', options.startIndex);
+ }
+ if (options.count !== undefined) {
+ 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: options.featureNS ? options.featureNS : this.featureNS_,
+ featurePrefix: options.featurePrefix,
+ geometryName: options.geometryName,
+ bbox: options.bbox,
+ propertyNames: 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;
+};
+
+
+/**
+ * Encode format as WFS `Transaction` and return the Node.
+ *
+ * @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
+ */
+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 (options) {
+ baseObj = options.gmlOptions ? options.gmlOptions : {};
+ if (options.handle) {
+ node.setAttribute('handle', options.handle);
+ }
+ }
+ ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
+ 'xsi:schemaLocation', this.schemaLocation_);
+ if (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 (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 (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 (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;
+};
+
+
+/**
+ * Read the projection from a WFS source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {?ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.WFS.prototype.readProjection;
+
+
+/**
+ * @inheritDoc
+ */
+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; n; n = n.nextSibling) {
+ if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+ return this.readProjectionFromNode(n);
+ }
+ }
+ return null;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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 (node.firstElementChild &&
+ node.firstElementChild.firstElementChild) {
+ node = node.firstElementChild.firstElementChild;
+ for (var n = node.firstElementChild; 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.asserts');
+goog.require('ol');
+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');
+
+
+
+/**
+ * @classdesc
+ * Geometry format for reading and writing data in the `WellKnownText` (WKT)
+ * format.
+ *
+ * @constructor
+ * @extends {ol.format.TextFeature}
+ * @param {olx.format.WKTOptions=} opt_options Options.
+ * @api stable
+ */
+ol.format.WKT = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ goog.base(this);
+
+ /**
+ * Split GeometryCollection into multiple features.
+ * @type {boolean}
+ * @private
+ */
+ this.splitCollection_ = options.splitCollection !== undefined ?
+ options.splitCollection : false;
+
+};
+goog.inherits(ol.format.WKT, ol.format.TextFeature);
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.WKT.EMPTY = 'EMPTY';
+
+
+/**
+ * @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 (coordinates.length === 0) {
+ return '';
+ }
+ return coordinates[0] + ' ' + coordinates[1];
+};
+
+
+/**
+ * @param {ol.geom.MultiPoint} geom MultiPoint geometry.
+ * @return {string} Coordinates part of MultiPoint as WKT.
+ * @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]) + ')');
+ }
+ return array.join(',');
+};
+
+
+/**
+ * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry.
+ * @return {string} Coordinates part of GeometryCollection as WKT.
+ * @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]));
+ }
+ return array.join(',');
+};
+
+
+/**
+ * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry.
+ * @return {string} Coordinates part of LineString as WKT.
+ * @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]);
+ }
+ return array.join(',');
+};
+
+
+/**
+ * @param {ol.geom.MultiLineString} geom MultiLineString geometry.
+ * @return {string} Coordinates part of MultiLineString as WKT.
+ * @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]) + ')');
+ }
+ return array.join(',');
+};
+
+
+/**
+ * @param {ol.geom.Polygon} geom Polygon geometry.
+ * @return {string} Coordinates part of Polygon as WKT.
+ * @private
+ */
+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(',');
+};
+
+
+/**
+ * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry.
+ * @return {string} Coordinates part of MultiPolygon as WKT.
+ * @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]) + ')');
+ }
+ return array.join(',');
+};
+
+
+/**
+ * Encode a geometry as WKT.
+ * @param {ol.geom.Geometry} geom The geometry to encode.
+ * @return {string} WKT string for the geometry.
+ * @private
+ */
+ol.format.WKT.encode_ = function(geom) {
+ var type = geom.getType();
+ var geometryEncoder = ol.format.WKT.GeometryEncoder_[type];
+ goog.asserts.assert(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 + ')';
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, function(ol.geom.Geometry): string>}
+ * @private
+ */
+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_
+};
+
+
+/**
+ * Parse a WKT string.
+ * @param {string} wkt WKT string.
+ * @return {ol.geom.Geometry|ol.geom.GeometryCollection|undefined}
+ * The geometry created.
+ * @private
+ */
+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();
+};
+
+
+/**
+ * Read a feature from a WKT 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.WKT.prototype.readFeature;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.WKT.prototype.readFeatureFromText = function(text, opt_options) {
+ var geom = this.readGeometryFromText(text, opt_options);
+ if (geom) {
+ var feature = new ol.Feature();
+ feature.setGeometry(geom);
+ return feature;
+ }
+ return null;
+};
+
+
+/**
+ * Read all features from a WKT 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.WKT.prototype.readFeatures;
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * Read a single geometry from a WKT source.
+ *
+ * @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;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.WKT.prototype.readGeometryFromText = function(text, opt_options) {
+ var geometry = this.parse_(text);
+ if (geometry) {
+ return /** @type {ol.geom.Geometry} */ (
+ ol.format.Feature.transformWithOptions(geometry, false, opt_options));
+ } else {
+ return null;
+ }
+};
+
+
+/**
+ * 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
+ */
+ol.format.WKT.prototype.writeFeature;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.WKT.prototype.writeFeatureText = function(feature, opt_options) {
+ var geometry = feature.getGeometry();
+ if (geometry) {
+ return this.writeGeometryText(geometry, opt_options);
+ }
+ 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
+ */
+ol.format.WKT.prototype.writeFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+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);
+};
+
+
+/**
+ * Write a single geometry as a WKT string.
+ *
+ * @function
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @return {string} WKT string.
+ * @api stable
+ */
+ol.format.WKT.prototype.writeGeometry;
+
+
+/**
+ * @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)));
+};
+
+
+/**
+ * @typedef {{type: number, value: (number|string|undefined), position: number}}
+ */
+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
+};
+
+
+
+/**
+ * Class to tokenize a WKT string.
+ * @param {string} wkt WKT string.
+ * @constructor
+ * @protected
+ */
+ol.format.WKT.Lexer = function(wkt) {
+
+ /**
+ * @type {string}
+ */
+ this.wkt = wkt;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.index_ = -1;
+};
+
+
+/**
+ * @param {string} c Character.
+ * @return {boolean} Whether the character is alphabetic.
+ * @private
+ */
+ol.format.WKT.Lexer.prototype.isAlpha_ = function(c) {
+ return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
+};
+
+
+/**
+ * @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
+ */
+ol.format.WKT.Lexer.prototype.isNumeric_ = function(c, opt_decimal) {
+ var decimal = opt_decimal !== undefined ? opt_decimal : false;
+ return c >= '0' && c <= '9' || c == '.' && !decimal;
+};
+
+
+/**
+ * @param {string} c Character.
+ * @return {boolean} Whether the character is whitespace.
+ * @private
+ */
+ol.format.WKT.Lexer.prototype.isWhiteSpace_ = function(c) {
+ return c == ' ' || c == '\t' || c == '\r' || c == '\n';
+};
+
+
+/**
+ * @return {string} Next string character.
+ * @private
+ */
+ol.format.WKT.Lexer.prototype.nextChar_ = function() {
+ return this.wkt.charAt(++this.index_);
+};
+
+
+/**
+ * Fetch and return the next token.
+ * @return {!ol.format.WKT.Token} Next string token.
+ */
+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;
+};
+
+
+/**
+ * @return {number} Numeric token value.
+ * @private
+ */
+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_--));
+};
+
+
+/**
+ * @return {string} String token value.
+ * @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();
+};
+
+
+
+/**
+ * Class to parse the tokens from the WKT string.
+ * @param {ol.format.WKT.Lexer} lexer
+ * @constructor
+ * @protected
+ */
+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;
+};
+
+
+/**
+ * Fetch the next token form the lexer and replace the active token.
+ * @private
+ */
+ol.format.WKT.Parser.prototype.consume_ = function() {
+ this.token_ = this.lexer_.nextToken();
+};
+
+
+/**
+ * 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.
+ */
+ol.format.WKT.Parser.prototype.match = function(type) {
+ var isMatch = this.token_.type == type;
+ if (isMatch) {
+ this.consume_();
+ }
+ return isMatch;
+};
+
+
+/**
+ * Try to parse the tokens provided by the lexer.
+ * @return {ol.geom.Geometry|ol.geom.GeometryCollection} The geometry.
+ */
+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;
+};
+
+
+/**
+ * @return {!(ol.geom.Geometry|ol.geom.GeometryCollection)} The geometry.
+ * @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 (!parser || !ctor) {
+ throw new Error('Invalid geometry type: ' + geomType);
+ }
+ var coordinates = parser.call(this);
+ return new ctor(coordinates);
+ }
+ }
+ throw new Error(this.formatErrorMessage_());
+};
+
+
+/**
+ * @return {!Array.<ol.geom.Geometry>} A collection of geometries.
+ * @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_());
+};
+
+
+/**
+ * @return {Array.<number>} All values in a point.
+ * @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_());
+};
+
+
+/**
+ * @return {!Array.<!Array.<number>>} All points in a linestring.
+ * @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_());
+};
+
+
+/**
+ * @return {!Array.<!Array.<number>>} All points in a polygon.
+ * @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_());
+};
+
+
+/**
+ * @return {!Array.<!Array.<number>>} All points in a multipoint.
+ * @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_());
+};
+
+
+/**
+ * @return {!Array.<!Array.<number>>} All linestring points
+ * in a multilinestring.
+ * @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_());
+};
+
+
+/**
+ * @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_());
+};
+
+
+/**
+ * @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 {
+ break;
+ }
+ }
+ if (coordinates.length == this.dimension_) {
+ return coordinates;
+ }
+ throw new Error(this.formatErrorMessage_());
+};
+
+
+/**
+ * @return {!Array.<!Array.<number>>} An array of points.
+ * @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;
+};
+
+
+/**
+ * @return {!Array.<!Array.<number>>} An array of points.
+ * @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;
+};
+
+
+/**
+ * @return {!Array.<!Array.<number>>} An array of points.
+ * @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;
+};
+
+
+/**
+ * @return {!Array.<!Array.<number>>} An array of points.
+ * @private
+ */
+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;
+};
+
+
+/**
+ * @return {boolean} Whether the token implies an empty geometry.
+ * @private
+ */
+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;
+};
+
+
+/**
+ * Create an error message for an unexpected token error.
+ * @return {string} Error message.
+ * @private
+ */
+ol.format.WKT.Parser.prototype.formatErrorMessage_ = function() {
+ return 'Unexpected `' + this.token_.value + '` at position ' +
+ this.token_.position + ' in `' + this.lexer_.wkt + '`';
+};
+
+
+/**
+ * @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
+};
+
+
+/**
+ * @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');
+
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('goog.object');
+goog.require('ol');
+goog.require('ol.format.XLink');
+goog.require('ol.format.XML');
+goog.require('ol.format.XSD');
+goog.require('ol.xml');
+
+
+
+/**
+ * @classdesc
+ * Format for reading WMS capabilities data
+ *
+ * @constructor
+ * @extends {ol.format.XML}
+ * @api
+ */
+ol.format.WMSCapabilities = function() {
+
+ goog.base(this);
+
+ /**
+ * @type {string|undefined}
+ */
+ this.version = undefined;
+};
+goog.inherits(ol.format.WMSCapabilities, ol.format.XML);
+
+
+/**
+ * Read a WMS capabilities document.
+ *
+ * @function
+ * @param {Document|Node|string} source The XML source.
+ * @return {Object} An object representing the WMS capabilities.
+ * @api
+ */
+ol.format.WMSCapabilities.prototype.read;
+
+
+/**
+ * @param {Document} doc Document.
+ * @return {Object} WMS Capability object.
+ */
+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; n; n = n.nextSibling) {
+ if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+ return this.readFromNode(n);
+ }
+ }
+ return null;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {Object} WMS Capability object.
+ */
+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 = node.getAttribute('version').trim();
+ 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 wmsCapabilityObject ? wmsCapabilityObject : null;
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Attribution object.
+ */
+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);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object} Bounding box object.
+ */
+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
+ };
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.Extent|undefined} Bounding box object.
+ */
+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 (!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 (westBoundLongitude === undefined || southBoundLatitude === undefined ||
+ eastBoundLongitude === undefined || northBoundLatitude === undefined) {
+ return undefined;
+ }
+ return /** @type {ol.Extent} */ ([
+ westBoundLongitude, southBoundLatitude,
+ eastBoundLongitude, northBoundLatitude
+ ]);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Capability object.
+ */
+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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Service object.
+ */
+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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Contact information object.
+ */
+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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Contact person object.
+ */
+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);
+};
+
+
+/**
+ * @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 {Node} node Node.
+ * @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,
+ '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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @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,
+ '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);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Layer object.
+ */
+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]);
+
+ var layerObject = /** @type {Object.<string,*>} */ (ol.xml.pushParseAndPop(
+ {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack));
+
+ if (!layerObject) {
+ return undefined;
+ }
+ var queryable =
+ ol.format.XSD.readBooleanString(node.getAttribute('queryable'));
+ if (queryable === undefined) {
+ queryable = parentLayerObject['queryable'];
+ }
+ layerObject['queryable'] = queryable !== undefined ? queryable : false;
+
+ var cascaded = ol.format.XSD.readNonNegativeIntegerString(
+ node.getAttribute('cascaded'));
+ if (cascaded === undefined) {
+ cascaded = parentLayerObject['cascaded'];
+ }
+ layerObject['cascaded'] = cascaded;
+
+ var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque'));
+ if (opaque === undefined) {
+ opaque = parentLayerObject['opaque'];
+ }
+ layerObject['opaque'] = opaque !== undefined ? opaque : false;
+
+ var noSubsets =
+ ol.format.XSD.readBooleanString(node.getAttribute('noSubsets'));
+ if (noSubsets === undefined) {
+ noSubsets = parentLayerObject['noSubsets'];
+ }
+ layerObject['noSubsets'] = noSubsets !== undefined ? noSubsets : false;
+
+ var fixedWidth =
+ ol.format.XSD.readDecimalString(node.getAttribute('fixedWidth'));
+ if (!fixedWidth) {
+ fixedWidth = parentLayerObject['fixedWidth'];
+ }
+ layerObject['fixedWidth'] = fixedWidth;
+
+ var fixedHeight =
+ ol.format.XSD.readDecimalString(node.getAttribute('fixedHeight'));
+ if (!fixedHeight) {
+ fixedHeight = parentLayerObject['fixedHeight'];
+ }
+ layerObject['fixedHeight'] = fixedHeight;
+
+ // See 7.2.4.8
+ var addKeys = ['Style', 'CRS', 'AuthorityURL'];
+ addKeys.forEach(function(key) {
+ if (key in parentLayerObject) {
+ var childValue = goog.object.setIfUndefined(layerObject, key, []);
+ childValue = childValue.concat(parentLayerObject[key]);
+ layerObject[key] = childValue;
+ }
+ });
+
+ var replaceKeys = ['EX_GeographicBoundingBox', 'BoundingBox', 'Dimension',
+ 'Attribution', 'MinScaleDenominator', 'MaxScaleDenominator'];
+ replaceKeys.forEach(function(key) {
+ if (!(key in layerObject)) {
+ var parentValue = parentLayerObject[key];
+ layerObject[key] = parentValue;
+ }
+ });
+
+ return layerObject;
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @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,
+ '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;
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Request object.
+ */
+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);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} DCP type object.
+ */
+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);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @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,
+ '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);
+};
+
+
+/**
+ * @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,
+ 'node.nodeType should be ELEMENT');
+ return ol.xml.pushParseAndPop(
+ {}, ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @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 (formatOnlineresource) {
+ var size = [
+ ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('width')),
+ ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('height'))
+ ];
+ formatOnlineresource['size'] = size;
+ return formatOnlineresource;
+ }
+ return undefined;
+};
+
+
+/**
+ * @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 (authorityObject) {
+ authorityObject['name'] = node.getAttribute('name');
+ return authorityObject;
+ }
+ return undefined;
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Metadata URL object.
+ */
+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 (metadataObject) {
+ metadataObject['type'] = node.getAttribute('type');
+ return metadataObject;
+ }
+ return undefined;
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<string>|undefined} Keyword list.
+ */
+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);
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Array.<string>}
+ */
+ol.format.WMSCapabilities.NAMESPACE_URIS_ = [
+ null,
+ 'http://www.opengis.net/wms'
+];
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+ 'Service': ol.xml.makeObjectPropertySetter(
+ ol.format.WMSCapabilities.readService_),
+ 'Capability': ol.xml.makeObjectPropertySetter(
+ ol.format.WMSCapabilities.readCapability_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.CAPABILITY_PARSERS_ = ol.xml.makeStructureNS(
+ 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_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.SERVICE_PARSERS_ = ol.xml.makeStructureNS(
+ 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)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_ = ol.xml.makeStructureNS(
+ 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)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+ 'ContactPerson': ol.xml.makeObjectPropertySetter(
+ ol.format.XSD.readString),
+ 'ContactOrganization': ol.xml.makeObjectPropertySetter(
+ ol.format.XSD.readString)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_ = ol.xml.makeStructureNS(
+ 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.makeStructureNS(
+ ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+ 'Format': ol.xml.makeArrayPusher(ol.format.XSD.readString)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.LAYER_PARSERS_ = ol.xml.makeStructureNS(
+ 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_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_ = ol.xml.makeStructureNS(
+ 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_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_ =
+ ol.xml.makeStructureNS(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)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.REQUEST_PARSERS_ = ol.xml.makeStructureNS(
+ 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_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+ 'Format': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString),
+ 'DCPType': ol.xml.makeObjectPropertyPusher(
+ ol.format.WMSCapabilities.readDCPType_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+ 'HTTP': ol.xml.makeObjectPropertySetter(
+ ol.format.WMSCapabilities.readHTTP_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.HTTP_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+ 'Get': ol.xml.makeObjectPropertySetter(
+ ol.format.WMSCapabilities.readFormatOnlineresource_),
+ 'Post': ol.xml.makeObjectPropertySetter(
+ ol.format.WMSCapabilities.readFormatOnlineresource_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.STYLE_PARSERS_ = ol.xml.makeStructureNS(
+ 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_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_ =
+ ol.xml.makeStructureNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+ 'Format': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+ 'OnlineResource': ol.xml.makeObjectPropertySetter(
+ ol.format.XLink.readHref)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_ = ol.xml.makeStructureNS(
+ 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.NodeType');
+goog.require('goog.object');
+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);
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+ol.format.WMSGetFeatureInfo.featureIdentifier_ = '_feature';
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+ol.format.WMSGetFeatureInfo.layerIdentifier_ = '_layer';
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<ol.Feature>} Features.
+ * @private
+ */
+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 = [];
+ if (node.childNodes.length === 0) {
+ return features;
+ }
+ if (localName == 'msGMLOutput') {
+ for (var i = 0, ii = node.childNodes.length; i < ii; i++) {
+ var layer = node.childNodes[i];
+ if (layer.nodeType !== goog.dom.NodeType.ELEMENT) {
+ continue;
+ }
+ 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');
+
+ var toRemove = ol.format.WMSGetFeatureInfo.layerIdentifier_;
+ var featureType = layer.localName.replace(toRemove, '') +
+ ol.format.WMSGetFeatureInfo.featureIdentifier_;
+
+ context['featureType'] = featureType;
+ context['featureNS'] = this.featureNS_;
+
+ var parsers = {};
+ parsers[featureType] = ol.xml.makeArrayPusher(
+ this.gmlFormat_.readFeatureElement, this.gmlFormat_);
+ var parsersNS = ol.xml.makeStructureNS(
+ [context['featureNS'], null], parsers);
+ layer.namespaceURI = this.featureNS_;
+ var layerFeatures = ol.xml.pushParseAndPop(
+ [], parsersNS, layer, objectStack, this.gmlFormat_);
+ if (layerFeatures) {
+ goog.array.extend(features, layerFeatures);
+ }
+ }
+ }
+ if (localName == 'FeatureCollection') {
+ var gmlFeatures = ol.xml.pushParseAndPop([],
+ this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node,
+ [{}], this.gmlFormat_);
+ if (gmlFeatures) {
+ features = gmlFeatures;
+ }
+ }
+ return features;
+};
+
+
+/**
+ * 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.format.WMSGetFeatureInfo.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.WMSGetFeatureInfo.prototype.readFeaturesFromNode =
+ function(node, opt_options) {
+ var options = {
+ 'featureType': this.featureType,
+ 'featureNS': this.featureNS
+ };
+ if (opt_options) {
+ goog.object.extend(options, this.getReadOptions(node, opt_options));
+ }
+ return this.readFeatures_(node, [options]);
+};
+
+goog.provide('ol.format.WMTSCapabilities');
+
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+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');
+
+
+
+/**
+ * @classdesc
+ * Format for reading WMTS capabilities data.
+ *
+ * @constructor
+ * @extends {ol.format.XML}
+ * @api
+ */
+ol.format.WMTSCapabilities = function() {
+ goog.base(this);
+
+ /**
+ * @type {ol.format.OWS}
+ * @private
+ */
+ this.owsParser_ = new ol.format.OWS();
+};
+goog.inherits(ol.format.WMTSCapabilities, ol.format.XML);
+
+
+/**
+ * Read a WMTS capabilities document.
+ *
+ * @function
+ * @param {Document|Node|string} source The XML source.
+ * @return {Object} An object representing the WMTS capabilities.
+ * @api
+ */
+ol.format.WMTSCapabilities.prototype.read;
+
+
+/**
+ * @param {Document} doc Document.
+ * @return {Object} WMTS Capability object.
+ */
+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; n; n = n.nextSibling) {
+ if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+ return this.readFromNode(n);
+ }
+ }
+ return null;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {Object} WMTS Capability object.
+ */
+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 = node.getAttribute('version').trim();
+ goog.asserts.assertString(this.version, 'this.version should be a string');
+ var WMTSCapabilityObject = this.owsParser_.readFromNode(node);
+ if (!WMTSCapabilityObject) {
+ return null;
+ }
+ WMTSCapabilityObject['version'] = this.version;
+ WMTSCapabilityObject = ol.xml.pushParseAndPop(WMTSCapabilityObject,
+ ol.format.WMTSCapabilities.PARSERS_, node, []);
+ return WMTSCapabilityObject ? WMTSCapabilityObject : null;
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Attribution object.
+ */
+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);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Layers object.
+ */
+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);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Tile Matrix Set object.
+ */
+ol.format.WMTSCapabilities.readTileMatrixSet_ = function(node, objectStack) {
+ return ol.xml.pushParseAndPop({},
+ ol.format.WMTSCapabilities.TMS_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Style object.
+ */
+ol.format.WMTSCapabilities.readStyle_ = function(node, objectStack) {
+ var style = ol.xml.pushParseAndPop({},
+ ol.format.WMTSCapabilities.STYLE_PARSERS_, node, objectStack);
+ if (!style) {
+ return undefined;
+ }
+ var isDefault = node.getAttribute('isDefault') === 'true';
+ style['isDefault'] = isDefault;
+ return style;
+
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Tile Matrix Set Link object.
+ */
+ol.format.WMTSCapabilities.readTileMatrixSetLink_ = function(node,
+ objectStack) {
+ return ol.xml.pushParseAndPop({},
+ ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Dimension object.
+ */
+ol.format.WMTSCapabilities.readDimensions_ = function(node, objectStack) {
+ return ol.xml.pushParseAndPop({},
+ ol.format.WMTSCapabilities.DIMENSION_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Resource URL object.
+ */
+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 (format) {
+ resource['format'] = format;
+ }
+ if (template) {
+ resource['template'] = template;
+ }
+ if (resourceType) {
+ resource['resourceType'] = resourceType;
+ }
+ return resource;
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} WGS84 BBox object.
+ */
+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);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Legend object.
+ */
+ol.format.WMTSCapabilities.readLegendUrl_ = function(node, objectStack) {
+ var legend = {};
+ legend['format'] = node.getAttribute('format');
+ legend['href'] = ol.format.XLink.readHref(node);
+ return legend;
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Coordinates object.
+ */
+ol.format.WMTSCapabilities.readCoordinates_ = function(node, objectStack) {
+ var coordinates = ol.format.XSD.readString(node).split(' ');
+ if (!coordinates || coordinates.length != 2) {
+ return undefined;
+ }
+ var x = +coordinates[0];
+ var y = +coordinates[1];
+ if (isNaN(x) || isNaN(y)) {
+ return undefined;
+ }
+ return [x, y];
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} TileMatrix object.
+ */
+ol.format.WMTSCapabilities.readTileMatrix_ = function(node, objectStack) {
+ return ol.xml.pushParseAndPop({},
+ ol.format.WMTSCapabilities.TM_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Array.<string>}
+ */
+ol.format.WMTSCapabilities.NAMESPACE_URIS_ = [
+ null,
+ 'http://www.opengis.net/wmts/1.0'
+];
+
+
+/**
+ * @const
+ * @private
+ * @type {Array.<string>}
+ */
+ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_ = [
+ null,
+ 'http://www.opengis.net/ows/1.1'
+];
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+ 'Contents': ol.xml.makeObjectPropertySetter(
+ ol.format.WMTSCapabilities.readContents_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.CONTENTS_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+ 'Layer': ol.xml.makeObjectPropertyPusher(
+ ol.format.WMTSCapabilities.readLayer_),
+ 'TileMatrixSet': ol.xml.makeObjectPropertyPusher(
+ ol.format.WMTSCapabilities.readTileMatrixSet_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.LAYER_PARSERS_ = ol.xml.makeStructureNS(
+ 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_),
+ 'Dimension': ol.xml.makeObjectPropertyPusher(
+ ol.format.WMTSCapabilities.readDimensions_),
+ 'ResourceURL': ol.xml.makeObjectPropertyPusher(
+ ol.format.WMTSCapabilities.readResourceUrl_)
+ }, ol.xml.makeStructureNS(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)
+ }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.STYLE_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+ 'LegendURL': ol.xml.makeObjectPropertyPusher(
+ ol.format.WMTSCapabilities.readLegendUrl_)
+ }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
+ 'Title': ol.xml.makeObjectPropertySetter(
+ ol.format.XSD.readString),
+ 'Identifier': ol.xml.makeObjectPropertySetter(
+ ol.format.XSD.readString)
+ }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+ 'TileMatrixSet': ol.xml.makeObjectPropertySetter(
+ ol.format.XSD.readString)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.DIMENSION_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+ 'Default': ol.xml.makeObjectPropertySetter(
+ ol.format.XSD.readString),
+ 'Value': ol.xml.makeObjectPropertyPusher(
+ ol.format.XSD.readString)
+ }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
+ 'Identifier': ol.xml.makeObjectPropertySetter(
+ ol.format.XSD.readString)
+ }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.WGS84_BBOX_READERS_ = ol.xml.makeStructureNS(
+ ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
+ 'LowerCorner': ol.xml.makeArrayPusher(
+ ol.format.WMTSCapabilities.readCoordinates_),
+ 'UpperCorner': ol.xml.makeArrayPusher(
+ ol.format.WMTSCapabilities.readCoordinates_)
+ });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.TMS_PARSERS_ = ol.xml.makeStructureNS(
+ ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+ 'WellKnownScaleSet': ol.xml.makeObjectPropertySetter(
+ ol.format.XSD.readString),
+ 'TileMatrix': ol.xml.makeObjectPropertyPusher(
+ ol.format.WMTSCapabilities.readTileMatrix_)
+ }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
+ 'SupportedCRS': ol.xml.makeObjectPropertySetter(
+ ol.format.XSD.readString),
+ 'Identifier': ol.xml.makeObjectPropertySetter(
+ ol.format.XSD.readString)
+ }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.TM_PARSERS_ = ol.xml.makeStructureNS(
+ 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.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
+ 'Identifier': ol.xml.makeObjectPropertySetter(
+ ol.format.XSD.readString)
+ }));
+
+goog.provide('ol.sphere.WGS84');
+
+goog.require('ol.Sphere');
+
+
+/**
+ * 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);
+
+// FIXME handle geolocation not supported
+
+goog.provide('ol.Geolocation');
+goog.provide('ol.GeolocationProperty');
+
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+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.math');
+goog.require('ol.proj');
+goog.require('ol.sphere.WGS84');
+
+
+/**
+ * @enum {string}
+ */
+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'
+};
+
+
+
+/**
+ * @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.Geolocation = function(opt_options) {
+
+ goog.base(this);
+
+ var 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 (options.projection !== undefined) {
+ this.setProjection(ol.proj.get(options.projection));
+ }
+ if (options.trackingOptions !== undefined) {
+ this.setTrackingOptions(options.trackingOptions);
+ }
+
+ this.setTracking(options.tracking !== undefined ? options.tracking : false);
+
+};
+goog.inherits(ol.Geolocation, ol.Object);
+
+
+/**
+ * @inheritDoc
+ */
+ol.Geolocation.prototype.disposeInternal = function() {
+ this.setTracking(false);
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @private
+ */
+ol.Geolocation.prototype.handleProjectionChanged_ = function() {
+ var projection = this.getProjection();
+ if (projection) {
+ this.transform_ = ol.proj.getTransformFromProjections(
+ ol.proj.get('EPSG:4326'), projection);
+ if (this.position_) {
+ this.set(
+ ol.GeolocationProperty.POSITION, this.transform_(this.position_));
+ }
+ }
+};
+
+
+/**
+ * @private
+ */
+ol.Geolocation.prototype.handleTrackingChanged_ = function() {
+ if (ol.has.GEOLOCATION) {
+ var tracking = this.getTracking();
+ if (tracking && this.watchId_ === undefined) {
+ this.watchId_ = goog.global.navigator.geolocation.watchPosition(
+ goog.bind(this.positionChange_, this),
+ goog.bind(this.positionError_, this),
+ this.getTrackingOptions());
+ } else if (!tracking && this.watchId_ !== undefined) {
+ goog.global.navigator.geolocation.clearWatch(this.watchId_);
+ this.watchId_ = 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,
+ coords.altitude === null ? undefined : coords.altitude);
+ this.set(ol.GeolocationProperty.ALTITUDE_ACCURACY,
+ coords.altitudeAccuracy === null ?
+ undefined : coords.altitudeAccuracy);
+ this.set(ol.GeolocationProperty.HEADING, coords.heading === null ?
+ undefined : ol.math.toRadians(coords.heading));
+ if (!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,
+ coords.speed === null ? 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();
+};
+
+
+/**
+ * @private
+ * @param {GeolocationPositionError} error error object.
+ */
+ol.Geolocation.prototype.positionError_ = function(error) {
+ error.type = goog.events.EventType.ERROR;
+ this.setTracking(false);
+ this.dispatchEvent(error);
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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));
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * Enable or disable tracking.
+ * @param {boolean} tracking Enable tracking.
+ * @observable
+ * @api stable
+ */
+ol.Geolocation.prototype.setTracking = function(tracking) {
+ this.set(ol.GeolocationProperty.TRACKING, tracking);
+};
+
+
+/**
+ * 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);
+};
+
+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');
+
+
+
+/**
+ * @classdesc
+ * Circle geometry.
+ *
+ * @constructor
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {ol.Coordinate} center Center.
+ * @param {number=} opt_radius Radius.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api
+ */
+ol.geom.Circle = function(center, opt_radius, opt_layout) {
+ goog.base(this);
+ var radius = opt_radius ? opt_radius : 0;
+ this.setCenterAndRadius(center, radius, opt_layout);
+};
+goog.inherits(ol.geom.Circle, ol.geom.SimpleGeometry);
+
+
+/**
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.Circle} Clone.
+ * @api
+ */
+ol.geom.Circle.prototype.clone = function() {
+ var circle = new ol.geom.Circle(null);
+ circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+ return circle;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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;
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+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_();
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+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);
+};
+
+
+/**
+ * Return the radius of the circle.
+ * @return {number} Radius.
+ * @api
+ */
+ol.geom.Circle.prototype.getRadius = function() {
+ return Math.sqrt(this.getRadiusSquared_());
+};
+
+
+/**
+ * @private
+ * @return {number} Radius squared.
+ */
+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;
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.geom.Circle.prototype.getType = function() {
+ return ol.geom.GeometryType.CIRCLE;
+};
+
+
+/**
+ * @inheritDoc
+ * @api stable
+ */
+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;
+
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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 (!center) {
+ this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
+ } else {
+ this.setLayout(opt_layout, center, 0);
+ if (!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();
+ }
+};
+
+
+/**
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ */
+ol.geom.Circle.prototype.setFlatCoordinates =
+ function(layout, flatCoordinates) {
+ this.setFlatCoordinatesInternal(layout, flatCoordinates);
+ this.changed();
+};
+
+
+/**
+ * Set the radius of the circle. The radius is in the units of the projection.
+ * @param {number} radius Radius.
+ * @api
+ */
+ol.geom.Circle.prototype.setRadius = function(radius) {
+ goog.asserts.assert(this.flatCoordinates,
+ 'truthy this.flatCoordinates expected');
+ this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius;
+ this.changed();
+};
+
+
+/**
+ * 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;
+
+goog.provide('ol.geom.flat.geodesic');
+
+goog.require('goog.asserts');
+goog.require('ol.TransformFunction');
+goog.require('ol.math');
+goog.require('ol.proj');
+
+
+/**
+ * @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
+
+ /** @type {Array.<number>} */
+ var flatCoordinates = [];
+
+ var geoA = interpolate(0);
+ var geoB = interpolate(1);
+
+ 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];
+
+ /** @type {Object.<string, boolean>} */
+ var fractions = {};
+
+ var maxIterations = 1e5;
+ var geoM, m, fracA, fracB, fracM, key;
+
+ while (--maxIterations > 0 && fractionStack.length > 0) {
+ // Pop the a coordinate off the stack
+ fracA = fractionStack.pop();
+ geoA = geoStack.pop();
+ a = stack.pop();
+ // Add the a coordinate if it has not been added yet
+ key = fracA.toString();
+ if (!(key in fractions)) {
+ 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(!(key in fractions),
+ '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');
+
+ return flatCoordinates;
+};
+
+
+/**
+* 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(ol.math.toRadians(lat1));
+ var sinLat1 = Math.sin(ol.math.toRadians(lat1));
+ var cosLat2 = Math.cos(ol.math.toRadians(lat2));
+ var sinLat2 = Math.sin(ol.math.toRadians(lat2));
+ var cosDeltaLon = Math.cos(ol.math.toRadians(lon2 - lon1));
+ var sinDeltaLon = Math.sin(ol.math.toRadians(lon2 - lon1));
+ var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon;
+
+ 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 = ol.math.toRadians(lon1) +
+ Math.atan2(Math.sin(theta) * sinD * cosLat1,
+ cosD - sinLat1 * Math.sin(lat));
+ return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)];
+ }, ol.proj.getTransform(geoProjection, projection), squaredTolerance);
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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);
+};
+
+goog.provide('ol.Graticule');
+
+goog.require('goog.asserts');
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryLayout');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.flat.geodesic');
+goog.require('ol.math');
+goog.require('ol.proj');
+goog.require('ol.render.EventType');
+goog.require('ol.style.Stroke');
+
+
+
+/**
+ * Render a grid for a coordinate system on a map.
+ * @constructor
+ * @param {olx.GraticuleOptions=} opt_options Options.
+ * @api
+ */
+ol.Graticule = function(opt_options) {
+
+ var options = opt_options || {};
+
+ /**
+ * @type {ol.Map}
+ * @private
+ */
+ this.map_ = null;
+
+ /**
+ * @type {ol.proj.Projection}
+ * @private
+ */
+ this.projection_ = null;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.maxLat_ = Infinity;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.maxLon_ = Infinity;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.minLat_ = -Infinity;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.minLon_ = -Infinity;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.maxLatP_ = Infinity;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.maxLonP_ = Infinity;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.minLatP_ = -Infinity;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.minLonP_ = -Infinity;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.targetSize_ = options.targetSize !== undefined ?
+ options.targetSize : 100;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.maxLines_ = options.maxLines !== undefined ? options.maxLines : 100;
+ goog.asserts.assert(this.maxLines_ > 0,
+ 'this.maxLines_ should be more than 0');
+
+ /**
+ * @type {Array.<ol.geom.LineString>}
+ * @private
+ */
+ this.meridians_ = [];
+
+ /**
+ * @type {Array.<ol.geom.LineString>}
+ * @private
+ */
+ this.parallels_ = [];
+
+ /**
+ * @type {ol.style.Stroke}
+ * @private
+ */
+ this.strokeStyle_ = options.strokeStyle !== undefined ?
+ options.strokeStyle : ol.Graticule.DEFAULT_STROKE_STYLE_;
+
+ /**
+ * @type {ol.TransformFunction|undefined}
+ * @private
+ */
+ this.fromLonLatTransform_ = undefined;
+
+ /**
+ * @type {ol.TransformFunction|undefined}
+ * @private
+ */
+ this.toLonLatTransform_ = undefined;
+
+ /**
+ * @type {ol.Coordinate}
+ * @private
+ */
+ this.projectionCenterLonLat_ = null;
+
+ this.setMap(options.map !== undefined ? options.map : null);
+};
+
+
+/**
+ * @type {ol.style.Stroke}
+ * @private
+ * @const
+ */
+ol.Graticule.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
+ color: 'rgba(0,0,0,0.2)'
+});
+
+
+/**
+ * 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];
+
+
+/**
+ * @param {number} lon Longitude.
+ * @param {number} minLat Minimal latitude.
+ * @param {number} maxLat Maximal latitude.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} index Index.
+ * @return {number} Index.
+ * @private
+ */
+ol.Graticule.prototype.addMeridian_ =
+ function(lon, minLat, maxLat, squaredTolerance, extent, index) {
+ var lineString = this.getMeridian_(lon, minLat, maxLat,
+ squaredTolerance, index);
+ if (ol.extent.intersects(lineString.getExtent(), extent)) {
+ this.meridians_[index++] = lineString;
+ }
+ return index;
+};
+
+
+/**
+ * @param {number} lat Latitude.
+ * @param {number} minLon Minimal longitude.
+ * @param {number} maxLon Maximal longitude.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} index Index.
+ * @return {number} Index.
+ * @private
+ */
+ol.Graticule.prototype.addParallel_ =
+ function(lat, minLon, maxLon, squaredTolerance, extent, index) {
+ var lineString = this.getParallel_(lat, minLon, maxLon, squaredTolerance,
+ index);
+ if (ol.extent.intersects(lineString.getExtent(), extent)) {
+ this.parallels_[index++] = lineString;
+ }
+ return index;
+};
+
+
+/**
+ * @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) {
+
+ 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;
+
+ var validExtent = [
+ Math.max(extent[0], this.minLonP_),
+ Math.max(extent[1], this.minLatP_),
+ Math.min(extent[2], this.maxLonP_),
+ Math.min(extent[3], this.maxLatP_)
+ ];
+
+ validExtent = ol.proj.transformExtent(validExtent, this.projection_,
+ 'EPSG:4326');
+ var maxLat = validExtent[3];
+ var maxLon = validExtent[2];
+ var minLat = validExtent[1];
+ var minLon = validExtent[0];
+
+ // Create meridians
+
+ centerLon = Math.floor(centerLon / interval) * interval;
+ lon = ol.math.clamp(centerLon, this.minLon_, this.maxLon_);
+
+ idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, 0);
+
+ cnt = 0;
+ while (lon != this.minLon_ && cnt++ < maxLines) {
+ lon = Math.max(lon - interval, this.minLon_);
+ idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
+ }
+
+ lon = ol.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, minLat, maxLat, squaredTolerance, extent, idx);
+ }
+
+ this.meridians_.length = idx;
+
+ // Create parallels
+
+ centerLat = Math.floor(centerLat / interval) * interval;
+ lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_);
+
+ idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, 0);
+
+ cnt = 0;
+ while (lat != this.minLat_ && cnt++ < maxLines) {
+ lat = Math.max(lat - interval, this.minLat_);
+ idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx);
+ }
+
+ lat = ol.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, minLon, maxLon, squaredTolerance, extent, idx);
+ }
+
+ this.parallels_.length = idx;
+
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * Get the map associated with this graticule.
+ * @return {ol.Map} The map.
+ * @api
+ */
+ol.Graticule.prototype.getMap = function() {
+ return this.map_;
+};
+
+
+/**
+ * @param {number} lon Longitude.
+ * @param {number} minLat Minimal latitude.
+ * @param {number} maxLat Maximal latitude.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @return {ol.geom.LineString} The meridian line string.
+ * @param {number} index Index.
+ * @private
+ */
+ol.Graticule.prototype.getMeridian_ = function(lon, minLat, maxLat,
+ 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,
+ minLat, maxLat, this.projection_, squaredTolerance);
+ goog.asserts.assert(flatCoordinates.length > 0,
+ 'flatCoordinates cannot be empty');
+ var lineString = this.meridians_[index] !== undefined ?
+ this.meridians_[index] : new ol.geom.LineString(null);
+ lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
+ return lineString;
+};
+
+
+/**
+ * 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_;
+};
+
+
+/**
+ * @param {number} lat Latitude.
+ * @param {number} minLon Minimal longitude.
+ * @param {number} maxLon Maximal longitude.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @return {ol.geom.LineString} The parallel line string.
+ * @param {number} index Index.
+ * @private
+ */
+ol.Graticule.prototype.getParallel_ = function(lat, minLon, maxLon,
+ 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 = this.parallels_[index] !== undefined ?
+ this.parallels_[index] : new ol.geom.LineString(null);
+ lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
+ return lineString;
+};
+
+
+/**
+ * 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_;
+};
+
+
+/**
+ * @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);
+
+ var updateProjectionInfo = !this.projection_ ||
+ !ol.proj.equivalent(this.projection_, projection);
+
+ if (updateProjectionInfo) {
+ this.updateProjectionInfo_(projection);
+ }
+
+ //Fix the extent if wrapped.
+ //(note: this is the same extent as vectorContext.extent_)
+ var offsetX = 0;
+ if (projection.canWrapX()) {
+ var projectionExtent = projection.getExtent();
+ var worldWidth = ol.extent.getWidth(projectionExtent);
+ var x = frameState.focus[0];
+ if (x < projectionExtent[0] || x > projectionExtent[2]) {
+ var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth);
+ offsetX = worldWidth * worldsAway;
+ extent = [
+ extent[0] + offsetX, extent[1],
+ extent[2] + offsetX, extent[3]
+ ];
+ }
+ }
+
+ 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);
+ }
+};
+
+
+/**
+ * @param {ol.proj.Projection} projection Projection.
+ * @private
+ */
+ol.Graticule.prototype.updateProjectionInfo_ = function(projection) {
+ goog.asserts.assert(projection, 'projection cannot be null');
+
+ var epsg4326Projection = ol.proj.get('EPSG:4326');
+
+ var extent = projection.getExtent();
+ var worldExtent = projection.getWorldExtent();
+ var worldExtentP = ol.proj.transformExtent(worldExtent,
+ epsg4326Projection, projection);
+
+ var maxLat = worldExtent[3];
+ var maxLon = worldExtent[2];
+ var minLat = worldExtent[1];
+ var minLon = worldExtent[0];
+
+ var maxLatP = worldExtentP[3];
+ var maxLonP = worldExtentP[2];
+ var minLatP = worldExtentP[1];
+ var minLonP = worldExtentP[0];
+
+ goog.asserts.assert(extent, 'extent cannot be null');
+ goog.asserts.assert(maxLat !== undefined, 'maxLat should be defined');
+ goog.asserts.assert(maxLon !== undefined, 'maxLon should be defined');
+ goog.asserts.assert(minLat !== undefined, 'minLat should be defined');
+ goog.asserts.assert(minLon !== undefined, 'minLon should be defined');
+
+ goog.asserts.assert(maxLatP !== undefined,
+ 'projected maxLat should be defined');
+ goog.asserts.assert(maxLonP !== undefined,
+ 'projected maxLon should be defined');
+ goog.asserts.assert(minLatP !== undefined,
+ 'projected minLat should be defined');
+ goog.asserts.assert(minLonP !== undefined,
+ 'projected minLon should be defined');
+
+ this.maxLat_ = maxLat;
+ this.maxLon_ = maxLon;
+ this.minLat_ = minLat;
+ this.minLon_ = minLon;
+
+ this.maxLatP_ = maxLatP;
+ this.maxLonP_ = maxLonP;
+ this.minLatP_ = minLatP;
+ this.minLonP_ = minLonP;
+
+
+ this.fromLonLatTransform_ = ol.proj.getTransform(
+ epsg4326Projection, projection);
+
+ this.toLonLatTransform_ = ol.proj.getTransform(
+ projection, epsg4326Projection);
+
+ this.projectionCenterLonLat_ = this.toLonLatTransform_(
+ ol.extent.getCenter(extent));
+
+ this.projection_ = projection;
+};
+
+
+/**
+ * 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 (this.map_) {
+ this.map_.un(ol.render.EventType.POSTCOMPOSE,
+ this.handlePostCompose_, this);
+ this.map_.render();
+ }
+ if (map) {
+ map.on(ol.render.EventType.POSTCOMPOSE,
+ this.handlePostCompose_, this);
+ map.render();
+ }
+ this.map_ = map;
+};
+
+goog.provide('ol.Image');
+
+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');
+
+
+
+/**
+ * @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.
+ */
+ol.Image = function(extent, resolution, pixelRatio, attributions, src,
+ crossOrigin, imageLoadFunction) {
+
+ goog.base(this, extent, resolution, pixelRatio, ol.ImageState.IDLE,
+ attributions);
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.src_ = src;
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement|Image|HTMLVideoElement}
+ */
+ this.image_ = new Image();
+ if (crossOrigin !== null) {
+ this.image_.crossOrigin = crossOrigin;
+ }
+
+ /**
+ * @private
+ * @type {Object.<number, (HTMLCanvasElement|Image|HTMLVideoElement)>}
+ */
+ this.imageByContext_ = {};
+
+ /**
+ * @private
+ * @type {Array.<goog.events.Key>}
+ */
+ this.imageListenerKeys_ = null;
+
+ /**
+ * @protected
+ * @type {ol.ImageState}
+ */
+ this.state = ol.ImageState.IDLE;
+
+ /**
+ * @private
+ * @type {ol.ImageLoadFunctionType}
+ */
+ this.imageLoadFunction_ = imageLoadFunction;
+
+};
+goog.inherits(ol.Image, ol.ImageBase);
+
+
+/**
+ * Get the HTML image element (may be a Canvas, Image, or Video).
+ * @param {Object=} opt_context Object.
+ * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
+ * @api
+ */
+ol.Image.prototype.getImage = function(opt_context) {
+ if (opt_context !== undefined) {
+ 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_;
+ }
+};
+
+
+/**
+ * Tracks loading or read errors.
+ *
+ * @private
+ */
+ol.Image.prototype.handleImageError_ = function() {
+ this.state = ol.ImageState.ERROR;
+ this.unlistenImage_();
+ this.changed();
+};
+
+
+/**
+ * Tracks successful image load.
+ *
+ * @private
+ */
+ol.Image.prototype.handleImageLoad_ = function() {
+ if (this.resolution === undefined) {
+ this.resolution = ol.extent.getHeight(this.extent) / this.image_.height;
+ }
+ this.state = ol.ImageState.LOADED;
+ this.unlistenImage_();
+ this.changed();
+};
+
+
+/**
+ * Load not yet loaded URI.
+ */
+ol.Image.prototype.load = function() {
+ if (this.state == ol.ImageState.IDLE) {
+ this.state = ol.ImageState.LOADING;
+ this.changed();
+ goog.asserts.assert(!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 {HTMLCanvasElement|Image|HTMLVideoElement} image Image.
+ */
+ol.Image.prototype.setImage = function(image) {
+ this.image_ = image;
+};
+
+
+/**
+ * Discards event handlers which listen for load completion or errors.
+ *
+ * @private
+ */
+ol.Image.prototype.unlistenImage_ = function() {
+ goog.asserts.assert(this.imageListenerKeys_,
+ 'this.imageListenerKeys_ should not be null');
+ this.imageListenerKeys_.forEach(goog.events.unlistenByKey);
+ this.imageListenerKeys_ = null;
+};
+
+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;
+ * }
+ *
+ * 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.ImageLoadFunctionType;
+
+goog.provide('ol.ImageTile');
+
+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');
+
+
+
+/**
+ * @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.
+ */
+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 (crossOrigin !== null) {
+ this.image_.crossOrigin = crossOrigin;
+ }
+
+ /**
+ * @private
+ * @type {Object.<number, Image>}
+ */
+ this.imageByContext_ = {};
+
+ /**
+ * @private
+ * @type {Array.<goog.events.Key>}
+ */
+ this.imageListenerKeys_ = null;
+
+ /**
+ * @private
+ * @type {ol.TileLoadFunctionType}
+ */
+ this.tileLoadFunction_ = tileLoadFunction;
+
+};
+goog.inherits(ol.ImageTile, ol.Tile);
+
+
+/**
+ * @inheritDoc
+ */
+ol.ImageTile.prototype.disposeInternal = function() {
+ if (this.state == ol.TileState.LOADING) {
+ this.unlistenImage_();
+ }
+ if (this.interimTile) {
+ goog.dispose(this.interimTile);
+ }
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * Get the image element for this tile.
+ * @inheritDoc
+ * @api
+ */
+ol.ImageTile.prototype.getImage = function(opt_context) {
+ if (opt_context !== undefined) {
+ 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_;
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.ImageTile.prototype.getKey = function() {
+ return this.src_;
+};
+
+
+/**
+ * Tracks loading or read errors.
+ *
+ * @private
+ */
+ol.ImageTile.prototype.handleImageError_ = function() {
+ this.state = ol.TileState.ERROR;
+ this.unlistenImage_();
+ this.changed();
+};
+
+
+/**
+ * Tracks successful image load.
+ *
+ * @private
+ */
+ol.ImageTile.prototype.handleImageLoad_ = function() {
+ if (this.image_.naturalWidth && this.image_.naturalHeight) {
+ this.state = ol.TileState.LOADED;
+ } else {
+ this.state = ol.TileState.EMPTY;
+ }
+ this.unlistenImage_();
+ this.changed();
+};
+
+
+/**
+ * Load not yet loaded URI.
+ */
+ol.ImageTile.prototype.load = function() {
+ if (this.state == ol.TileState.IDLE) {
+ this.state = ol.TileState.LOADING;
+ this.changed();
+ goog.asserts.assert(!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_);
+ }
+};
+
+
+/**
+ * Discards event handlers which listen for load completion or errors.
+ *
+ * @private
+ */
+ol.ImageTile.prototype.unlistenImage_ = function() {
+ goog.asserts.assert(this.imageListenerKeys_,
+ 'this.imageListenerKeys_ should not be null');
+ this.imageListenerKeys_.forEach(goog.events.unlistenByKey);
+ this.imageListenerKeys_ = null;
+};
+
+// 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.
+ *
+ * @see ../demos/filedrophandler.html
+ */
+
+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');
+
+
+
+/**
+ * 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
+ */
+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);
+
+ 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);
+
+
+/**
+ * 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}
+ */
+goog.events.FileDropHandler.prototype.dndContainsFiles_ = false;
+
+
+/**
+ * A logger, used to help us debug the algorithm.
+ * @type {goog.log.Logger}
+ * @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();
+};
+
+
+/**
+ * Dispatches the DROP event.
+ * @param {goog.events.BrowserEvent} e The underlying browser event.
+ * @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);
+};
+
+
+/**
+ * Handles dragenter on the document.
+ * @param {goog.events.BrowserEvent} e The dragenter event.
+ * @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();
+ }
+ goog.log.log(this.logger_, goog.log.Level.FINER,
+ 'dndContainsFiles_: ' + this.dndContainsFiles_);
+};
+
+
+/**
+ * Handles dragging something over the document.
+ * @param {goog.events.BrowserEvent} e The dragover event.
+ * @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';
+ }
+};
+
+
+/**
+ * Handles dragging something over the element (drop zone).
+ * @param {goog.events.BrowserEvent} e The dragover event.
+ * @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;
+
+ // IE bug #811625 (https://goo.gl/UWuxX0) will throw error SCRIPT65535
+ // when attempting to set property effectAllowed on IE10+.
+ // See more: https://github.com/google/closure-library/issues/485.
+ try {
+ dt.effectAllowed = 'all';
+ } catch (err) {
+ }
+ dt.dropEffect = 'copy';
+ }
+};
+
+
+/**
+ * Handles dropping something onto the element (drop zone).
+ * @param {goog.events.BrowserEvent} e The drop event.
+ * @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);
+ }
+};
+
+// 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.
+ */
+
+/**
+ * @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.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ * @author brenneman@google.com (Shawn Brenneman)
+ */
+
+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');
+
+
+
+/**
+ * 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>} *&#47;
+ * 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
+ * @implements {goog.Thenable<VALUE>}
+ * @template VALUE
+ */
+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;
+
+ /**
+ * 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/, '');
+ }
+ }
+ }
+};
+
+
+/**
+ * @define {boolean} Whether unhandled errors should always get rethrown to the
+ * global scope. Defaults to the value of goog.DEBUG.
+ */
+goog.define('goog.async.Deferred.STRICT_ERRORS', false);
+
+
+/**
+ * @define {boolean} Whether to attempt to make stack traces long. Defaults to
+ * the value of goog.DEBUG.
+ */
+goog.define('goog.async.Deferred.LONG_STACK_TRACES', false);
+
+
+/**
+ * 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.
+ */
+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.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();
+ }
+};
+
+
+/**
+ * Handle a single branch being canceled. Once all branches are canceled, this
+ * Deferred will be canceled as well.
+ *
+ * @private
+ */
+goog.async.Deferred.prototype.branchCancel_ = function() {
+ this.branches_--;
+ if (this.branches_ <= 0) {
+ this.cancel();
+ }
+};
+
+
+/**
+ * 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
+ */
+goog.async.Deferred.prototype.continue_ = function(isSuccess, res) {
+ this.blocked_ = false;
+ this.updateResult_(isSuccess, res);
+};
+
+
+/**
+ * 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_();
+};
+
+
+/**
+ * Verifies that the Deferred has not yet been fired.
+ *
+ * @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;
+ }
+};
+
+
+/**
+ * Fire the execution sequence for this Deferred by passing the starting result
+ * to the first registered callback.
+ * @param {VALUE=} opt_result The starting result.
+ */
+goog.async.Deferred.prototype.callback = function(opt_result) {
+ this.check_();
+ this.assertNotDeferred_(opt_result);
+ this.updateResult_(true /* isSuccess */, opt_result);
+};
+
+
+/**
+ * Fire the execution sequence for this Deferred by passing the starting error
+ * result to the first registered errback.
+ * @param {*=} opt_result The starting error.
+ */
+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
+ */
+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_;
+ }
+};
+
+
+/**
+ * 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
+ */
+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.');
+};
+
+
+/**
+ * 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
+ */
+goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) {
+ return this.addCallbacks(cb, null, opt_scope);
+};
+
+
+/**
+ * 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
+ */
+goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) {
+ return this.addCallbacks(null, eb, opt_scope);
+};
+
+
+/**
+ * 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
+ */
+goog.async.Deferred.prototype.addBoth = function(f, opt_scope) {
+ return this.addCallbacks(f, f, opt_scope);
+};
+
+
+/**
+ * Like addBoth, but propagates uncaught exceptions in the 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<VALUE>} This Deferred.
+ * @template T
+ */
+goog.async.Deferred.prototype.addFinally = function(f, opt_scope) {
+ var self = this;
+ return this.addCallbacks(f, function(err) {
+ var result = f.call(self, err);
+ if (!goog.isDef(result)) {
+ throw err;
+ }
+ return result;
+ }, opt_scope);
+};
+
+
+/**
+ * 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):?)} cb The function to be called on a
+ * successful result.
+ * @param {?(function(this:T,?):?)} 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;
+};
+
+
+/**
+ * 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);
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * 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.
+ */
+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));
+};
+
+
+/**
+ * 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.
+ */
+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;
+};
+
+
+/**
+ * @return {boolean} Whether the execution sequence has been started on this
+ * Deferred by invoking {@code callback} or {@code errback}.
+ */
+goog.async.Deferred.prototype.hasFired = function() {
+ return this.fired_;
+};
+
+
+/**
+ * @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
+ */
+goog.async.Deferred.prototype.isError = function(res) {
+ return res instanceof Error;
+};
+
+
+/**
+ * @return {boolean} Whether an errback exists in the remaining sequence.
+ * @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]);
+ });
+};
+
+
+/**
+ * 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
+ */
+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) ||
+ (typeof goog.global['Promise'] === 'function' &&
+ res instanceof goog.global['Promise'])) {
+ 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);
+ }
+};
+
+
+/**
+ * Creates a Deferred that has an initial result.
+ *
+ * @param {*=} opt_result The result.
+ * @return {!goog.async.Deferred} The new Deferred.
+ */
+goog.async.Deferred.succeed = function(opt_result) {
+ var d = new goog.async.Deferred();
+ d.callback(opt_result);
+ return d;
+};
+
+
+/**
+ * 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
+ */
+goog.async.Deferred.fromPromise = function(promise) {
+ var d = new goog.async.Deferred();
+ d.callback();
+ d.addCallback(function() {
+ return promise;
+ });
+ return d;
+};
+
+
+/**
+ * Creates a Deferred that has an initial error result.
+ *
+ * @param {*} res The error result.
+ * @return {!goog.async.Deferred} The new Deferred.
+ */
+goog.async.Deferred.fail = function(res) {
+ var d = new goog.async.Deferred();
+ d.errback(res);
+ return d;
+};
+
+
+/**
+ * Creates a Deferred that has already been canceled.
+ *
+ * @return {!goog.async.Deferred} The new Deferred.
+ */
+goog.async.Deferred.canceled = function() {
+ var d = new goog.async.Deferred();
+ d.cancel();
+ return d;
+};
+
+
+/**
+ * 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
+ */
+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);
+ }
+};
+
+
+
+/**
+ * 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}
+ */
+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';
+
+
+
+/**
+ * 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}
+ */
+goog.async.Deferred.CanceledError = 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.CanceledError, goog.debug.Error);
+
+
+/** @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;
+};
+
+
+/**
+ * Actually throws the error and removes it from the list of pending
+ * deferred errors.
+ */
+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_;
+};
+
+
+/**
+ * Resets the error throw timer.
+ */
+goog.async.Deferred.Error_.prototype.resetTimer = function() {
+ goog.global.clearTimeout(this.id_);
+};
+
+
+/**
+ * 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
+ */
+goog.async.Deferred.scheduleError_ = function(error) {
+ var deferredError = new goog.async.Deferred.Error_(error);
+ goog.async.Deferred.errorMap_[deferredError.id_] = deferredError;
+ return deferredError.id_;
+};
+
+
+/**
+ * Unschedules an error from being thrown.
+ * @param {number} id Id of the deferred error to unschedule.
+ * @private
+ */
+goog.async.Deferred.unscheduleError_ = function(id) {
+ var error = goog.async.Deferred.errorMap_[id];
+ if (error) {
+ error.resetTimer();
+ delete goog.async.Deferred.errorMap_[id];
+ }
+};
+
+
+/**
+ * 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.
+
+/**
+ * @fileoverview A wrapper for the HTML5 FileError 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');
+
+
+
+/**
+ * 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
+ */
+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));
+};
+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}
+ */
+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'
+};
+
+
+/**
+ * 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 {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;
+};
+
+
+/**
+ * Returns the code that corresponds to the given name.
+ * @param {string} name
+ * @return {goog.fs.Error.ErrorCode} code
+ * @private
+ */
+goog.fs.Error.getCodeFromName_ = function(name) {
+ return goog.fs.Error.NameToCodeMap_[name];
+};
+
+
+/**
+ * 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,
+
+ goog.fs.Error.ErrorName.ENCODING,
+ goog.fs.Error.ErrorCode.ENCODING,
+
+ goog.fs.Error.ErrorName.INVALID_MODIFICATION,
+ goog.fs.Error.ErrorCode.INVALID_MODIFICATION,
+
+ goog.fs.Error.ErrorName.INVALID_STATE,
+ goog.fs.Error.ErrorCode.INVALID_STATE,
+
+ goog.fs.Error.ErrorName.NOT_FOUND,
+ goog.fs.Error.ErrorCode.NOT_FOUND,
+
+ goog.fs.Error.ErrorName.NOT_READABLE,
+ goog.fs.Error.ErrorCode.NOT_READABLE,
+
+ goog.fs.Error.ErrorName.NO_MODIFICATION_ALLOWED,
+ goog.fs.Error.ErrorCode.NO_MODIFICATION_ALLOWED,
+
+ goog.fs.Error.ErrorName.PATH_EXISTS,
+ goog.fs.Error.ErrorCode.PATH_EXISTS,
+
+ goog.fs.Error.ErrorName.QUOTA_EXCEEDED,
+ goog.fs.Error.ErrorCode.QUOTA_EXCEEDED,
+
+ 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.
+
+/**
+ * @fileoverview A wrapper for the HTML5 File ProgressEvent objects.
+ *
+ */
+goog.provide('goog.fs.ProgressEvent');
+
+goog.require('goog.events.Event');
+
+
+
+/**
+ * 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
+ */
+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 {boolean} Whether or not the total size of the of the file being
+ * saved is known.
+ */
+goog.fs.ProgressEvent.prototype.isLengthComputable = function() {
+ return this.event_.lengthComputable;
+};
+
+
+/**
+ * @return {number} The number of bytes saved so far.
+ */
+goog.fs.ProgressEvent.prototype.getLoaded = function() {
+ return this.event_.loaded;
+};
+
+
+/**
+ * @return {number} The total number of bytes in the file being saved.
+ */
+goog.fs.ProgressEvent.prototype.getTotal = function() {
+ return this.event_.total;
+};
+
+// 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.
+ *
+ */
+
+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');
+
+
+
+/**
+ * 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
+ */
+goog.fs.FileReader = function() {
+ goog.fs.FileReader.base(this, 'constructor');
+
+ /**
+ * 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);
+};
+goog.inherits(goog.fs.FileReader, goog.events.EventTarget);
+
+
+/**
+ * Possible states for a FileReader.
+ *
+ * @enum {number}
+ */
+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
+};
+
+
+/**
+ * Events emitted by a FileReader.
+ *
+ * @enum {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'
+};
+
+
+/**
+ * Abort the reading of the file.
+ */
+goog.fs.FileReader.prototype.abort = function() {
+ try {
+ this.reader_.abort();
+ } catch (e) {
+ throw new goog.fs.Error(e, 'aborting read');
+ }
+};
+
+
+/**
+ * @return {goog.fs.FileReader.ReadyState} The current state of the FileReader.
+ */
+goog.fs.FileReader.prototype.getReadyState = function() {
+ return /** @type {goog.fs.FileReader.ReadyState} */ (this.reader_.readyState);
+};
+
+
+/**
+ * @return {*} The result of the file read.
+ */
+goog.fs.FileReader.prototype.getResult = function() {
+ return this.reader_.result;
+};
+
+
+/**
+ * @return {goog.fs.Error} The error encountered while reading, if any.
+ */
+goog.fs.FileReader.prototype.getError = function() {
+ return this.reader_.error &&
+ new goog.fs.Error(this.reader_.error, 'reading file');
+};
+
+
+/**
+ * Wrap a progress event emitted by the underlying file reader and re-emit it.
+ *
+ * @param {!ProgressEvent} event The underlying event.
+ * @private
+ */
+goog.fs.FileReader.prototype.dispatchProgressEvent_ = function(event) {
+ this.dispatchEvent(new goog.fs.ProgressEvent(event, this));
+};
+
+
+/** @override */
+goog.fs.FileReader.prototype.disposeInternal = function() {
+ goog.fs.FileReader.base(this, 'disposeInternal');
+ delete this.reader_;
+};
+
+
+/**
+ * Starts reading a blob as a binary string.
+ * @param {!Blob} blob The blob to read.
+ */
+goog.fs.FileReader.prototype.readAsBinaryString = function(blob) {
+ this.reader_.readAsBinaryString(blob);
+};
+
+
+/**
+ * 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}.
+ */
+goog.fs.FileReader.readAsBinaryString = function(blob) {
+ var reader = new goog.fs.FileReader();
+ var d = goog.fs.FileReader.createDeferred_(reader);
+ reader.readAsBinaryString(blob);
+ return d;
+};
+
+
+/**
+ * Starts reading a blob as an array buffer.
+ * @param {!Blob} blob The blob to read.
+ */
+goog.fs.FileReader.prototype.readAsArrayBuffer = function(blob) {
+ this.reader_.readAsArrayBuffer(blob);
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * 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);
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * Starts reading a blob as a data URL.
+ * @param {!Blob} blob The blob to read.
+ */
+goog.fs.FileReader.prototype.readAsDataUrl = function(blob) {
+ this.reader_.readAsDataURL(blob);
+};
+
+
+/**
+ * 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.fs.FileReader.readAsDataUrl = function(blob) {
+ var reader = new goog.fs.FileReader();
+ var d = goog.fs.FileReader.createDeferred_(reader);
+ reader.readAsDataUrl(blob);
+ return d;
+};
+
+
+/**
+ * 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.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
+
+goog.provide('ol.interaction.DragAndDrop');
+goog.provide('ol.interaction.DragAndDropEvent');
+
+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');
+
+
+
+/**
+ * @classdesc
+ * Handles input of vector data by drag and drop.
+ *
+ * @constructor
+ * @extends {ol.interaction.Interaction}
+ * @fires ol.interaction.DragAndDropEvent
+ * @param {olx.interaction.DragAndDropOptions=} opt_options Options.
+ * @api stable
+ */
+ol.interaction.DragAndDrop = function(opt_options) {
+
+ var options = opt_options ? opt_options : {};
+
+ goog.base(this, {
+ handleEvent: ol.interaction.DragAndDrop.handleEvent
+ });
+
+ /**
+ * @private
+ * @type {Array.<function(new: ol.format.Feature)>}
+ */
+ this.formatConstructors_ = options.formatConstructors ?
+ options.formatConstructors : [];
+
+ /**
+ * @private
+ * @type {ol.proj.Projection}
+ */
+ this.projection_ = options.projection ?
+ ol.proj.get(options.projection) : null;
+
+ /**
+ * @private
+ * @type {goog.events.FileDropHandler}
+ */
+ this.fileDropHandler_ = null;
+
+ /**
+ * @private
+ * @type {goog.events.Key|undefined}
+ */
+ this.dropListenKey_ = undefined;
+
+};
+goog.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction);
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.DragAndDrop.prototype.disposeInternal = function() {
+ if (this.dropListenKey_) {
+ goog.events.unlistenByKey(this.dropListenKey_);
+ }
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @param {goog.events.BrowserEvent} event Event.
+ * @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);
+ }
+};
+
+
+/**
+ * @param {File} file File.
+ * @param {string} result Result.
+ * @private
+ */
+ol.interaction.DragAndDrop.prototype.handleResult_ = function(file, result) {
+ var map = this.getMap();
+ goog.asserts.assert(map, 'map must be set');
+ var projection = this.projection_;
+ if (!projection) {
+ var view = map.getView();
+ goog.asserts.assert(view, 'map must have view');
+ projection = view.getProjection();
+ goog.asserts.assert(projection !== undefined,
+ '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 (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 (geometry) {
+ geometry.applyTransform(transform);
+ }
+ features.push(feature);
+ }
+ }
+ }
+ this.dispatchEvent(
+ new ol.interaction.DragAndDropEvent(
+ ol.interaction.DragAndDropEventType.ADD_FEATURES, this, file,
+ features, projection));
+};
+
+
+/**
+ * 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
+ */
+ol.interaction.DragAndDrop.handleEvent = goog.functions.TRUE;
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.DragAndDrop.prototype.setMap = function(map) {
+ if (this.dropListenKey_) {
+ goog.events.unlistenByKey(this.dropListenKey_);
+ this.dropListenKey_ = undefined;
+ }
+ if (this.fileDropHandler_) {
+ goog.dispose(this.fileDropHandler_);
+ this.fileDropHandler_ = null;
+ }
+ goog.asserts.assert(this.dropListenKey_ === undefined,
+ 'this.dropListenKey_ should be undefined');
+ goog.base(this, 'setMap', map);
+ if (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);
+ }
+};
+
+
+/**
+ * @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;
+ }
+};
+
+
+/**
+ * @enum {string}
+ */
+ol.interaction.DragAndDropEventType = {
+ /**
+ * Triggered when features are added
+ * @event ol.interaction.DragAndDropEvent#addfeatures
+ * @api stable
+ */
+ ADD_FEATURES: 'addfeatures'
+};
+
+
+
+/**
+ * @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.
+ */
+ol.interaction.DragAndDropEvent =
+ function(type, target, file, opt_features, opt_projection) {
+
+ goog.base(this, type, target);
+
+ /**
+ * The features parsed from dropped data.
+ * @type {Array.<ol.Feature>|undefined}
+ * @api stable
+ */
+ this.features = opt_features;
+
+ /**
+ * The dropped file.
+ * @type {File}
+ * @api stable
+ */
+ this.file = file;
+
+ /**
+ * The feature projection.
+ * @type {ol.proj.Projection|undefined}
+ * @api
+ */
+ this.projection = opt_projection;
+
+};
+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.require('goog.math');
+goog.require('goog.math.Coordinate');
+
+
+
+/**
+ * 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
+ * @extends {goog.math.Coordinate}
+ */
+goog.math.Vec2 = function(x, y) {
+ /**
+ * X-value
+ * @type {number}
+ */
+ this.x = x;
+
+ /**
+ * Y-value
+ * @type {number}
+ */
+ this.y = y;
+};
+goog.inherits(goog.math.Vec2, goog.math.Coordinate);
+
+
+/**
+ * @return {!goog.math.Vec2} A random unit-length vector.
+ */
+goog.math.Vec2.randomUnit = function() {
+ var angle = Math.random() * Math.PI * 2;
+ return new goog.math.Vec2(Math.cos(angle), Math.sin(angle));
+};
+
+
+/**
+ * @return {!goog.math.Vec2} A random vector inside the unit-disc.
+ */
+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);
+};
+
+
+/**
+ * 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.math.Vec2.fromCoordinate = function(a) {
+ return new goog.math.Vec2(a.x, a.y);
+};
+
+
+/**
+ * @return {!goog.math.Vec2} A new vector with the same coordinates as this one.
+ * @override
+ */
+goog.math.Vec2.prototype.clone = function() {
+ return new goog.math.Vec2(this.x, this.y);
+};
+
+
+/**
+ * Returns the magnitude of the vector measured from the origin.
+ * @return {number} The length of the vector.
+ */
+goog.math.Vec2.prototype.magnitude = function() {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+};
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * @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);
+
+
+/**
+ * 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;
+};
+
+
+/**
+ * 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());
+};
+
+
+/**
+ * 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.math.Vec2.prototype.add = function(b) {
+ this.x += b.x;
+ this.y += b.y;
+ return this;
+};
+
+
+/**
+ * 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.math.Vec2.prototype.subtract = function(b) {
+ this.x -= b.x;
+ this.y -= b.y;
+ return this;
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.math.Vec2.rotateAroundPoint = function(v, axisPoint, angle) {
+ var res = v.clone();
+ return res.subtract(axisPoint).rotate(angle).add(axisPoint);
+};
+
+
+/**
+ * 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.math.Vec2.prototype.equals = function(b) {
+ return this == b || !!b && this.x == b.x && this.y == b.y;
+};
+
+
+/**
+ * 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;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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.
+ */
+goog.math.Vec2.equals = goog.math.Coordinate.equals;
+
+
+/**
+ * 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.
+ */
+goog.math.Vec2.sum = function(a, b) {
+ return new goog.math.Vec2(a.x + b.x, a.y + b.y);
+};
+
+
+/**
+ * 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.
+ */
+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 the determinant of two vectors.
+ * @param {!goog.math.Vec2} a The first vector.
+ * @param {!goog.math.Vec2} b The second vector.
+ * @return {number} The determinant of the two vectors.
+ */
+goog.math.Vec2.determinant = function(a, b) {
+ return a.x * b.y - a.y * b.x;
+};
+
+
+/**
+ * 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');
+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 = 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_ = 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_ = options.duration !== undefined ? 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();
+ map.render();
+ if (this.lastAngle_ !== undefined) {
+ var angleDelta = theta - this.lastAngle_;
+ ol.interaction.Interaction.rotateWithoutConstraints(
+ map, view, view.getRotation() - angleDelta);
+ }
+ this.lastAngle_ = theta;
+ if (this.lastMagnitude_ !== undefined) {
+ var resolution = this.lastMagnitude_ * (view.getResolution() / magnitude);
+ ol.interaction.Interaction.zoomWithoutConstraints(map, view, resolution);
+ }
+ if (this.lastMagnitude_ !== undefined) {
+ this.lastScaleDelta_ = this.lastMagnitude_ / magnitude;
+ }
+ this.lastMagnitude_ = magnitude;
+};
+
+
+/**
+ * @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 direction = this.lastScaleDelta_ - 1;
+ ol.interaction.Interaction.rotate(map, view, view.getRotation());
+ ol.interaction.Interaction.zoom(map, view, view.getResolution(),
+ undefined, this.duration_, direction);
+ this.lastScaleDelta_ = 0;
+ return false;
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragRotateAndZoom}
+ * @private
+ */
+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;
+ }
+};
+
+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');
+
+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');
+
+
+/**
+ * @enum {string}
+ */
+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'
+};
+
+
+
+/**
+ * @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.interaction.DrawEvent = function(type, feature) {
+
+ goog.base(this, type);
+
+ /**
+ * 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.interaction.Pointer}
+ * @fires ol.interaction.DrawEvent
+ * @param {olx.interaction.DrawOptions} options Options.
+ * @api stable
+ */
+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;
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.freehand_ = false;
+
+ /**
+ * Target source for drawn features.
+ * @type {ol.source.Vector}
+ * @private
+ */
+ this.source_ = options.source ? options.source : null;
+
+ /**
+ * Target collection for drawn features.
+ * @type {ol.Collection.<ol.Feature>}
+ * @private
+ */
+ this.features_ = options.features ? options.features : null;
+
+ /**
+ * Pixel distance for snapping.
+ * @type {number}
+ * @private
+ */
+ this.snapTolerance_ = options.snapTolerance ? options.snapTolerance : 12;
+
+ /**
+ * 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_);
+
+ /**
+ * 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_ = 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_ = options.maxPoints ? options.maxPoints : Infinity;
+
+ var geometryFunction = options.geometryFunction;
+ if (!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 = 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 {
+ 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 (geometry) {
+ geometry.setCoordinates(coordinates);
+ } else {
+ geometry = new Constructor(coordinates);
+ }
+ return geometry;
+ };
+ }
+ }
+
+ /**
+ * @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;
+
+ /**
+ * Sketch feature.
+ * @type {ol.Feature}
+ * @private
+ */
+ this.sketchFeature_ = null;
+
+ /**
+ * Sketch point.
+ * @type {ol.Feature}
+ * @private
+ */
+ this.sketchPoint_ = null;
+
+ /**
+ * Sketch coordinates. Used when drawing a line or polygon.
+ * @type {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>}
+ * @private
+ */
+ this.sketchCoords_ = null;
+
+ /**
+ * Sketch line. Used when drawing polygon.
+ * @type {ol.Feature}
+ * @private
+ */
+ this.sketchLine_ = null;
+
+ /**
+ * Sketch line coordinates. Used when drawing a polygon or circle.
+ * @type {Array.<ol.Coordinate>}
+ * @private
+ */
+ this.sketchLineCoords_ = null;
+
+ /**
+ * 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.squaredClickTolerance_ = options.clickTolerance ?
+ options.clickTolerance * options.clickTolerance : 36;
+
+ /**
+ * Draw overlay where our sketch features are drawn.
+ * @type {ol.layer.Vector}
+ * @private
+ */
+ this.overlay_ = new ol.layer.Vector({
+ source: new ol.source.Vector({
+ useSpatialIndex: false,
+ wrapX: options.wrapX ? options.wrapX : false
+ }),
+ style: options.style ? options.style :
+ ol.interaction.Draw.getDefaultStyleFunction()
+ });
+
+ /**
+ * Name of the geometry attribute for newly created features.
+ * @type {string|undefined}
+ * @private
+ */
+ this.geometryName_ = options.geometryName;
+
+ /**
+ * @private
+ * @type {ol.events.ConditionType}
+ */
+ this.condition_ = options.condition ?
+ options.condition : ol.events.condition.noModifierKeys;
+
+ /**
+ * @private
+ * @type {ol.events.ConditionType}
+ */
+ this.freehandCondition_ = options.freehandCondition ?
+ options.freehandCondition : ol.events.condition.shiftKeyOnly;
+
+ 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_();
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * @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 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 (!this.finishCoordinate_) {
+ this.startDrawing_(event);
+ }
+ 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) {
+ 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 (!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;
+};
+
+
+/**
+ * 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.finishCoordinate_) {
+ this.modifyDrawing_(event);
+ } else {
+ this.createOrUpdateSketchPoint_(event);
+ }
+ return true;
+};
+
+
+/**
+ * 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 (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;
+};
+
+
+/**
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
+ */
+ol.interaction.Draw.prototype.createOrUpdateSketchPoint_ = function(event) {
+ var coordinates = event.coordinate.slice();
+ if (!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);
+ }
+};
+
+
+/**
+ * Start the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
+ */
+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_;
+ }
+ }
+ if (this.sketchLineCoords_) {
+ this.sketchLine_ = new ol.Feature(
+ new ol.geom.LineString(this.sketchLineCoords_));
+ }
+ var geometry = this.geometryFunction_(this.sketchCoords_);
+ goog.asserts.assert(geometry !== undefined, 'geometry should be defined');
+ this.sketchFeature_ = new ol.Feature();
+ if (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_));
+};
+
+
+/**
+ * Modify the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
+ */
+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 {
+ coordinates = this.sketchCoords_;
+ last = coordinates[coordinates.length - 1];
+ }
+ last[0] = coordinate[0];
+ last[1] = coordinate[1];
+ goog.asserts.assert(this.sketchCoords_, 'sketchCoords_ expected');
+ this.geometryFunction_(this.sketchCoords_, geometry);
+ if (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 (!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 (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_();
+};
+
+
+/**
+ * Add a new coordinate to the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
+ */
+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();
+ }
+};
+
+
+/**
+ * Remove last point of the feature currently being drawn.
+ * @api
+ */
+ol.interaction.Draw.prototype.removeLastPoint = function() {
+ var geometry = this.sketchFeature_.getGeometry();
+ goog.asserts.assertInstanceof(geometry, ol.geom.SimpleGeometry,
+ 'geometry must be an ol.geom.SimpleGeometry');
+ var coordinates, sketchLineGeom;
+ if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
+ coordinates = this.sketchCoords_;
+ coordinates.splice(-2, 1);
+ this.geometryFunction_(coordinates, geometry);
+ } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
+ coordinates = this.sketchCoords_[0];
+ coordinates.splice(-2, 1);
+ sketchLineGeom = this.sketchLine_.getGeometry();
+ goog.asserts.assertInstanceof(sketchLineGeom, ol.geom.LineString,
+ 'sketchLineGeom must be an ol.geom.LineString');
+ sketchLineGeom.setCoordinates(coordinates);
+ this.geometryFunction_(this.sketchCoords_, geometry);
+ }
+
+ if (coordinates.length === 0) {
+ this.finishCoordinate_ = null;
+ }
+
+ this.updateSketchFeatures_();
+};
+
+
+/**
+ * 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.interaction.Draw.prototype.finishDrawing = function() {
+ var sketchFeature = this.abortDrawing_();
+ goog.asserts.assert(sketchFeature, 'sketchFeature expected to be truthy');
+ 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);
+ }
+
+ // 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]));
+ }
+
+ // First dispatch event to allow full set up of feature
+ this.dispatchEvent(new ol.interaction.DrawEvent(
+ ol.interaction.DrawEventType.DRAWEND, sketchFeature));
+
+ // Then insert feature
+ if (this.features_) {
+ this.features_.push(sketchFeature);
+ }
+ if (this.source_) {
+ this.source_.addFeature(sketchFeature);
+ }
+};
+
+
+/**
+ * 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 (sketchFeature) {
+ this.sketchFeature_ = null;
+ this.sketchPoint_ = null;
+ this.sketchLine_ = null;
+ this.overlay_.getSource().clear(true);
+ }
+ return sketchFeature;
+};
+
+
+/**
+ * Extend an existing geometry by adding additional points. This only works
+ * on features with `LineString` geometries, where the interaction will
+ * extend lines by adding points to the end of the coordinates array.
+ * @param {!ol.Feature} feature Feature to be extended.
+ * @api
+ */
+ol.interaction.Draw.prototype.extend = function(feature) {
+ var geometry = feature.getGeometry();
+ goog.asserts.assert(this.mode_ == ol.interaction.DrawMode.LINE_STRING,
+ 'interaction mode must be "line"');
+ goog.asserts.assert(geometry, 'feature must have a geometry');
+ goog.asserts.assert(geometry.getType() == ol.geom.GeometryType.LINE_STRING,
+ 'feature geometry must be a line string');
+ var lineString = /** @type {ol.geom.LineString} */ (geometry);
+ this.sketchFeature_ = feature;
+ this.sketchCoords_ = lineString.getCoordinates();
+ var last = this.sketchCoords_[this.sketchCoords_.length - 1];
+ this.finishCoordinate_ = last.slice();
+ this.sketchCoords_.push(last.slice());
+ this.updateSketchFeatures_();
+ this.dispatchEvent(new ol.interaction.DrawEvent(
+ ol.interaction.DrawEventType.DRAWSTART, this.sketchFeature_));
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.Draw.prototype.shouldStopEvent = goog.functions.FALSE;
+
+
+/**
+ * Redraw the sketch features.
+ * @private
+ */
+ol.interaction.Draw.prototype.updateSketchFeatures_ = function() {
+ var sketchFeatures = [];
+ if (this.sketchFeature_) {
+ sketchFeatures.push(this.sketchFeature_);
+ }
+ if (this.sketchLine_) {
+ sketchFeatures.push(this.sketchLine_);
+ }
+ if (this.sketchPoint_) {
+ sketchFeatures.push(this.sketchPoint_);
+ }
+ var overlaySource = this.overlay_.getSource();
+ overlaySource.clear(true);
+ overlaySource.addFeatures(sketchFeatures);
+};
+
+
+/**
+ * @private
+ */
+ol.interaction.Draw.prototype.updateState_ = function() {
+ var map = this.getMap();
+ var active = this.getActive();
+ if (!map || !active) {
+ this.abortDrawing_();
+ }
+ this.overlay_.setMap(active ? map : null);
+};
+
+
+/**
+ * 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.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 = 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 = opt_angle ? opt_angle :
+ Math.atan((end[1] - center[1]) / (end[0] - center[0]));
+ ol.geom.Polygon.makeRegular(geometry, center, radius, angle);
+ return geometry;
+ }
+ );
+};
+
+
+/**
+ * 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.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(mode !== undefined, 'mode should be defined');
+ return mode;
+};
+
+
+/**
+ * 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.interaction.DrawGeometryFunctionType;
+
+
+/**
+ * Draw mode. This collapses multi-part geometry types with their single-part
+ * cousins.
+ * @enum {string}
+ */
+ol.interaction.DrawMode = {
+ POINT: 'Point',
+ LINE_STRING: 'LineString',
+ POLYGON: 'Polygon',
+ CIRCLE: 'Circle'
+};
+
+goog.provide('ol.interaction.Modify');
+goog.provide('ol.interaction.ModifyEvent');
+
+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.functions');
+goog.require('ol');
+goog.require('ol.Collection');
+goog.require('ol.CollectionEventType');
+goog.require('ol.Feature');
+goog.require('ol.MapBrowserEvent.EventType');
+goog.require('ol.MapBrowserPointerEvent');
+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');
+
+
+/**
+ * @enum {string}
+ */
+ol.ModifyEventType = {
+ /**
+ * Triggered upon feature modification start
+ * @event ol.interaction.ModifyEvent#modifystart
+ * @api
+ */
+ MODIFYSTART: 'modifystart',
+ /**
+ * Triggered upon feature modification end
+ * @event ol.interaction.ModifyEvent#modifyend
+ * @api
+ */
+ MODIFYEND: 'modifyend'
+};
+
+
+
+/**
+ * @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.interaction.ModifyEvent = function(type, features, mapBrowserPointerEvent) {
+
+ goog.base(this, type);
+
+ /**
+ * The features being modified.
+ * @type {ol.Collection.<ol.Feature>}
+ * @api
+ */
+ this.features = features;
+
+ /**
+ * Associated {@link ol.MapBrowserPointerEvent}.
+ * @type {ol.MapBrowserPointerEvent}
+ * @api
+ */
+ this.mapBrowserPointerEvent = mapBrowserPointerEvent;
+};
+goog.inherits(ol.interaction.ModifyEvent, goog.events.Event);
+
+
+/**
+ * @typedef {{depth: (Array.<number>|undefined),
+ * feature: ol.Feature,
+ * geometry: ol.geom.SimpleGeometry,
+ * index: (number|undefined),
+ * segment: Array.<ol.Extent>}}
+ */
+ol.interaction.SegmentDataType;
+
+
+
+/**
+ * @classdesc
+ * Interaction for modifying feature geometries.
+ *
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @param {olx.interaction.ModifyOptions} options Options.
+ * @fires ol.interaction.ModifyEvent
+ * @api
+ */
+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_ = 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];
+
+ /**
+ * Tracks if the next `singleclick` event should be ignored to prevent
+ * accidental deletion right after vertex creation.
+ * @type {boolean}
+ * @private
+ */
+ this.ignoreNextSingleClick_ = false;
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.modified_ = false;
+
+ /**
+ * Segment RTree for each layer
+ * @type {ol.structs.RBush.<ol.interaction.SegmentDataType>}
+ * @private
+ */
+ this.rBush_ = new ol.structs.RBush();
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.pixelTolerance_ = options.pixelTolerance !== undefined ?
+ options.pixelTolerance : 10;
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.snappedToVertex_ = false;
+
+ /**
+ * Indicate whether the interaction is currently changing a feature's
+ * coordinates.
+ * @type {boolean}
+ * @private
+ */
+ this.changingFeature_ = false;
+
+ /**
+ * @type {Array}
+ * @private
+ */
+ this.dragSegments_ = null;
+
+ /**
+ * 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: !!options.wrapX
+ }),
+ style: 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_
+ };
+
+ /**
+ * @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);
+
+};
+goog.inherits(ol.interaction.Modify, ol.interaction.Pointer);
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @private
+ */
+ol.interaction.Modify.prototype.addFeature_ = function(feature) {
+ var geometry = feature.getGeometry();
+ if (geometry.getType() in this.SEGMENT_WRITERS_) {
+ this.SEGMENT_WRITERS_[geometry.getType()].call(this, feature, geometry);
+ }
+ var map = this.getMap();
+ if (map) {
+ this.handlePointerAtPixel_(this.lastPixel_, map);
+ }
+ goog.events.listen(feature, goog.events.EventType.CHANGE,
+ this.handleFeatureChange_, false, this);
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} evt Map browser event
+ * @private
+ */
+ol.interaction.Modify.prototype.willModifyFeatures_ = function(evt) {
+ if (!this.modified_) {
+ this.modified_ = true;
+ this.dispatchEvent(new ol.interaction.ModifyEvent(
+ ol.ModifyEventType.MODIFYSTART, this.features_, evt));
+ }
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @private
+ */
+ol.interaction.Modify.prototype.removeFeature_ = function(feature) {
+ this.removeFeatureSegmentData_(feature);
+ // Remove the vertex feature if the collection of canditate features
+ // is empty.
+ if (this.vertexFeature_ && this.features_.getLength() === 0) {
+ this.overlay_.getSource().removeFeature(this.vertexFeature_);
+ this.vertexFeature_ = null;
+ }
+ goog.events.unlisten(feature, goog.events.EventType.CHANGE,
+ this.handleFeatureChange_, false, this);
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @private
+ */
+ol.interaction.Modify.prototype.removeFeatureSegmentData_ = function(feature) {
+ var rBush = this.rBush_;
+ var /** @type {Array.<ol.interaction.SegmentDataType>} */ nodesToRemove = [];
+ rBush.forEach(
+ /**
+ * @param {ol.interaction.SegmentDataType} node RTree node.
+ */
+ function(node) {
+ if (feature === node.feature) {
+ nodesToRemove.push(node);
+ }
+ });
+ for (var i = nodesToRemove.length - 1; i >= 0; --i) {
+ rBush.remove(nodesToRemove[i]);
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.Modify.prototype.setMap = function(map) {
+ this.overlay_.setMap(map);
+ goog.base(this, 'setMap', map);
+};
+
+
+/**
+ * @param {ol.CollectionEvent} evt Event.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @param {goog.events.Event} evt Event.
+ * @private
+ */
+ol.interaction.Modify.prototype.handleFeatureChange_ = function(evt) {
+ if (!this.changingFeature_) {
+ var feature = /** @type {ol.Feature} */ (evt.target);
+ this.removeFeature_(feature);
+ this.addFeature_(feature);
+ }
+};
+
+
+/**
+ * @param {ol.CollectionEvent} evt Event.
+ * @private
+ */
+ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) {
+ var feature = /** @type {ol.Feature} */ (evt.element);
+ this.removeFeature_(feature);
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Point} geometry Geometry.
+ * @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]
+ });
+ this.rBush_.insert(geometry.getExtent(), segmentData);
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiPoint} geometry Geometry.
+ * @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);
+ }
+};
+
+
+/**
+ * @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);
+ }
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiLineString} geometry Geometry.
+ * @private
+ */
+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);
+ }
+ }
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Polygon} geometry Geometry.
+ * @private
+ */
+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);
+ }
+ }
+};
+
+
+/**
+ * @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);
+ }
+ }
+ }
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.GeometryCollection} geometry Geometry.
+ * @private
+ */
+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 {ol.Coordinate} coordinates Coordinates.
+ * @return {ol.Feature} Vertex feature.
+ * @private
+ */
+ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ =
+ function(coordinates) {
+ var vertexFeature = this.vertexFeature_;
+ if (!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;
+};
+
+
+/**
+ * @param {ol.interaction.SegmentDataType} a
+ * @param {ol.interaction.SegmentDataType} b
+ * @return {number}
+ * @private
+ */
+ol.interaction.Modify.compareIndexes_ = function(a, b) {
+ return a.index - b.index;
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.Modify}
+ * @private
+ */
+ol.interaction.Modify.handleDownEvent_ = function(evt) {
+ this.handlePointerAtPixel_(evt.pixel, evt.map);
+ this.dragSegments_ = [];
+ this.modified_ = false;
+ var vertexFeature = this.vertexFeature_;
+ if (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;
+ }
+
+ 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]);
+ }
+ }
+ if (insertVertices.length) {
+ this.willModifyFeatures_(evt);
+ }
+ for (i = insertVertices.length - 1; i >= 0; --i) {
+ this.insertVertex_.apply(this, insertVertices[i]);
+ }
+ }
+ return !!this.vertexFeature_;
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @this {ol.interaction.Modify}
+ * @private
+ */
+ol.interaction.Modify.handleDragEvent_ = function(evt) {
+ this.ignoreNextSingleClick_ = false;
+ this.willModifyFeatures_(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];
+
+ while (vertex.length < geometry.getStride()) {
+ vertex.push(0);
+ }
+
+ 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;
+ }
+
+ this.setGeometryCoordinates_(geometry, coordinates);
+ }
+ this.createOrUpdateVertexFeature_(vertex);
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Modify}
+ * @private
+ */
+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);
+ }
+ if (this.modified_) {
+ this.dispatchEvent(new ol.interaction.ModifyEvent(
+ ol.ModifyEventType.MODIFYEND, this.features_, evt));
+ this.modified_ = false;
+ }
+ return false;
+};
+
+
+/**
+ * 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
+ */
+ol.interaction.Modify.handleEvent = function(mapBrowserEvent) {
+ if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
+ return true;
+ }
+
+ var handled;
+ if (!mapBrowserEvent.map.getView().getHints()[ol.ViewHint.INTERACTING] &&
+ mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE &&
+ !this.handlingDownUpSequence) {
+ this.handlePointerMove_(mapBrowserEvent);
+ }
+ if (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');
+ this.willModifyFeatures_(mapBrowserEvent);
+ handled = this.removeVertex_();
+ this.dispatchEvent(new ol.interaction.ModifyEvent(
+ ol.ModifyEventType.MODIFYEND, this.features_, mapBrowserEvent));
+ this.modified_ = false;
+ } else {
+ handled = true;
+ }
+ }
+
+ if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK) {
+ this.ignoreNextSingleClick_ = false;
+ }
+
+ return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) &&
+ !handled;
+};
+
+
+/**
+ * @param {ol.MapBrowserEvent} evt Event.
+ * @private
+ */
+ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) {
+ this.lastPixel_ = evt.pixel;
+ this.handlePointerAtPixel_(evt.pixel, evt.map);
+};
+
+
+/**
+ * @param {ol.Pixel} pixel Pixel
+ * @param {ol.Map} map Map.
+ * @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);
+ };
+
+ 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 (this.vertexFeature_) {
+ this.overlay_.getSource().removeFeature(this.vertexFeature_);
+ this.vertexFeature_ = null;
+ }
+};
+
+
+/**
+ * @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;
+ }
+
+ this.setGeometryCoordinates_(geometry, coordinates);
+ var rTree = this.rBush_;
+ goog.asserts.assert(segment !== undefined, 'segment should be defined');
+ rTree.remove(segmentData);
+ goog.asserts.assert(index !== undefined, '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;
+};
+
+
+/**
+ * Removes a vertex from all matching features.
+ * @return {boolean} True when a vertex was removed.
+ * @private
+ */
+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 (left !== undefined) {
+ newSegment[0] = left;
+ }
+ if (right !== undefined) {
+ newSegment[1] = right;
+ }
+ if (newSegment[0] !== undefined && newSegment[1] !== undefined) {
+ 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]);
+ this.setGeometryCoordinates_(geometry, 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);
+
+ if (this.vertexFeature_) {
+ this.overlay_.getSource().removeFeature(this.vertexFeature_);
+ this.vertexFeature_ = null;
+ }
+ }
+ }
+ }
+ return true;
+};
+
+
+/**
+ * @param {ol.geom.SimpleGeometry} geometry Geometry.
+ * @param {Array} coordinates Coordinates.
+ * @private
+ */
+ol.interaction.Modify.prototype.setGeometryCoordinates_ =
+ function(geometry, coordinates) {
+ this.changingFeature_ = true;
+ geometry.setCoordinates(coordinates);
+ this.changingFeature_ = false;
+};
+
+
+/**
+ * @param {ol.geom.SimpleGeometry} geometry Geometry.
+ * @param {number} index Index.
+ * @param {Array.<number>|undefined} depth Depth.
+ * @param {number} delta Delta (1 or -1).
+ * @private
+ */
+ol.interaction.Modify.prototype.updateSegmentIndices_ = function(
+ geometry, index, depth, delta) {
+ this.rBush_.forEachInExtent(geometry.getExtent(), function(segmentDataMatch) {
+ if (segmentDataMatch.geometry === geometry &&
+ (depth === undefined || segmentDataMatch.depth === undefined ||
+ goog.array.equals(
+ /** @type {null|{length: number}} */ (segmentDataMatch.depth),
+ depth)) &&
+ segmentDataMatch.index > index) {
+ segmentDataMatch.index += delta;
+ }
+ });
+};
+
+
+/**
+ * @return {ol.style.StyleFunction} Styles.
+ */
+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.SelectEvent');
+goog.provide('ol.interaction.SelectEventType');
+goog.provide('ol.interaction.SelectFilterFunction');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.functions');
+goog.require('goog.object');
+goog.require('ol.CollectionEventType');
+goog.require('ol.Feature');
+goog.require('ol.array');
+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');
+
+
+/**
+ * @enum {string}
+ */
+ol.interaction.SelectEventType = {
+ /**
+ * Triggered when feature(s) has been (de)selected.
+ * @event ol.interaction.SelectEvent#select
+ * @api
+ */
+ SELECT: 'select'
+};
+
+
+/**
+ * A function that takes an {@link ol.Feature} or {@link ol.render.Feature} and
+ * an {@link ol.layer.Layer} and returns `true` if the feature may be selected
+ * or `false` otherwise.
+ * @typedef {function((ol.Feature|ol.render.Feature), ol.layer.Layer):
+ * boolean}
+ * @api
+ */
+ol.interaction.SelectFilterFunction;
+
+
+
+/**
+ * @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
+ */
+ol.interaction.SelectEvent =
+ function(type, selected, deselected, mapBrowserEvent) {
+ goog.base(this, type);
+
+ /**
+ * Selected features array.
+ * @type {Array.<ol.Feature>}
+ * @api
+ */
+ this.selected = selected;
+
+ /**
+ * 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.interaction.SelectEvent, goog.events.Event);
+
+
+
+/**
+ * @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.
+ *
+ * Selected features are added to an internal unmanaged layer.
+ *
+ * @constructor
+ * @extends {ol.interaction.Interaction}
+ * @param {olx.interaction.SelectOptions=} opt_options Options.
+ * @fires ol.interaction.SelectEvent
+ * @api stable
+ */
+ol.interaction.Select = function(opt_options) {
+
+ goog.base(this, {
+ handleEvent: ol.interaction.Select.handleEvent
+ });
+
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * @private
+ * @type {ol.events.ConditionType}
+ */
+ this.condition_ = options.condition ?
+ options.condition : ol.events.condition.singleClick;
+
+ /**
+ * @private
+ * @type {ol.events.ConditionType}
+ */
+ this.addCondition_ = options.addCondition ?
+ options.addCondition : ol.events.condition.never;
+
+ /**
+ * @private
+ * @type {ol.events.ConditionType}
+ */
+ this.removeCondition_ = options.removeCondition ?
+ options.removeCondition : ol.events.condition.never;
+
+ /**
+ * @private
+ * @type {ol.events.ConditionType}
+ */
+ this.toggleCondition_ = options.toggleCondition ?
+ options.toggleCondition : ol.events.condition.shiftKeyOnly;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.multi_ = options.multi ? options.multi : false;
+
+ /**
+ * @private
+ * @type {ol.interaction.SelectFilterFunction}
+ */
+ this.filter_ = options.filter ? options.filter :
+ goog.functions.TRUE;
+
+ var layerFilter;
+ if (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 ol.array.includes(layers, layer);
+ };
+ }
+ } else {
+ layerFilter = goog.functions.TRUE;
+ }
+
+ /**
+ * @private
+ * @type {function(ol.layer.Layer): boolean}
+ */
+ this.layerFilter_ = layerFilter;
+
+ /**
+ * An association between selected feature (key)
+ * and layer (value)
+ * @private
+ * @type {Object.<number, ol.layer.Layer>}
+ */
+ this.featureLayerAssociation_ = {};
+
+ /**
+ * @private
+ * @type {ol.layer.Vector}
+ */
+ this.featureOverlay_ = new ol.layer.Vector({
+ source: new ol.source.Vector({
+ useSpatialIndex: false,
+ features: options.features,
+ wrapX: options.wrapX
+ }),
+ style: 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);
+
+};
+goog.inherits(ol.interaction.Select, ol.interaction.Interaction);
+
+
+/**
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {ol.layer.Layer} layer Layer.
+ * @private
+ */
+ol.interaction.Select.prototype.addFeatureLayerAssociation_ =
+ function(feature, layer) {
+ var key = goog.getUid(feature);
+ this.featureLayerAssociation_[key] = layer;
+};
+
+
+/**
+ * Get the selected features.
+ * @return {ol.Collection.<ol.Feature>} Features collection.
+ * @api stable
+ */
+ol.interaction.Select.prototype.getFeatures = function() {
+ return this.featureOverlay_.getSource().getFeaturesCollection();
+};
+
+
+/**
+ * Returns the associated {@link ol.layer.Vector vectorlayer} of
+ * the (last) selected feature. Note that this will not work with any
+ * programmatic method like pushing features to
+ * {@link ol.interaction.Select#getFeatures collection}.
+ * @param {ol.Feature|ol.render.Feature} feature Feature
+ * @return {ol.layer.Vector} Layer.
+ * @api
+ */
+ol.interaction.Select.prototype.getLayer = function(feature) {
+ goog.asserts.assertInstanceof(feature, ol.Feature,
+ 'feature should be an ol.Feature');
+ var key = goog.getUid(feature);
+ return /** @type {ol.layer.Vector} */ (this.featureLayerAssociation_[key]);
+};
+
+
+/**
+ * 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
+ */
+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|ol.render.Feature} feature Feature.
+ * @param {ol.layer.Layer} layer Layer.
+ */
+ function(feature, layer) {
+ if (this.filter_(feature, layer)) {
+ selected.push(feature);
+ this.addFeatureLayerAssociation_(feature, layer);
+ 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);
+ // Modify object this.featureLayerAssociation_
+ if (selected.length === 0) {
+ goog.object.clear(this.featureLayerAssociation_);
+ } else {
+ if (deselected.length > 0) {
+ deselected.forEach(function(feature) {
+ this.removeFeatureLayerAssociation_(feature);
+ }, this);
+ }
+ }
+ }
+ } else {
+ // Modify the currently selected feature(s).
+ map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
+ /**
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {ol.layer.Layer} layer Layer.
+ */
+ function(feature, layer) {
+ if (!ol.array.includes(features.getArray(), feature)) {
+ if (add || toggle) {
+ if (this.filter_(feature, layer)) {
+ selected.push(feature);
+ this.addFeatureLayerAssociation_(feature, layer);
+ }
+ }
+ } else {
+ if (remove || toggle) {
+ deselected.push(feature);
+ this.removeFeatureLayerAssociation_(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.interaction.SelectEvent(ol.interaction.SelectEventType.SELECT,
+ selected, deselected, mapBrowserEvent));
+ }
+ return ol.events.condition.pointerMove(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
+ */
+ol.interaction.Select.prototype.setMap = function(map) {
+ var currentMap = this.getMap();
+ var selectedFeatures =
+ this.featureOverlay_.getSource().getFeaturesCollection();
+ if (currentMap) {
+ selectedFeatures.forEach(currentMap.unskipFeature, currentMap);
+ }
+ goog.base(this, 'setMap', map);
+ this.featureOverlay_.setMap(map);
+ if (map) {
+ selectedFeatures.forEach(map.skipFeature, map);
+ }
+};
+
+
+/**
+ * @return {ol.style.StyleFunction} Styles.
+ */
+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()];
+ };
+};
+
+
+/**
+ * @param {ol.CollectionEvent} evt Event.
+ * @private
+ */
+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 (map) {
+ map.skipFeature(feature);
+ }
+};
+
+
+/**
+ * @param {ol.CollectionEvent} evt Event.
+ * @private
+ */
+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 (map) {
+ map.unskipFeature(feature);
+ }
+};
+
+
+/**
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @private
+ */
+ol.interaction.Select.prototype.removeFeatureLayerAssociation_ =
+ function(feature) {
+ var key = goog.getUid(feature);
+ delete this.featureLayerAssociation_[key];
+};
+
+goog.provide('ol.interaction.Snap');
+goog.provide('ol.interaction.SnapProperty');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('ol');
+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');
+
+
+
+/**
+ * @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
+ */
+ol.interaction.Snap = function(opt_options) {
+
+ goog.base(this, {
+ handleEvent: ol.interaction.Snap.handleEvent_,
+ handleDownEvent: goog.functions.TRUE,
+ handleUpEvent: ol.interaction.Snap.handleUpEvent_
+ });
+
+ var options = opt_options ? opt_options : {};
+
+ /**
+ * @type {ol.source.Vector}
+ * @private
+ */
+ this.source_ = options.source ? options.source : null;
+
+ /**
+ * @type {ol.Collection.<ol.Feature>}
+ * @private
+ */
+ this.features_ = options.features ? options.features : null;
+
+ /**
+ * @type {Array.<goog.events.Key>}
+ * @private
+ */
+ this.featuresListenerKeys_ = [];
+
+ /**
+ * @type {Object.<number, goog.events.Key>}
+ * @private
+ */
+ this.geometryChangeListenerKeys_ = {};
+
+ /**
+ * @type {Object.<number, goog.events.Key>}
+ * @private
+ */
+ this.geometryModifyListenerKeys_ = {};
+
+ /**
+ * Extents are preserved so indexed segment can be quickly removed
+ * when its feature geometry changes
+ * @type {Object.<number, ol.Extent>}
+ * @private
+ */
+ this.indexedFeaturesExtents_ = {};
+
+ /**
+ * 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
+ */
+ this.pendingFeatures_ = {};
+
+ /**
+ * Used for distance sorting in sortByDistance_
+ * @type {ol.Coordinate}
+ * @private
+ */
+ this.pixelCoordinate_ = null;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.pixelTolerance_ = options.pixelTolerance !== undefined ?
+ 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);
+
+
+/**
+ * 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
+ */
+ol.interaction.Snap.prototype.addFeature = function(feature, opt_listen) {
+ var listen = opt_listen !== undefined ? opt_listen : true;
+ var geometry = feature.getGeometry();
+ var segmentWriter = this.SEGMENT_WRITERS_[geometry.getType()];
+ if (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);
+ }
+ }
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @private
+ */
+ol.interaction.Snap.prototype.forEachFeatureAdd_ = function(feature) {
+ this.addFeature(feature);
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @private
+ */
+ol.interaction.Snap.prototype.forEachFeatureRemove_ = function(feature) {
+ this.removeFeature(feature);
+};
+
+
+/**
+ * @return {ol.Collection.<ol.Feature>|Array.<ol.Feature>}
+ * @private
+ */
+ol.interaction.Snap.prototype.getFeatures_ = function() {
+ var features;
+ if (this.features_) {
+ features = this.features_;
+ } else if (this.source_) {
+ features = this.source_.getFeatures();
+ }
+ goog.asserts.assert(features !== undefined, 'features should be defined');
+ return features;
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @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;
+ }
+ goog.asserts.assertInstanceof(feature, ol.Feature,
+ 'feature should be an ol.Feature');
+ this.removeFeature(feature);
+};
+
+
+/**
+ * @param {goog.events.Event} evt Event.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature which geometry was modified.
+ * @param {goog.events.Event} evt Event.
+ * @private
+ */
+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);
+ }
+};
+
+
+/**
+ * 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 = opt_unlisten !== undefined ? 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];
+
+ ol.Observable.unByKey(this.geometryChangeListenerKeys_[feature_uid]);
+ delete this.geometryChangeListenerKeys_[feature_uid];
+ }
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.Snap.prototype.setMap = function(map) {
+ var currentMap = this.getMap();
+ var keys = this.featuresListenerKeys_;
+ var features = this.getFeatures_();
+
+ if (currentMap) {
+ keys.forEach(ol.Observable.unByKey);
+ keys.length = 0;
+ features.forEach(this.forEachFeatureRemove_, this);
+ }
+
+ goog.base(this, 'setMap', map);
+
+ if (map) {
+ if (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 (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);
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.Snap.prototype.shouldStopEvent = goog.functions.FALSE;
+
+
+/**
+ * @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) {
+
+ 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 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
+ });
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature
+ * @private
+ */
+ol.interaction.Snap.prototype.updateFeature_ = function(feature) {
+ this.removeFeature(feature, false);
+ this.addFeature(feature, false);
+};
+
+
+/**
+ * @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]);
+ }
+};
+
+
+/**
+ * @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);
+ }
+};
+
+
+/**
+ * @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);
+ }
+ }
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiPoint} geometry Geometry.
+ * @private
+ */
+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);
+ }
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiPolygon} geometry Geometry.
+ * @private
+ */
+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);
+ }
+ }
+ }
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Point} geometry Geometry.
+ * @private
+ */
+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);
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Polygon} geometry Geometry.
+ * @private
+ */
+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);
+ }
+ }
+};
+
+
+/**
+ * @typedef {{
+ * snapped: {boolean},
+ * vertex: (ol.Coordinate|null),
+ * vertexPixel: (ol.Pixel|null)
+ * }}
+ */
+ol.interaction.Snap.ResultType;
+
+
+/**
+ * @typedef {{
+ * feature: ol.Feature,
+ * segment: Array.<ol.Coordinate>
+ * }}
+ */
+ol.interaction.Snap.SegmentDataType;
+
+
+/**
+ * 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
+ */
+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);
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Snap}
+ * @private
+ */
+ol.interaction.Snap.handleUpEvent_ = function(evt) {
+ var featuresToUpdate = goog.object.getValues(this.pendingFeatures_);
+ if (featuresToUpdate.length) {
+ featuresToUpdate.forEach(this.updateFeature_, this);
+ this.pendingFeatures_ = {};
+ }
+ return false;
+};
+
+
+/**
+ * Sort segments by distance, helper function
+ * @param {ol.interaction.Snap.SegmentDataType} a
+ * @param {ol.interaction.Snap.SegmentDataType} b
+ * @return {number}
+ * @this {ol.interaction.Snap}
+ */
+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.interaction.Translate');
+goog.provide('ol.interaction.TranslateEvent');
+
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('ol.array');
+goog.require('ol.interaction.Pointer');
+
+
+/**
+ * @enum {string}
+ */
+ol.interaction.TranslateEventType = {
+ /**
+ * Triggered upon feature translation start.
+ * @event ol.interaction.TranslateEvent#translatestart
+ * @api
+ */
+ TRANSLATESTART: 'translatestart',
+ /**
+ * Triggered upon feature translation.
+ * @event ol.interaction.TranslateEvent#translating
+ * @api
+ */
+ TRANSLATING: 'translating',
+ /**
+ * Triggered upon feature translation end.
+ * @event ol.interaction.TranslateEvent#translateend
+ * @api
+ */
+ TRANSLATEEND: 'translateend'
+};
+
+
+
+/**
+ * @classdesc
+ * Events emitted by {@link ol.interaction.Translate} instances are instances of
+ * this type.
+ *
+ * @constructor
+ * @extends {goog.events.Event}
+ * @implements {oli.interaction.TranslateEvent}
+ * @param {ol.interaction.TranslateEventType} type Type.
+ * @param {ol.Collection.<ol.Feature>} features The features translated.
+ * @param {ol.Coordinate} coordinate The event coordinate.
+ */
+ol.interaction.TranslateEvent = function(type, features, coordinate) {
+
+ goog.base(this, type);
+
+ /**
+ * The features being translated.
+ * @type {ol.Collection.<ol.Feature>}
+ * @api
+ */
+ this.features = features;
+
+ /**
+ * The coordinate of the drag event.
+ * @const
+ * @type {ol.Coordinate}
+ * @api
+ */
+ this.coordinate = coordinate;
+};
+goog.inherits(ol.interaction.TranslateEvent, goog.events.Event);
+
+
+
+/**
+ * @classdesc
+ * Interaction for translating (moving) features.
+ *
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @fires ol.interaction.TranslateEvent
+ * @param {olx.interaction.TranslateOptions} options Options.
+ * @api
+ */
+ol.interaction.Translate = function(options) {
+ goog.base(this, {
+ handleDownEvent: ol.interaction.Translate.handleDownEvent_,
+ handleDragEvent: ol.interaction.Translate.handleDragEvent_,
+ handleMoveEvent: ol.interaction.Translate.handleMoveEvent_,
+ handleUpEvent: ol.interaction.Translate.handleUpEvent_
+ });
+
+
+ /**
+ * @type {string|undefined}
+ * @private
+ */
+ this.previousCursor_ = undefined;
+
+
+ /**
+ * The last position we translated to.
+ * @type {ol.Coordinate}
+ * @private
+ */
+ this.lastCoordinate_ = null;
+
+
+ /**
+ * @type {ol.Collection.<ol.Feature>}
+ * @private
+ */
+ this.features_ = options.features !== undefined ? options.features : null;
+
+ /**
+ * @type {ol.Feature}
+ * @private
+ */
+ this.lastFeature_ = null;
+};
+goog.inherits(ol.interaction.Translate, ol.interaction.Pointer);
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.Translate}
+ * @private
+ */
+ol.interaction.Translate.handleDownEvent_ = function(event) {
+ this.lastFeature_ = this.featuresAtPixel_(event.pixel, event.map);
+ if (!this.lastCoordinate_ && this.lastFeature_) {
+ this.lastCoordinate_ = event.coordinate;
+ ol.interaction.Translate.handleMoveEvent_.call(this, event);
+ this.dispatchEvent(
+ new ol.interaction.TranslateEvent(
+ ol.interaction.TranslateEventType.TRANSLATESTART, this.features_,
+ event.coordinate));
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Translate}
+ * @private
+ */
+ol.interaction.Translate.handleUpEvent_ = function(event) {
+ if (this.lastCoordinate_) {
+ this.lastCoordinate_ = null;
+ ol.interaction.Translate.handleMoveEvent_.call(this, event);
+ this.dispatchEvent(
+ new ol.interaction.TranslateEvent(
+ ol.interaction.TranslateEventType.TRANSLATEEND, this.features_,
+ event.coordinate));
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @this {ol.interaction.Translate}
+ * @private
+ */
+ol.interaction.Translate.handleDragEvent_ = function(event) {
+ if (this.lastCoordinate_) {
+ var newCoordinate = event.coordinate;
+ var deltaX = newCoordinate[0] - this.lastCoordinate_[0];
+ var deltaY = newCoordinate[1] - this.lastCoordinate_[1];
+
+ if (this.features_) {
+ this.features_.forEach(function(feature) {
+ var geom = feature.getGeometry();
+ geom.translate(deltaX, deltaY);
+ feature.setGeometry(geom);
+ });
+ } else if (this.lastFeature_) {
+ var geom = this.lastFeature_.getGeometry();
+ geom.translate(deltaX, deltaY);
+ this.lastFeature_.setGeometry(geom);
+ }
+
+ this.lastCoordinate_ = newCoordinate;
+ this.dispatchEvent(
+ new ol.interaction.TranslateEvent(
+ ol.interaction.TranslateEventType.TRANSLATING, this.features_,
+ newCoordinate));
+ }
+};
+
+
+/**
+ * @param {ol.MapBrowserEvent} event Event.
+ * @this {ol.interaction.Translate}
+ * @private
+ */
+ol.interaction.Translate.handleMoveEvent_ = function(event)
+ {
+ var elem = event.map.getTargetElement();
+ var intersectingFeature = event.map.forEachFeatureAtPixel(event.pixel,
+ function(feature) {
+ return feature;
+ });
+
+ if (intersectingFeature) {
+ var isSelected = false;
+
+ if (this.features_ &&
+ ol.array.includes(this.features_.getArray(), intersectingFeature)) {
+ isSelected = true;
+ }
+
+ this.previousCursor_ = elem.style.cursor;
+
+ // WebKit browsers don't support the grab icons without a prefix
+ elem.style.cursor = this.lastCoordinate_ ?
+ '-webkit-grabbing' : (isSelected ? '-webkit-grab' : 'pointer');
+
+ // Thankfully, attempting to set the standard ones will silently fail,
+ // keeping the prefixed icons
+ elem.style.cursor = !this.lastCoordinate_ ?
+ 'grabbing' : (isSelected ? 'grab' : 'pointer');
+
+ } else {
+ elem.style.cursor = this.previousCursor_ !== undefined ?
+ this.previousCursor_ : '';
+ this.previousCursor_ = undefined;
+ }
+};
+
+
+/**
+ * Tests to see if the given coordinates intersects any of our selected
+ * features.
+ * @param {ol.Pixel} pixel Pixel coordinate to test for intersection.
+ * @param {ol.Map} map Map to test the intersection on.
+ * @return {ol.Feature} Returns the feature found at the specified pixel
+ * coordinates.
+ * @private
+ */
+ol.interaction.Translate.prototype.featuresAtPixel_ = function(pixel, map) {
+ var found = null;
+
+ var intersectingFeature = map.forEachFeatureAtPixel(pixel,
+ function(feature) {
+ return feature;
+ });
+
+ if (this.features_ &&
+ ol.array.includes(this.features_.getArray(), intersectingFeature)) {
+ found = intersectingFeature;
+ }
+
+ return found;
+};
+
+goog.provide('ol.layer.Heatmap');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.object');
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.dom');
+goog.require('ol.layer.Vector');
+goog.require('ol.math');
+goog.require('ol.render.EventType');
+goog.require('ol.style.Icon');
+goog.require('ol.style.Style');
+
+
+/**
+ * @enum {string}
+ */
+ol.layer.HeatmapLayerProperty = {
+ BLUR: 'blur',
+ GRADIENT: 'gradient',
+ RADIUS: 'radius'
+};
+
+
+
+/**
+ * @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
+ */
+ol.layer.Heatmap = function(opt_options) {
+ var options = 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_ = options.shadow !== undefined ? 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(options.gradient ?
+ options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT);
+
+ this.setBlur(options.blur !== undefined ? options.blur : 15);
+
+ this.setRadius(options.radius !== undefined ? 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 = 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(this.styleCache_, 'this.styleCache_ expected');
+ goog.asserts.assert(this.circleImage_ !== undefined,
+ 'this.circleImage_ should be defined');
+ var weight = weightFunction(feature);
+ var opacity = weight !== undefined ? ol.math.clamp(weight, 0, 1) : 1;
+ // cast to 8 bits
+ var index = (255 * opacity) | 0;
+ var style = this.styleCache_[index];
+ if (!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);
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ */
+ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00'];
+
+
+/**
+ * @param {Array.<string>} colors
+ * @return {Uint8ClampedArray}
+ * @private
+ */
+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;
+};
+
+
+/**
+ * @return {string}
+ * @private
+ */
+ol.layer.Heatmap.prototype.createCircle_ = function() {
+ var radius = this.getRadius();
+ var blur = this.getBlur();
+ goog.asserts.assert(radius !== undefined && blur !== undefined,
+ '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();
+};
+
+
+/**
+ * Return the blur size in pixels.
+ * @return {number} Blur size in pixels.
+ * @api
+ * @observable
+ */
+ol.layer.Heatmap.prototype.getBlur = function() {
+ return /** @type {number} */ (this.get(ol.layer.HeatmapLayerProperty.BLUR));
+};
+
+
+/**
+ * Return the gradient colors as array of strings.
+ * @return {Array.<string>} Colors.
+ * @api
+ * @observable
+ */
+ol.layer.Heatmap.prototype.getGradient = function() {
+ return /** @type {Array.<string>} */ (
+ this.get(ol.layer.HeatmapLayerProperty.GRADIENT));
+};
+
+
+/**
+ * Return the size of the radius in pixels.
+ * @return {number} Radius size in pixel.
+ * @api
+ * @observable
+ */
+ol.layer.Heatmap.prototype.getRadius = function() {
+ return /** @type {number} */ (this.get(ol.layer.HeatmapLayerProperty.RADIUS));
+};
+
+
+/**
+ * @private
+ */
+ol.layer.Heatmap.prototype.handleGradientChanged_ = function() {
+ this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient());
+};
+
+
+/**
+ * @private
+ */
+ol.layer.Heatmap.prototype.handleStyleChanged_ = function() {
+ this.circleImage_ = this.createCircle_();
+ this.styleCache_ = new Array(256);
+ this.changed();
+};
+
+
+/**
+ * @param {ol.render.Event} event Post compose event
+ * @private
+ */
+ol.layer.Heatmap.prototype.handleRender_ = function(event) {
+ goog.asserts.assert(event.type == ol.render.EventType.RENDER,
+ 'event.type should be RENDER');
+ goog.asserts.assert(this.gradient_, 'this.gradient_ expected');
+ 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;
+ 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);
+};
+
+
+/**
+ * Set the blur size in pixels.
+ * @param {number} blur Blur size in pixels.
+ * @api
+ * @observable
+ */
+ol.layer.Heatmap.prototype.setBlur = function(blur) {
+ this.set(ol.layer.HeatmapLayerProperty.BLUR, blur);
+};
+
+
+/**
+ * Set the gradient colors as array of strings.
+ * @param {Array.<string>} colors Gradient.
+ * @api
+ * @observable
+ */
+ol.layer.Heatmap.prototype.setGradient = function(colors) {
+ this.set(ol.layer.HeatmapLayerProperty.GRADIENT, colors);
+};
+
+
+/**
+ * Set the size of the radius in pixels.
+ * @param {number} radius Radius size in pixel.
+ * @api
+ * @observable
+ */
+ol.layer.Heatmap.prototype.setRadius = function(radius) {
+ this.set(ol.layer.HeatmapLayerProperty.RADIUS, radius);
+};
+
+goog.provide('ol.raster.Operation');
+goog.provide('ol.raster.OperationType');
+
+
+/**
+ * Raster operation type. Supported values are `'pixel'` and `'image'`.
+ * @enum {string}
+ * @api
+ */
+ol.raster.OperationType = {
+ PIXEL: 'pixel',
+ IMAGE: 'image'
+};
+
+
+/**
+ * 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
+ */
+ol.raster.Operation;
+
+goog.provide('ol.raster.Pixel');
+
+
+/**
+ * An array of numbers representing pixel values.
+ * @typedef {Array.<number>} ol.raster.Pixel
+ * @api
+ */
+ol.raster.Pixel;
+
+goog.provide('ol.render');
+
+goog.require('goog.vec.Mat4');
+goog.require('ol.render.canvas.Immediate');
+goog.require('ol.vec.Mat4');
+
+
+/**
+ * Binds a Canvas Immediate API to a canvas context, to allow drawing geometries
+ * to the context's canvas.
+ *
+ * The units for geometry coordinates are css pixels relative to the top left
+ * corner of the canvas element.
+ * ```js
+ * var canvas = document.createElement('canvas');
+ * var render = ol.render.toContext(canvas.getContext('2d'),
+ * { size: [100, 100] });
+ * render.setFillStrokeStyle(new ol.style.Fill({ color: blue }));
+ * render.drawPolygonGeometry(
+ * new ol.geom.Polygon([[[0, 0], [100, 100], [100, 0], [0, 0]]]));
+ * ```
+ *
+ * Note that {@link ol.render.canvas.Immediate#drawAsync} and
+ * {@link ol.render.canvas.Immediate#drawFeature} cannot be used.
+ *
+ * @param {CanvasRenderingContext2D} context Canvas context.
+ * @param {olx.render.ToContextOptions=} opt_options Options.
+ * @return {ol.render.canvas.Immediate} Canvas Immediate.
+ * @api
+ */
+ol.render.toContext = function(context, opt_options) {
+ var canvas = context.canvas;
+ var options = opt_options ? opt_options : {};
+ var pixelRatio = options.pixelRatio || ol.has.DEVICE_PIXEL_RATIO;
+ var size = options.size;
+ if (size) {
+ canvas.width = size[0] * pixelRatio;
+ canvas.height = size[1] * pixelRatio;
+ canvas.style.width = size[0] + 'px';
+ canvas.style.height = size[1] + 'px';
+ }
+ var extent = [0, 0, canvas.width, canvas.height];
+ var transform = ol.vec.Mat4.makeTransform2D(goog.vec.Mat4.createNumber(),
+ 0, 0, pixelRatio, pixelRatio, 0, 0, 0);
+ return new ol.render.canvas.Immediate(context, pixelRatio, extent, transform,
+ 0);
+};
+
+goog.provide('ol.reproj.Tile');
+goog.provide('ol.reproj.TileFunctionType');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.math');
+goog.require('goog.object');
+goog.require('ol.Tile');
+goog.require('ol.TileState');
+goog.require('ol.extent');
+goog.require('ol.math');
+goog.require('ol.proj');
+goog.require('ol.reproj');
+goog.require('ol.reproj.Triangulation');
+
+
+/**
+ * @typedef {function(number, number, number, number) : ol.Tile}
+ */
+ol.reproj.TileFunctionType;
+
+
+
+/**
+ * @classdesc
+ * Class encapsulating single reprojected tile.
+ * See {@link ol.source.TileImage}.
+ *
+ * @constructor
+ * @extends {ol.Tile}
+ * @param {ol.proj.Projection} sourceProj Source projection.
+ * @param {ol.tilegrid.TileGrid} sourceTileGrid Source tile grid.
+ * @param {ol.proj.Projection} targetProj Target projection.
+ * @param {ol.tilegrid.TileGrid} targetTileGrid Target tile grid.
+ * @param {ol.TileCoord} tileCoord Coordinate of the tile.
+ * @param {ol.TileCoord} wrappedTileCoord Coordinate of the tile wrapped in X.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.reproj.TileFunctionType} getTileFunction
+ * Function returning source tiles (z, x, y, pixelRatio).
+ * @param {number=} opt_errorThreshold Acceptable reprojection error (in px).
+ * @param {boolean=} opt_renderEdges Render reprojection edges.
+ */
+ol.reproj.Tile = function(sourceProj, sourceTileGrid,
+ targetProj, targetTileGrid, tileCoord, wrappedTileCoord,
+ pixelRatio, getTileFunction,
+ opt_errorThreshold,
+ opt_renderEdges) {
+ goog.base(this, tileCoord, ol.TileState.IDLE);
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.renderEdges_ = opt_renderEdges !== undefined ? opt_renderEdges : false;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.pixelRatio_ = pixelRatio;
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement}
+ */
+ this.canvas_ = null;
+
+ /**
+ * @private
+ * @type {Object.<number, HTMLCanvasElement>}
+ */
+ this.canvasByContext_ = {};
+
+ /**
+ * @private
+ * @type {ol.tilegrid.TileGrid}
+ */
+ this.sourceTileGrid_ = sourceTileGrid;
+
+ /**
+ * @private
+ * @type {ol.tilegrid.TileGrid}
+ */
+ this.targetTileGrid_ = targetTileGrid;
+
+ /**
+ * @private
+ * @type {ol.TileCoord}
+ */
+ this.wrappedTileCoord_ = wrappedTileCoord ? wrappedTileCoord : tileCoord;
+
+ /**
+ * @private
+ * @type {!Array.<ol.Tile>}
+ */
+ this.sourceTiles_ = [];
+
+ /**
+ * @private
+ * @type {Array.<goog.events.Key>}
+ */
+ this.sourcesListenerKeys_ = null;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.sourceZ_ = 0;
+
+ var targetExtent = targetTileGrid.getTileCoordExtent(this.wrappedTileCoord_);
+ var maxTargetExtent = this.targetTileGrid_.getExtent();
+ var maxSourceExtent = this.sourceTileGrid_.getExtent();
+
+ var limitedTargetExtent = maxTargetExtent ?
+ ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent;
+
+ if (ol.extent.getArea(limitedTargetExtent) === 0) {
+ // Tile is completely outside range -> EMPTY
+ // TODO: is it actually correct that the source even creates the tile ?
+ this.state = ol.TileState.EMPTY;
+ return;
+ }
+
+ var sourceProjExtent = sourceProj.getExtent();
+ if (sourceProjExtent) {
+ if (!maxSourceExtent) {
+ maxSourceExtent = sourceProjExtent;
+ } else {
+ maxSourceExtent = ol.extent.getIntersection(
+ maxSourceExtent, sourceProjExtent);
+ }
+ }
+
+ var targetResolution = targetTileGrid.getResolution(
+ this.wrappedTileCoord_[0]);
+
+ var targetCenter = ol.extent.getCenter(limitedTargetExtent);
+ var sourceResolution = ol.reproj.calculateSourceResolution(
+ sourceProj, targetProj, targetCenter, targetResolution);
+
+ if (!goog.math.isFiniteNumber(sourceResolution) || sourceResolution <= 0) {
+ // invalid sourceResolution -> EMPTY
+ // probably edges of the projections when no extent is defined
+ this.state = ol.TileState.EMPTY;
+ return;
+ }
+
+ var errorThresholdInPixels = opt_errorThreshold !== undefined ?
+ opt_errorThreshold : ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD;
+
+ /**
+ * @private
+ * @type {!ol.reproj.Triangulation}
+ */
+ this.triangulation_ = new ol.reproj.Triangulation(
+ sourceProj, targetProj, limitedTargetExtent, maxSourceExtent,
+ sourceResolution * errorThresholdInPixels);
+
+ if (this.triangulation_.getTriangles().length === 0) {
+ // no valid triangles -> EMPTY
+ this.state = ol.TileState.EMPTY;
+ return;
+ }
+
+ this.sourceZ_ = sourceTileGrid.getZForResolution(sourceResolution);
+ var sourceExtent = this.triangulation_.calculateSourceExtent();
+
+ if (maxSourceExtent) {
+ if (sourceProj.canWrapX()) {
+ sourceExtent[1] = ol.math.clamp(
+ sourceExtent[1], maxSourceExtent[1], maxSourceExtent[3]);
+ sourceExtent[3] = ol.math.clamp(
+ sourceExtent[3], maxSourceExtent[1], maxSourceExtent[3]);
+ } else {
+ sourceExtent = ol.extent.getIntersection(sourceExtent, maxSourceExtent);
+ }
+ }
+
+ if (!ol.extent.getArea(sourceExtent)) {
+ this.state = ol.TileState.EMPTY;
+ } else {
+ var sourceRange = sourceTileGrid.getTileRangeForExtentAndZ(
+ sourceExtent, this.sourceZ_);
+
+ var tilesRequired = sourceRange.getWidth() * sourceRange.getHeight();
+ if (!goog.asserts.assert(
+ tilesRequired < ol.RASTER_REPROJECTION_MAX_SOURCE_TILES,
+ 'reasonable number of tiles is required')) {
+ this.state = ol.TileState.ERROR;
+ return;
+ }
+ for (var srcX = sourceRange.minX; srcX <= sourceRange.maxX; srcX++) {
+ for (var srcY = sourceRange.minY; srcY <= sourceRange.maxY; srcY++) {
+ var tile = getTileFunction(this.sourceZ_, srcX, srcY, pixelRatio);
+ if (tile) {
+ this.sourceTiles_.push(tile);
+ }
+ }
+ }
+
+ if (this.sourceTiles_.length === 0) {
+ this.state = ol.TileState.EMPTY;
+ }
+ }
+};
+goog.inherits(ol.reproj.Tile, ol.Tile);
+
+
+/**
+ * @inheritDoc
+ */
+ol.reproj.Tile.prototype.disposeInternal = function() {
+ if (this.state == ol.TileState.LOADING) {
+ this.unlistenSources_();
+ }
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.reproj.Tile.prototype.getImage = function(opt_context) {
+ if (opt_context !== undefined) {
+ var image;
+ var key = goog.getUid(opt_context);
+ if (key in this.canvasByContext_) {
+ return this.canvasByContext_[key];
+ } else if (goog.object.isEmpty(this.canvasByContext_)) {
+ image = this.canvas_;
+ } else {
+ image = /** @type {HTMLCanvasElement} */ (this.canvas_.cloneNode(false));
+ }
+ this.canvasByContext_[key] = image;
+ return image;
+ } else {
+ return this.canvas_;
+ }
+};
+
+
+/**
+ * @private
+ */
+ol.reproj.Tile.prototype.reproject_ = function() {
+ var sources = [];
+ this.sourceTiles_.forEach(function(tile, i, arr) {
+ if (tile && tile.getState() == ol.TileState.LOADED) {
+ sources.push({
+ extent: this.sourceTileGrid_.getTileCoordExtent(tile.tileCoord),
+ image: tile.getImage()
+ });
+ }
+ }, this);
+ this.sourceTiles_.length = 0;
+
+ var z = this.wrappedTileCoord_[0];
+ var size = this.targetTileGrid_.getTileSize(z);
+ var width = goog.isNumber(size) ? size : size[0];
+ var height = goog.isNumber(size) ? size : size[1];
+ var targetResolution = this.targetTileGrid_.getResolution(z);
+ var sourceResolution = this.sourceTileGrid_.getResolution(this.sourceZ_);
+
+ var targetExtent = this.targetTileGrid_.getTileCoordExtent(
+ this.wrappedTileCoord_);
+ this.canvas_ = ol.reproj.render(width, height, this.pixelRatio_,
+ sourceResolution, this.sourceTileGrid_.getExtent(),
+ targetResolution, targetExtent, this.triangulation_, sources,
+ this.renderEdges_);
+
+ this.state = ol.TileState.LOADED;
+ this.changed();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.reproj.Tile.prototype.load = function() {
+ if (this.state == ol.TileState.IDLE) {
+ this.state = ol.TileState.LOADING;
+ this.changed();
+
+ var leftToLoad = 0;
+
+ goog.asserts.assert(!this.sourcesListenerKeys_,
+ 'this.sourcesListenerKeys_ should be null');
+
+ this.sourcesListenerKeys_ = [];
+ this.sourceTiles_.forEach(function(tile, i, arr) {
+ var state = tile.getState();
+ if (state == ol.TileState.IDLE || state == ol.TileState.LOADING) {
+ leftToLoad++;
+
+ var sourceListenKey;
+ sourceListenKey = tile.listen(goog.events.EventType.CHANGE,
+ function(e) {
+ var state = tile.getState();
+ if (state == ol.TileState.LOADED ||
+ state == ol.TileState.ERROR ||
+ state == ol.TileState.EMPTY) {
+ goog.events.unlistenByKey(sourceListenKey);
+ leftToLoad--;
+ goog.asserts.assert(leftToLoad >= 0,
+ 'leftToLoad should not be negative');
+ if (leftToLoad === 0) {
+ this.unlistenSources_();
+ this.reproject_();
+ }
+ }
+ }, false, this);
+ this.sourcesListenerKeys_.push(sourceListenKey);
+ }
+ }, this);
+
+ this.sourceTiles_.forEach(function(tile, i, arr) {
+ var state = tile.getState();
+ if (state == ol.TileState.IDLE) {
+ tile.load();
+ }
+ });
+
+ if (leftToLoad === 0) {
+ this.reproject_();
+ }
+ }
+};
+
+
+/**
+ * @private
+ */
+ol.reproj.Tile.prototype.unlistenSources_ = function() {
+ goog.asserts.assert(this.sourcesListenerKeys_,
+ 'this.sourcesListenerKeys_ should not be null');
+ this.sourcesListenerKeys_.forEach(goog.events.unlistenByKey);
+ this.sourcesListenerKeys_ = null;
+};
+
+// 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.
+ *
+ */
+
+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');
+goog.require('goog.object');
+
+
+/**
+ * The name of the property of goog.global under which the JavaScript
+ * verification object is stored by the loaded script.
+ * @private {string}
+ */
+goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification';
+
+
+/**
+ * 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;
+
+
+/**
+ * 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.
+ * attributes: Additional attributes to set on the script tag.
+ *
+ * @typedef {{
+ * timeout: (number|undefined),
+ * document: (HTMLDocument|undefined),
+ * cleanupWhenDone: (boolean|undefined),
+ * attributes: (!Object<string, string>|undefined)
+ * }}
+ */
+goog.net.jsloader.Options;
+
+
+/**
+ * Scripts (URIs) waiting to be loaded.
+ * @private {!Array<string>}
+ */
+goog.net.jsloader.scriptsToLoad_ = [];
+
+
+/**
+ * The deferred result of loading the URIs in scriptsToLoad_.
+ * We need to return this to a caller that wants to load URIs while
+ * a deferred is already working on them.
+ * @private {!goog.async.Deferred<null>}
+ */
+goog.net.jsloader.scriptLoadingDeferred_;
+
+
+/**
+ * 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.
+ * @return {!goog.async.Deferred} The deferred result, that may be used to add
+ * callbacks
+ */
+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.async.Deferred.succeed(null);
+ }
+
+ 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 the deferred
+ // result of the ongoing script load, so the caller knows when they
+ // finish loading.
+ return goog.net.jsloader.scriptLoadingDeferred_;
+ }
+
+ 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);
+ }
+ return deferred;
+ };
+ goog.net.jsloader.scriptLoadingDeferred_ = popAndLoadNextScript();
+ return goog.net.jsloader.scriptLoadingDeferred_;
+};
+
+
+/**
+ * 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.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));
+ };
+
+ var properties = options.attributes || {};
+ goog.object.extend(properties, {
+ '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
+ });
+ goog.dom.setProperties(script, properties);
+ var scriptParent = goog.net.jsloader.getScriptParentElement_(doc);
+ scriptParent.appendChild(script);
+
+ return deferred;
+};
+
+
+/**
+ * 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.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.'));
+ }
+
+ // 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;
+};
+
+
+/**
+ * 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];
+ }
+};
+
+
+/**
+ * Cancels a given request.
+ * @this {{script_: Element, timeout_: number}} The request context.
+ * @private
+ */
+goog.net.jsloader.cancel_ = function() {
+ var request = this;
+ if (request && request.script_) {
+ var scriptNode = request.script_;
+ if (scriptNode && scriptNode.tagName == goog.dom.TagName.SCRIPT) {
+ goog.net.jsloader.cleanup_(scriptNode, true, request.timeout_);
+ }
+ }
+};
+
+
+/**
+ * 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);
+ }
+
+ 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);
+ }
+};
+
+
+/**
+ * Possible error codes for jsloader.
+ * @enum {number}
+ */
+goog.net.jsloader.ErrorCode = {
+ LOAD_ERROR: 0,
+ TIMEOUT: 1,
+ VERIFY_ERROR: 2,
+ VERIFY_OBJECT_ALREADY_EXISTS: 3
+};
+
+
+
+/**
+ * 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.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
+
+/**
+ * @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.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
+
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * The name of the property of goog.global under which the callback is
+ * stored.
+ */
+goog.net.Jsonp.CALLBACKS = '_callbacks_';
+
+
+/**
+ * Used to generate unique callback IDs. The counter must be global because
+ * all channels share a common callback object.
+ * @private
+ */
+goog.net.Jsonp.scriptCounter_ = 0;
+
+
+/**
+ * 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.Jsonp.prototype.setRequestTimeout = function(timeout) {
+ this.timeout_ = timeout;
+};
+
+
+/**
+ * Returns the current timeout value, in milliseconds.
+ *
+ * @return {number} The timeout value.
+ */
+goog.net.Jsonp.prototype.getRequestTimeout = function() {
+ return this.timeout_;
+};
+
+
+/**
+ * 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.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};
+};
+
+
+/**
+ * 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.Jsonp.prototype.cancel = function(request) {
+ if (request) {
+ if (request.deferred_) {
+ request.deferred_.cancel();
+ }
+ if (request.id_) {
+ goog.net.Jsonp.cleanup_(request.id_, false);
+ }
+ }
+};
+
+
+/**
+ * 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
+ */
+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);
+ }
+ };
+};
+
+
+/**
+ * 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.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;
+};
+
+
+/**
+ * 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.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;
+ }
+ }
+};
+
+
+/**
+ * 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
+ */
+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;
+};
+
+
+// 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.source.TileImage');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('ol.ImageTile');
+goog.require('ol.TileCache');
+goog.require('ol.TileState');
+goog.require('ol.proj');
+goog.require('ol.reproj.Tile');
+goog.require('ol.source.UrlTile');
+
+
+
+/**
+ * @classdesc
+ * Base class for sources providing images divided into a tile grid.
+ *
+ * @constructor
+ * @fires ol.source.TileEvent
+ * @extends {ol.source.UrlTile}
+ * @param {olx.source.TileImageOptions} options Image tile options.
+ * @api
+ */
+ol.source.TileImage = function(options) {
+
+ goog.base(this, {
+ attributions: options.attributions,
+ extent: options.extent,
+ logo: options.logo,
+ opaque: options.opaque,
+ projection: options.projection,
+ state: options.state !== undefined ?
+ /** @type {ol.source.State} */ (options.state) : undefined,
+ tileGrid: options.tileGrid,
+ tileLoadFunction: options.tileLoadFunction ?
+ options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction,
+ tilePixelRatio: options.tilePixelRatio,
+ tileUrlFunction: options.tileUrlFunction,
+ url: options.url,
+ urls: options.urls,
+ wrapX: options.wrapX
+ });
+
+ /**
+ * @protected
+ * @type {?string}
+ */
+ this.crossOrigin =
+ options.crossOrigin !== undefined ? options.crossOrigin : null;
+
+ /**
+ * @protected
+ * @type {function(new: ol.ImageTile, ol.TileCoord, ol.TileState, string,
+ * ?string, ol.TileLoadFunctionType)}
+ */
+ this.tileClass = options.tileClass !== undefined ?
+ options.tileClass : ol.ImageTile;
+
+ /**
+ * @protected
+ * @type {Object.<string, ol.TileCache>}
+ */
+ this.tileCacheForProjection = {};
+
+ /**
+ * @protected
+ * @type {Object.<string, ol.tilegrid.TileGrid>}
+ */
+ this.tileGridForProjection = {};
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.reprojectionErrorThreshold_ = options.reprojectionErrorThreshold;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.renderReprojectionEdges_ = false;
+};
+goog.inherits(ol.source.TileImage, ol.source.UrlTile);
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.TileImage.prototype.canExpireCache = function() {
+ if (!ol.ENABLE_RASTER_REPROJECTION) {
+ return goog.base(this, 'canExpireCache');
+ }
+ var canExpire = this.tileCache.canExpireCache();
+ if (canExpire) {
+ return true;
+ } else {
+ return goog.object.some(this.tileCacheForProjection, function(tileCache) {
+ return tileCache.canExpireCache();
+ });
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.TileImage.prototype.expireCache = function(projection, usedTiles) {
+ if (!ol.ENABLE_RASTER_REPROJECTION) {
+ goog.base(this, 'expireCache', projection, usedTiles);
+ return;
+ }
+ var usedTileCache = this.getTileCacheForProjection(projection);
+
+ this.tileCache.expireCache(this.tileCache == usedTileCache ? usedTiles : {});
+ goog.object.forEach(this.tileCacheForProjection, function(tileCache) {
+ tileCache.expireCache(tileCache == usedTileCache ? usedTiles : {});
+ });
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.TileImage.prototype.getTileGridForProjection = function(projection) {
+ if (!ol.ENABLE_RASTER_REPROJECTION) {
+ return goog.base(this, 'getTileGridForProjection', projection);
+ }
+ var thisProj = this.getProjection();
+ if (this.tileGrid &&
+ (!thisProj || ol.proj.equivalent(thisProj, projection))) {
+ return this.tileGrid;
+ } else {
+ var projKey = goog.getUid(projection).toString();
+ if (!(projKey in this.tileGridForProjection)) {
+ this.tileGridForProjection[projKey] =
+ ol.tilegrid.getForProjection(projection);
+ }
+ return this.tileGridForProjection[projKey];
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.TileImage.prototype.getTileCacheForProjection = function(projection) {
+ if (!ol.ENABLE_RASTER_REPROJECTION) {
+ return goog.base(this, 'getTileCacheForProjection', projection);
+ }
+ var thisProj = this.getProjection();
+ if (!thisProj || ol.proj.equivalent(thisProj, projection)) {
+ return this.tileCache;
+ } else {
+ var projKey = goog.getUid(projection).toString();
+ if (!(projKey in this.tileCacheForProjection)) {
+ this.tileCacheForProjection[projKey] = new ol.TileCache();
+ }
+ return this.tileCacheForProjection[projKey];
+ }
+};
+
+
+/**
+ * @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} projection Projection.
+ * @param {string} key The key set on the tile.
+ * @return {ol.Tile} Tile.
+ * @private
+ */
+ol.source.TileImage.prototype.createTile_ =
+ function(z, x, y, pixelRatio, projection, key) {
+ var tileCoord = [z, x, y];
+ var urlTileCoord = this.getTileCoordForTileUrlFunction(
+ tileCoord, projection);
+ var tileUrl = urlTileCoord ?
+ this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined;
+ var tile = new this.tileClass(
+ tileCoord,
+ tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY,
+ tileUrl !== undefined ? tileUrl : '',
+ this.crossOrigin,
+ this.tileLoadFunction);
+ tile.key = key;
+ goog.events.listen(tile, goog.events.EventType.CHANGE,
+ this.handleTileChange, false, this);
+ return tile;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.TileImage.prototype.getTile =
+ function(z, x, y, pixelRatio, projection) {
+ if (!ol.ENABLE_RASTER_REPROJECTION ||
+ !this.getProjection() ||
+ !projection ||
+ ol.proj.equivalent(this.getProjection(), projection)) {
+ return this.getTileInternal(z, x, y, pixelRatio, projection);
+ } else {
+ var cache = this.getTileCacheForProjection(projection);
+ var tileCoord = [z, x, y];
+ var tileCoordKey = this.getKeyZXY.apply(this, tileCoord);
+ if (cache.containsKey(tileCoordKey)) {
+ return /** @type {!ol.Tile} */ (cache.get(tileCoordKey));
+ } else {
+ var sourceProjection = this.getProjection();
+ var sourceTileGrid = this.getTileGridForProjection(sourceProjection);
+ var targetTileGrid = this.getTileGridForProjection(projection);
+ var wrappedTileCoord =
+ this.getTileCoordForTileUrlFunction(tileCoord, projection);
+ var tile = new ol.reproj.Tile(
+ sourceProjection, sourceTileGrid,
+ projection, targetTileGrid,
+ tileCoord, wrappedTileCoord, this.getTilePixelRatio(),
+ goog.bind(function(z, x, y, pixelRatio) {
+ return this.getTileInternal(z, x, y, pixelRatio, sourceProjection);
+ }, this), this.reprojectionErrorThreshold_,
+ this.renderReprojectionEdges_);
+
+ cache.set(tileCoordKey, tile);
+ return tile;
+ }
+ }
+};
+
+
+/**
+ * @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} projection Projection.
+ * @return {!ol.Tile} Tile.
+ * @protected
+ */
+ol.source.TileImage.prototype.getTileInternal =
+ function(z, x, y, pixelRatio, projection) {
+ var /** @type {ol.Tile} */ tile = null;
+ var tileCoordKey = this.getKeyZXY(z, x, y);
+ var paramsKey = this.getKeyParams();
+ if (!this.tileCache.containsKey(tileCoordKey)) {
+ goog.asserts.assert(projection, 'argument projection is truthy');
+ tile = this.createTile_(z, x, y, pixelRatio, projection, paramsKey);
+ this.tileCache.set(tileCoordKey, tile);
+ } else {
+ tile = /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
+ if (tile.key != paramsKey) {
+ // The source's params changed. If the tile has an interim tile and if we
+ // can use it then we use it. Otherwise we create a new tile. In both
+ // cases we attempt to assign an interim tile to the new tile.
+ var /** @type {ol.Tile} */ interimTile = tile;
+ if (tile.interimTile && tile.interimTile.key == paramsKey) {
+ goog.asserts.assert(tile.interimTile.getState() == ol.TileState.LOADED);
+ goog.asserts.assert(tile.interimTile.interimTile === null);
+ tile = tile.interimTile;
+ if (interimTile.getState() == ol.TileState.LOADED) {
+ tile.interimTile = interimTile;
+ }
+ } else {
+ tile = this.createTile_(z, x, y, pixelRatio, projection, paramsKey);
+ if (interimTile.getState() == ol.TileState.LOADED) {
+ tile.interimTile = interimTile;
+ } else if (interimTile.interimTile &&
+ interimTile.interimTile.getState() == ol.TileState.LOADED) {
+ tile.interimTile = interimTile.interimTile;
+ interimTile.interimTile = null;
+ }
+ }
+ if (tile.interimTile) {
+ tile.interimTile.interimTile = null;
+ }
+ this.tileCache.replace(tileCoordKey, tile);
+ }
+ }
+ goog.asserts.assert(tile);
+ return tile;
+};
+
+
+/**
+ * Sets whether to render reprojection edges or not (usually for debugging).
+ * @param {boolean} render Render the edges.
+ * @api
+ */
+ol.source.TileImage.prototype.setRenderReprojectionEdges = function(render) {
+ if (!ol.ENABLE_RASTER_REPROJECTION ||
+ this.renderReprojectionEdges_ == render) {
+ return;
+ }
+ this.renderReprojectionEdges_ = render;
+ goog.object.forEach(this.tileCacheForProjection, function(tileCache) {
+ tileCache.clear();
+ });
+ this.changed();
+};
+
+
+/**
+ * Sets the tile grid to use when reprojecting the tiles to the given
+ * projection instead of the default tile grid for the projection.
+ *
+ * This can be useful when the default tile grid cannot be created
+ * (e.g. projection has no extent defined) or
+ * for optimization reasons (custom tile size, resolutions, ...).
+ *
+ * @param {ol.proj.ProjectionLike} projection Projection.
+ * @param {ol.tilegrid.TileGrid} tilegrid Tile grid to use for the projection.
+ * @api
+ */
+ol.source.TileImage.prototype.setTileGridForProjection =
+ function(projection, tilegrid) {
+ if (ol.ENABLE_RASTER_REPROJECTION) {
+ var proj = ol.proj.get(projection);
+ if (proj) {
+ var projKey = goog.getUid(proj).toString();
+ if (!(projKey in this.tileGridForProjection)) {
+ this.tileGridForProjection[projKey] = tilegrid;
+ }
+ }
+ }
+};
+
+
+/**
+ * @param {ol.ImageTile} imageTile Image tile.
+ * @param {string} src Source.
+ */
+ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) {
+ imageTile.getImage().src = src;
+};
+
+goog.provide('ol.source.BingMaps');
+
+goog.require('goog.Uri');
+goog.require('goog.asserts');
+goog.require('goog.net.Jsonp');
+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');
+
+
+
+/**
+ * @classdesc
+ * Layer source for Bing Maps tile data.
+ *
+ * @constructor
+ * @extends {ol.source.TileImage}
+ * @param {olx.source.BingMapsOptions} options Bing Maps options.
+ * @api stable
+ */
+ol.source.BingMaps = function(options) {
+
+ goog.base(this, {
+ crossOrigin: 'anonymous',
+ opaque: true,
+ projection: ol.proj.get('EPSG:3857'),
+ reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+ state: ol.source.State.LOADING,
+ tileLoadFunction: options.tileLoadFunction,
+ wrapX: options.wrapX !== undefined ? options.wrapX : true
+ });
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.culture_ = options.culture !== undefined ? options.culture : 'en-us';
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.maxZoom_ = options.maxZoom !== undefined ? 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.BingMaps, ol.source.TileImage);
+
+
+/**
+ * The attribution containing a link to the Microsoft® Bing™ Maps Platform APIs’
+ * Terms Of Use.
+ * @const
+ * @type {ol.Attribution}
+ * @api
+ */
+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>'
+});
+
+
+/**
+ * @param {BingMapsImageryMetadataResponse} response Response.
+ */
+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;
+ }
+
+ 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(
+ resource.imageUrlSubdomains.map(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 (!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 = resource.imageryProviders.map(function(imageryProvider) {
+ var html = imageryProvider.attribution;
+ /** @type {Object.<string, Array.<ol.TileRange>>} */
+ var tileRanges = {};
+ imageryProvider.coverageAreas.forEach(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);
+ }
+
+ this.setLogo(brandLogoUri);
+
+ this.setState(ol.source.State.READY);
+
+};
+
+// FIXME keep cluster cache by resolution ?
+// FIXME distance not respected because of the centroid
+
+goog.provide('ol.source.Cluster');
+
+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');
+
+
+
+/**
+ * @classdesc
+ * Layer source to cluster vector data.
+ *
+ * @constructor
+ * @param {olx.source.ClusterOptions} options
+ * @extends {ol.source.Vector}
+ * @api
+ */
+ol.source.Cluster = function(options) {
+ goog.base(this, {
+ attributions: options.attributions,
+ extent: options.extent,
+ logo: options.logo,
+ projection: options.projection,
+ wrapX: options.wrapX
+ });
+
+ /**
+ * @type {number|undefined}
+ * @private
+ */
+ this.resolution_ = undefined;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.distance_ = options.distance !== undefined ? 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);
+};
+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_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.Cluster.prototype.loadFeatures = function(extent, resolution,
+ projection) {
+ this.source_.loadFeatures(extent, resolution, projection);
+ if (resolution !== this.resolution_) {
+ this.clear();
+ this.resolution_ = resolution;
+ this.cluster_();
+ this.addFeatures(this.features_);
+ }
+};
+
+
+/**
+ * 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 (this.resolution_ === undefined) {
+ return;
+ }
+ this.features_.length = 0;
+ var extent = ol.extent.createEmpty();
+ var mapDistance = this.distance_ * this.resolution_;
+ var features = this.source_.getFeatures();
+
+ /**
+ * @type {Object.<string, boolean>}
+ */
+ var clustered = {};
+
+ for (var i = 0, ii = features.length; i < ii; i++) {
+ var feature = features[i];
+ if (!(goog.getUid(feature).toString() in clustered)) {
+ 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 = neighbors.filter(function(neighbor) {
+ var uid = goog.getUid(neighbor).toString();
+ if (!(uid in clustered)) {
+ 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,
+ 'number of clustered equals number of features in the source');
+};
+
+
+/**
+ * @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');
+goog.require('ol.ImageLoadFunctionType');
+goog.require('ol.extent');
+goog.require('ol.source.Image');
+
+
+
+/**
+ * @classdesc
+ * Source for images from Mapguide servers
+ *
+ * @constructor
+ * @fires ol.source.ImageEvent
+ * @extends {ol.source.Image}
+ * @param {olx.source.ImageMapGuideOptions} options Options.
+ * @api stable
+ */
+ol.source.ImageMapGuide = function(options) {
+
+ goog.base(this, {
+ projection: options.projection,
+ resolutions: options.resolutions
+ });
+
+ /**
+ * @private
+ * @type {?string}
+ */
+ this.crossOrigin_ =
+ options.crossOrigin !== undefined ? options.crossOrigin : null;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.displayDpi_ = options.displayDpi !== undefined ?
+ options.displayDpi : 96;
+
+ /**
+ * @private
+ * @type {Object}
+ */
+ this.params_ = options.params !== undefined ? options.params : {};
+
+ /**
+ * @private
+ * @type {string|undefined}
+ */
+ this.url_ = options.url;
+
+ /**
+ * @private
+ * @type {ol.ImageLoadFunctionType}
+ */
+ this.imageLoadFunction_ = options.imageLoadFunction !== undefined ?
+ options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.metersPerUnit_ = options.metersPerUnit !== undefined ?
+ options.metersPerUnit : 1;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.ratio_ = options.ratio !== undefined ? options.ratio : 1;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.useOverlay_ = options.useOverlay !== undefined ?
+ options.useOverlay : false;
+
+ /**
+ * @private
+ * @type {ol.Image}
+ */
+ this.image_ = null;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedRevision_ = 0;
+
+};
+goog.inherits(ol.source.ImageMapGuide, ol.source.Image);
+
+
+/**
+ * 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 stable
+ */
+ol.source.ImageMapGuide.prototype.getParams = function() {
+ return this.params_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.ImageMapGuide.prototype.getImageInternal =
+ function(extent, resolution, pixelRatio, projection) {
+ resolution = this.findNearestResolution(resolution);
+ pixelRatio = this.hidpi_ ? pixelRatio : 1;
+
+ var image = this.image_;
+ if (image &&
+ this.renderedRevision_ == this.getRevision() &&
+ image.getResolution() == resolution &&
+ image.getPixelRatio() == pixelRatio &&
+ ol.extent.containsExtent(image.getExtent(), extent)) {
+ return image;
+ }
+
+ if (this.ratio_ != 1) {
+ 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];
+
+ if (this.url_ !== undefined) {
+ var imageUrl = this.getUrl(this.url_, this.params_, extent, size,
+ projection);
+ 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;
+ }
+ this.image_ = image;
+ this.renderedRevision_ = this.getRevision();
+
+ return image;
+};
+
+
+/**
+ * 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 {number} metersPerUnit The meters-per-unit value.
+ * @param {number} dpi The display resolution.
+ * @return {number} The computed map scale.
+ */
+ol.source.ImageMapGuide.getScale = function(extent, size, metersPerUnit, dpi) {
+ var mcsW = ol.extent.getWidth(extent);
+ var mcsH = ol.extent.getHeight(extent);
+ var devW = size[0];
+ var devH = size[1];
+ var mpp = 0.0254 / dpi;
+ if (devH * mcsW > devW * mcsH) {
+ return mcsW * metersPerUnit / (devW * mpp); // width limited
+ } else {
+ return mcsH * metersPerUnit / (devH * mpp); // height limited
+ }
+};
+
+
+/**
+ * Update the user-provided params.
+ * @param {Object} params Params.
+ * @api stable
+ */
+ol.source.ImageMapGuide.prototype.updateParams = function(params) {
+ goog.object.extend(this.params_, params);
+ this.changed();
+};
+
+
+/**
+ * @param {string} baseUrl The mapagent url.
+ * @param {Object.<string, string|number>} params Request parameters.
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.Size} size Size.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {string} The mapagent map image request URL.
+ */
+ol.source.ImageMapGuide.prototype.getUrl =
+ function(baseUrl, params, extent, size, projection) {
+ var scale = ol.source.ImageMapGuide.getScale(extent, size,
+ this.metersPerUnit_, this.displayDpi_);
+ var center = ol.extent.getCenter(extent);
+ var baseParams = {
+ 'OPERATION': this.useOverlay_ ? 'GETDYNAMICMAPOVERLAYIMAGE' : 'GETMAPIMAGE',
+ 'VERSION': '2.0.0',
+ 'LOCALE': 'en',
+ 'CLIENTAGENT': 'ol.source.ImageMapGuide source',
+ 'CLIP': '1',
+ 'SETDISPLAYDPI': this.displayDpi_,
+ 'SETDISPLAYWIDTH': Math.round(size[0]),
+ 'SETDISPLAYHEIGHT': Math.round(size[1]),
+ 'SETVIEWSCALE': scale,
+ 'SETVIEWCENTERX': center[0],
+ 'SETVIEWCENTERY': center[1]
+ };
+ goog.object.extend(baseParams, params);
+ return goog.uri.utils.appendParamsFromMap(baseUrl, baseParams);
+};
+
+
+/**
+ * Set the image load function of the MapGuide source.
+ * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
+ * @api
+ */
+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.ImageLoadFunctionType');
+goog.require('ol.ImageState');
+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 = options.attributions !== undefined ?
+ options.attributions : null;
+
+ var imageExtent = options.imageExtent;
+
+ var crossOrigin = options.crossOrigin !== undefined ?
+ options.crossOrigin : null;
+
+ var /** @type {ol.ImageLoadFunctionType} */ imageLoadFunction =
+ options.imageLoadFunction !== undefined ?
+ options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
+
+ goog.base(this, {
+ attributions: attributions,
+ logo: options.logo,
+ projection: ol.proj.get(options.projection)
+ });
+
+ /**
+ * @private
+ * @type {ol.Image}
+ */
+ this.image_ = new ol.Image(imageExtent, undefined, 1, attributions,
+ options.url, crossOrigin, imageLoadFunction);
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.imageSize_ = options.imageSize ? options.imageSize : null;
+
+ goog.events.listen(this.image_, goog.events.EventType.CHANGE,
+ this.handleImageChange, false, this);
+
+};
+goog.inherits(ol.source.ImageStatic, ol.source.Image);
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.ImageStatic.prototype.getImageInternal =
+ function(extent, resolution, pixelRatio, projection) {
+ if (ol.extent.intersects(extent, this.image_.getExtent())) {
+ return this.image_;
+ }
+ return null;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.ImageStatic.prototype.handleImageChange = function(evt) {
+ if (this.image_.getState() == ol.ImageState.LOADED) {
+ var imageExtent = this.image_.getExtent();
+ var image = this.image_.getImage();
+ var imageWidth, imageHeight;
+ if (this.imageSize_) {
+ imageWidth = this.imageSize_[0];
+ imageHeight = this.imageSize_[1];
+ } else {
+ imageWidth = image.width;
+ imageHeight = image.height;
+ }
+ var resolution = ol.extent.getHeight(imageExtent) / imageHeight;
+ var targetWidth = Math.ceil(ol.extent.getWidth(imageExtent) / resolution);
+ if (targetWidth != imageWidth) {
+ var canvas = /** @type {HTMLCanvasElement} */
+ (document.createElement('canvas'));
+ canvas.width = targetWidth;
+ canvas.height = /** @type {number} */ (imageHeight);
+ var context = canvas.getContext('2d');
+ context.drawImage(image, 0, 0, imageWidth, imageHeight,
+ 0, 0, canvas.width, canvas.height);
+ this.image_.setImage(canvas);
+ }
+ }
+ goog.base(this, 'handleImageChange', evt);
+};
+
+goog.provide('ol.source.wms');
+goog.provide('ol.source.wms.ServerType');
+
+
+/**
+ * Available server types: `'carmentaserver'`, `'geoserver'`, `'mapserver'`,
+ * `'qgis'`. These are servers that have vendor parameters beyond the WMS
+ * specification that OpenLayers can make use of.
+ * @enum {string}
+ * @api
+ */
+ol.source.wms.ServerType = {
+ CARMENTA_SERVER: 'carmentaserver',
+ GEOSERVER: 'geoserver',
+ MAPSERVER: 'mapserver',
+ QGIS: 'qgis'
+};
+
+// FIXME cannot be shared between maps with different projections
+
+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');
+goog.require('ol');
+goog.require('ol.Image');
+goog.require('ol.ImageLoadFunctionType');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.source.Image');
+goog.require('ol.source.wms');
+goog.require('ol.source.wms.ServerType');
+
+
+
+/**
+ * @classdesc
+ * 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
+ */
+ol.source.ImageWMS = function(opt_options) {
+
+ var options = opt_options || {};
+
+ goog.base(this, {
+ attributions: options.attributions,
+ logo: options.logo,
+ projection: options.projection,
+ resolutions: options.resolutions
+ });
+
+ /**
+ * @private
+ * @type {?string}
+ */
+ this.crossOrigin_ =
+ options.crossOrigin !== undefined ? options.crossOrigin : null;
+
+ /**
+ * @private
+ * @type {string|undefined}
+ */
+ this.url_ = options.url;
+
+ /**
+ * @private
+ * @type {ol.ImageLoadFunctionType}
+ */
+ this.imageLoadFunction_ = options.imageLoadFunction !== undefined ?
+ options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
+
+ /**
+ * @private
+ * @type {Object}
+ */
+ this.params_ = options.params;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.v13_ = true;
+ this.updateV13_();
+
+ /**
+ * @private
+ * @type {ol.source.wms.ServerType|undefined}
+ */
+ this.serverType_ =
+ /** @type {ol.source.wms.ServerType|undefined} */ (options.serverType);
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;
+
+ /**
+ * @private
+ * @type {ol.Image}
+ */
+ this.image_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.imageSize_ = [0, 0];
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.renderedRevision_ = 0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.ratio_ = options.ratio !== undefined ? options.ratio : 1.5;
+
+};
+goog.inherits(ol.source.ImageWMS, ol.source.Image);
+
+
+/**
+ * @const
+ * @type {ol.Size}
+ * @private
+ */
+ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_ = [101, 101];
+
+
+/**
+ * Return the GetFeatureInfo URL for the passed coordinate, resolution, and
+ * projection. Return `undefined` if the GetFeatureInfo URL cannot be
+ * constructed.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} resolution Resolution.
+ * @param {ol.proj.ProjectionLike} projection Projection.
+ * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should
+ * be provided. If `QUERY_LAYERS` is not provided then the layers specified
+ * in the `LAYERS` parameter will be used. `VERSION` should not be
+ * specified here.
+ * @return {string|undefined} GetFeatureInfo URL.
+ * @api stable
+ */
+ol.source.ImageWMS.prototype.getGetFeatureInfoUrl =
+ function(coordinate, resolution, projection, params) {
+
+ goog.asserts.assert(!('VERSION' in params),
+ 'key VERSION is not allowed in params');
+
+ if (this.url_ === undefined) {
+ return undefined;
+ }
+
+ var extent = ol.extent.getForViewAndSize(
+ coordinate, resolution, 0,
+ ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_);
+
+ var baseParams = {
+ 'SERVICE': 'WMS',
+ 'VERSION': ol.DEFAULT_WMS_VERSION,
+ 'REQUEST': 'GetFeatureInfo',
+ 'FORMAT': 'image/png',
+ 'TRANSPARENT': true,
+ '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);
+ baseParams[this.v13_ ? 'I' : 'X'] = x;
+ baseParams[this.v13_ ? 'J' : 'Y'] = y;
+
+ return this.getRequestUrl_(
+ extent, ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_,
+ 1, ol.proj.get(projection), baseParams);
+};
+
+
+/**
+ * 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 stable
+ */
+ol.source.ImageWMS.prototype.getParams = function() {
+ return this.params_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.ImageWMS.prototype.getImageInternal =
+ function(extent, resolution, pixelRatio, projection) {
+
+ if (this.url_ === undefined) {
+ return null;
+ }
+
+ resolution = this.findNearestResolution(resolution);
+
+ if (pixelRatio != 1 && (!this.hidpi_ || this.serverType_ === undefined)) {
+ pixelRatio = 1;
+ }
+
+ extent = extent.slice();
+ var centerX = (extent[0] + extent[2]) / 2;
+ var centerY = (extent[1] + extent[3]) / 2;
+
+ var imageResolution = resolution / pixelRatio;
+ var imageWidth = ol.extent.getWidth(extent) / imageResolution;
+ var imageHeight = ol.extent.getHeight(extent) / imageResolution;
+
+ var image = this.image_;
+ if (image &&
+ this.renderedRevision_ == this.getRevision() &&
+ image.getResolution() == resolution &&
+ image.getPixelRatio() == pixelRatio &&
+ ol.extent.containsExtent(image.getExtent(), extent)) {
+ return image;
+ }
+
+ if (this.ratio_ != 1) {
+ var halfWidth = this.ratio_ * ol.extent.getWidth(extent) / 2;
+ var halfHeight = this.ratio_ * ol.extent.getHeight(extent) / 2;
+ extent[0] = centerX - halfWidth;
+ extent[1] = centerY - halfHeight;
+ extent[2] = centerX + halfWidth;
+ extent[3] = centerY + halfHeight;
+ }
+
+ var params = {
+ 'SERVICE': 'WMS',
+ 'VERSION': ol.DEFAULT_WMS_VERSION,
+ 'REQUEST': 'GetMap',
+ 'FORMAT': 'image/png',
+ 'TRANSPARENT': true
+ };
+ goog.object.extend(params, this.params_);
+
+ this.imageSize_[0] = Math.ceil(imageWidth * this.ratio_);
+ this.imageSize_[1] = Math.ceil(imageHeight * this.ratio_);
+
+ var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio,
+ projection, params);
+
+ this.image_ = new ol.Image(extent, resolution, pixelRatio,
+ this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_);
+
+ 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.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.proj.Projection} projection Projection.
+ * @param {Object} params Params.
+ * @return {string} Request URL.
+ * @private
+ */
+ol.source.ImageWMS.prototype.getRequestUrl_ =
+ function(extent, size, pixelRatio, projection, params) {
+
+ goog.asserts.assert(this.url_ !== undefined, 'url is defined');
+
+ params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();
+
+ if (!('STYLES' in this.params_)) {
+ /* jshint -W053 */
+ params['STYLES'] = new String('');
+ /* jshint +W053 */
+ }
+
+ if (pixelRatio != 1) {
+ switch (this.serverType_) {
+ case ol.source.wms.ServerType.GEOSERVER:
+ var dpi = (90 * pixelRatio + 0.5) | 0;
+ if ('FORMAT_OPTIONS' in params) {
+ params['FORMAT_OPTIONS'] += ';dpi:' + dpi;
+ } else {
+ params['FORMAT_OPTIONS'] = 'dpi:' + dpi;
+ }
+ break;
+ case ol.source.wms.ServerType.MAPSERVER:
+ params['MAP_RESOLUTION'] = 90 * pixelRatio;
+ break;
+ case ol.source.wms.ServerType.CARMENTA_SERVER:
+ case ol.source.wms.ServerType.QGIS:
+ params['DPI'] = 90 * pixelRatio;
+ break;
+ default:
+ goog.asserts.fail('unknown serverType configured');
+ break;
+ }
+ }
+
+ params['WIDTH'] = size[0];
+ params['HEIGHT'] = size[1];
+
+ var axisOrientation = projection.getAxisOrientation();
+ var bbox;
+ if (this.v13_ && axisOrientation.substr(0, 2) == 'ne') {
+ bbox = [extent[1], extent[0], extent[3], extent[2]];
+ } else {
+ bbox = extent;
+ }
+ params['BBOX'] = bbox.join(',');
+
+ return goog.uri.utils.appendParamsFromMap(this.url_, params);
+};
+
+
+/**
+ * Return the URL used for this WMS source.
+ * @return {string|undefined} URL.
+ * @api stable
+ */
+ol.source.ImageWMS.prototype.getUrl = function() {
+ return this.url_;
+};
+
+
+/**
+ * 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
+ */
+ol.source.ImageWMS.prototype.setUrl = function(url) {
+ if (url != this.url_) {
+ this.url_ = url;
+ this.image_ = null;
+ this.changed();
+ }
+};
+
+
+/**
+ * Update the user-provided params.
+ * @param {Object} params Params.
+ * @api stable
+ */
+ol.source.ImageWMS.prototype.updateParams = function(params) {
+ goog.object.extend(this.params_, params);
+ this.updateV13_();
+ this.image_ = null;
+ this.changed();
+};
+
+
+/**
+ * @private
+ */
+ol.source.ImageWMS.prototype.updateV13_ = function() {
+ var version =
+ goog.object.get(this.params_, 'VERSION', ol.DEFAULT_WMS_VERSION);
+ this.v13_ = goog.string.compareVersions(version, '1.3') >= 0;
+};
+
+goog.provide('ol.source.XYZ');
+
+goog.require('ol.source.TileImage');
+
+
+
+/**
+ * @classdesc
+ * 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}
+ * @param {olx.source.XYZOptions} options XYZ options.
+ * @api stable
+ */
+ol.source.XYZ = function(options) {
+ var projection = options.projection !== undefined ?
+ options.projection : 'EPSG:3857';
+
+ var tileGrid = options.tileGrid !== undefined ? options.tileGrid :
+ ol.tilegrid.createXYZ({
+ extent: ol.tilegrid.extentFromProjection(projection),
+ maxZoom: options.maxZoom,
+ tileSize: options.tileSize
+ });
+
+ goog.base(this, {
+ attributions: options.attributions,
+ crossOrigin: options.crossOrigin,
+ logo: options.logo,
+ projection: projection,
+ reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+ tileGrid: tileGrid,
+ tileLoadFunction: options.tileLoadFunction,
+ tilePixelRatio: options.tilePixelRatio,
+ tileUrlFunction: options.tileUrlFunction,
+ url: options.url,
+ urls: options.urls,
+ wrapX: options.wrapX !== undefined ? options.wrapX : true
+ });
+
+};
+goog.inherits(ol.source.XYZ, ol.source.TileImage);
+
+goog.provide('ol.source.OSM');
+
+goog.require('ol.Attribution');
+goog.require('ol.source.XYZ');
+
+
+
+/**
+ * @classdesc
+ * Layer source for the OpenStreetMap tile server.
+ *
+ * @constructor
+ * @extends {ol.source.XYZ}
+ * @param {olx.source.OSMOptions=} opt_options Open Street Map options.
+ * @api stable
+ */
+ol.source.OSM = function(opt_options) {
+
+ var options = opt_options || {};
+
+ var attributions;
+ if (options.attributions !== undefined) {
+ attributions = options.attributions;
+ } else {
+ attributions = [ol.source.OSM.ATTRIBUTION];
+ }
+
+ var crossOrigin = options.crossOrigin !== undefined ?
+ options.crossOrigin : 'anonymous';
+
+ var url = options.url !== undefined ?
+ options.url : 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png';
+
+ goog.base(this, {
+ attributions: attributions,
+ crossOrigin: crossOrigin,
+ opaque: true,
+ maxZoom: options.maxZoom !== undefined ? options.maxZoom : 19,
+ reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+ tileLoadFunction: options.tileLoadFunction,
+ url: url,
+ wrapX: options.wrapX
+ });
+
+};
+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
+ */
+ol.source.OSM.ATTRIBUTION = new ol.Attribution({
+ html: '&copy; ' +
+ '<a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> ' +
+ 'contributors.'
+});
+
+goog.provide('ol.source.MapQuest');
+
+goog.require('goog.asserts');
+goog.require('ol.Attribution');
+goog.require('ol.source.OSM');
+goog.require('ol.source.XYZ');
+
+
+
+/**
+ * @classdesc
+ * Layer source for the MapQuest tile server.
+ *
+ * @constructor
+ * @extends {ol.source.XYZ}
+ * @param {olx.source.MapQuestOptions=} opt_options MapQuest options.
+ * @api stable
+ */
+ol.source.MapQuest = function(opt_options) {
+
+ var options = opt_options || {};
+ goog.asserts.assert(options.layer in ol.source.MapQuestConfig,
+ 'known layer configured');
+
+ var layerConfig = ol.source.MapQuestConfig[options.layer];
+
+ /**
+ * Layer. Possible values are `osm`, `sat`, and `hyb`.
+ * @type {string}
+ * @private
+ */
+ this.layer_ = options.layer;
+
+ var url = options.url !== undefined ? 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: 'https://developer.mapquest.com/content/osm/mq_logo.png',
+ maxZoom: layerConfig.maxZoom,
+ reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+ opaque: true,
+ tileLoadFunction: options.tileLoadFunction,
+ url: url
+ });
+
+};
+goog.inherits(ol.source.MapQuest, ol.source.XYZ);
+
+
+/**
+ * @const
+ * @type {ol.Attribution}
+ */
+ol.source.MapQuest.TILE_ATTRIBUTION = new ol.Attribution({
+ html: 'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a>'
+});
+
+
+/**
+ * @type {Object.<string, {maxZoom: number, attributions: (Array.<ol.Attribution>)}>}
+ */
+ol.source.MapQuestConfig = {
+ 'osm': {
+ maxZoom: 19,
+ attributions: [
+ ol.source.MapQuest.TILE_ATTRIBUTION,
+ ol.source.OSM.ATTRIBUTION
+ ]
+ },
+ 'sat': {
+ maxZoom: 18,
+ attributions: [
+ ol.source.MapQuest.TILE_ATTRIBUTION,
+ new ol.Attribution({
+ html: 'Portions Courtesy NASA/JPL-Caltech and ' +
+ 'U.S. Depart. of Agriculture, Farm Service Agency'
+ })
+ ]
+ },
+ 'hyb': {
+ maxZoom: 18,
+ attributions: [
+ ol.source.MapQuest.TILE_ATTRIBUTION,
+ ol.source.OSM.ATTRIBUTION
+ ]
+ }
+};
+
+
+/**
+ * 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(_dereq_,module,exports){
+var Processor = _dereq_('./processor');
+
+exports.Processor = Processor;
+
+},{"./processor":2}],2:[function(_dereq_,module,exports){
+/* eslint-disable dot-notation */
+
+/**
+ * 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.
+ */
+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 numBuffers = buffers.length;
+ var numBytes = buffers[0].byteLength;
+ var output, b;
+
+ 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();
+};
+
+module.exports = Processor;
+
+},{}]},{},[1])(1)
+});
+ol.ext.pixelworks = module.exports;
+})();
+
+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.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.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 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.Image}
+ * @param {olx.source.RasterOptions} options Options.
+ * @api
+ */
+ol.source.Raster = function(options) {
+
+ /**
+ * @private
+ * @type {*}
+ */
+ this.worker_ = null;
+
+ /**
+ * @private
+ * @type {ol.raster.OperationType}
+ */
+ this.operationType_ = options.operationType !== undefined ?
+ options.operationType : ol.raster.OperationType.PIXEL;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.threads_ = options.threads !== undefined ? 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.canvasContext_ = ol.dom.createCanvasContext2D();
+
+ /**
+ * @private
+ * @type {ol.TileQueue}
+ */
+ this.tileQueue_ = new ol.TileQueue(
+ function() { return 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
+ */
+ this.renderedState_ = null;
+
+ /**
+ * The most recently rendered image canvas.
+ * @type {ol.ImageCanvas}
+ * @private
+ */
+ 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 (options.operation !== undefined) {
+ 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);
+};
+
+
+/**
+ * @inheritDoc
+ */
+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;
+ }
+ }
+ return ready;
+};
+
+
+/**
+ * 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.Raster.prototype.onWorkerComplete_ =
+ function(frameState, callback, err, output, data) {
+ if (err) {
+ callback(err);
+ return;
+ }
+ if (!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);
+};
+
+
+/**
+ * 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.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);
+ }
+};
+
+
+/**
+ * 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_(source);
+ } else if (source instanceof ol.source.Image) {
+ renderer = ol.source.Raster.createImageRenderer_(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.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.Attribution');
+goog.require('ol.source.OSM');
+goog.require('ol.source.XYZ');
+
+
+/**
+ * @type {Object.<string, {extension: string, opaque: boolean}>}
+ */
+ol.source.StamenLayerConfig = {
+ 'terrain': {
+ extension: 'jpg',
+ opaque: true
+ },
+ 'terrain-background': {
+ extension: 'jpg',
+ opaque: true
+ },
+ 'terrain-labels': {
+ extension: 'png',
+ opaque: false
+ },
+ 'terrain-lines': {
+ extension: 'png',
+ opaque: false
+ },
+ 'toner-background': {
+ extension: 'png',
+ opaque: true
+ },
+ 'toner': {
+ extension: 'png',
+ opaque: true
+ },
+ 'toner-hybrid': {
+ extension: 'png',
+ opaque: false
+ },
+ 'toner-labels': {
+ extension: 'png',
+ opaque: false
+ },
+ 'toner-lines': {
+ extension: 'png',
+ opaque: false
+ },
+ 'toner-lite': {
+ extension: 'png',
+ opaque: true
+ },
+ 'watercolor': {
+ extension: 'jpg',
+ opaque: true
+ }
+};
+
+
+/**
+ * @type {Object.<string, {minZoom: number, maxZoom: number}>}
+ */
+ol.source.StamenProviderConfig = {
+ 'terrain': {
+ minZoom: 4,
+ maxZoom: 18
+ },
+ 'toner': {
+ minZoom: 0,
+ maxZoom: 20
+ },
+ 'watercolor': {
+ minZoom: 3,
+ maxZoom: 16
+ }
+};
+
+
+
+/**
+ * @classdesc
+ * Layer source for the Stamen tile server.
+ *
+ * @constructor
+ * @extends {ol.source.XYZ}
+ * @param {olx.source.StamenOptions} options Stamen options.
+ * @api stable
+ */
+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,
+ 'known provider configured');
+ var providerConfig = ol.source.StamenProviderConfig[provider];
+
+ goog.asserts.assert(options.layer in ol.source.StamenLayerConfig,
+ 'known layer configured');
+ var layerConfig = ol.source.StamenLayerConfig[options.layer];
+
+ var url = options.url !== undefined ? options.url :
+ 'https://stamen-tiles-{a-d}.a.ssl.fastly.net/' + options.layer +
+ '/{z}/{x}/{y}.' + layerConfig.extension;
+
+ goog.base(this, {
+ attributions: ol.source.Stamen.ATTRIBUTIONS,
+ crossOrigin: 'anonymous',
+ maxZoom: providerConfig.maxZoom,
+ // FIXME uncomment the following when tilegrid supports minZoom
+ //minZoom: providerConfig.minZoom,
+ opaque: layerConfig.opaque,
+ reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+ tileLoadFunction: options.tileLoadFunction,
+ url: url
+ });
+
+};
+goog.inherits(ol.source.Stamen, ol.source.XYZ);
+
+
+/**
+ * @const
+ * @type {Array.<ol.Attribution>}
+ */
+ol.source.Stamen.ATTRIBUTIONS = [
+ new ol.Attribution({
+ 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>.'
+ }),
+ ol.source.OSM.ATTRIBUTION
+];
+
+goog.provide('ol.source.TileArcGISRest');
+
+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.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 = opt_options || {};
+
+ var params = options.params !== undefined ? options.params : {};
+
+ goog.base(this, {
+ attributions: options.attributions,
+ crossOrigin: options.crossOrigin,
+ logo: options.logo,
+ projection: options.projection,
+ reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+ tileGrid: options.tileGrid,
+ tileLoadFunction: options.tileLoadFunction,
+ tileUrlFunction: goog.bind(this.tileUrlFunction_, this),
+ url: options.url,
+ urls: options.urls,
+ wrapX: options.wrapX !== undefined ? options.wrapX : true
+ });
+
+ /**
+ * @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 (!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(
+ params['DPI'] ? params['DPI'] * pixelRatio : 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);
+ }
+};
+
+
+/**
+ * @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 (!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.TileCoord');
+goog.require('ol.TileState');
+goog.require('ol.dom');
+goog.require('ol.size');
+goog.require('ol.source.Tile');
+goog.require('ol.tilecoord');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.Tile}
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.Size} tileSize Tile size.
+ * @param {string} text Text.
+ * @private
+ */
+ol.DebugTile_ = function(tileCoord, tileSize, text) {
+
+ goog.base(this, tileCoord, ol.TileState.LOADED);
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.tileSize_ = tileSize;
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.text_ = text;
+
+ /**
+ * @private
+ * @type {Object.<number, HTMLCanvasElement>}
+ */
+ this.canvasByContext_ = {};
+
+};
+goog.inherits(ol.DebugTile_, ol.Tile);
+
+
+/**
+ * Get the image element for this tile.
+ * @param {Object=} opt_context Optional context. Only used by the DOM
+ * renderer.
+ * @return {HTMLCanvasElement} Image.
+ */
+ol.DebugTile_.prototype.getImage = function(opt_context) {
+ var key = opt_context !== undefined ? goog.getUid(opt_context) : -1;
+ if (key in this.canvasByContext_) {
+ return this.canvasByContext_[key];
+ } else {
+
+ var tileSize = this.tileSize_;
+ var context = ol.dom.createCanvasContext2D(tileSize[0], tileSize[1]);
+
+ context.strokeStyle = 'black';
+ 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(this.text_, tileSize[0] / 2, tileSize[1] / 2);
+
+ this.canvasByContext_[key] = context.canvas;
+ return context.canvas;
+
+ }
+};
+
+
+
+/**
+ * @classdesc
+ * A pseudo tile source, which does not fetch tiles from a server, but renders
+ * a grid outline for the tile grid/projection along with the coordinates for
+ * each tile. See examples/canvas-tiles for an example.
+ *
+ * Uses Canvas context2d, so requires Canvas support.
+ *
+ * @constructor
+ * @extends {ol.source.Tile}
+ * @param {olx.source.TileDebugOptions} options Debug tile options.
+ * @api
+ */
+ol.source.TileDebug = function(options) {
+
+ goog.base(this, {
+ opaque: false,
+ projection: options.projection,
+ tileGrid: options.tileGrid,
+ wrapX: options.wrapX !== undefined ? options.wrapX : true
+ });
+
+};
+goog.inherits(ol.source.TileDebug, ol.source.Tile);
+
+
+/**
+ * @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));
+ } else {
+ var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z));
+ var tileCoord = [z, x, y];
+ var textTileCoord = this.getTileCoordForTileUrlFunction(tileCoord);
+ var text = !textTileCoord ? '' : ol.tilecoord.toString(
+ this.getTileCoordForTileUrlFunction(textTileCoord));
+ var tile = new ol.DebugTile_(tileCoord, tileSize, text);
+ this.tileCache.set(tileCoordKey, tile);
+ return tile;
+ }
+};
+
+// FIXME check order of async callbacks
+
+/**
+ * @see http://mapbox.com/developers/api/
+ */
+
+goog.provide('ol.source.TileJSON');
+goog.provide('ol.tilejson');
+
+goog.require('goog.asserts');
+goog.require('goog.net.Jsonp');
+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');
+
+
+
+/**
+ * @classdesc
+ * Layer source for tile data in TileJSON format.
+ *
+ * @constructor
+ * @extends {ol.source.TileImage}
+ * @param {olx.source.TileJSONOptions} options TileJSON options.
+ * @api stable
+ */
+ol.source.TileJSON = function(options) {
+
+ goog.base(this, {
+ attributions: options.attributions,
+ crossOrigin: options.crossOrigin,
+ projection: ol.proj.get('EPSG:3857'),
+ reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+ state: ol.source.State.LOADING,
+ tileLoadFunction: options.tileLoadFunction,
+ wrapX: options.wrapX !== undefined ? options.wrapX : true
+ });
+
+ var request = new goog.net.Jsonp(options.url);
+ request.send(undefined, goog.bind(this.handleTileJSONResponse, this),
+ goog.bind(this.handleTileJSONError, this));
+
+};
+goog.inherits(ol.source.TileJSON, ol.source.TileImage);
+
+
+/**
+ * @protected
+ * @param {TileJSON} tileJSON Tile JSON.
+ */
+ol.source.TileJSON.prototype.handleTileJSONResponse = function(tileJSON) {
+
+ var epsg4326Projection = ol.proj.get('EPSG:4326');
+
+ var sourceProjection = this.getProjection();
+ var extent;
+ if (tileJSON.bounds !== undefined) {
+ var transform = ol.proj.getTransformFromProjections(
+ epsg4326Projection, sourceProjection);
+ extent = ol.extent.applyTransform(tileJSON.bounds, transform);
+ }
+
+ if (tileJSON.scheme !== undefined) {
+ goog.asserts.assert(tileJSON.scheme == 'xyz', 'tileJSON-scheme is "xyz"');
+ }
+ var minZoom = tileJSON.minzoom || 0;
+ var maxZoom = tileJSON.maxzoom || 22;
+ var tileGrid = ol.tilegrid.createXYZ({
+ extent: ol.tilegrid.extentFromProjection(sourceProjection),
+ maxZoom: maxZoom,
+ minZoom: minZoom
+ });
+ this.tileGrid = tileGrid;
+
+ this.tileUrlFunction =
+ ol.TileUrlFunction.createFromTemplates(tileJSON.tiles, tileGrid);
+
+ if (tileJSON.attribution !== undefined && !this.getAttributions()) {
+ var attributionExtent = extent !== undefined ?
+ extent : epsg4326Projection.getExtent();
+ /** @type {Object.<string, Array.<ol.TileRange>>} */
+ var tileRanges = {};
+ var z, zKey;
+ for (z = minZoom; z <= maxZoom; ++z) {
+ zKey = z.toString();
+ tileRanges[zKey] =
+ [tileGrid.getTileRangeForExtentAndZ(attributionExtent, z)];
+ }
+ this.setAttributions([
+ new ol.Attribution({
+ html: tileJSON.attribution,
+ tileRanges: tileRanges
+ })
+ ]);
+ }
+
+ this.setState(ol.source.State.READY);
+
+};
+
+
+/**
+ * @protected
+ */
+ol.source.TileJSON.prototype.handleTileJSONError = function() {
+ this.setState(ol.source.State.ERROR);
+};
+
+goog.provide('ol.source.TileUTFGrid');
+
+goog.require('goog.asserts');
+goog.require('goog.async.nextTick');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.net.Jsonp');
+goog.require('ol.Attribution');
+goog.require('ol.Tile');
+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');
+
+
+
+/**
+ * @classdesc
+ * Layer source for UTFGrid interaction data loaded from TileJSON format.
+ *
+ * @constructor
+ * @extends {ol.source.Tile}
+ * @param {olx.source.TileUTFGridOptions} options Source options.
+ * @api
+ */
+ol.source.TileUTFGrid = function(options) {
+ goog.base(this, {
+ projection: ol.proj.get('EPSG:3857'),
+ state: ol.source.State.LOADING
+ });
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.preemptive_ = options.preemptive !== undefined ?
+ options.preemptive : true;
+
+ /**
+ * @private
+ * @type {!ol.TileUrlFunctionType}
+ */
+ this.tileUrlFunction_ = ol.TileUrlFunction.nullTileUrlFunction;
+
+ /**
+ * @private
+ * @type {string|undefined}
+ */
+ this.template_ = undefined;
+
+ var request = new goog.net.Jsonp(options.url);
+ request.send(undefined, goog.bind(this.handleTileJSONResponse, this));
+};
+goog.inherits(ol.source.TileUTFGrid, ol.source.Tile);
+
+
+/**
+ * Return the template from TileJSON.
+ * @return {string|undefined} The template from TileJSON.
+ * @api
+ */
+ol.source.TileUTFGrid.prototype.getTemplate = function() {
+ return this.template_;
+};
+
+
+/**
+ * Calls the callback (synchronously by default) with the available data
+ * for given coordinate and resolution (or `null` if not yet loaded or
+ * in case of an error).
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} resolution Resolution.
+ * @param {function(this: T, Object)} callback Callback.
+ * @param {T=} opt_this The object to use as `this` in the callback.
+ * @param {boolean=} opt_request If `true` the callback is always async.
+ * The tile data is requested if not yet loaded.
+ * @template T
+ * @api
+ */
+ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution = function(
+ coordinate, resolution, callback, opt_this, opt_request) {
+ if (this.tileGrid) {
+ var tileCoord = this.tileGrid.getTileCoordForCoordAndResolution(
+ coordinate, resolution);
+ var tile = /** @type {!ol.source.TileUTFGridTile_} */(this.getTile(
+ tileCoord[0], tileCoord[1], tileCoord[2], 1, this.getProjection()));
+ tile.forDataAtCoordinate(coordinate, callback, opt_this, opt_request);
+ } else {
+ if (opt_request === true) {
+ goog.async.nextTick(function() {
+ callback.call(opt_this, null);
+ });
+ } else {
+ callback.call(opt_this, null);
+ }
+ }
+};
+
+
+/**
+ * TODO: very similar to ol.source.TileJSON#handleTileJSONResponse
+ * @protected
+ * @param {TileJSON} tileJSON Tile JSON.
+ */
+ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) {
+
+ var epsg4326Projection = ol.proj.get('EPSG:4326');
+
+ var sourceProjection = this.getProjection();
+ var extent;
+ if (tileJSON.bounds !== undefined) {
+ var transform = ol.proj.getTransformFromProjections(
+ epsg4326Projection, sourceProjection);
+ extent = ol.extent.applyTransform(tileJSON.bounds, transform);
+ }
+
+ if (tileJSON.scheme !== undefined) {
+ goog.asserts.assert(tileJSON.scheme == 'xyz', 'tileJSON-scheme is "xyz"');
+ }
+ var minZoom = tileJSON.minzoom || 0;
+ var maxZoom = tileJSON.maxzoom || 22;
+ var tileGrid = ol.tilegrid.createXYZ({
+ extent: ol.tilegrid.extentFromProjection(sourceProjection),
+ maxZoom: maxZoom,
+ minZoom: minZoom
+ });
+ this.tileGrid = tileGrid;
+
+ this.template_ = tileJSON.template;
+
+ var grids = tileJSON.grids;
+ if (!grids) {
+ this.setState(ol.source.State.ERROR);
+ return;
+ }
+
+ this.tileUrlFunction_ =
+ ol.TileUrlFunction.createFromTemplates(grids, tileGrid);
+
+ if (tileJSON.attribution !== undefined) {
+ var attributionExtent = extent !== undefined ?
+ extent : epsg4326Projection.getExtent();
+ /** @type {Object.<string, Array.<ol.TileRange>>} */
+ var tileRanges = {};
+ var z, zKey;
+ for (z = minZoom; z <= maxZoom; ++z) {
+ zKey = z.toString();
+ tileRanges[zKey] =
+ [tileGrid.getTileRangeForExtentAndZ(attributionExtent, z)];
+ }
+ this.setAttributions([
+ new ol.Attribution({
+ html: tileJSON.attribution,
+ tileRanges: tileRanges
+ })
+ ]);
+ }
+
+ this.setState(ol.source.State.READY);
+
+};
+
+
+/**
+ * @inheritDoc
+ */
+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));
+ } else {
+ goog.asserts.assert(projection, 'argument projection is truthy');
+ var tileCoord = [z, x, y];
+ var urlTileCoord =
+ this.getTileCoordForTileUrlFunction(tileCoord, projection);
+ var tileUrl = this.tileUrlFunction_(urlTileCoord, pixelRatio, projection);
+ var tile = new ol.source.TileUTFGridTile_(
+ tileCoord,
+ tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY,
+ tileUrl !== undefined ? tileUrl : '',
+ this.tileGrid.getTileCoordExtent(tileCoord),
+ this.preemptive_);
+ this.tileCache.set(tileCoordKey, tile);
+ return tile;
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+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);
+ }
+};
+
+
+
+/**
+ * @constructor
+ * @extends {ol.Tile}
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.TileState} state State.
+ * @param {string} src Image source URI.
+ * @param {ol.Extent} extent Extent of the tile.
+ * @param {boolean} preemptive Load the tile when visible (before it's needed).
+ * @private
+ */
+ol.source.TileUTFGridTile_ =
+ function(tileCoord, state, src, extent, preemptive) {
+
+ goog.base(this, tileCoord, state);
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.src_ = src;
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.extent_ = extent;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.preemptive_ = preemptive;
+
+ /**
+ * @private
+ * @type {Array.<string>}
+ */
+ this.grid_ = null;
+
+ /**
+ * @private
+ * @type {Array.<string>}
+ */
+ this.keys_ = null;
+
+ /**
+ * @private
+ * @type {Object.<string, Object>|undefined}
+ */
+ this.data_ = null;
+};
+goog.inherits(ol.source.TileUTFGridTile_, ol.Tile);
+
+
+/**
+ * Get the image element for this tile.
+ * @param {Object=} opt_context Optional context. Only used for the DOM
+ * renderer.
+ * @return {Image} Image.
+ */
+ol.source.TileUTFGridTile_.prototype.getImage = function(opt_context) {
+ return null;
+};
+
+
+/**
+ * Synchronously returns data at given coordinate (if available).
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {Object}
+ */
+ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) {
+ if (!this.grid_ || !this.keys_ || !this.data_) {
+ return null;
+ }
+ var xRelative = (coordinate[0] - this.extent_[0]) /
+ (this.extent_[2] - this.extent_[0]);
+ var yRelative = (coordinate[1] - this.extent_[1]) /
+ (this.extent_[3] - this.extent_[1]);
+
+ var row = this.grid_[Math.floor((1 - yRelative) * this.grid_.length)];
+
+ if (!goog.isString(row)) {
+ return null;
+ }
+
+ var code = row.charCodeAt(Math.floor(xRelative * row.length));
+ if (code >= 93) {
+ code--;
+ }
+ if (code >= 35) {
+ code--;
+ }
+ code -= 32;
+
+ return (code in this.keys_) ? this.data_[this.keys_[code]] : null;
+};
+
+
+/**
+ * Calls the callback (synchronously by default) with the available data
+ * for given coordinate (or `null` if not yet loaded).
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {function(this: T, Object)} callback Callback.
+ * @param {T=} opt_this The object to use as `this` in the callback.
+ * @param {boolean=} opt_request If `true` the callback is always async.
+ * The tile data is requested if not yet loaded.
+ * @template T
+ */
+ol.source.TileUTFGridTile_.prototype.forDataAtCoordinate =
+ function(coordinate, callback, opt_this, opt_request) {
+ if (this.state == ol.TileState.IDLE && opt_request === true) {
+ goog.events.listenOnce(this, goog.events.EventType.CHANGE, function(e) {
+ callback.call(opt_this, this.getData(coordinate));
+ }, false, this);
+ this.loadInternal_();
+ } else {
+ if (opt_request === true) {
+ goog.async.nextTick(function() {
+ callback.call(opt_this, this.getData(coordinate));
+ }, this);
+ } else {
+ callback.call(opt_this, this.getData(coordinate));
+ }
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.TileUTFGridTile_.prototype.getKey = function() {
+ return this.src_;
+};
+
+
+/**
+ * @private
+ */
+ol.source.TileUTFGridTile_.prototype.handleError_ = function() {
+ this.state = ol.TileState.ERROR;
+ this.changed();
+};
+
+
+/**
+ * @param {!UTFGridJSON} json
+ * @private
+ */
+ol.source.TileUTFGridTile_.prototype.handleLoad_ = function(json) {
+ this.grid_ = json.grid;
+ this.keys_ = json.keys;
+ this.data_ = json.data;
+
+ this.state = ol.TileState.EMPTY;
+ this.changed();
+};
+
+
+/**
+ * @private
+ */
+ol.source.TileUTFGridTile_.prototype.loadInternal_ = function() {
+ if (this.state == ol.TileState.IDLE) {
+ this.state = ol.TileState.LOADING;
+ var request = new goog.net.Jsonp(this.src_);
+ request.send(undefined, goog.bind(this.handleLoad_, this),
+ goog.bind(this.handleError_, this));
+ }
+};
+
+
+/**
+ * Load not yet loaded URI.
+ */
+ol.source.TileUTFGridTile_.prototype.load = function() {
+ if (this.preemptive_) {
+ this.loadInternal_();
+ }
+};
+
+// FIXME add minZoom support
+// FIXME add date line wrap (tile coord transform)
+// FIXME cannot be shared between maps with different projections
+
+goog.provide('ol.source.TileWMS');
+
+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.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');
+goog.require('ol.tilecoord');
+
+
+
+/**
+ * @classdesc
+ * Layer source for tile data from WMS servers.
+ *
+ * @constructor
+ * @extends {ol.source.TileImage}
+ * @param {olx.source.TileWMSOptions=} opt_options Tile WMS options.
+ * @api stable
+ */
+ol.source.TileWMS = function(opt_options) {
+
+ var options = opt_options || {};
+
+ var params = options.params !== undefined ? options.params : {};
+
+ var transparent = goog.object.get(params, 'TRANSPARENT', true);
+
+ goog.base(this, {
+ attributions: options.attributions,
+ crossOrigin: options.crossOrigin,
+ logo: options.logo,
+ opaque: !transparent,
+ projection: options.projection,
+ reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+ tileGrid: options.tileGrid,
+ tileLoadFunction: options.tileLoadFunction,
+ tileUrlFunction: goog.bind(this.tileUrlFunction_, this),
+ url: options.url,
+ urls: options.urls,
+ wrapX: options.wrapX !== undefined ? options.wrapX : true
+ });
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.gutter_ = options.gutter !== undefined ? options.gutter : 0;
+
+ /**
+ * @private
+ * @type {Object}
+ */
+ this.params_ = params;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.v13_ = true;
+
+ /**
+ * @private
+ * @type {ol.source.wms.ServerType|undefined}
+ */
+ this.serverType_ =
+ /** @type {ol.source.wms.ServerType|undefined} */ (options.serverType);
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.coordKeyPrefix_ = '';
+ this.resetCoordKeyPrefix_();
+
+ /**
+ * @private
+ * @type {ol.Extent}
+ */
+ this.tmpExtent_ = ol.extent.createEmpty();
+
+ this.updateV13_();
+
+};
+goog.inherits(ol.source.TileWMS, ol.source.TileImage);
+
+
+/**
+ * Return the GetFeatureInfo URL for the passed coordinate, resolution, and
+ * projection. Return `undefined` if the GetFeatureInfo URL cannot be
+ * constructed.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} resolution Resolution.
+ * @param {ol.proj.ProjectionLike} projection Projection.
+ * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should
+ * be provided. If `QUERY_LAYERS` is not provided then the layers specified
+ * in the `LAYERS` parameter will be used. `VERSION` should not be
+ * specified here.
+ * @return {string|undefined} GetFeatureInfo URL.
+ * @api stable
+ */
+ol.source.TileWMS.prototype.getGetFeatureInfoUrl =
+ function(coordinate, resolution, projection, params) {
+
+ goog.asserts.assert(!('VERSION' in params),
+ 'key VERSION is not allowed in params');
+
+ var projectionObj = ol.proj.get(projection);
+
+ var tileGrid = this.getTileGrid();
+ if (!tileGrid) {
+ tileGrid = this.getTileGridForProjection(projectionObj);
+ }
+
+ var tileCoord = tileGrid.getTileCoordForCoordAndResolution(
+ coordinate, resolution);
+
+ if (tileGrid.getResolutions().length <= tileCoord[0]) {
+ return undefined;
+ }
+
+ var tileResolution = tileGrid.getResolution(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 = ol.size.buffer(tileSize, gutter, this.tmpSize);
+ tileExtent = ol.extent.buffer(tileExtent,
+ tileResolution * gutter, tileExtent);
+ }
+
+ var baseParams = {
+ 'SERVICE': 'WMS',
+ 'VERSION': ol.DEFAULT_WMS_VERSION,
+ 'REQUEST': 'GetFeatureInfo',
+ 'FORMAT': 'image/png',
+ 'TRANSPARENT': true,
+ '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);
+
+ baseParams[this.v13_ ? 'I' : 'X'] = x;
+ baseParams[this.v13_ ? 'J' : 'Y'] = y;
+
+ return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
+ 1, projectionObj, baseParams);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.TileWMS.prototype.getGutter = function() {
+ return this.gutter_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.TileWMS.prototype.getKeyZXY = function(z, x, y) {
+ return this.coordKeyPrefix_ + goog.base(this, 'getKeyZXY', z, x, y);
+};
+
+
+/**
+ * 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 stable
+ */
+ol.source.TileWMS.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.TileWMS.prototype.getRequestUrl_ =
+ function(tileCoord, tileSize, tileExtent,
+ pixelRatio, projection, params) {
+
+ var urls = this.urls;
+ if (!urls) {
+ return undefined;
+ }
+
+ params['WIDTH'] = tileSize[0];
+ params['HEIGHT'] = tileSize[1];
+
+ params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();
+
+ if (!('STYLES' in this.params_)) {
+ /* jshint -W053 */
+ params['STYLES'] = new String('');
+ /* jshint +W053 */
+ }
+
+ if (pixelRatio != 1) {
+ switch (this.serverType_) {
+ case ol.source.wms.ServerType.GEOSERVER:
+ var dpi = (90 * pixelRatio + 0.5) | 0;
+ if ('FORMAT_OPTIONS' in params) {
+ params['FORMAT_OPTIONS'] += ';dpi:' + dpi;
+ } else {
+ params['FORMAT_OPTIONS'] = 'dpi:' + dpi;
+ }
+ break;
+ case ol.source.wms.ServerType.MAPSERVER:
+ params['MAP_RESOLUTION'] = 90 * pixelRatio;
+ break;
+ case ol.source.wms.ServerType.CARMENTA_SERVER:
+ case ol.source.wms.ServerType.QGIS:
+ params['DPI'] = 90 * pixelRatio;
+ break;
+ default:
+ goog.asserts.fail('unknown serverType configured');
+ break;
+ }
+ }
+
+ var axisOrientation = projection.getAxisOrientation();
+ var bbox = tileExtent;
+ if (this.v13_ && axisOrientation.substr(0, 2) == 'ne') {
+ var tmp;
+ tmp = tileExtent[0];
+ bbox[0] = tileExtent[1];
+ bbox[1] = tmp;
+ tmp = tileExtent[2];
+ bbox[2] = tileExtent[3];
+ bbox[3] = tmp;
+ }
+ params['BBOX'] = bbox.join(',');
+
+ var url;
+ if (urls.length == 1) {
+ url = urls[0];
+ } else {
+ var index = goog.math.modulo(ol.tilecoord.hash(tileCoord), urls.length);
+ url = urls[index];
+ }
+ 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.TileWMS.prototype.getTilePixelSize =
+ function(z, pixelRatio, projection) {
+ var tileSize = goog.base(this, 'getTilePixelSize', z, pixelRatio, projection);
+ if (pixelRatio == 1 || !this.hidpi_ || this.serverType_ === undefined) {
+ return tileSize;
+ } else {
+ return ol.size.scale(tileSize, pixelRatio, this.tmpSize);
+ }
+};
+
+
+/**
+ * @private
+ */
+ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() {
+ var i = 0;
+ var res = [];
+
+ if (this.urls) {
+ var j, jj;
+ for (j = 0, jj = this.urls.length; j < jj; ++j) {
+ res[i++] = this.urls[j];
+ }
+ }
+
+ var key;
+ for (key in this.params_) {
+ res[i++] = key + '-' + this.params_[key];
+ }
+
+ this.coordKeyPrefix_ = res.join('#');
+};
+
+
+/**
+ * @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.TileWMS.prototype.tileUrlFunction_ =
+ function(tileCoord, pixelRatio, projection) {
+
+ var tileGrid = this.getTileGrid();
+ if (!tileGrid) {
+ tileGrid = this.getTileGridForProjection(projection);
+ }
+
+ if (tileGrid.getResolutions().length <= tileCoord[0]) {
+ return undefined;
+ }
+
+ if (pixelRatio != 1 && (!this.hidpi_ || this.serverType_ === undefined)) {
+ pixelRatio = 1;
+ }
+
+ var tileResolution = tileGrid.getResolution(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 = ol.size.buffer(tileSize, gutter, this.tmpSize);
+ tileExtent = ol.extent.buffer(tileExtent,
+ tileResolution * gutter, tileExtent);
+ }
+
+ if (pixelRatio != 1) {
+ tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize);
+ }
+
+ var baseParams = {
+ 'SERVICE': 'WMS',
+ 'VERSION': ol.DEFAULT_WMS_VERSION,
+ 'REQUEST': 'GetMap',
+ 'FORMAT': 'image/png',
+ '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.TileWMS.prototype.updateParams = function(params) {
+ goog.object.extend(this.params_, params);
+ this.resetCoordKeyPrefix_();
+ this.updateV13_();
+ this.changed();
+};
+
+
+/**
+ * @private
+ */
+ol.source.TileWMS.prototype.updateV13_ = function() {
+ var version =
+ goog.object.get(this.params_, 'VERSION', ol.DEFAULT_WMS_VERSION);
+ this.v13_ = goog.string.compareVersions(version, '1.3') >= 0;
+};
+
+goog.provide('ol.tilegrid.WMTS');
+
+goog.require('goog.asserts');
+goog.require('ol.proj');
+goog.require('ol.tilegrid.TileGrid');
+
+
+
+/**
+ * @classdesc
+ * Set the grid pattern for sources accessing WMTS tiled-image servers.
+ *
+ * @constructor
+ * @extends {ol.tilegrid.TileGrid}
+ * @param {olx.tilegrid.WMTSOptions} options WMTS options.
+ * @struct
+ * @api
+ */
+ol.tilegrid.WMTS = function(options) {
+
+ goog.asserts.assert(
+ options.resolutions.length == options.matrixIds.length,
+ 'options resolutions and matrixIds must have equal length (%s == %s)',
+ options.resolutions.length, options.matrixIds.length);
+
+ /**
+ * @private
+ * @type {!Array.<string>}
+ */
+ this.matrixIds_ = options.matrixIds;
+ // 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,
+ sizes: options.sizes
+ });
+
+};
+goog.inherits(ol.tilegrid.WMTS, ol.tilegrid.TileGrid);
+
+
+/**
+ * @param {number} z Z.
+ * @return {string} MatrixId..
+ */
+ol.tilegrid.WMTS.prototype.getMatrixId = function(z) {
+ 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
+ */
+ol.tilegrid.WMTS.prototype.getMatrixIds = function() {
+ return this.matrixIds_;
+};
+
+
+/**
+ * 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, opt_extent) {
+
+ /** @type {!Array.<number>} */
+ var resolutions = [];
+ /** @type {!Array.<string>} */
+ var matrixIds = [];
+ /** @type {!Array.<ol.Coordinate>} */
+ var origins = [];
+ /** @type {!Array.<ol.Size>} */
+ var tileSizes = [];
+ /** @type {!Array.<ol.Size>} */
+ var sizes = [];
+
+ var supportedCRSPropName = 'SupportedCRS';
+ var matrixIdsPropName = 'TileMatrix';
+ var identifierPropName = 'Identifier';
+ var scaleDenominatorPropName = 'ScaleDenominator';
+ var topLeftCornerPropName = 'TopLeftCorner';
+ var tileWidthPropName = 'TileWidth';
+ var tileHeightPropName = 'TileHeight';
+
+ 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';
+
+ matrixSet[matrixIdsPropName].sort(function(a, b) {
+ return b[scaleDenominatorPropName] - a[scaleDenominatorPropName];
+ });
+
+ matrixSet[matrixIdsPropName].forEach(function(elt, index, array) {
+ matrixIds.push(elt[identifierPropName]);
+ var resolution = elt[scaleDenominatorPropName] * 0.28E-3 / metersPerUnit;
+ var tileWidth = elt[tileWidthPropName];
+ var tileHeight = elt[tileHeightPropName];
+ 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,
+ sizes: sizes
+ });
+};
+
+goog.provide('ol.source.WMTS');
+goog.provide('ol.source.WMTSRequestEncoding');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.object');
+goog.require('goog.uri.utils');
+goog.require('ol.TileUrlFunction');
+goog.require('ol.TileUrlFunctionType');
+goog.require('ol.array');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.source.TileImage');
+goog.require('ol.tilegrid.WMTS');
+
+
+/**
+ * Request encoding. One of 'KVP', 'REST'.
+ * @enum {string}
+ * @api
+ */
+ol.source.WMTSRequestEncoding = {
+ KVP: 'KVP', // see spec §8
+ REST: 'REST' // see spec §10
+};
+
+
+
+/**
+ * @classdesc
+ * Layer source for tile data from WMTS servers.
+ *
+ * @constructor
+ * @extends {ol.source.TileImage}
+ * @param {olx.source.WMTSOptions} options WMTS options.
+ * @api stable
+ */
+ol.source.WMTS = function(options) {
+
+ // TODO: add support for TileMatrixLimits
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.version_ = options.version !== undefined ? options.version : '1.0.0';
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.format_ = options.format !== undefined ? options.format : 'image/jpeg';
+
+ /**
+ * @private
+ * @type {!Object}
+ */
+ this.dimensions_ = options.dimensions !== undefined ? options.dimensions : {};
+
+ /**
+ * @private
+ * @type {string}
+ */
+ this.dimensionsKey_ = '';
+ this.resetDimensionsKey_();
+
+ /**
+ * @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 (urls === undefined && options.url !== undefined) {
+ urls = ol.TileUrlFunction.expandUrl(options.url);
+ }
+
+ // FIXME: should we guess this requestEncoding from options.url(s)
+ // structure? that would mean KVP only if a template is not provided.
+
+ /**
+ * @private
+ * @type {ol.source.WMTSRequestEncoding}
+ */
+ this.requestEncoding_ = options.requestEncoding !== undefined ?
+ /** @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': this.layer_,
+ 'style': this.style_,
+ 'tilematrixset': this.matrixSet_
+ };
+
+ if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
+ goog.object.extend(context, {
+ 'Service': 'WMTS',
+ 'Request': 'GetTile',
+ 'Version': this.version_,
+ 'Format': this.format_
+ });
+ }
+
+ var dimensions = this.dimensions_;
+
+ /**
+ * @param {string} template Template.
+ * @return {ol.TileUrlFunctionType} Tile URL function.
+ */
+ function createFromWMTSTemplate(template) {
+
+ // TODO: we may want to create our own appendParams function so that params
+ // order conforms to wmts spec guidance, and so that we can avoid to escape
+ // special template params
+
+ template = (requestEncoding == ol.source.WMTSRequestEncoding.KVP) ?
+ goog.uri.utils.appendParamsFromMap(template, context) :
+ template.replace(/\{(\w+?)\}/g, function(m, p) {
+ return (p.toLowerCase() in context) ? context[p.toLowerCase()] : m;
+ });
+
+ 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 (!tileCoord) {
+ return undefined;
+ } else {
+ var localContext = {
+ 'TileMatrix': tileGrid.getMatrixId(tileCoord[0]),
+ 'TileCol': tileCoord[1],
+ 'TileRow': -tileCoord[2] - 1
+ };
+ goog.object.extend(localContext, dimensions);
+ var url = template;
+ if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
+ url = goog.uri.utils.appendParamsFromMap(url, localContext);
+ } else {
+ url = url.replace(/\{(\w+?)\}/g, function(m, p) {
+ return localContext[p];
+ });
+ }
+ return url;
+ }
+ });
+ }
+
+ var tileUrlFunction = (urls && urls.length > 0) ?
+ ol.TileUrlFunction.createFromTileUrlFunctions(
+ urls.map(createFromWMTSTemplate)) :
+ ol.TileUrlFunction.nullTileUrlFunction;
+
+ goog.base(this, {
+ attributions: options.attributions,
+ crossOrigin: options.crossOrigin,
+ logo: options.logo,
+ projection: options.projection,
+ reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+ tileClass: options.tileClass,
+ tileGrid: tileGrid,
+ tileLoadFunction: options.tileLoadFunction,
+ tilePixelRatio: options.tilePixelRatio,
+ tileUrlFunction: tileUrlFunction,
+ urls: urls,
+ wrapX: options.wrapX !== undefined ? options.wrapX : false
+ });
+
+};
+goog.inherits(ol.source.WMTS, ol.source.TileImage);
+
+
+/**
+ * Get the dimensions, i.e. those passed to the constructor through the
+ * "dimensions" option, and possibly updated using the updateDimensions
+ * method.
+ * @return {!Object} Dimensions.
+ * @api
+ */
+ol.source.WMTS.prototype.getDimensions = function() {
+ return this.dimensions_;
+};
+
+
+/**
+ * Return the image format of the WMTS source.
+ * @return {string} Format.
+ * @api
+ */
+ol.source.WMTS.prototype.getFormat = function() {
+ return this.format_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.WMTS.prototype.getKeyParams = function() {
+ return this.dimensionsKey_;
+};
+
+
+/**
+ * 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 version of the WMTS source.
+ * @return {string} Version.
+ * @api
+ */
+ol.source.WMTS.prototype.getVersion = function() {
+ return this.version_;
+};
+
+
+/**
+ * @private
+ */
+ol.source.WMTS.prototype.resetDimensionsKey_ = function() {
+ var i = 0;
+ var res = [];
+ for (var key in this.dimensions_) {
+ res[i++] = key + '-' + this.dimensions_[key];
+ }
+ this.dimensionsKey_ = res.join('/');
+};
+
+
+/**
+ * Update the dimensions.
+ * @param {Object} dimensions Dimensions.
+ * @api
+ */
+ol.source.WMTS.prototype.updateDimensions = function(dimensions) {
+ goog.object.extend(this.dimensions_, dimensions);
+ this.resetDimensionsKey_();
+ this.changed();
+};
+
+
+/**
+ * Generate source options from a capabilities object.
+ * @param {Object} wmtsCap An object representing the capabilities document.
+ * @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, config) {
+
+ // TODO: add support for TileMatrixLimits
+ goog.asserts.assert(config['layer'],
+ 'config "layer" must not be null');
+
+ var layers = wmtsCap['Contents']['Layer'];
+ var l = goog.array.find(layers, function(elt, index, array) {
+ return elt['Identifier'] == config['layer'];
+ });
+ goog.asserts.assert(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 ('projection' in config) {
+ 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;
+ }
+ matrixSet = /** @type {string} */
+ (l['TileMatrixSetLink'][idx]['TileMatrixSet']);
+
+ goog.asserts.assert(matrixSet, 'TileMatrixSet must not be null');
+
+ var format = /** @type {string} */ (l['Format'][0]);
+ if ('format' in config) {
+ format = config['format'];
+ }
+ idx = goog.array.findIndex(l['Style'], function(elt, index, array) {
+ if ('style' in config) {
+ return elt['Title'] == config['style'];
+ } else {
+ return elt['isDefault'];
+ }
+ });
+ if (idx < 0) {
+ idx = 0;
+ }
+ var style = /** @type {string} */ (l['Style'][idx]['Identifier']);
+
+ var dimensions = {};
+ if ('Dimension' in l) {
+ l['Dimension'].forEach(function(elt, index, array) {
+ var key = elt['Identifier'];
+ var value = elt['Default'];
+ if (value !== undefined) {
+ goog.asserts.assert(ol.array.includes(elt['Value'], value),
+ 'default value contained in values');
+ } else {
+ value = elt['Value'][0];
+ }
+ goog.asserts.assert(value !== undefined, '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(matrixSetObj,
+ 'found matrixSet in Contents/TileMatrixSet');
+
+ var projection;
+ if ('projection' in config) {
+ 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 (wgs84BoundingBox !== undefined) {
+ 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 (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, extent);
+
+ /** @type {!Array.<string>} */
+ var urls = [];
+ var requestEncoding = config['requestEncoding'];
+ requestEncoding = requestEncoding !== undefined ? requestEncoding : '';
+
+ goog.asserts.assert(
+ ol.array.includes(['REST', 'RESTful', 'KVP', ''], requestEncoding),
+ 'requestEncoding (%s) is one of "REST", "RESTful", "KVP" or ""',
+ requestEncoding);
+
+ if (!wmtsCap.hasOwnProperty('OperationsMetadata') ||
+ !wmtsCap['OperationsMetadata'].hasOwnProperty('GetTile') ||
+ requestEncoding.indexOf('REST') === 0) {
+ // Add REST tile resource url
+ requestEncoding = ol.source.WMTSRequestEncoding.REST;
+ l['ResourceURL'].forEach(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 && ol.array.includes(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: config['layer'],
+ matrixSet: matrixSet,
+ format: format,
+ projection: projection,
+ requestEncoding: requestEncoding,
+ tileGrid: tileGrid,
+ style: style,
+ dimensions: dimensions,
+ wrapX: wrapX
+ };
+
+};
+
+goog.provide('ol.source.Zoomify');
+
+goog.require('goog.asserts');
+goog.require('ol');
+goog.require('ol.ImageTile');
+goog.require('ol.TileCoord');
+goog.require('ol.TileState');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.source.TileImage');
+goog.require('ol.tilegrid.TileGrid');
+
+
+/**
+ * @enum {string}
+ */
+ol.source.ZoomifyTierSizeCalculation = {
+ DEFAULT: 'default',
+ TRUNCATED: 'truncated'
+};
+
+
+
+/**
+ * @classdesc
+ * Layer source for tile data in Zoomify format.
+ *
+ * @constructor
+ * @extends {ol.source.TileImage}
+ * @param {olx.source.ZoomifyOptions=} opt_options Options.
+ * @api stable
+ */
+ol.source.Zoomify = function(opt_options) {
+
+ var options = opt_options || {};
+
+ var size = options.size;
+ var tierSizeCalculation = options.tierSizeCalculation !== undefined ?
+ options.tierSizeCalculation :
+ ol.source.ZoomifyTierSizeCalculation.DEFAULT;
+
+ var imageWidth = size[0];
+ var imageHeight = size[1];
+ var tierSizeInTiles = [];
+ var tileSize = ol.DEFAULT_TILE_SIZE;
+
+ switch (tierSizeCalculation) {
+ case ol.source.ZoomifyTierSizeCalculation.DEFAULT:
+ while (imageWidth > tileSize || imageHeight > tileSize) {
+ tierSizeInTiles.push([
+ Math.ceil(imageWidth / tileSize),
+ Math.ceil(imageHeight / tileSize)
+ ]);
+ tileSize += tileSize;
+ }
+ break;
+ case ol.source.ZoomifyTierSizeCalculation.TRUNCATED:
+ var width = imageWidth;
+ var height = imageHeight;
+ while (width > tileSize || height > tileSize) {
+ tierSizeInTiles.push([
+ Math.ceil(width / tileSize),
+ Math.ceil(height / tileSize)
+ ]);
+ width >>= 1;
+ height >>= 1;
+ }
+ break;
+ default:
+ goog.asserts.fail();
+ break;
+ }
+
+ tierSizeInTiles.push([1, 1]);
+ tierSizeInTiles.reverse();
+
+ var resolutions = [1];
+ var tileCountUpToTier = [0];
+ var i, ii;
+ for (i = 1, ii = tierSizeInTiles.length; i < ii; i++) {
+ resolutions.push(1 << i);
+ tileCountUpToTier.push(
+ tierSizeInTiles[i - 1][0] * tierSizeInTiles[i - 1][1] +
+ tileCountUpToTier[i - 1]
+ );
+ }
+ resolutions.reverse();
+
+ 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;
+
+ /**
+ * @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 (!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,
+ crossOrigin: options.crossOrigin,
+ logo: options.logo,
+ reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+ tileClass: ol.source.ZoomifyTile_,
+ tileGrid: tileGrid,
+ tileUrlFunction: tileUrlFunction
+ });
+
+};
+goog.inherits(ol.source.Zoomify, ol.source.TileImage);
+
+
+
+/**
+ * @constructor
+ * @extends {ol.ImageTile}
+ * @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.
+ * @private
+ */
+ol.source.ZoomifyTile_ = function(
+ tileCoord, state, src, crossOrigin, tileLoadFunction) {
+
+ goog.base(this, tileCoord, state, src, crossOrigin, tileLoadFunction);
+
+ /**
+ * @private
+ * @type {Object.<string,
+ * HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
+ */
+ this.zoomifyImageByContext_ = {};
+
+};
+goog.inherits(ol.source.ZoomifyTile_, ol.ImageTile);
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.ZoomifyTile_.prototype.getImage = function(opt_context) {
+ var tileSize = ol.DEFAULT_TILE_SIZE;
+ var key = opt_context !== undefined ?
+ goog.getUid(opt_context).toString() : '';
+ if (key in this.zoomifyImageByContext_) {
+ return this.zoomifyImageByContext_[key];
+ } else {
+ var image = goog.base(this, 'getImage', opt_context);
+ if (this.state == ol.TileState.LOADED) {
+ if (image.width == tileSize && image.height == tileSize) {
+ this.zoomifyImageByContext_[key] = image;
+ return image;
+ } else {
+ var context = ol.dom.createCanvasContext2D(tileSize, tileSize);
+ context.drawImage(image, 0, 0);
+ this.zoomifyImageByContext_[key] = context.canvas;
+ return context.canvas;
+ }
+ } else {
+ return image;
+ }
+ }
+};
+
+goog.provide('ol.style.Atlas');
+goog.provide('ol.style.AtlasManager');
+
+goog.require('goog.asserts');
+goog.require('goog.functions');
+goog.require('goog.object');
+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` and the position of the hit-detection image
+ * inside the hit-detection atlas image `hitImage`.
+ * @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement,
+ * hitImage: HTMLCanvasElement}}
+ */
+ol.style.AtlasManagerInfo;
+
+
+
+/**
+ * Manages the creation of image atlases.
+ *
+ * Images added to this manager will be inserted into an atlas, which
+ * will be used for rendering.
+ * The `size` given in the constructor is the size for the first
+ * atlas. After that, when new atlases are created, they will have
+ * twice the size as the latest atlas (until `maxSize` is reached).
+ *
+ * If an application uses many images or very large images, it is recommended
+ * to set a higher `size` value to avoid the creation of too many atlases.
+ *
+ * @constructor
+ * @struct
+ * @api
+ * @param {olx.style.AtlasManagerOptions=} opt_options Options.
+ */
+ol.style.AtlasManager = function(opt_options) {
+
+ var options = opt_options || {};
+
+ /**
+ * The size in pixels of the latest atlas image.
+ * @private
+ * @type {number}
+ */
+ this.currentSize_ = options.initialSize !== undefined ?
+ options.initialSize : ol.INITIAL_ATLAS_SIZE;
+
+ /**
+ * The maximum size in pixels of atlas images.
+ * @private
+ * @type {number}
+ */
+ this.maxSize_ = options.maxSize !== undefined ?
+ options.maxSize : ol.MAX_ATLAS_SIZE != -1 ?
+ ol.MAX_ATLAS_SIZE : ol.WEBGL_MAX_TEXTURE_SIZE !== undefined ?
+ ol.WEBGL_MAX_TEXTURE_SIZE : 2048;
+
+ /**
+ * The size in pixels between images.
+ * @private
+ * @type {number}
+ */
+ this.space_ = options.space !== undefined ? options.space : 1;
+
+ /**
+ * @private
+ * @type {Array.<ol.style.Atlas>}
+ */
+ this.atlases_ = [new ol.style.Atlas(this.currentSize_, this.space_)];
+
+ /**
+ * The size in pixels of the latest atlas image for hit-detection images.
+ * @private
+ * @type {number}
+ */
+ this.currentHitSize_ = this.currentSize_;
+
+ /**
+ * @private
+ * @type {Array.<ol.style.Atlas>}
+ */
+ this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)];
+};
+
+
+/**
+ * @param {string} id The identifier of the entry to check.
+ * @return {?ol.style.AtlasManagerInfo} The position and atlas image for the
+ * entry, or `null` if the entry is not part of the atlas manager.
+ */
+ol.style.AtlasManager.prototype.getInfo = function(id) {
+ /** @type {?ol.style.AtlasInfo} */
+ var info = this.getInfo_(this.atlases_, id);
+
+ if (!info) {
+ return null;
+ }
+ /** @type {?ol.style.AtlasInfo} */
+ var hitInfo = this.getInfo_(this.hitAtlases_, id);
+ goog.asserts.assert(hitInfo, 'hitInfo must not be null');
+
+ return this.mergeInfos_(info, hitInfo);
+};
+
+
+/**
+ * @private
+ * @param {Array.<ol.style.Atlas>} atlases The atlases to search.
+ * @param {string} id The identifier of the entry to check.
+ * @return {?ol.style.AtlasInfo} The position and atlas image for the entry,
+ * or `null` if the entry is not part of the atlases.
+ */
+ol.style.AtlasManager.prototype.getInfo_ = function(atlases, id) {
+ var atlas, info, i, ii;
+ for (i = 0, ii = atlases.length; i < ii; ++i) {
+ atlas = atlases[i];
+ info = atlas.get(id);
+ if (info) {
+ return info;
+ }
+ }
+ return null;
+};
+
+
+/**
+ * @private
+ * @param {ol.style.AtlasInfo} info The info for the real image.
+ * @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,
+ hitImage: hitInfo.image
+ });
+};
+
+
+/**
+ * Add an image to the atlas manager.
+ *
+ * If an entry for the given id already exists, the entry will
+ * be overridden (but the space on the atlas graphic will not be freed).
+ *
+ * If `renderHitCallback` is provided, the image (or the hit-detection version
+ * of the image) will be rendered into a separate hit-detection atlas image.
+ *
+ * @param {string} id The identifier of the entry to add.
+ * @param {number} width The width.
+ * @param {number} height The height.
+ * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
+ * Called to render the new image onto an atlas image.
+ * @param {function(CanvasRenderingContext2D, number, number)=}
+ * opt_renderHitCallback Called to render a hit-detection image onto a hit
+ * detection atlas image.
+ * @param {Object=} opt_this Value to use as `this` when executing
+ * `renderCallback` and `renderHitCallback`.
+ * @return {?ol.style.AtlasManagerInfo} The position and atlas image for the
+ * entry, or `null` if the image is too big.
+ */
+ol.style.AtlasManager.prototype.add =
+ function(id, width, height,
+ renderCallback, opt_renderHitCallback, opt_this) {
+ if (width + this.space_ > this.maxSize_ ||
+ height + this.space_ > this.maxSize_) {
+ return null;
+ }
+
+ /** @type {?ol.style.AtlasInfo} */
+ var info = this.add_(false,
+ id, width, height, renderCallback, opt_this);
+ if (!info) {
+ 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 = opt_renderHitCallback !== undefined ?
+ opt_renderHitCallback : goog.functions.NULL;
+
+ /** @type {?ol.style.AtlasInfo} */
+ var hitInfo = this.add_(true,
+ id, width, height, renderHitCallback, opt_this);
+ goog.asserts.assert(hitInfo, 'hitInfo must not be null');
+
+ return this.mergeInfos_(info, hitInfo);
+};
+
+
+/**
+ * @private
+ * @param {boolean} isHitAtlas If the hit-detection atlases are used.
+ * @param {string} id The identifier of the entry to add.
+ * @param {number} width The width.
+ * @param {number} height The height.
+ * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
+ * Called to render the new image onto an atlas image.
+ * @param {Object=} opt_this Value to use as `this` when executing
+ * `renderCallback` and `renderHitCallback`.
+ * @return {?ol.style.AtlasInfo} The position and atlas image for the entry,
+ * or `null` if the image is too big.
+ */
+ol.style.AtlasManager.prototype.add_ =
+ function(isHitAtlas, id, width, height,
+ renderCallback, opt_this) {
+ var atlases = (isHitAtlas) ? this.hitAtlases_ : this.atlases_;
+ var atlas, info, i, ii;
+ for (i = 0, ii = atlases.length; i < ii; ++i) {
+ atlas = atlases[i];
+ info = atlas.add(id, width, height, renderCallback, opt_this);
+ if (info) {
+ return info;
+ } else if (!info && i === ii - 1) {
+ // the entry could not be added to one of the existing atlases,
+ // create a new atlas that is twice as big and try to add to this one.
+ var size;
+ if (isHitAtlas) {
+ size = Math.min(this.currentHitSize_ * 2, this.maxSize_);
+ this.currentHitSize_ = size;
+ } else {
+ size = Math.min(this.currentSize_ * 2, this.maxSize_);
+ this.currentSize_ = size;
+ }
+ atlas = new ol.style.Atlas(size, this.space_);
+ atlases.push(atlas);
+ // run the loop another time
+ ++ii;
+ }
+ }
+ goog.asserts.fail('Failed to add to atlasmanager');
+};
+
+
+/**
+ * Provides information for an image inside an atlas.
+ * `offsetX` and `offsetY` are the position of the image inside
+ * the atlas image `image`.
+ * @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement}}
+ */
+ol.style.AtlasInfo;
+
+
+
+/**
+ * This class facilitates the creation of image atlases.
+ *
+ * Images added to an atlas will be rendered onto a single
+ * atlas canvas. The distribution of images on the canvas is
+ * managed with the bin packing algorithm described in:
+ * http://www.blackpawn.com/texts/lightmaps/
+ *
+ * @constructor
+ * @struct
+ * @param {number} size The size in pixels of the sprite image.
+ * @param {number} space The space in pixels between images.
+ * Because texture coordinates are float values, the edges of
+ * images might not be completely correct (in a way that the
+ * edges overlap when being rendered). To avoid this we add a
+ * padding around each image.
+ */
+ol.style.Atlas = function(size, space) {
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.space_ = space;
+
+ /**
+ * @private
+ * @type {Array.<ol.style.Atlas.Block>}
+ */
+ this.emptyBlocks_ = [{x: 0, y: 0, width: size, height: size}];
+
+ /**
+ * @private
+ * @type {Object.<string, ol.style.AtlasInfo>}
+ */
+ this.entries_ = {};
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement}
+ */
+ this.canvas_ = /** @type {HTMLCanvasElement} */
+ (document.createElement('CANVAS'));
+ this.canvas_.width = size;
+ this.canvas_.height = size;
+
+ /**
+ * @private
+ * @type {CanvasRenderingContext2D}
+ */
+ this.context_ = /** @type {CanvasRenderingContext2D} */
+ (this.canvas_.getContext('2d'));
+};
+
+
+/**
+ * @param {string} id The identifier of the entry to check.
+ * @return {?ol.style.AtlasInfo}
+ */
+ol.style.Atlas.prototype.get = function(id) {
+ return /** @type {?ol.style.AtlasInfo} */ (
+ goog.object.get(this.entries_, id, null));
+};
+
+
+/**
+ * @param {string} id The identifier of the entry to add.
+ * @param {number} width The width.
+ * @param {number} height The height.
+ * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
+ * Called to render the new image onto an atlas image.
+ * @param {Object=} opt_this Value to use as `this` when executing
+ * `renderCallback`.
+ * @return {?ol.style.AtlasInfo} The position and atlas image for the entry.
+ */
+ol.style.Atlas.prototype.add =
+ function(id, width, height, renderCallback, opt_this) {
+ var block, i, ii;
+ for (i = 0, ii = this.emptyBlocks_.length; i < ii; ++i) {
+ block = this.emptyBlocks_[i];
+ if (block.width >= width + this.space_ &&
+ block.height >= height + this.space_) {
+ // we found a block that is big enough for our entry
+ var entry = {
+ offsetX: block.x + this.space_,
+ offsetY: block.y + this.space_,
+ image: this.canvas_
+ };
+ this.entries_[id] = entry;
+
+ // render the image on the atlas image
+ renderCallback.call(opt_this, this.context_,
+ block.x + this.space_, block.y + this.space_);
+
+ // split the block after the insertion, either horizontally or vertically
+ this.split_(i, block, width + this.space_, height + this.space_);
+
+ return entry;
+ }
+ }
+
+ // there is no space for the new entry in this atlas
+ return null;
+};
+
+
+/**
+ * @private
+ * @param {number} index The index of the block.
+ * @param {ol.style.Atlas.Block} block The block to split.
+ * @param {number} width The width of the entry to insert.
+ * @param {number} height The height of the entry to insert.
+ */
+ol.style.Atlas.prototype.split_ =
+ function(index, block, width, height) {
+ var deltaWidth = block.width - width;
+ var deltaHeight = block.height - height;
+
+ /** @type {ol.style.Atlas.Block} */
+ var newBlock1;
+ /** @type {ol.style.Atlas.Block} */
+ var newBlock2;
+
+ if (deltaWidth > deltaHeight) {
+ // split vertically
+ // block right of the inserted entry
+ newBlock1 = {
+ x: block.x + width,
+ y: block.y,
+ width: block.width - width,
+ height: block.height
+ };
+
+ // block below the inserted entry
+ newBlock2 = {
+ x: block.x,
+ y: block.y + height,
+ width: width,
+ height: block.height - height
+ };
+ this.updateBlocks_(index, newBlock1, newBlock2);
+ } else {
+ // split horizontally
+ // block right of the inserted entry
+ newBlock1 = {
+ x: block.x + width,
+ y: block.y,
+ width: block.width - width,
+ height: height
+ };
+
+ // block below the inserted entry
+ newBlock2 = {
+ x: block.x,
+ y: block.y + height,
+ width: block.width,
+ height: block.height - height
+ };
+ this.updateBlocks_(index, newBlock1, newBlock2);
+ }
+};
+
+
+/**
+ * Remove the old block and insert new blocks at the same array position.
+ * The new blocks are inserted at the same position, so that splitted
+ * blocks (that are potentially smaller) are filled first.
+ * @private
+ * @param {number} index The index of the block to remove.
+ * @param {ol.style.Atlas.Block} newBlock1 The 1st block to add.
+ * @param {ol.style.Atlas.Block} newBlock2 The 2nd block to add.
+ */
+ol.style.Atlas.prototype.updateBlocks_ =
+ function(index, newBlock1, newBlock2) {
+ var args = [index, 1];
+ if (newBlock1.width > 0 && newBlock1.height > 0) {
+ args.push(newBlock1);
+ }
+ if (newBlock2.width > 0 && newBlock2.height > 0) {
+ args.push(newBlock2);
+ }
+ this.emptyBlocks_.splice.apply(this.emptyBlocks_, args);
+};
+
+
+/**
+ * @typedef {{x: number, y: number, width: number, height: number}}
+ */
+ol.style.Atlas.Block;
+
+goog.provide('ol.style.RegularShape');
+
+goog.require('goog.asserts');
+goog.require('ol');
+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');
+goog.require('ol.style.Stroke');
+
+
+
+/**
+ * @classdesc
+ * Set regular shape style for vector features. The resulting shape will be
+ * a regular polygon when `radius` is provided, or a star when `radius1` and
+ * `radius2` are provided.
+ *
+ * @constructor
+ * @param {olx.style.RegularShapeOptions} options Options.
+ * @extends {ol.style.Image}
+ * @implements {ol.structs.IHasChecksum}
+ * @api
+ */
+ol.style.RegularShape = function(options) {
+
+ goog.asserts.assert(
+ options.radius !== undefined || options.radius1 !== undefined,
+ 'must provide either "radius" or "radius1"');
+
+ /**
+ * @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_ = options.fill !== undefined ? options.fill : null;
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.origin_ = [0, 0];
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.points_ = options.points;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.radius_ = /** @type {number} */ (options.radius !== undefined ?
+ options.radius : options.radius1);
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.radius2_ =
+ options.radius2 !== undefined ? options.radius2 : this.radius_;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.angle_ = options.angle !== undefined ? options.angle : 0;
+
+ /**
+ * @private
+ * @type {ol.style.Stroke}
+ */
+ this.stroke_ = options.stroke !== undefined ? options.stroke : null;
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.anchor_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.size_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.imageSize_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.hitDetectionImageSize_ = null;
+
+ this.render_(options.atlasManager);
+
+ /**
+ * @type {boolean}
+ */
+ var snapToPixel = options.snapToPixel !== undefined ?
+ options.snapToPixel : true;
+
+ goog.base(this, {
+ opacity: 1,
+ rotateWithView: false,
+ rotation: options.rotation !== undefined ? options.rotation : 0,
+ scale: 1,
+ snapToPixel: snapToPixel
+ });
+
+};
+goog.inherits(ol.style.RegularShape, ol.style.Image);
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.RegularShape.prototype.getAnchor = function() {
+ return this.anchor_;
+};
+
+
+/**
+ * Get the angle used in generating the shape.
+ * @return {number} Shape's rotation in radians.
+ * @api
+ */
+ol.style.RegularShape.prototype.getAngle = function() {
+ return this.angle_;
+};
+
+
+/**
+ * Get the fill style for the shape.
+ * @return {ol.style.Fill} Fill style.
+ * @api
+ */
+ol.style.RegularShape.prototype.getFill = function() {
+ return this.fill_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.getHitDetectionImage = function(pixelRatio) {
+ return this.hitDetectionCanvas_;
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.RegularShape.prototype.getImage = function(pixelRatio) {
+ return this.canvas_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.getImageSize = function() {
+ return this.imageSize_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.getHitDetectionImageSize = function() {
+ return this.hitDetectionImageSize_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.getImageState = function() {
+ return ol.style.ImageState.LOADED;
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.RegularShape.prototype.getOrigin = function() {
+ return this.origin_;
+};
+
+
+/**
+ * Get the number of points for generating the shape.
+ * @return {number} Number of points for stars and regular polygons.
+ * @api
+ */
+ol.style.RegularShape.prototype.getPoints = function() {
+ return this.points_;
+};
+
+
+/**
+ * Get the (primary) radius for the shape.
+ * @return {number} Radius.
+ * @api
+ */
+ol.style.RegularShape.prototype.getRadius = function() {
+ return this.radius_;
+};
+
+
+/**
+ * Get the secondary radius for the shape.
+ * @return {number} Radius2.
+ * @api
+ */
+ol.style.RegularShape.prototype.getRadius2 = function() {
+ return this.radius2_;
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.RegularShape.prototype.getSize = function() {
+ return this.size_;
+};
+
+
+/**
+ * Get the stroke style for the shape.
+ * @return {ol.style.Stroke} Stroke style.
+ * @api
+ */
+ol.style.RegularShape.prototype.getStroke = function() {
+ return this.stroke_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.listenImageChange = ol.nullFunction;
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.load = ol.nullFunction;
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.unlistenImageChange = ol.nullFunction;
+
+
+/**
+ * @typedef {{
+ * strokeStyle: (string|undefined),
+ * strokeWidth: number,
+ * size: number,
+ * lineCap: string,
+ * lineDash: Array.<number>,
+ * lineJoin: string,
+ * miterLimit: number
+ * }}
+ */
+ol.style.RegularShape.RenderOptions;
+
+
+/**
+ * @private
+ * @param {ol.style.AtlasManager|undefined} atlasManager
+ */
+ol.style.RegularShape.prototype.render_ = function(atlasManager) {
+ var imageSize;
+ var lineCap = '';
+ var lineJoin = '';
+ var miterLimit = 0;
+ var lineDash = null;
+ var strokeStyle;
+ var strokeWidth = 0;
+
+ if (this.stroke_) {
+ strokeStyle = ol.color.asString(this.stroke_.getColor());
+ strokeWidth = this.stroke_.getWidth();
+ if (strokeWidth === undefined) {
+ strokeWidth = ol.render.canvas.defaultLineWidth;
+ }
+ lineDash = this.stroke_.getLineDash();
+ if (!ol.has.CANVAS_LINE_DASH) {
+ lineDash = null;
+ }
+ lineJoin = this.stroke_.getLineJoin();
+ if (lineJoin === undefined) {
+ lineJoin = ol.render.canvas.defaultLineJoin;
+ }
+ lineCap = this.stroke_.getLineCap();
+ if (lineCap === undefined) {
+ lineCap = ol.render.canvas.defaultLineCap;
+ }
+ miterLimit = this.stroke_.getMiterLimit();
+ if (miterLimit === undefined) {
+ miterLimit = ol.render.canvas.defaultMiterLimit;
+ }
+ }
+
+ var size = 2 * (this.radius_ + strokeWidth) + 1;
+
+ /** @type {ol.style.RegularShape.RenderOptions} */
+ var renderOptions = {
+ strokeStyle: strokeStyle,
+ strokeWidth: strokeWidth,
+ size: size,
+ lineCap: lineCap,
+ lineDash: lineDash,
+ lineJoin: lineJoin,
+ miterLimit: miterLimit
+ };
+
+ if (atlasManager === undefined) {
+ // no atlas manager is used, create a new canvas
+ this.canvas_ = /** @type {HTMLCanvasElement} */
+ (document.createElement('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;
+
+ 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 = !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, 'shape size is too large');
+
+ 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];
+ }
+ }
+
+ this.anchor_ = [size / 2, size / 2];
+ this.size_ = [size, size];
+ this.imageSize_ = [imageSize, imageSize];
+};
+
+
+/**
+ * @private
+ * @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).
+ */
+ol.style.RegularShape.prototype.draw_ = function(renderOptions, context, x, y) {
+ var i, angle0, radiusC;
+ // reset transform
+ context.setTransform(1, 0, 0, 1, 0, 0);
+
+ // then move to (x, y)
+ context.translate(x, y);
+
+ context.beginPath();
+ if (this.radius2_ !== this.radius_) {
+ this.points_ = 2 * this.points_;
+ }
+ for (i = 0; i <= this.points_; i++) {
+ angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_;
+ radiusC = i % 2 === 0 ? this.radius_ : this.radius2_;
+ context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
+ renderOptions.size / 2 + radiusC * Math.sin(angle0));
+ }
+
+ if (this.fill_) {
+ context.fillStyle = ol.color.asString(this.fill_.getColor());
+ context.fill();
+ }
+ if (this.stroke_) {
+ context.strokeStyle = renderOptions.strokeStyle;
+ context.lineWidth = renderOptions.strokeWidth;
+ if (renderOptions.lineDash) {
+ context.setLineDash(renderOptions.lineDash);
+ }
+ context.lineCap = renderOptions.lineCap;
+ context.lineJoin = renderOptions.lineJoin;
+ context.miterLimit = renderOptions.miterLimit;
+ context.stroke();
+ }
+ context.closePath();
+};
+
+
+/**
+ * @private
+ * @param {ol.style.RegularShape.RenderOptions} renderOptions
+ */
+ol.style.RegularShape.prototype.createHitDetectionCanvas_ =
+ function(renderOptions) {
+ this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
+ if (this.fill_) {
+ this.hitDetectionCanvas_ = this.canvas_;
+ return;
+ }
+
+ // if no fill style is set, create an extra hit-detection image with a
+ // default fill style
+ this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
+ (document.createElement('CANVAS'));
+ var canvas = this.hitDetectionCanvas_;
+
+ canvas.height = renderOptions.size;
+ canvas.width = renderOptions.size;
+
+ var context = /** @type {CanvasRenderingContext2D} */
+ (canvas.getContext('2d'));
+ this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
+};
+
+
+/**
+ * @private
+ * @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).
+ */
+ol.style.RegularShape.prototype.drawHitDetectionCanvas_ =
+ function(renderOptions, context, x, y) {
+ // reset transform
+ context.setTransform(1, 0, 0, 1, 0, 0);
+
+ // then move to (x, y)
+ context.translate(x, y);
+
+ context.beginPath();
+ if (this.radius2_ !== this.radius_) {
+ this.points_ = 2 * this.points_;
+ }
+ var i, radiusC, angle0;
+ for (i = 0; i <= this.points_; i++) {
+ angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_;
+ radiusC = i % 2 === 0 ? this.radius_ : this.radius2_;
+ context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
+ renderOptions.size / 2 + radiusC * Math.sin(angle0));
+ }
+
+ context.fillStyle = ol.render.canvas.defaultFillStyle;
+ context.fill();
+ if (this.stroke_) {
+ context.strokeStyle = renderOptions.strokeStyle;
+ context.lineWidth = renderOptions.strokeWidth;
+ if (renderOptions.lineDash) {
+ context.setLineDash(renderOptions.lineDash);
+ }
+ context.stroke();
+ }
+ context.closePath();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.getChecksum = function() {
+ var strokeChecksum = this.stroke_ ?
+ this.stroke_.getChecksum() : '-';
+ var fillChecksum = this.fill_ ?
+ this.fill_.getChecksum() : '-';
+
+ var recalculate = !this.checksums_ ||
+ (strokeChecksum != this.checksums_[1] ||
+ fillChecksum != this.checksums_[2] ||
+ this.radius_ != this.checksums_[3] ||
+ this.radius2_ != this.checksums_[4] ||
+ this.angle_ != this.checksums_[5] ||
+ this.points_ != this.checksums_[6]);
+
+ if (recalculate) {
+ var checksum = 'r' + strokeChecksum + fillChecksum +
+ (this.radius_ !== undefined ? this.radius_.toString() : '-') +
+ (this.radius2_ !== undefined ? this.radius2_.toString() : '-') +
+ (this.angle_ !== undefined ? this.angle_.toString() : '-') +
+ (this.points_ !== undefined ? this.points_.toString() : '-');
+ this.checksums_ = [checksum, strokeChecksum, fillChecksum,
+ this.radius_, this.radius2_, this.angle_, this.points_];
+ }
+
+ return this.checksums_[0];
+};
+
+// Copyright 2009 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 file has been auto-generated by GenJsDeps, please do not edit.
+
+goog.addDependency('demos/editor/equationeditor.js', ['goog.demos.editor.EquationEditor'], ['goog.ui.equation.EquationEditorDialog']);
+goog.addDependency('demos/editor/helloworld.js', ['goog.demos.editor.HelloWorld'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.Plugin']);
+goog.addDependency('demos/editor/helloworlddialog.js', ['goog.demos.editor.HelloWorldDialog', 'goog.demos.editor.HelloWorldDialog.OkEvent'], ['goog.dom.TagName', 'goog.events.Event', 'goog.string', 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.AbstractDialog.Builder', 'goog.ui.editor.AbstractDialog.EventType']);
+goog.addDependency('demos/editor/helloworlddialogplugin.js', ['goog.demos.editor.HelloWorldDialogPlugin', 'goog.demos.editor.HelloWorldDialogPlugin.Command'], ['goog.demos.editor.HelloWorldDialog', 'goog.dom.TagName', 'goog.editor.plugins.AbstractDialogPlugin', 'goog.editor.range', 'goog.functions', 'goog.ui.editor.AbstractDialog.EventType']);
+
+/**
+ * @fileoverview Custom exports file.
+ * @suppress {checkVars,extraRequire}
+ */
+
+goog.require('ol');
+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.Extent');
+goog.require('ol.Feature');
+goog.require('ol.FeatureLoader');
+goog.require('ol.FeatureStyleFunction');
+goog.require('ol.FeatureUrlFunction');
+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');
+goog.require('ol.MapBrowserEventHandler');
+goog.require('ol.MapBrowserPointerEvent');
+goog.require('ol.MapEvent');
+goog.require('ol.MapEventType');
+goog.require('ol.MapProperty');
+goog.require('ol.Object');
+goog.require('ol.ObjectEvent');
+goog.require('ol.ObjectEventType');
+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.VectorTile');
+goog.require('ol.View');
+goog.require('ol.ViewHint');
+goog.require('ol.ViewProperty');
+goog.require('ol.animation');
+goog.require('ol.color');
+goog.require('ol.control');
+goog.require('ol.control.Attribution');
+goog.require('ol.control.Control');
+goog.require('ol.control.FullScreen');
+goog.require('ol.control.MousePosition');
+goog.require('ol.control.OverviewMap');
+goog.require('ol.control.Rotate');
+goog.require('ol.control.ScaleLine');
+goog.require('ol.control.ScaleLineProperty');
+goog.require('ol.control.ScaleLineUnits');
+goog.require('ol.control.Zoom');
+goog.require('ol.control.ZoomSlider');
+goog.require('ol.control.ZoomToExtent');
+goog.require('ol.coordinate');
+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.featureloader');
+goog.require('ol.format.EsriJSON');
+goog.require('ol.format.Feature');
+goog.require('ol.format.GML');
+goog.require('ol.format.GML2');
+goog.require('ol.format.GML3');
+goog.require('ol.format.GMLBase');
+goog.require('ol.format.GPX');
+goog.require('ol.format.GeoJSON');
+goog.require('ol.format.IGC');
+goog.require('ol.format.IGCZ');
+goog.require('ol.format.KML');
+goog.require('ol.format.MVT');
+goog.require('ol.format.OSMXML');
+goog.require('ol.format.Polyline');
+goog.require('ol.format.TopoJSON');
+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');
+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.has');
+goog.require('ol.interaction');
+goog.require('ol.interaction.DoubleClickZoom');
+goog.require('ol.interaction.DragAndDrop');
+goog.require('ol.interaction.DragAndDropEvent');
+goog.require('ol.interaction.DragBox');
+goog.require('ol.interaction.DragPan');
+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');
+goog.require('ol.interaction.KeyboardZoom');
+goog.require('ol.interaction.Modify');
+goog.require('ol.interaction.ModifyEvent');
+goog.require('ol.interaction.MouseWheelZoom');
+goog.require('ol.interaction.PinchRotate');
+goog.require('ol.interaction.PinchZoom');
+goog.require('ol.interaction.Pointer');
+goog.require('ol.interaction.Select');
+goog.require('ol.interaction.SelectEvent');
+goog.require('ol.interaction.SelectEventType');
+goog.require('ol.interaction.SelectFilterFunction');
+goog.require('ol.interaction.Snap');
+goog.require('ol.interaction.SnapProperty');
+goog.require('ol.interaction.Translate');
+goog.require('ol.interaction.TranslateEvent');
+goog.require('ol.layer.Base');
+goog.require('ol.layer.Group');
+goog.require('ol.layer.Heatmap');
+goog.require('ol.layer.Image');
+goog.require('ol.layer.Layer');
+goog.require('ol.layer.LayerProperty');
+goog.require('ol.layer.LayerState');
+goog.require('ol.layer.Tile');
+goog.require('ol.layer.Vector');
+goog.require('ol.layer.VectorTile');
+goog.require('ol.loadingstrategy');
+goog.require('ol.proj');
+goog.require('ol.proj.METERS_PER_UNIT');
+goog.require('ol.proj.Projection');
+goog.require('ol.proj.ProjectionLike');
+goog.require('ol.proj.Units');
+goog.require('ol.proj.common');
+goog.require('ol.render');
+goog.require('ol.render.Event');
+goog.require('ol.render.EventType');
+goog.require('ol.render.Feature');
+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.Image');
+goog.require('ol.source.ImageCanvas');
+goog.require('ol.source.ImageEvent');
+goog.require('ol.source.ImageMapGuide');
+goog.require('ol.source.ImageStatic');
+goog.require('ol.source.ImageVector');
+goog.require('ol.source.ImageWMS');
+goog.require('ol.source.MapQuest');
+goog.require('ol.source.OSM');
+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.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.TileWMS');
+goog.require('ol.source.UrlTile');
+goog.require('ol.source.Vector');
+goog.require('ol.source.VectorEvent');
+goog.require('ol.source.VectorEventType');
+goog.require('ol.source.VectorTile');
+goog.require('ol.source.WMTS');
+goog.require('ol.source.WMTSRequestEncoding');
+goog.require('ol.source.XYZ');
+goog.require('ol.source.Zoomify');
+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');
+goog.require('ol.style.IconOrigin');
+goog.require('ol.style.Image');
+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.tilejson');
+goog.require('ol.webgl.Context');
+goog.require('ol.xml');
+
+
+goog.exportSymbol(
+ 'ol.animation.bounce',
+ ol.animation.bounce,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.animation.pan',
+ ol.animation.pan,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.animation.rotate',
+ ol.animation.rotate,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.animation.zoom',
+ ol.animation.zoom,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.Attribution',
+ ol.Attribution,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.Attribution.prototype,
+ 'getHTML',
+ ol.Attribution.prototype.getHTML);
+
+goog.exportProperty(
+ ol.CollectionEvent.prototype,
+ 'element',
+ ol.CollectionEvent.prototype.element);
+
+goog.exportSymbol(
+ 'ol.Collection',
+ ol.Collection,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'clear',
+ ol.Collection.prototype.clear);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'extend',
+ ol.Collection.prototype.extend);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'forEach',
+ ol.Collection.prototype.forEach);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'getArray',
+ ol.Collection.prototype.getArray);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'item',
+ ol.Collection.prototype.item);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'getLength',
+ ol.Collection.prototype.getLength);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'insertAt',
+ ol.Collection.prototype.insertAt);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'pop',
+ ol.Collection.prototype.pop);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'push',
+ ol.Collection.prototype.push);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'remove',
+ ol.Collection.prototype.remove);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'removeAt',
+ ol.Collection.prototype.removeAt);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'setAt',
+ ol.Collection.prototype.setAt);
+
+goog.exportSymbol(
+ 'ol.coordinate.add',
+ ol.coordinate.add,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.coordinate.createStringXY',
+ ol.coordinate.createStringXY,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.coordinate.format',
+ ol.coordinate.format,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.coordinate.rotate',
+ ol.coordinate.rotate,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.coordinate.toStringHDMS',
+ ol.coordinate.toStringHDMS,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.coordinate.toStringXY',
+ ol.coordinate.toStringXY,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.DeviceOrientation',
+ ol.DeviceOrientation,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'getAlpha',
+ ol.DeviceOrientation.prototype.getAlpha);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'getBeta',
+ ol.DeviceOrientation.prototype.getBeta);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'getGamma',
+ ol.DeviceOrientation.prototype.getGamma);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'getHeading',
+ ol.DeviceOrientation.prototype.getHeading);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'getTracking',
+ ol.DeviceOrientation.prototype.getTracking);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'setTracking',
+ ol.DeviceOrientation.prototype.setTracking);
+
+goog.exportSymbol(
+ 'ol.easing.easeIn',
+ ol.easing.easeIn,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.easing.easeOut',
+ ol.easing.easeOut,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.easing.inAndOut',
+ ol.easing.inAndOut,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.easing.linear',
+ ol.easing.linear,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.easing.upAndDown',
+ ol.easing.upAndDown,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.boundingExtent',
+ ol.extent.boundingExtent,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.buffer',
+ ol.extent.buffer,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.containsCoordinate',
+ ol.extent.containsCoordinate,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.containsExtent',
+ ol.extent.containsExtent,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.containsXY',
+ ol.extent.containsXY,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.createEmpty',
+ ol.extent.createEmpty,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.equals',
+ ol.extent.equals,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.extend',
+ ol.extent.extend,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.getBottomLeft',
+ ol.extent.getBottomLeft,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.getBottomRight',
+ ol.extent.getBottomRight,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.getCenter',
+ ol.extent.getCenter,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.getHeight',
+ ol.extent.getHeight,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.getIntersection',
+ ol.extent.getIntersection,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.getSize',
+ ol.extent.getSize,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.getTopLeft',
+ ol.extent.getTopLeft,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.getTopRight',
+ ol.extent.getTopRight,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.getWidth',
+ ol.extent.getWidth,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.intersects',
+ ol.extent.intersects,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.isEmpty',
+ ol.extent.isEmpty,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.extent.applyTransform',
+ ol.extent.applyTransform,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.Feature',
+ ol.Feature,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'clone',
+ ol.Feature.prototype.clone);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'getGeometry',
+ ol.Feature.prototype.getGeometry);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'getId',
+ ol.Feature.prototype.getId);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'getGeometryName',
+ ol.Feature.prototype.getGeometryName);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'getStyle',
+ ol.Feature.prototype.getStyle);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'getStyleFunction',
+ ol.Feature.prototype.getStyleFunction);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'setGeometry',
+ ol.Feature.prototype.setGeometry);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'setStyle',
+ ol.Feature.prototype.setStyle);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'setId',
+ ol.Feature.prototype.setId);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'setGeometryName',
+ ol.Feature.prototype.setGeometryName);
+
+goog.exportSymbol(
+ 'ol.featureloader.tile',
+ ol.featureloader.tile,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.featureloader.xhr',
+ ol.featureloader.xhr,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.Geolocation',
+ ol.Geolocation,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getAccuracy',
+ ol.Geolocation.prototype.getAccuracy);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getAccuracyGeometry',
+ ol.Geolocation.prototype.getAccuracyGeometry);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getAltitude',
+ ol.Geolocation.prototype.getAltitude);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getAltitudeAccuracy',
+ ol.Geolocation.prototype.getAltitudeAccuracy);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getHeading',
+ ol.Geolocation.prototype.getHeading);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getPosition',
+ ol.Geolocation.prototype.getPosition);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getProjection',
+ ol.Geolocation.prototype.getProjection);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getSpeed',
+ ol.Geolocation.prototype.getSpeed);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getTracking',
+ ol.Geolocation.prototype.getTracking);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getTrackingOptions',
+ ol.Geolocation.prototype.getTrackingOptions);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'setProjection',
+ ol.Geolocation.prototype.setProjection);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'setTracking',
+ ol.Geolocation.prototype.setTracking);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'setTrackingOptions',
+ ol.Geolocation.prototype.setTrackingOptions);
+
+goog.exportSymbol(
+ 'ol.Graticule',
+ ol.Graticule,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.Graticule.prototype,
+ 'getMap',
+ ol.Graticule.prototype.getMap);
+
+goog.exportProperty(
+ ol.Graticule.prototype,
+ 'getMeridians',
+ ol.Graticule.prototype.getMeridians);
+
+goog.exportProperty(
+ ol.Graticule.prototype,
+ 'getParallels',
+ ol.Graticule.prototype.getParallels);
+
+goog.exportProperty(
+ ol.Graticule.prototype,
+ 'setMap',
+ ol.Graticule.prototype.setMap);
+
+goog.exportSymbol(
+ 'ol.has.DEVICE_PIXEL_RATIO',
+ ol.has.DEVICE_PIXEL_RATIO,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.has.CANVAS',
+ ol.has.CANVAS,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.has.DEVICE_ORIENTATION',
+ ol.has.DEVICE_ORIENTATION,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.has.GEOLOCATION',
+ ol.has.GEOLOCATION,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.has.TOUCH',
+ ol.has.TOUCH,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.has.WEBGL',
+ ol.has.WEBGL,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.Image.prototype,
+ 'getImage',
+ ol.Image.prototype.getImage);
+
+goog.exportProperty(
+ ol.ImageTile.prototype,
+ 'getImage',
+ ol.ImageTile.prototype.getImage);
+
+goog.exportSymbol(
+ 'ol.Kinetic',
+ ol.Kinetic,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.loadingstrategy.all',
+ ol.loadingstrategy.all,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.loadingstrategy.bbox',
+ ol.loadingstrategy.bbox,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.loadingstrategy.tile',
+ ol.loadingstrategy.tile,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.Map',
+ ol.Map,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'addControl',
+ ol.Map.prototype.addControl);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'addInteraction',
+ ol.Map.prototype.addInteraction);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'addLayer',
+ ol.Map.prototype.addLayer);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'addOverlay',
+ ol.Map.prototype.addOverlay);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'beforeRender',
+ ol.Map.prototype.beforeRender);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ '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',
+ ol.Map.prototype.getEventCoordinate);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getEventPixel',
+ ol.Map.prototype.getEventPixel);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getTarget',
+ ol.Map.prototype.getTarget);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getTargetElement',
+ ol.Map.prototype.getTargetElement);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getCoordinateFromPixel',
+ ol.Map.prototype.getCoordinateFromPixel);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getControls',
+ ol.Map.prototype.getControls);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getOverlays',
+ ol.Map.prototype.getOverlays);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getOverlayById',
+ ol.Map.prototype.getOverlayById);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getInteractions',
+ ol.Map.prototype.getInteractions);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getLayerGroup',
+ ol.Map.prototype.getLayerGroup);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getLayers',
+ ol.Map.prototype.getLayers);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getPixelFromCoordinate',
+ ol.Map.prototype.getPixelFromCoordinate);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getSize',
+ ol.Map.prototype.getSize);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getView',
+ ol.Map.prototype.getView);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getViewport',
+ ol.Map.prototype.getViewport);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'renderSync',
+ ol.Map.prototype.renderSync);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'render',
+ ol.Map.prototype.render);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'removeControl',
+ ol.Map.prototype.removeControl);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'removeInteraction',
+ ol.Map.prototype.removeInteraction);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'removeLayer',
+ ol.Map.prototype.removeLayer);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'removeOverlay',
+ ol.Map.prototype.removeOverlay);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'setLayerGroup',
+ ol.Map.prototype.setLayerGroup);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'setSize',
+ ol.Map.prototype.setSize);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'setTarget',
+ ol.Map.prototype.setTarget);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'setView',
+ ol.Map.prototype.setView);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'updateSize',
+ ol.Map.prototype.updateSize);
+
+goog.exportProperty(
+ ol.MapBrowserEvent.prototype,
+ 'originalEvent',
+ ol.MapBrowserEvent.prototype.originalEvent);
+
+goog.exportProperty(
+ ol.MapBrowserEvent.prototype,
+ 'pixel',
+ ol.MapBrowserEvent.prototype.pixel);
+
+goog.exportProperty(
+ ol.MapBrowserEvent.prototype,
+ 'coordinate',
+ ol.MapBrowserEvent.prototype.coordinate);
+
+goog.exportProperty(
+ ol.MapBrowserEvent.prototype,
+ 'dragging',
+ ol.MapBrowserEvent.prototype.dragging);
+
+goog.exportProperty(
+ ol.MapBrowserEvent.prototype,
+ 'preventDefault',
+ ol.MapBrowserEvent.prototype.preventDefault);
+
+goog.exportProperty(
+ ol.MapBrowserEvent.prototype,
+ 'stopPropagation',
+ ol.MapBrowserEvent.prototype.stopPropagation);
+
+goog.exportProperty(
+ ol.MapEvent.prototype,
+ 'map',
+ ol.MapEvent.prototype.map);
+
+goog.exportProperty(
+ ol.MapEvent.prototype,
+ 'frameState',
+ ol.MapEvent.prototype.frameState);
+
+goog.exportProperty(
+ ol.ObjectEvent.prototype,
+ 'key',
+ ol.ObjectEvent.prototype.key);
+
+goog.exportProperty(
+ ol.ObjectEvent.prototype,
+ 'oldValue',
+ ol.ObjectEvent.prototype.oldValue);
+
+goog.exportSymbol(
+ 'ol.Object',
+ ol.Object,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'get',
+ ol.Object.prototype.get);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'getKeys',
+ ol.Object.prototype.getKeys);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'getProperties',
+ ol.Object.prototype.getProperties);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'set',
+ ol.Object.prototype.set);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'setProperties',
+ ol.Object.prototype.setProperties);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'unset',
+ ol.Object.prototype.unset);
+
+goog.exportSymbol(
+ 'ol.Observable',
+ ol.Observable,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.Observable.unByKey',
+ ol.Observable.unByKey,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.Observable.prototype,
+ 'changed',
+ ol.Observable.prototype.changed);
+
+goog.exportProperty(
+ ol.Observable.prototype,
+ 'dispatchEvent',
+ ol.Observable.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.Observable.prototype,
+ 'getRevision',
+ ol.Observable.prototype.getRevision);
+
+goog.exportProperty(
+ ol.Observable.prototype,
+ 'on',
+ ol.Observable.prototype.on);
+
+goog.exportProperty(
+ ol.Observable.prototype,
+ 'once',
+ ol.Observable.prototype.once);
+
+goog.exportProperty(
+ ol.Observable.prototype,
+ 'un',
+ ol.Observable.prototype.un);
+
+goog.exportProperty(
+ ol.Observable.prototype,
+ 'unByKey',
+ ol.Observable.prototype.unByKey);
+
+goog.exportSymbol(
+ 'ol.inherits',
+ ol.inherits,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.Overlay',
+ ol.Overlay,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'getElement',
+ ol.Overlay.prototype.getElement);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'getId',
+ ol.Overlay.prototype.getId);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'getMap',
+ ol.Overlay.prototype.getMap);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'getOffset',
+ ol.Overlay.prototype.getOffset);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'getPosition',
+ ol.Overlay.prototype.getPosition);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'getPositioning',
+ ol.Overlay.prototype.getPositioning);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'setElement',
+ ol.Overlay.prototype.setElement);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'setMap',
+ ol.Overlay.prototype.setMap);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'setOffset',
+ ol.Overlay.prototype.setOffset);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'setPosition',
+ ol.Overlay.prototype.setPosition);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'setPositioning',
+ ol.Overlay.prototype.setPositioning);
+
+goog.exportSymbol(
+ 'ol.render.toContext',
+ ol.render.toContext,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.size.toSize',
+ ol.size.toSize,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.Tile.prototype,
+ 'getTileCoord',
+ ol.Tile.prototype.getTileCoord);
+
+goog.exportProperty(
+ ol.VectorTile.prototype,
+ 'getFormat',
+ ol.VectorTile.prototype.getFormat);
+
+goog.exportProperty(
+ ol.VectorTile.prototype,
+ 'setLoader',
+ ol.VectorTile.prototype.setLoader);
+
+goog.exportSymbol(
+ 'ol.View',
+ ol.View,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'constrainCenter',
+ ol.View.prototype.constrainCenter);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'constrainResolution',
+ ol.View.prototype.constrainResolution);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'constrainRotation',
+ ol.View.prototype.constrainRotation);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'getCenter',
+ ol.View.prototype.getCenter);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'calculateExtent',
+ ol.View.prototype.calculateExtent);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'getProjection',
+ ol.View.prototype.getProjection);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'getResolution',
+ ol.View.prototype.getResolution);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'getRotation',
+ ol.View.prototype.getRotation);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'getZoom',
+ ol.View.prototype.getZoom);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'fit',
+ ol.View.prototype.fit);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'centerOn',
+ ol.View.prototype.centerOn);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'rotate',
+ ol.View.prototype.rotate);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'setCenter',
+ ol.View.prototype.setCenter);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'setResolution',
+ ol.View.prototype.setResolution);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'setRotation',
+ ol.View.prototype.setRotation);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'setZoom',
+ ol.View.prototype.setZoom);
+
+goog.exportSymbol(
+ 'ol.xml.getAllTextContent',
+ ol.xml.getAllTextContent,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.xml.parse',
+ ol.xml.parse,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.webgl.Context.prototype,
+ 'getGL',
+ ol.webgl.Context.prototype.getGL);
+
+goog.exportProperty(
+ ol.webgl.Context.prototype,
+ 'useProgram',
+ ol.webgl.Context.prototype.useProgram);
+
+goog.exportSymbol(
+ 'ol.tilegrid.TileGrid',
+ ol.tilegrid.TileGrid,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.tilegrid.TileGrid.prototype,
+ 'getMaxZoom',
+ ol.tilegrid.TileGrid.prototype.getMaxZoom);
+
+goog.exportProperty(
+ ol.tilegrid.TileGrid.prototype,
+ 'getMinZoom',
+ ol.tilegrid.TileGrid.prototype.getMinZoom);
+
+goog.exportProperty(
+ ol.tilegrid.TileGrid.prototype,
+ 'getOrigin',
+ ol.tilegrid.TileGrid.prototype.getOrigin);
+
+goog.exportProperty(
+ ol.tilegrid.TileGrid.prototype,
+ 'getResolution',
+ ol.tilegrid.TileGrid.prototype.getResolution);
+
+goog.exportProperty(
+ ol.tilegrid.TileGrid.prototype,
+ 'getResolutions',
+ ol.tilegrid.TileGrid.prototype.getResolutions);
+
+goog.exportProperty(
+ ol.tilegrid.TileGrid.prototype,
+ 'getTileCoordExtent',
+ ol.tilegrid.TileGrid.prototype.getTileCoordExtent);
+
+goog.exportProperty(
+ ol.tilegrid.TileGrid.prototype,
+ 'getTileCoordForCoordAndResolution',
+ ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution);
+
+goog.exportProperty(
+ ol.tilegrid.TileGrid.prototype,
+ 'getTileCoordForCoordAndZ',
+ ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ);
+
+goog.exportProperty(
+ ol.tilegrid.TileGrid.prototype,
+ 'getTileSize',
+ ol.tilegrid.TileGrid.prototype.getTileSize);
+
+goog.exportSymbol(
+ 'ol.tilegrid.createXYZ',
+ ol.tilegrid.createXYZ,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.tilegrid.WMTS',
+ ol.tilegrid.WMTS,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.tilegrid.WMTS.prototype,
+ 'getMatrixIds',
+ ol.tilegrid.WMTS.prototype.getMatrixIds);
+
+goog.exportSymbol(
+ 'ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet',
+ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.style.AtlasManager',
+ ol.style.AtlasManager,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.style.Circle',
+ ol.style.Circle,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.style.Circle.prototype,
+ 'getFill',
+ ol.style.Circle.prototype.getFill);
+
+goog.exportProperty(
+ ol.style.Circle.prototype,
+ 'getImage',
+ ol.style.Circle.prototype.getImage);
+
+goog.exportProperty(
+ ol.style.Circle.prototype,
+ 'getRadius',
+ ol.style.Circle.prototype.getRadius);
+
+goog.exportProperty(
+ ol.style.Circle.prototype,
+ 'getStroke',
+ ol.style.Circle.prototype.getStroke);
+
+goog.exportSymbol(
+ 'ol.style.Fill',
+ ol.style.Fill,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.style.Fill.prototype,
+ 'getColor',
+ ol.style.Fill.prototype.getColor);
+
+goog.exportProperty(
+ ol.style.Fill.prototype,
+ 'setColor',
+ ol.style.Fill.prototype.setColor);
+
+goog.exportSymbol(
+ 'ol.style.Icon',
+ ol.style.Icon,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'getAnchor',
+ ol.style.Icon.prototype.getAnchor);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'getImage',
+ ol.style.Icon.prototype.getImage);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'getOrigin',
+ ol.style.Icon.prototype.getOrigin);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'getSrc',
+ ol.style.Icon.prototype.getSrc);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'getSize',
+ ol.style.Icon.prototype.getSize);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'load',
+ ol.style.Icon.prototype.load);
+
+goog.exportSymbol(
+ 'ol.style.Image',
+ ol.style.Image,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.style.Image.prototype,
+ 'getOpacity',
+ ol.style.Image.prototype.getOpacity);
+
+goog.exportProperty(
+ ol.style.Image.prototype,
+ 'getRotateWithView',
+ ol.style.Image.prototype.getRotateWithView);
+
+goog.exportProperty(
+ ol.style.Image.prototype,
+ 'getRotation',
+ ol.style.Image.prototype.getRotation);
+
+goog.exportProperty(
+ ol.style.Image.prototype,
+ 'getScale',
+ ol.style.Image.prototype.getScale);
+
+goog.exportProperty(
+ ol.style.Image.prototype,
+ 'getSnapToPixel',
+ ol.style.Image.prototype.getSnapToPixel);
+
+goog.exportProperty(
+ ol.style.Image.prototype,
+ 'setOpacity',
+ ol.style.Image.prototype.setOpacity);
+
+goog.exportProperty(
+ ol.style.Image.prototype,
+ 'setRotation',
+ ol.style.Image.prototype.setRotation);
+
+goog.exportProperty(
+ ol.style.Image.prototype,
+ 'setScale',
+ ol.style.Image.prototype.setScale);
+
+goog.exportSymbol(
+ 'ol.style.RegularShape',
+ ol.style.RegularShape,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getAnchor',
+ ol.style.RegularShape.prototype.getAnchor);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getAngle',
+ ol.style.RegularShape.prototype.getAngle);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getFill',
+ ol.style.RegularShape.prototype.getFill);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getImage',
+ ol.style.RegularShape.prototype.getImage);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getOrigin',
+ ol.style.RegularShape.prototype.getOrigin);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getPoints',
+ ol.style.RegularShape.prototype.getPoints);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getRadius',
+ ol.style.RegularShape.prototype.getRadius);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getRadius2',
+ ol.style.RegularShape.prototype.getRadius2);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getSize',
+ ol.style.RegularShape.prototype.getSize);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getStroke',
+ ol.style.RegularShape.prototype.getStroke);
+
+goog.exportSymbol(
+ 'ol.style.Stroke',
+ ol.style.Stroke,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.style.Stroke.prototype,
+ 'getColor',
+ ol.style.Stroke.prototype.getColor);
+
+goog.exportProperty(
+ ol.style.Stroke.prototype,
+ 'getLineCap',
+ ol.style.Stroke.prototype.getLineCap);
+
+goog.exportProperty(
+ ol.style.Stroke.prototype,
+ 'getLineDash',
+ ol.style.Stroke.prototype.getLineDash);
+
+goog.exportProperty(
+ ol.style.Stroke.prototype,
+ 'getLineJoin',
+ ol.style.Stroke.prototype.getLineJoin);
+
+goog.exportProperty(
+ ol.style.Stroke.prototype,
+ 'getMiterLimit',
+ ol.style.Stroke.prototype.getMiterLimit);
+
+goog.exportProperty(
+ ol.style.Stroke.prototype,
+ 'getWidth',
+ ol.style.Stroke.prototype.getWidth);
+
+goog.exportProperty(
+ ol.style.Stroke.prototype,
+ 'setColor',
+ ol.style.Stroke.prototype.setColor);
+
+goog.exportProperty(
+ ol.style.Stroke.prototype,
+ 'setLineCap',
+ ol.style.Stroke.prototype.setLineCap);
+
+goog.exportProperty(
+ ol.style.Stroke.prototype,
+ 'setLineDash',
+ ol.style.Stroke.prototype.setLineDash);
+
+goog.exportProperty(
+ ol.style.Stroke.prototype,
+ 'setLineJoin',
+ ol.style.Stroke.prototype.setLineJoin);
+
+goog.exportProperty(
+ ol.style.Stroke.prototype,
+ 'setMiterLimit',
+ ol.style.Stroke.prototype.setMiterLimit);
+
+goog.exportProperty(
+ ol.style.Stroke.prototype,
+ 'setWidth',
+ ol.style.Stroke.prototype.setWidth);
+
+goog.exportSymbol(
+ 'ol.style.Style',
+ ol.style.Style,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.style.Style.prototype,
+ 'getGeometry',
+ ol.style.Style.prototype.getGeometry);
+
+goog.exportProperty(
+ ol.style.Style.prototype,
+ 'getGeometryFunction',
+ ol.style.Style.prototype.getGeometryFunction);
+
+goog.exportProperty(
+ ol.style.Style.prototype,
+ 'getFill',
+ ol.style.Style.prototype.getFill);
+
+goog.exportProperty(
+ ol.style.Style.prototype,
+ 'getImage',
+ ol.style.Style.prototype.getImage);
+
+goog.exportProperty(
+ ol.style.Style.prototype,
+ 'getStroke',
+ ol.style.Style.prototype.getStroke);
+
+goog.exportProperty(
+ ol.style.Style.prototype,
+ 'getText',
+ ol.style.Style.prototype.getText);
+
+goog.exportProperty(
+ ol.style.Style.prototype,
+ 'getZIndex',
+ ol.style.Style.prototype.getZIndex);
+
+goog.exportProperty(
+ ol.style.Style.prototype,
+ 'setGeometry',
+ ol.style.Style.prototype.setGeometry);
+
+goog.exportProperty(
+ ol.style.Style.prototype,
+ 'setZIndex',
+ ol.style.Style.prototype.setZIndex);
+
+goog.exportSymbol(
+ 'ol.style.Text',
+ ol.style.Text,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'getFont',
+ ol.style.Text.prototype.getFont);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'getOffsetX',
+ ol.style.Text.prototype.getOffsetX);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'getOffsetY',
+ ol.style.Text.prototype.getOffsetY);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'getFill',
+ ol.style.Text.prototype.getFill);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'getRotation',
+ ol.style.Text.prototype.getRotation);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'getScale',
+ ol.style.Text.prototype.getScale);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'getStroke',
+ ol.style.Text.prototype.getStroke);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'getText',
+ ol.style.Text.prototype.getText);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'getTextAlign',
+ ol.style.Text.prototype.getTextAlign);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'getTextBaseline',
+ ol.style.Text.prototype.getTextBaseline);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'setFont',
+ ol.style.Text.prototype.setFont);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'setOffsetX',
+ ol.style.Text.prototype.setOffsetX);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'setOffsetY',
+ ol.style.Text.prototype.setOffsetY);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'setFill',
+ ol.style.Text.prototype.setFill);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'setRotation',
+ ol.style.Text.prototype.setRotation);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'setScale',
+ ol.style.Text.prototype.setScale);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'setStroke',
+ ol.style.Text.prototype.setStroke);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'setText',
+ ol.style.Text.prototype.setText);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'setTextAlign',
+ ol.style.Text.prototype.setTextAlign);
+
+goog.exportProperty(
+ ol.style.Text.prototype,
+ 'setTextBaseline',
+ ol.style.Text.prototype.setTextBaseline);
+
+goog.exportSymbol(
+ 'ol.Sphere',
+ 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,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.source.BingMaps.TOS_ATTRIBUTION',
+ ol.source.BingMaps.TOS_ATTRIBUTION,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.source.Cluster',
+ ol.source.Cluster,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.source.Cluster.prototype,
+ 'getSource',
+ ol.source.Cluster.prototype.getSource);
+
+goog.exportSymbol(
+ 'ol.source.ImageCanvas',
+ ol.source.ImageCanvas,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.source.ImageMapGuide',
+ ol.source.ImageMapGuide,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.source.ImageMapGuide.prototype,
+ '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,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.source.ImageVector',
+ ol.source.ImageVector,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'getSource',
+ ol.source.ImageVector.prototype.getSource);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'getStyle',
+ ol.source.ImageVector.prototype.getStyle);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'getStyleFunction',
+ ol.source.ImageVector.prototype.getStyleFunction);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'setStyle',
+ ol.source.ImageVector.prototype.setStyle);
+
+goog.exportSymbol(
+ 'ol.source.ImageWMS',
+ ol.source.ImageWMS,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'getGetFeatureInfoUrl',
+ ol.source.ImageWMS.prototype.getGetFeatureInfoUrl);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ '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',
+ ol.source.ImageWMS.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'updateParams',
+ ol.source.ImageWMS.prototype.updateParams);
+
+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,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.source.OSM.ATTRIBUTION',
+ ol.source.OSM.ATTRIBUTION,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.source.Raster',
+ ol.source.Raster,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'setOperation',
+ ol.source.Raster.prototype.setOperation);
+
+goog.exportProperty(
+ 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',
+ ol.source.Source,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.source.Source.prototype,
+ 'getAttributions',
+ ol.source.Source.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.Source.prototype,
+ 'getLogo',
+ ol.source.Source.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.Source.prototype,
+ 'getProjection',
+ ol.source.Source.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.Source.prototype,
+ 'getState',
+ ol.source.Source.prototype.getState);
+
+goog.exportProperty(
+ ol.source.Source.prototype,
+ 'setAttributions',
+ ol.source.Source.prototype.setAttributions);
+
+goog.exportSymbol(
+ 'ol.source.Stamen',
+ ol.source.Stamen,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ '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,
+ 'updateParams',
+ ol.source.TileArcGISRest.prototype.updateParams);
+
+goog.exportSymbol(
+ 'ol.source.TileDebug',
+ ol.source.TileDebug,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.source.TileImage',
+ ol.source.TileImage,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'setRenderReprojectionEdges',
+ ol.source.TileImage.prototype.setRenderReprojectionEdges);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'setTileGridForProjection',
+ ol.source.TileImage.prototype.setTileGridForProjection);
+
+goog.exportSymbol(
+ 'ol.source.TileJSON',
+ ol.source.TileJSON,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.source.Tile',
+ ol.source.Tile,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ '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,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'getTemplate',
+ ol.source.TileUTFGrid.prototype.getTemplate);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'forDataAtCoordinateAndResolution',
+ ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution);
+
+goog.exportSymbol(
+ 'ol.source.TileWMS',
+ ol.source.TileWMS,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getGetFeatureInfoUrl',
+ ol.source.TileWMS.prototype.getGetFeatureInfoUrl);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getParams',
+ ol.source.TileWMS.prototype.getParams);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'updateParams',
+ ol.source.TileWMS.prototype.updateParams);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'getTileLoadFunction',
+ ol.source.UrlTile.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'getTileUrlFunction',
+ ol.source.UrlTile.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'getUrls',
+ ol.source.UrlTile.prototype.getUrls);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'setTileLoadFunction',
+ ol.source.UrlTile.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'setTileUrlFunction',
+ ol.source.UrlTile.prototype.setTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'setUrl',
+ ol.source.UrlTile.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'setUrls',
+ ol.source.UrlTile.prototype.setUrls);
+
+goog.exportSymbol(
+ 'ol.source.Vector',
+ ol.source.Vector,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'addFeature',
+ ol.source.Vector.prototype.addFeature);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'addFeatures',
+ ol.source.Vector.prototype.addFeatures);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'clear',
+ ol.source.Vector.prototype.clear);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'forEachFeature',
+ ol.source.Vector.prototype.forEachFeature);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'forEachFeatureInExtent',
+ ol.source.Vector.prototype.forEachFeatureInExtent);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ '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',
+ ol.source.Vector.prototype.getFeatures);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ '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',
+ ol.source.Vector.prototype.getClosestFeatureToCoordinate);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'getExtent',
+ ol.source.Vector.prototype.getExtent);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'getFeatureById',
+ ol.source.Vector.prototype.getFeatureById);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'removeFeature',
+ ol.source.Vector.prototype.removeFeature);
+
+goog.exportProperty(
+ ol.source.VectorEvent.prototype,
+ 'feature',
+ ol.source.VectorEvent.prototype.feature);
+
+goog.exportSymbol(
+ 'ol.source.VectorTile',
+ ol.source.VectorTile,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.source.WMTS',
+ ol.source.WMTS,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ '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,
+ '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.exportSymbol(
+ 'ol.source.Zoomify',
+ ol.source.Zoomify,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.render.Event.prototype,
+ 'vectorContext',
+ ol.render.Event.prototype.vectorContext);
+
+goog.exportProperty(
+ ol.render.Event.prototype,
+ 'frameState',
+ ol.render.Event.prototype.frameState);
+
+goog.exportProperty(
+ ol.render.Event.prototype,
+ 'context',
+ ol.render.Event.prototype.context);
+
+goog.exportProperty(
+ ol.render.Event.prototype,
+ 'glContext',
+ ol.render.Event.prototype.glContext);
+
+goog.exportProperty(
+ ol.render.Feature.prototype,
+ 'get',
+ ol.render.Feature.prototype.get);
+
+goog.exportProperty(
+ ol.render.Feature.prototype,
+ 'getExtent',
+ ol.render.Feature.prototype.getExtent);
+
+goog.exportProperty(
+ ol.render.Feature.prototype,
+ 'getGeometry',
+ ol.render.Feature.prototype.getGeometry);
+
+goog.exportProperty(
+ ol.render.Feature.prototype,
+ 'getProperties',
+ ol.render.Feature.prototype.getProperties);
+
+goog.exportProperty(
+ ol.render.Feature.prototype,
+ 'getType',
+ ol.render.Feature.prototype.getType);
+
+goog.exportSymbol(
+ 'ol.render.VectorContext',
+ ol.render.VectorContext,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'drawAsync',
+ ol.render.webgl.Immediate.prototype.drawAsync);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'drawCircleGeometry',
+ ol.render.webgl.Immediate.prototype.drawCircleGeometry);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'drawFeature',
+ ol.render.webgl.Immediate.prototype.drawFeature);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'drawGeometryCollectionGeometry',
+ ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'drawPointGeometry',
+ ol.render.webgl.Immediate.prototype.drawPointGeometry);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'drawLineStringGeometry',
+ ol.render.webgl.Immediate.prototype.drawLineStringGeometry);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'drawMultiLineStringGeometry',
+ ol.render.webgl.Immediate.prototype.drawMultiLineStringGeometry);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'drawMultiPointGeometry',
+ ol.render.webgl.Immediate.prototype.drawMultiPointGeometry);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'drawMultiPolygonGeometry',
+ ol.render.webgl.Immediate.prototype.drawMultiPolygonGeometry);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'drawPolygonGeometry',
+ ol.render.webgl.Immediate.prototype.drawPolygonGeometry);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'drawText',
+ ol.render.webgl.Immediate.prototype.drawText);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'setFillStrokeStyle',
+ ol.render.webgl.Immediate.prototype.setFillStrokeStyle);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'setImageStyle',
+ ol.render.webgl.Immediate.prototype.setImageStyle);
+
+goog.exportProperty(
+ ol.render.webgl.Immediate.prototype,
+ 'setTextStyle',
+ ol.render.webgl.Immediate.prototype.setTextStyle);
+
+goog.exportProperty(
+ ol.render.canvas.Immediate.prototype,
+ 'drawAsync',
+ ol.render.canvas.Immediate.prototype.drawAsync);
+
+goog.exportProperty(
+ ol.render.canvas.Immediate.prototype,
+ 'drawCircleGeometry',
+ ol.render.canvas.Immediate.prototype.drawCircleGeometry);
+
+goog.exportProperty(
+ ol.render.canvas.Immediate.prototype,
+ 'drawFeature',
+ ol.render.canvas.Immediate.prototype.drawFeature);
+
+goog.exportProperty(
+ ol.render.canvas.Immediate.prototype,
+ 'drawPointGeometry',
+ ol.render.canvas.Immediate.prototype.drawPointGeometry);
+
+goog.exportProperty(
+ ol.render.canvas.Immediate.prototype,
+ 'drawMultiPointGeometry',
+ ol.render.canvas.Immediate.prototype.drawMultiPointGeometry);
+
+goog.exportProperty(
+ ol.render.canvas.Immediate.prototype,
+ 'drawLineStringGeometry',
+ ol.render.canvas.Immediate.prototype.drawLineStringGeometry);
+
+goog.exportProperty(
+ ol.render.canvas.Immediate.prototype,
+ 'drawMultiLineStringGeometry',
+ ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry);
+
+goog.exportProperty(
+ ol.render.canvas.Immediate.prototype,
+ 'drawPolygonGeometry',
+ ol.render.canvas.Immediate.prototype.drawPolygonGeometry);
+
+goog.exportProperty(
+ ol.render.canvas.Immediate.prototype,
+ 'drawMultiPolygonGeometry',
+ ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry);
+
+goog.exportProperty(
+ ol.render.canvas.Immediate.prototype,
+ 'setFillStrokeStyle',
+ ol.render.canvas.Immediate.prototype.setFillStrokeStyle);
+
+goog.exportProperty(
+ ol.render.canvas.Immediate.prototype,
+ 'setImageStyle',
+ ol.render.canvas.Immediate.prototype.setImageStyle);
+
+goog.exportProperty(
+ ol.render.canvas.Immediate.prototype,
+ 'setTextStyle',
+ ol.render.canvas.Immediate.prototype.setTextStyle);
+
+goog.exportSymbol(
+ 'ol.proj.common.add',
+ ol.proj.common.add,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.proj.METERS_PER_UNIT',
+ ol.proj.METERS_PER_UNIT,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.proj.Projection',
+ ol.proj.Projection,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.proj.Projection.prototype,
+ 'getCode',
+ ol.proj.Projection.prototype.getCode);
+
+goog.exportProperty(
+ ol.proj.Projection.prototype,
+ 'getExtent',
+ ol.proj.Projection.prototype.getExtent);
+
+goog.exportProperty(
+ ol.proj.Projection.prototype,
+ 'getUnits',
+ ol.proj.Projection.prototype.getUnits);
+
+goog.exportProperty(
+ ol.proj.Projection.prototype,
+ 'getMetersPerUnit',
+ ol.proj.Projection.prototype.getMetersPerUnit);
+
+goog.exportProperty(
+ ol.proj.Projection.prototype,
+ 'getWorldExtent',
+ ol.proj.Projection.prototype.getWorldExtent);
+
+goog.exportProperty(
+ ol.proj.Projection.prototype,
+ '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',
+ ol.proj.Projection.prototype.setExtent);
+
+goog.exportProperty(
+ ol.proj.Projection.prototype,
+ '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,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.proj.addProjection',
+ ol.proj.addProjection,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.proj.addCoordinateTransforms',
+ 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,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.proj.getTransform',
+ ol.proj.getTransform,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.proj.transform',
+ ol.proj.transform,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.proj.transformExtent',
+ ol.proj.transformExtent,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.layer.Heatmap',
+ 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,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'getSource',
+ ol.layer.Image.prototype.getSource);
+
+goog.exportSymbol(
+ 'ol.layer.Layer',
+ ol.layer.Layer,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ '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',
+ ol.layer.Layer.prototype.setSource);
+
+goog.exportSymbol(
+ 'ol.layer.Base',
+ ol.layer.Base,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'getExtent',
+ ol.layer.Base.prototype.getExtent);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'getMaxResolution',
+ ol.layer.Base.prototype.getMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'getMinResolution',
+ ol.layer.Base.prototype.getMinResolution);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'getOpacity',
+ ol.layer.Base.prototype.getOpacity);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'getVisible',
+ ol.layer.Base.prototype.getVisible);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'getZIndex',
+ ol.layer.Base.prototype.getZIndex);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'setExtent',
+ ol.layer.Base.prototype.setExtent);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'setMaxResolution',
+ ol.layer.Base.prototype.setMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'setMinResolution',
+ ol.layer.Base.prototype.setMinResolution);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'setOpacity',
+ ol.layer.Base.prototype.setOpacity);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'setVisible',
+ ol.layer.Base.prototype.setVisible);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'setZIndex',
+ ol.layer.Base.prototype.setZIndex);
+
+goog.exportSymbol(
+ 'ol.layer.Group',
+ ol.layer.Group,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'getLayers',
+ ol.layer.Group.prototype.getLayers);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'setLayers',
+ ol.layer.Group.prototype.setLayers);
+
+goog.exportSymbol(
+ 'ol.layer.Tile',
+ ol.layer.Tile,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'getPreload',
+ ol.layer.Tile.prototype.getPreload);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'getSource',
+ ol.layer.Tile.prototype.getSource);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'setPreload',
+ ol.layer.Tile.prototype.setPreload);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'getUseInterimTilesOnError',
+ ol.layer.Tile.prototype.getUseInterimTilesOnError);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'setUseInterimTilesOnError',
+ ol.layer.Tile.prototype.setUseInterimTilesOnError);
+
+goog.exportSymbol(
+ 'ol.layer.Vector',
+ ol.layer.Vector,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'getSource',
+ ol.layer.Vector.prototype.getSource);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'getStyle',
+ ol.layer.Vector.prototype.getStyle);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'getStyleFunction',
+ ol.layer.Vector.prototype.getStyleFunction);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'setStyle',
+ ol.layer.Vector.prototype.setStyle);
+
+goog.exportSymbol(
+ 'ol.layer.VectorTile',
+ ol.layer.VectorTile,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getPreload',
+ ol.layer.VectorTile.prototype.getPreload);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getSource',
+ ol.layer.VectorTile.prototype.getSource);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getUseInterimTilesOnError',
+ ol.layer.VectorTile.prototype.getUseInterimTilesOnError);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'setPreload',
+ ol.layer.VectorTile.prototype.setPreload);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'setUseInterimTilesOnError',
+ ol.layer.VectorTile.prototype.setUseInterimTilesOnError);
+
+goog.exportSymbol(
+ 'ol.interaction.DoubleClickZoom',
+ ol.interaction.DoubleClickZoom,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.DoubleClickZoom.handleEvent',
+ ol.interaction.DoubleClickZoom.handleEvent,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.DragAndDrop',
+ ol.interaction.DragAndDrop,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.DragAndDrop.handleEvent',
+ ol.interaction.DragAndDrop.handleEvent,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.interaction.DragAndDropEvent.prototype,
+ 'features',
+ ol.interaction.DragAndDropEvent.prototype.features);
+
+goog.exportProperty(
+ ol.interaction.DragAndDropEvent.prototype,
+ 'file',
+ ol.interaction.DragAndDropEvent.prototype.file);
+
+goog.exportProperty(
+ ol.interaction.DragAndDropEvent.prototype,
+ 'projection',
+ ol.interaction.DragAndDropEvent.prototype.projection);
+
+goog.exportProperty(
+ ol.DragBoxEvent.prototype,
+ 'coordinate',
+ ol.DragBoxEvent.prototype.coordinate);
+
+goog.exportSymbol(
+ 'ol.interaction.DragBox',
+ ol.interaction.DragBox,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'getGeometry',
+ ol.interaction.DragBox.prototype.getGeometry);
+
+goog.exportSymbol(
+ 'ol.interaction.DragPan',
+ ol.interaction.DragPan,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.DragRotateAndZoom',
+ ol.interaction.DragRotateAndZoom,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.DragRotate',
+ ol.interaction.DragRotate,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.DragZoom',
+ ol.interaction.DragZoom,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.interaction.DrawEvent.prototype,
+ 'feature',
+ ol.interaction.DrawEvent.prototype.feature);
+
+goog.exportSymbol(
+ 'ol.interaction.Draw',
+ ol.interaction.Draw,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.Draw.handleEvent',
+ ol.interaction.Draw.handleEvent,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'removeLastPoint',
+ ol.interaction.Draw.prototype.removeLastPoint);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'finishDrawing',
+ ol.interaction.Draw.prototype.finishDrawing);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'extend',
+ ol.interaction.Draw.prototype.extend);
+
+goog.exportSymbol(
+ 'ol.interaction.Draw.createRegularPolygon',
+ ol.interaction.Draw.createRegularPolygon,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.Interaction',
+ ol.interaction.Interaction,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'getActive',
+ ol.interaction.Interaction.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'getMap',
+ ol.interaction.Interaction.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'setActive',
+ ol.interaction.Interaction.prototype.setActive);
+
+goog.exportSymbol(
+ 'ol.interaction.defaults',
+ ol.interaction.defaults,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.KeyboardPan',
+ ol.interaction.KeyboardPan,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.KeyboardPan.handleEvent',
+ ol.interaction.KeyboardPan.handleEvent,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.KeyboardZoom',
+ ol.interaction.KeyboardZoom,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.KeyboardZoom.handleEvent',
+ ol.interaction.KeyboardZoom.handleEvent,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.interaction.ModifyEvent.prototype,
+ 'features',
+ ol.interaction.ModifyEvent.prototype.features);
+
+goog.exportProperty(
+ ol.interaction.ModifyEvent.prototype,
+ 'mapBrowserPointerEvent',
+ ol.interaction.ModifyEvent.prototype.mapBrowserPointerEvent);
+
+goog.exportSymbol(
+ 'ol.interaction.Modify',
+ ol.interaction.Modify,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.Modify.handleEvent',
+ ol.interaction.Modify.handleEvent,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.MouseWheelZoom',
+ ol.interaction.MouseWheelZoom,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.MouseWheelZoom.handleEvent',
+ ol.interaction.MouseWheelZoom.handleEvent,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'setMouseAnchor',
+ ol.interaction.MouseWheelZoom.prototype.setMouseAnchor);
+
+goog.exportSymbol(
+ 'ol.interaction.PinchRotate',
+ ol.interaction.PinchRotate,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.PinchZoom',
+ ol.interaction.PinchZoom,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.Pointer',
+ ol.interaction.Pointer,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.interaction.Pointer.handleEvent',
+ ol.interaction.Pointer.handleEvent,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.interaction.SelectEvent.prototype,
+ 'selected',
+ ol.interaction.SelectEvent.prototype.selected);
+
+goog.exportProperty(
+ ol.interaction.SelectEvent.prototype,
+ 'deselected',
+ ol.interaction.SelectEvent.prototype.deselected);
+
+goog.exportProperty(
+ ol.interaction.SelectEvent.prototype,
+ 'mapBrowserEvent',
+ ol.interaction.SelectEvent.prototype.mapBrowserEvent);
+
+goog.exportSymbol(
+ 'ol.interaction.Select',
+ ol.interaction.Select,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'getFeatures',
+ ol.interaction.Select.prototype.getFeatures);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'getLayer',
+ ol.interaction.Select.prototype.getLayer);
+
+goog.exportSymbol(
+ 'ol.interaction.Select.handleEvent',
+ ol.interaction.Select.handleEvent,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ '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.exportProperty(
+ ol.interaction.TranslateEvent.prototype,
+ 'features',
+ ol.interaction.TranslateEvent.prototype.features);
+
+goog.exportProperty(
+ ol.interaction.TranslateEvent.prototype,
+ 'coordinate',
+ ol.interaction.TranslateEvent.prototype.coordinate);
+
+goog.exportSymbol(
+ 'ol.interaction.Translate',
+ ol.interaction.Translate,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.geom.Circle',
+ ol.geom.Circle,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'clone',
+ ol.geom.Circle.prototype.clone);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'getCenter',
+ ol.geom.Circle.prototype.getCenter);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'getRadius',
+ ol.geom.Circle.prototype.getRadius);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ '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',
+ ol.geom.Circle.prototype.setCenter);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'setCenterAndRadius',
+ ol.geom.Circle.prototype.setCenterAndRadius);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'setRadius',
+ ol.geom.Circle.prototype.setRadius);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'transform',
+ ol.geom.Circle.prototype.transform);
+
+goog.exportSymbol(
+ 'ol.geom.Geometry',
+ ol.geom.Geometry,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.geom.Geometry.prototype,
+ 'getClosestPoint',
+ ol.geom.Geometry.prototype.getClosestPoint);
+
+goog.exportProperty(
+ ol.geom.Geometry.prototype,
+ 'getExtent',
+ ol.geom.Geometry.prototype.getExtent);
+
+goog.exportProperty(
+ ol.geom.Geometry.prototype,
+ 'simplify',
+ ol.geom.Geometry.prototype.simplify);
+
+goog.exportProperty(
+ ol.geom.Geometry.prototype,
+ 'transform',
+ ol.geom.Geometry.prototype.transform);
+
+goog.exportSymbol(
+ 'ol.geom.GeometryCollection',
+ ol.geom.GeometryCollection,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'clone',
+ ol.geom.GeometryCollection.prototype.clone);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'getGeometries',
+ ol.geom.GeometryCollection.prototype.getGeometries);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'getType',
+ ol.geom.GeometryCollection.prototype.getType);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'intersectsExtent',
+ ol.geom.GeometryCollection.prototype.intersectsExtent);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'setGeometries',
+ ol.geom.GeometryCollection.prototype.setGeometries);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'applyTransform',
+ ol.geom.GeometryCollection.prototype.applyTransform);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'translate',
+ ol.geom.GeometryCollection.prototype.translate);
+
+goog.exportSymbol(
+ 'ol.geom.LinearRing',
+ ol.geom.LinearRing,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'clone',
+ ol.geom.LinearRing.prototype.clone);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'getArea',
+ ol.geom.LinearRing.prototype.getArea);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'getCoordinates',
+ ol.geom.LinearRing.prototype.getCoordinates);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'getType',
+ ol.geom.LinearRing.prototype.getType);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'setCoordinates',
+ ol.geom.LinearRing.prototype.setCoordinates);
+
+goog.exportSymbol(
+ 'ol.geom.LineString',
+ ol.geom.LineString,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'appendCoordinate',
+ ol.geom.LineString.prototype.appendCoordinate);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ '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',
+ ol.geom.LineString.prototype.getCoordinateAtM);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'getCoordinates',
+ ol.geom.LineString.prototype.getCoordinates);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'getLength',
+ ol.geom.LineString.prototype.getLength);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'getType',
+ ol.geom.LineString.prototype.getType);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'intersectsExtent',
+ ol.geom.LineString.prototype.intersectsExtent);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'setCoordinates',
+ ol.geom.LineString.prototype.setCoordinates);
+
+goog.exportSymbol(
+ 'ol.geom.MultiLineString',
+ ol.geom.MultiLineString,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'appendLineString',
+ ol.geom.MultiLineString.prototype.appendLineString);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'clone',
+ ol.geom.MultiLineString.prototype.clone);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'getCoordinateAtM',
+ ol.geom.MultiLineString.prototype.getCoordinateAtM);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'getCoordinates',
+ ol.geom.MultiLineString.prototype.getCoordinates);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'getLineString',
+ ol.geom.MultiLineString.prototype.getLineString);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'getLineStrings',
+ ol.geom.MultiLineString.prototype.getLineStrings);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'getType',
+ ol.geom.MultiLineString.prototype.getType);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'intersectsExtent',
+ ol.geom.MultiLineString.prototype.intersectsExtent);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'setCoordinates',
+ ol.geom.MultiLineString.prototype.setCoordinates);
+
+goog.exportSymbol(
+ 'ol.geom.MultiPoint',
+ ol.geom.MultiPoint,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'appendPoint',
+ ol.geom.MultiPoint.prototype.appendPoint);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'clone',
+ ol.geom.MultiPoint.prototype.clone);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'getCoordinates',
+ ol.geom.MultiPoint.prototype.getCoordinates);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'getPoint',
+ ol.geom.MultiPoint.prototype.getPoint);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'getPoints',
+ ol.geom.MultiPoint.prototype.getPoints);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'getType',
+ ol.geom.MultiPoint.prototype.getType);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'intersectsExtent',
+ ol.geom.MultiPoint.prototype.intersectsExtent);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'setCoordinates',
+ ol.geom.MultiPoint.prototype.setCoordinates);
+
+goog.exportSymbol(
+ 'ol.geom.MultiPolygon',
+ ol.geom.MultiPolygon,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'appendPolygon',
+ ol.geom.MultiPolygon.prototype.appendPolygon);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'clone',
+ ol.geom.MultiPolygon.prototype.clone);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'getArea',
+ ol.geom.MultiPolygon.prototype.getArea);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'getCoordinates',
+ ol.geom.MultiPolygon.prototype.getCoordinates);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'getInteriorPoints',
+ ol.geom.MultiPolygon.prototype.getInteriorPoints);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'getPolygon',
+ ol.geom.MultiPolygon.prototype.getPolygon);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'getPolygons',
+ ol.geom.MultiPolygon.prototype.getPolygons);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'getType',
+ ol.geom.MultiPolygon.prototype.getType);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'intersectsExtent',
+ ol.geom.MultiPolygon.prototype.intersectsExtent);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'setCoordinates',
+ ol.geom.MultiPolygon.prototype.setCoordinates);
+
+goog.exportSymbol(
+ 'ol.geom.Point',
+ ol.geom.Point,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'clone',
+ ol.geom.Point.prototype.clone);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'getCoordinates',
+ ol.geom.Point.prototype.getCoordinates);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'getType',
+ ol.geom.Point.prototype.getType);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'intersectsExtent',
+ ol.geom.Point.prototype.intersectsExtent);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'setCoordinates',
+ ol.geom.Point.prototype.setCoordinates);
+
+goog.exportSymbol(
+ 'ol.geom.Polygon',
+ ol.geom.Polygon,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'appendLinearRing',
+ ol.geom.Polygon.prototype.appendLinearRing);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'clone',
+ ol.geom.Polygon.prototype.clone);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getArea',
+ ol.geom.Polygon.prototype.getArea);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getCoordinates',
+ ol.geom.Polygon.prototype.getCoordinates);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getInteriorPoint',
+ ol.geom.Polygon.prototype.getInteriorPoint);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getLinearRingCount',
+ ol.geom.Polygon.prototype.getLinearRingCount);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getLinearRing',
+ ol.geom.Polygon.prototype.getLinearRing);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getLinearRings',
+ ol.geom.Polygon.prototype.getLinearRings);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getType',
+ ol.geom.Polygon.prototype.getType);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'intersectsExtent',
+ ol.geom.Polygon.prototype.intersectsExtent);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'setCoordinates',
+ ol.geom.Polygon.prototype.setCoordinates);
+
+goog.exportSymbol(
+ 'ol.geom.Polygon.circular',
+ ol.geom.Polygon.circular,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.geom.Polygon.fromExtent',
+ 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,
+ 'getFirstCoordinate',
+ ol.geom.SimpleGeometry.prototype.getFirstCoordinate);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'getLastCoordinate',
+ ol.geom.SimpleGeometry.prototype.getLastCoordinate);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'getLayout',
+ ol.geom.SimpleGeometry.prototype.getLayout);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'applyTransform',
+ ol.geom.SimpleGeometry.prototype.applyTransform);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ '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,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.format.GeoJSON',
+ ol.format.GeoJSON,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.GeoJSON.prototype,
+ 'readFeature',
+ ol.format.GeoJSON.prototype.readFeature);
+
+goog.exportProperty(
+ ol.format.GeoJSON.prototype,
+ 'readFeatures',
+ ol.format.GeoJSON.prototype.readFeatures);
+
+goog.exportProperty(
+ ol.format.GeoJSON.prototype,
+ 'readGeometry',
+ ol.format.GeoJSON.prototype.readGeometry);
+
+goog.exportProperty(
+ ol.format.GeoJSON.prototype,
+ 'readProjection',
+ ol.format.GeoJSON.prototype.readProjection);
+
+goog.exportProperty(
+ ol.format.GeoJSON.prototype,
+ 'writeFeature',
+ ol.format.GeoJSON.prototype.writeFeature);
+
+goog.exportProperty(
+ ol.format.GeoJSON.prototype,
+ 'writeFeatureObject',
+ ol.format.GeoJSON.prototype.writeFeatureObject);
+
+goog.exportProperty(
+ ol.format.GeoJSON.prototype,
+ 'writeFeatures',
+ ol.format.GeoJSON.prototype.writeFeatures);
+
+goog.exportProperty(
+ ol.format.GeoJSON.prototype,
+ 'writeFeaturesObject',
+ ol.format.GeoJSON.prototype.writeFeaturesObject);
+
+goog.exportProperty(
+ ol.format.GeoJSON.prototype,
+ 'writeGeometry',
+ ol.format.GeoJSON.prototype.writeGeometry);
+
+goog.exportProperty(
+ ol.format.GeoJSON.prototype,
+ 'writeGeometryObject',
+ ol.format.GeoJSON.prototype.writeGeometryObject);
+
+goog.exportSymbol(
+ 'ol.format.GPX',
+ ol.format.GPX,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.GPX.prototype,
+ 'readFeature',
+ ol.format.GPX.prototype.readFeature);
+
+goog.exportProperty(
+ ol.format.GPX.prototype,
+ 'readFeatures',
+ ol.format.GPX.prototype.readFeatures);
+
+goog.exportProperty(
+ ol.format.GPX.prototype,
+ 'readProjection',
+ ol.format.GPX.prototype.readProjection);
+
+goog.exportProperty(
+ ol.format.GPX.prototype,
+ 'writeFeatures',
+ ol.format.GPX.prototype.writeFeatures);
+
+goog.exportProperty(
+ ol.format.GPX.prototype,
+ 'writeFeaturesNode',
+ ol.format.GPX.prototype.writeFeaturesNode);
+
+goog.exportSymbol(
+ 'ol.format.IGC',
+ ol.format.IGC,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.IGC.prototype,
+ 'readFeature',
+ ol.format.IGC.prototype.readFeature);
+
+goog.exportProperty(
+ ol.format.IGC.prototype,
+ 'readFeatures',
+ ol.format.IGC.prototype.readFeatures);
+
+goog.exportProperty(
+ ol.format.IGC.prototype,
+ 'readProjection',
+ ol.format.IGC.prototype.readProjection);
+
+goog.exportSymbol(
+ 'ol.format.KML',
+ ol.format.KML,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.KML.prototype,
+ 'readFeature',
+ ol.format.KML.prototype.readFeature);
+
+goog.exportProperty(
+ ol.format.KML.prototype,
+ 'readFeatures',
+ ol.format.KML.prototype.readFeatures);
+
+goog.exportProperty(
+ ol.format.KML.prototype,
+ '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',
+ ol.format.KML.prototype.readProjection);
+
+goog.exportProperty(
+ ol.format.KML.prototype,
+ 'writeFeatures',
+ ol.format.KML.prototype.writeFeatures);
+
+goog.exportProperty(
+ ol.format.KML.prototype,
+ 'writeFeaturesNode',
+ ol.format.KML.prototype.writeFeaturesNode);
+
+goog.exportSymbol(
+ 'ol.format.MVT',
+ ol.format.MVT,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.MVT.prototype,
+ 'setLayers',
+ ol.format.MVT.prototype.setLayers);
+
+goog.exportSymbol(
+ 'ol.format.OSMXML',
+ ol.format.OSMXML,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.OSMXML.prototype,
+ 'readFeatures',
+ ol.format.OSMXML.prototype.readFeatures);
+
+goog.exportProperty(
+ ol.format.OSMXML.prototype,
+ 'readProjection',
+ ol.format.OSMXML.prototype.readProjection);
+
+goog.exportSymbol(
+ 'ol.format.Polyline',
+ ol.format.Polyline,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.format.Polyline.encodeDeltas',
+ ol.format.Polyline.encodeDeltas,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.format.Polyline.decodeDeltas',
+ ol.format.Polyline.decodeDeltas,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.format.Polyline.encodeFloats',
+ ol.format.Polyline.encodeFloats,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.format.Polyline.decodeFloats',
+ ol.format.Polyline.decodeFloats,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.Polyline.prototype,
+ 'readFeature',
+ ol.format.Polyline.prototype.readFeature);
+
+goog.exportProperty(
+ ol.format.Polyline.prototype,
+ 'readFeatures',
+ ol.format.Polyline.prototype.readFeatures);
+
+goog.exportProperty(
+ ol.format.Polyline.prototype,
+ 'readGeometry',
+ ol.format.Polyline.prototype.readGeometry);
+
+goog.exportProperty(
+ ol.format.Polyline.prototype,
+ 'readProjection',
+ ol.format.Polyline.prototype.readProjection);
+
+goog.exportProperty(
+ ol.format.Polyline.prototype,
+ 'writeGeometry',
+ ol.format.Polyline.prototype.writeGeometry);
+
+goog.exportSymbol(
+ 'ol.format.TopoJSON',
+ ol.format.TopoJSON,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.TopoJSON.prototype,
+ 'readFeatures',
+ ol.format.TopoJSON.prototype.readFeatures);
+
+goog.exportProperty(
+ ol.format.TopoJSON.prototype,
+ 'readProjection',
+ ol.format.TopoJSON.prototype.readProjection);
+
+goog.exportSymbol(
+ 'ol.format.WFS',
+ ol.format.WFS,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.WFS.prototype,
+ 'readFeatures',
+ ol.format.WFS.prototype.readFeatures);
+
+goog.exportProperty(
+ ol.format.WFS.prototype,
+ 'readTransactionResponse',
+ ol.format.WFS.prototype.readTransactionResponse);
+
+goog.exportProperty(
+ ol.format.WFS.prototype,
+ 'readFeatureCollectionMetadata',
+ ol.format.WFS.prototype.readFeatureCollectionMetadata);
+
+goog.exportProperty(
+ ol.format.WFS.prototype,
+ 'writeGetFeature',
+ ol.format.WFS.prototype.writeGetFeature);
+
+goog.exportProperty(
+ ol.format.WFS.prototype,
+ 'writeTransaction',
+ ol.format.WFS.prototype.writeTransaction);
+
+goog.exportProperty(
+ ol.format.WFS.prototype,
+ 'readProjection',
+ ol.format.WFS.prototype.readProjection);
+
+goog.exportSymbol(
+ 'ol.format.WKT',
+ ol.format.WKT,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.WKT.prototype,
+ 'readFeature',
+ ol.format.WKT.prototype.readFeature);
+
+goog.exportProperty(
+ ol.format.WKT.prototype,
+ 'readFeatures',
+ ol.format.WKT.prototype.readFeatures);
+
+goog.exportProperty(
+ ol.format.WKT.prototype,
+ 'readGeometry',
+ ol.format.WKT.prototype.readGeometry);
+
+goog.exportProperty(
+ ol.format.WKT.prototype,
+ 'writeFeature',
+ ol.format.WKT.prototype.writeFeature);
+
+goog.exportProperty(
+ ol.format.WKT.prototype,
+ 'writeFeatures',
+ ol.format.WKT.prototype.writeFeatures);
+
+goog.exportProperty(
+ ol.format.WKT.prototype,
+ 'writeGeometry',
+ ol.format.WKT.prototype.writeGeometry);
+
+goog.exportSymbol(
+ 'ol.format.WMSCapabilities',
+ ol.format.WMSCapabilities,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.WMSCapabilities.prototype,
+ 'read',
+ ol.format.WMSCapabilities.prototype.read);
+
+goog.exportSymbol(
+ 'ol.format.WMSGetFeatureInfo',
+ ol.format.WMSGetFeatureInfo,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.WMSGetFeatureInfo.prototype,
+ '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,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.format.GML3',
+ ol.format.GML3,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.GML3.prototype,
+ 'writeGeometryNode',
+ ol.format.GML3.prototype.writeGeometryNode);
+
+goog.exportProperty(
+ ol.format.GML3.prototype,
+ 'writeFeatures',
+ ol.format.GML3.prototype.writeFeatures);
+
+goog.exportProperty(
+ ol.format.GML3.prototype,
+ 'writeFeaturesNode',
+ ol.format.GML3.prototype.writeFeaturesNode);
+
+goog.exportSymbol(
+ 'ol.format.GML',
+ ol.format.GML,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.format.GML.prototype,
+ 'writeFeatures',
+ ol.format.GML.prototype.writeFeatures);
+
+goog.exportProperty(
+ ol.format.GML.prototype,
+ 'writeFeaturesNode',
+ ol.format.GML.prototype.writeFeaturesNode);
+
+goog.exportProperty(
+ ol.format.GMLBase.prototype,
+ 'readFeatures',
+ ol.format.GMLBase.prototype.readFeatures);
+
+goog.exportSymbol(
+ 'ol.events.condition.altKeyOnly',
+ ol.events.condition.altKeyOnly,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.events.condition.altShiftKeysOnly',
+ ol.events.condition.altShiftKeysOnly,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.events.condition.always',
+ ol.events.condition.always,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.events.condition.click',
+ ol.events.condition.click,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.events.condition.never',
+ ol.events.condition.never,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.events.condition.pointerMove',
+ ol.events.condition.pointerMove,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.events.condition.singleClick',
+ 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,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.events.condition.platformModifierKeyOnly',
+ ol.events.condition.platformModifierKeyOnly,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.events.condition.shiftKeyOnly',
+ ol.events.condition.shiftKeyOnly,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.events.condition.targetNotEditable',
+ ol.events.condition.targetNotEditable,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.events.condition.mouseOnly',
+ ol.events.condition.mouseOnly,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.control.Attribution',
+ ol.control.Attribution,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.control.Attribution.render',
+ ol.control.Attribution.render,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'getCollapsible',
+ ol.control.Attribution.prototype.getCollapsible);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'setCollapsible',
+ ol.control.Attribution.prototype.setCollapsible);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'setCollapsed',
+ ol.control.Attribution.prototype.setCollapsed);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'getCollapsed',
+ ol.control.Attribution.prototype.getCollapsed);
+
+goog.exportSymbol(
+ 'ol.control.Control',
+ ol.control.Control,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'getMap',
+ ol.control.Control.prototype.getMap);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ '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,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.control.FullScreen',
+ ol.control.FullScreen,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.control.MousePosition',
+ ol.control.MousePosition,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.control.MousePosition.render',
+ ol.control.MousePosition.render,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'getCoordinateFormat',
+ ol.control.MousePosition.prototype.getCoordinateFormat);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'getProjection',
+ ol.control.MousePosition.prototype.getProjection);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'setCoordinateFormat',
+ ol.control.MousePosition.prototype.setCoordinateFormat);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'setProjection',
+ ol.control.MousePosition.prototype.setProjection);
+
+goog.exportSymbol(
+ 'ol.control.OverviewMap',
+ ol.control.OverviewMap,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.control.OverviewMap.render',
+ ol.control.OverviewMap.render,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'getCollapsible',
+ ol.control.OverviewMap.prototype.getCollapsible);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'setCollapsible',
+ ol.control.OverviewMap.prototype.setCollapsible);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'setCollapsed',
+ ol.control.OverviewMap.prototype.setCollapsed);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'getCollapsed',
+ ol.control.OverviewMap.prototype.getCollapsed);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'getOverviewMap',
+ ol.control.OverviewMap.prototype.getOverviewMap);
+
+goog.exportSymbol(
+ 'ol.control.Rotate',
+ ol.control.Rotate,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.control.Rotate.render',
+ ol.control.Rotate.render,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.control.ScaleLine',
+ ol.control.ScaleLine,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'getUnits',
+ ol.control.ScaleLine.prototype.getUnits);
+
+goog.exportSymbol(
+ 'ol.control.ScaleLine.render',
+ ol.control.ScaleLine.render,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'setUnits',
+ ol.control.ScaleLine.prototype.setUnits);
+
+goog.exportSymbol(
+ 'ol.control.Zoom',
+ ol.control.Zoom,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.control.ZoomSlider',
+ ol.control.ZoomSlider,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.control.ZoomSlider.render',
+ ol.control.ZoomSlider.render,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.control.ZoomToExtent',
+ ol.control.ZoomToExtent,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.color.asArray',
+ ol.color.asArray,
+ OPENLAYERS);
+
+goog.exportSymbol(
+ 'ol.color.asString',
+ ol.color.asString,
+ OPENLAYERS);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'changed',
+ ol.Object.prototype.changed);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'dispatchEvent',
+ ol.Object.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'getRevision',
+ ol.Object.prototype.getRevision);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'on',
+ ol.Object.prototype.on);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'once',
+ ol.Object.prototype.once);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'un',
+ ol.Object.prototype.un);
+
+goog.exportProperty(
+ ol.Object.prototype,
+ 'unByKey',
+ ol.Object.prototype.unByKey);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'get',
+ ol.Collection.prototype.get);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'getKeys',
+ ol.Collection.prototype.getKeys);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'getProperties',
+ ol.Collection.prototype.getProperties);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'set',
+ ol.Collection.prototype.set);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'setProperties',
+ ol.Collection.prototype.setProperties);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'unset',
+ ol.Collection.prototype.unset);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'changed',
+ ol.Collection.prototype.changed);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'dispatchEvent',
+ ol.Collection.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'getRevision',
+ ol.Collection.prototype.getRevision);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'on',
+ ol.Collection.prototype.on);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'once',
+ ol.Collection.prototype.once);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'un',
+ ol.Collection.prototype.un);
+
+goog.exportProperty(
+ ol.Collection.prototype,
+ 'unByKey',
+ ol.Collection.prototype.unByKey);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'get',
+ ol.DeviceOrientation.prototype.get);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'getKeys',
+ ol.DeviceOrientation.prototype.getKeys);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'getProperties',
+ ol.DeviceOrientation.prototype.getProperties);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'set',
+ ol.DeviceOrientation.prototype.set);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'setProperties',
+ ol.DeviceOrientation.prototype.setProperties);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'unset',
+ ol.DeviceOrientation.prototype.unset);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'changed',
+ ol.DeviceOrientation.prototype.changed);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'dispatchEvent',
+ ol.DeviceOrientation.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'getRevision',
+ ol.DeviceOrientation.prototype.getRevision);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'on',
+ ol.DeviceOrientation.prototype.on);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'once',
+ ol.DeviceOrientation.prototype.once);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'un',
+ ol.DeviceOrientation.prototype.un);
+
+goog.exportProperty(
+ ol.DeviceOrientation.prototype,
+ 'unByKey',
+ ol.DeviceOrientation.prototype.unByKey);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'get',
+ ol.Feature.prototype.get);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'getKeys',
+ ol.Feature.prototype.getKeys);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'getProperties',
+ ol.Feature.prototype.getProperties);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'set',
+ ol.Feature.prototype.set);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'setProperties',
+ ol.Feature.prototype.setProperties);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'unset',
+ ol.Feature.prototype.unset);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'changed',
+ ol.Feature.prototype.changed);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'dispatchEvent',
+ ol.Feature.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'getRevision',
+ ol.Feature.prototype.getRevision);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'on',
+ ol.Feature.prototype.on);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'once',
+ ol.Feature.prototype.once);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'un',
+ ol.Feature.prototype.un);
+
+goog.exportProperty(
+ ol.Feature.prototype,
+ 'unByKey',
+ ol.Feature.prototype.unByKey);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'get',
+ ol.Geolocation.prototype.get);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getKeys',
+ ol.Geolocation.prototype.getKeys);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getProperties',
+ ol.Geolocation.prototype.getProperties);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'set',
+ ol.Geolocation.prototype.set);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'setProperties',
+ ol.Geolocation.prototype.setProperties);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'unset',
+ ol.Geolocation.prototype.unset);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'changed',
+ ol.Geolocation.prototype.changed);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'dispatchEvent',
+ ol.Geolocation.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'getRevision',
+ ol.Geolocation.prototype.getRevision);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'on',
+ ol.Geolocation.prototype.on);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'once',
+ ol.Geolocation.prototype.once);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'un',
+ ol.Geolocation.prototype.un);
+
+goog.exportProperty(
+ ol.Geolocation.prototype,
+ 'unByKey',
+ ol.Geolocation.prototype.unByKey);
+
+goog.exportProperty(
+ ol.ImageTile.prototype,
+ 'getTileCoord',
+ ol.ImageTile.prototype.getTileCoord);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'get',
+ ol.Map.prototype.get);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getKeys',
+ ol.Map.prototype.getKeys);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getProperties',
+ ol.Map.prototype.getProperties);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'set',
+ ol.Map.prototype.set);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'setProperties',
+ ol.Map.prototype.setProperties);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'unset',
+ ol.Map.prototype.unset);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'changed',
+ ol.Map.prototype.changed);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'dispatchEvent',
+ ol.Map.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getRevision',
+ ol.Map.prototype.getRevision);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'on',
+ ol.Map.prototype.on);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'once',
+ ol.Map.prototype.once);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'un',
+ ol.Map.prototype.un);
+
+goog.exportProperty(
+ ol.Map.prototype,
+ 'unByKey',
+ ol.Map.prototype.unByKey);
+
+goog.exportProperty(
+ ol.MapBrowserEvent.prototype,
+ 'map',
+ ol.MapBrowserEvent.prototype.map);
+
+goog.exportProperty(
+ ol.MapBrowserEvent.prototype,
+ 'frameState',
+ ol.MapBrowserEvent.prototype.frameState);
+
+goog.exportProperty(
+ ol.MapBrowserPointerEvent.prototype,
+ 'originalEvent',
+ ol.MapBrowserPointerEvent.prototype.originalEvent);
+
+goog.exportProperty(
+ ol.MapBrowserPointerEvent.prototype,
+ 'pixel',
+ ol.MapBrowserPointerEvent.prototype.pixel);
+
+goog.exportProperty(
+ ol.MapBrowserPointerEvent.prototype,
+ 'coordinate',
+ ol.MapBrowserPointerEvent.prototype.coordinate);
+
+goog.exportProperty(
+ ol.MapBrowserPointerEvent.prototype,
+ 'dragging',
+ ol.MapBrowserPointerEvent.prototype.dragging);
+
+goog.exportProperty(
+ ol.MapBrowserPointerEvent.prototype,
+ 'preventDefault',
+ ol.MapBrowserPointerEvent.prototype.preventDefault);
+
+goog.exportProperty(
+ ol.MapBrowserPointerEvent.prototype,
+ 'stopPropagation',
+ ol.MapBrowserPointerEvent.prototype.stopPropagation);
+
+goog.exportProperty(
+ ol.MapBrowserPointerEvent.prototype,
+ 'map',
+ ol.MapBrowserPointerEvent.prototype.map);
+
+goog.exportProperty(
+ ol.MapBrowserPointerEvent.prototype,
+ 'frameState',
+ ol.MapBrowserPointerEvent.prototype.frameState);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'get',
+ ol.Overlay.prototype.get);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'getKeys',
+ ol.Overlay.prototype.getKeys);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'getProperties',
+ ol.Overlay.prototype.getProperties);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'set',
+ ol.Overlay.prototype.set);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'setProperties',
+ ol.Overlay.prototype.setProperties);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'unset',
+ ol.Overlay.prototype.unset);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'changed',
+ ol.Overlay.prototype.changed);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'dispatchEvent',
+ ol.Overlay.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'getRevision',
+ ol.Overlay.prototype.getRevision);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'on',
+ ol.Overlay.prototype.on);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'once',
+ ol.Overlay.prototype.once);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'un',
+ ol.Overlay.prototype.un);
+
+goog.exportProperty(
+ ol.Overlay.prototype,
+ 'unByKey',
+ ol.Overlay.prototype.unByKey);
+
+goog.exportProperty(
+ ol.VectorTile.prototype,
+ 'getTileCoord',
+ ol.VectorTile.prototype.getTileCoord);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'get',
+ ol.View.prototype.get);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'getKeys',
+ ol.View.prototype.getKeys);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'getProperties',
+ ol.View.prototype.getProperties);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'set',
+ ol.View.prototype.set);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'setProperties',
+ ol.View.prototype.setProperties);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'unset',
+ ol.View.prototype.unset);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'changed',
+ ol.View.prototype.changed);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'dispatchEvent',
+ ol.View.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'getRevision',
+ ol.View.prototype.getRevision);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'on',
+ ol.View.prototype.on);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'once',
+ ol.View.prototype.once);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'un',
+ ol.View.prototype.un);
+
+goog.exportProperty(
+ ol.View.prototype,
+ 'unByKey',
+ ol.View.prototype.unByKey);
+
+goog.exportProperty(
+ ol.tilegrid.WMTS.prototype,
+ 'getMaxZoom',
+ ol.tilegrid.WMTS.prototype.getMaxZoom);
+
+goog.exportProperty(
+ ol.tilegrid.WMTS.prototype,
+ 'getMinZoom',
+ ol.tilegrid.WMTS.prototype.getMinZoom);
+
+goog.exportProperty(
+ ol.tilegrid.WMTS.prototype,
+ 'getOrigin',
+ ol.tilegrid.WMTS.prototype.getOrigin);
+
+goog.exportProperty(
+ ol.tilegrid.WMTS.prototype,
+ 'getResolution',
+ ol.tilegrid.WMTS.prototype.getResolution);
+
+goog.exportProperty(
+ ol.tilegrid.WMTS.prototype,
+ 'getResolutions',
+ ol.tilegrid.WMTS.prototype.getResolutions);
+
+goog.exportProperty(
+ ol.tilegrid.WMTS.prototype,
+ 'getTileCoordExtent',
+ ol.tilegrid.WMTS.prototype.getTileCoordExtent);
+
+goog.exportProperty(
+ ol.tilegrid.WMTS.prototype,
+ 'getTileCoordForCoordAndResolution',
+ ol.tilegrid.WMTS.prototype.getTileCoordForCoordAndResolution);
+
+goog.exportProperty(
+ ol.tilegrid.WMTS.prototype,
+ 'getTileCoordForCoordAndZ',
+ ol.tilegrid.WMTS.prototype.getTileCoordForCoordAndZ);
+
+goog.exportProperty(
+ ol.tilegrid.WMTS.prototype,
+ 'getTileSize',
+ ol.tilegrid.WMTS.prototype.getTileSize);
+
+goog.exportProperty(
+ ol.style.Circle.prototype,
+ 'getOpacity',
+ ol.style.Circle.prototype.getOpacity);
+
+goog.exportProperty(
+ ol.style.Circle.prototype,
+ 'getRotateWithView',
+ ol.style.Circle.prototype.getRotateWithView);
+
+goog.exportProperty(
+ ol.style.Circle.prototype,
+ 'getRotation',
+ ol.style.Circle.prototype.getRotation);
+
+goog.exportProperty(
+ ol.style.Circle.prototype,
+ 'getScale',
+ ol.style.Circle.prototype.getScale);
+
+goog.exportProperty(
+ ol.style.Circle.prototype,
+ 'getSnapToPixel',
+ ol.style.Circle.prototype.getSnapToPixel);
+
+goog.exportProperty(
+ ol.style.Circle.prototype,
+ 'setOpacity',
+ ol.style.Circle.prototype.setOpacity);
+
+goog.exportProperty(
+ ol.style.Circle.prototype,
+ 'setRotation',
+ ol.style.Circle.prototype.setRotation);
+
+goog.exportProperty(
+ ol.style.Circle.prototype,
+ 'setScale',
+ ol.style.Circle.prototype.setScale);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'getOpacity',
+ ol.style.Icon.prototype.getOpacity);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'getRotateWithView',
+ ol.style.Icon.prototype.getRotateWithView);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'getRotation',
+ ol.style.Icon.prototype.getRotation);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'getScale',
+ ol.style.Icon.prototype.getScale);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'getSnapToPixel',
+ ol.style.Icon.prototype.getSnapToPixel);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'setOpacity',
+ ol.style.Icon.prototype.setOpacity);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'setRotation',
+ ol.style.Icon.prototype.setRotation);
+
+goog.exportProperty(
+ ol.style.Icon.prototype,
+ 'setScale',
+ ol.style.Icon.prototype.setScale);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getOpacity',
+ ol.style.RegularShape.prototype.getOpacity);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getRotateWithView',
+ ol.style.RegularShape.prototype.getRotateWithView);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getRotation',
+ ol.style.RegularShape.prototype.getRotation);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getScale',
+ ol.style.RegularShape.prototype.getScale);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'getSnapToPixel',
+ ol.style.RegularShape.prototype.getSnapToPixel);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'setOpacity',
+ ol.style.RegularShape.prototype.setOpacity);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ 'setRotation',
+ ol.style.RegularShape.prototype.setRotation);
+
+goog.exportProperty(
+ ol.style.RegularShape.prototype,
+ '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',
+ ol.source.Source.prototype.changed);
+
+goog.exportProperty(
+ ol.source.Source.prototype,
+ 'dispatchEvent',
+ ol.source.Source.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.Source.prototype,
+ 'getRevision',
+ ol.source.Source.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.Source.prototype,
+ 'on',
+ ol.source.Source.prototype.on);
+
+goog.exportProperty(
+ ol.source.Source.prototype,
+ 'once',
+ ol.source.Source.prototype.once);
+
+goog.exportProperty(
+ ol.source.Source.prototype,
+ 'un',
+ ol.source.Source.prototype.un);
+
+goog.exportProperty(
+ ol.source.Source.prototype,
+ 'unByKey',
+ ol.source.Source.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'getAttributions',
+ ol.source.Tile.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'getLogo',
+ ol.source.Tile.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'getProjection',
+ ol.source.Tile.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'getState',
+ ol.source.Tile.prototype.getState);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'setAttributions',
+ ol.source.Tile.prototype.setAttributions);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'get',
+ ol.source.Tile.prototype.get);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'getKeys',
+ ol.source.Tile.prototype.getKeys);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'getProperties',
+ ol.source.Tile.prototype.getProperties);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'set',
+ ol.source.Tile.prototype.set);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'setProperties',
+ ol.source.Tile.prototype.setProperties);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'unset',
+ ol.source.Tile.prototype.unset);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'changed',
+ ol.source.Tile.prototype.changed);
+
+goog.exportProperty(
+ ol.source.Tile.prototype,
+ 'dispatchEvent',
+ ol.source.Tile.prototype.dispatchEvent);
+
+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.UrlTile.prototype,
+ 'getTileGrid',
+ ol.source.UrlTile.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'getAttributions',
+ ol.source.UrlTile.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'getLogo',
+ ol.source.UrlTile.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'getProjection',
+ ol.source.UrlTile.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'getState',
+ ol.source.UrlTile.prototype.getState);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'setAttributions',
+ ol.source.UrlTile.prototype.setAttributions);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'get',
+ ol.source.UrlTile.prototype.get);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'getKeys',
+ ol.source.UrlTile.prototype.getKeys);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'getProperties',
+ ol.source.UrlTile.prototype.getProperties);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'set',
+ ol.source.UrlTile.prototype.set);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'setProperties',
+ ol.source.UrlTile.prototype.setProperties);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'unset',
+ ol.source.UrlTile.prototype.unset);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'changed',
+ ol.source.UrlTile.prototype.changed);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'dispatchEvent',
+ ol.source.UrlTile.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'getRevision',
+ ol.source.UrlTile.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'on',
+ ol.source.UrlTile.prototype.on);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'once',
+ ol.source.UrlTile.prototype.once);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'un',
+ ol.source.UrlTile.prototype.un);
+
+goog.exportProperty(
+ ol.source.UrlTile.prototype,
+ 'unByKey',
+ ol.source.UrlTile.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'getTileLoadFunction',
+ ol.source.TileImage.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'getTileUrlFunction',
+ ol.source.TileImage.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'getUrls',
+ ol.source.TileImage.prototype.getUrls);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'setTileLoadFunction',
+ ol.source.TileImage.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'setTileUrlFunction',
+ ol.source.TileImage.prototype.setTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'setUrl',
+ ol.source.TileImage.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'setUrls',
+ ol.source.TileImage.prototype.setUrls);
+
+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,
+ 'setAttributions',
+ ol.source.TileImage.prototype.setAttributions);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'get',
+ ol.source.TileImage.prototype.get);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'getKeys',
+ ol.source.TileImage.prototype.getKeys);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'getProperties',
+ ol.source.TileImage.prototype.getProperties);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'set',
+ ol.source.TileImage.prototype.set);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'setProperties',
+ ol.source.TileImage.prototype.setProperties);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'unset',
+ ol.source.TileImage.prototype.unset);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'changed',
+ ol.source.TileImage.prototype.changed);
+
+goog.exportProperty(
+ ol.source.TileImage.prototype,
+ 'dispatchEvent',
+ ol.source.TileImage.prototype.dispatchEvent);
+
+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,
+ 'setRenderReprojectionEdges',
+ ol.source.BingMaps.prototype.setRenderReprojectionEdges);
+
+goog.exportProperty(
+ ol.source.BingMaps.prototype,
+ 'setTileGridForProjection',
+ ol.source.BingMaps.prototype.setTileGridForProjection);
+
+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,
+ 'getUrls',
+ ol.source.BingMaps.prototype.getUrls);
+
+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,
+ 'setUrl',
+ ol.source.BingMaps.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.BingMaps.prototype,
+ 'setUrls',
+ ol.source.BingMaps.prototype.setUrls);
+
+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,
+ 'setAttributions',
+ ol.source.BingMaps.prototype.setAttributions);
+
+goog.exportProperty(
+ ol.source.BingMaps.prototype,
+ 'get',
+ ol.source.BingMaps.prototype.get);
+
+goog.exportProperty(
+ ol.source.BingMaps.prototype,
+ 'getKeys',
+ ol.source.BingMaps.prototype.getKeys);
+
+goog.exportProperty(
+ ol.source.BingMaps.prototype,
+ 'getProperties',
+ ol.source.BingMaps.prototype.getProperties);
+
+goog.exportProperty(
+ ol.source.BingMaps.prototype,
+ 'set',
+ ol.source.BingMaps.prototype.set);
+
+goog.exportProperty(
+ ol.source.BingMaps.prototype,
+ 'setProperties',
+ ol.source.BingMaps.prototype.setProperties);
+
+goog.exportProperty(
+ ol.source.BingMaps.prototype,
+ 'unset',
+ ol.source.BingMaps.prototype.unset);
+
+goog.exportProperty(
+ ol.source.BingMaps.prototype,
+ 'changed',
+ ol.source.BingMaps.prototype.changed);
+
+goog.exportProperty(
+ ol.source.BingMaps.prototype,
+ 'dispatchEvent',
+ ol.source.BingMaps.prototype.dispatchEvent);
+
+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,
+ 'setAttributions',
+ ol.source.Vector.prototype.setAttributions);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'get',
+ ol.source.Vector.prototype.get);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'getKeys',
+ ol.source.Vector.prototype.getKeys);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'getProperties',
+ ol.source.Vector.prototype.getProperties);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'set',
+ ol.source.Vector.prototype.set);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'setProperties',
+ ol.source.Vector.prototype.setProperties);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'unset',
+ ol.source.Vector.prototype.unset);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'changed',
+ ol.source.Vector.prototype.changed);
+
+goog.exportProperty(
+ ol.source.Vector.prototype,
+ 'dispatchEvent',
+ ol.source.Vector.prototype.dispatchEvent);
+
+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,
+ 'getFeaturesCollection',
+ ol.source.Cluster.prototype.getFeaturesCollection);
+
+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,
+ 'getFeaturesInExtent',
+ ol.source.Cluster.prototype.getFeaturesInExtent);
+
+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,
+ 'setAttributions',
+ ol.source.Cluster.prototype.setAttributions);
+
+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.Cluster.prototype,
+ 'changed',
+ ol.source.Cluster.prototype.changed);
+
+goog.exportProperty(
+ ol.source.Cluster.prototype,
+ 'dispatchEvent',
+ ol.source.Cluster.prototype.dispatchEvent);
+
+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.Image.prototype,
+ 'getAttributions',
+ ol.source.Image.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.Image.prototype,
+ 'getLogo',
+ ol.source.Image.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.Image.prototype,
+ 'getProjection',
+ ol.source.Image.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.Image.prototype,
+ 'getState',
+ ol.source.Image.prototype.getState);
+
+goog.exportProperty(
+ ol.source.Image.prototype,
+ 'setAttributions',
+ ol.source.Image.prototype.setAttributions);
+
+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',
+ ol.source.Image.prototype.changed);
+
+goog.exportProperty(
+ ol.source.Image.prototype,
+ 'dispatchEvent',
+ ol.source.Image.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.Image.prototype,
+ 'getRevision',
+ ol.source.Image.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.Image.prototype,
+ 'on',
+ ol.source.Image.prototype.on);
+
+goog.exportProperty(
+ ol.source.Image.prototype,
+ 'once',
+ ol.source.Image.prototype.once);
+
+goog.exportProperty(
+ ol.source.Image.prototype,
+ 'un',
+ ol.source.Image.prototype.un);
+
+goog.exportProperty(
+ ol.source.Image.prototype,
+ 'unByKey',
+ ol.source.Image.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.ImageCanvas.prototype,
+ 'getAttributions',
+ ol.source.ImageCanvas.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.ImageCanvas.prototype,
+ 'getLogo',
+ ol.source.ImageCanvas.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.ImageCanvas.prototype,
+ 'getProjection',
+ ol.source.ImageCanvas.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.ImageCanvas.prototype,
+ 'getState',
+ ol.source.ImageCanvas.prototype.getState);
+
+goog.exportProperty(
+ ol.source.ImageCanvas.prototype,
+ 'setAttributions',
+ ol.source.ImageCanvas.prototype.setAttributions);
+
+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',
+ ol.source.ImageCanvas.prototype.changed);
+
+goog.exportProperty(
+ ol.source.ImageCanvas.prototype,
+ 'dispatchEvent',
+ ol.source.ImageCanvas.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.ImageCanvas.prototype,
+ 'getRevision',
+ ol.source.ImageCanvas.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.ImageCanvas.prototype,
+ 'on',
+ ol.source.ImageCanvas.prototype.on);
+
+goog.exportProperty(
+ ol.source.ImageCanvas.prototype,
+ 'once',
+ ol.source.ImageCanvas.prototype.once);
+
+goog.exportProperty(
+ ol.source.ImageCanvas.prototype,
+ 'un',
+ ol.source.ImageCanvas.prototype.un);
+
+goog.exportProperty(
+ ol.source.ImageCanvas.prototype,
+ 'unByKey',
+ ol.source.ImageCanvas.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.ImageMapGuide.prototype,
+ 'getAttributions',
+ ol.source.ImageMapGuide.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.ImageMapGuide.prototype,
+ 'getLogo',
+ ol.source.ImageMapGuide.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.ImageMapGuide.prototype,
+ 'getProjection',
+ ol.source.ImageMapGuide.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.ImageMapGuide.prototype,
+ 'getState',
+ ol.source.ImageMapGuide.prototype.getState);
+
+goog.exportProperty(
+ ol.source.ImageMapGuide.prototype,
+ 'setAttributions',
+ ol.source.ImageMapGuide.prototype.setAttributions);
+
+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',
+ ol.source.ImageMapGuide.prototype.changed);
+
+goog.exportProperty(
+ ol.source.ImageMapGuide.prototype,
+ 'dispatchEvent',
+ ol.source.ImageMapGuide.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.ImageMapGuide.prototype,
+ 'getRevision',
+ ol.source.ImageMapGuide.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.ImageMapGuide.prototype,
+ 'on',
+ ol.source.ImageMapGuide.prototype.on);
+
+goog.exportProperty(
+ ol.source.ImageMapGuide.prototype,
+ 'once',
+ ol.source.ImageMapGuide.prototype.once);
+
+goog.exportProperty(
+ ol.source.ImageMapGuide.prototype,
+ 'un',
+ ol.source.ImageMapGuide.prototype.un);
+
+goog.exportProperty(
+ ol.source.ImageMapGuide.prototype,
+ 'unByKey',
+ ol.source.ImageMapGuide.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.ImageStatic.prototype,
+ 'getAttributions',
+ ol.source.ImageStatic.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.ImageStatic.prototype,
+ 'getLogo',
+ ol.source.ImageStatic.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.ImageStatic.prototype,
+ 'getProjection',
+ ol.source.ImageStatic.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.ImageStatic.prototype,
+ 'getState',
+ ol.source.ImageStatic.prototype.getState);
+
+goog.exportProperty(
+ ol.source.ImageStatic.prototype,
+ 'setAttributions',
+ ol.source.ImageStatic.prototype.setAttributions);
+
+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',
+ ol.source.ImageStatic.prototype.changed);
+
+goog.exportProperty(
+ ol.source.ImageStatic.prototype,
+ 'dispatchEvent',
+ ol.source.ImageStatic.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.ImageStatic.prototype,
+ 'getRevision',
+ ol.source.ImageStatic.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.ImageStatic.prototype,
+ 'on',
+ ol.source.ImageStatic.prototype.on);
+
+goog.exportProperty(
+ ol.source.ImageStatic.prototype,
+ 'once',
+ ol.source.ImageStatic.prototype.once);
+
+goog.exportProperty(
+ ol.source.ImageStatic.prototype,
+ 'un',
+ ol.source.ImageStatic.prototype.un);
+
+goog.exportProperty(
+ ol.source.ImageStatic.prototype,
+ 'unByKey',
+ ol.source.ImageStatic.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'getAttributions',
+ ol.source.ImageVector.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'getLogo',
+ ol.source.ImageVector.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'getProjection',
+ ol.source.ImageVector.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'getState',
+ ol.source.ImageVector.prototype.getState);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'setAttributions',
+ ol.source.ImageVector.prototype.setAttributions);
+
+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',
+ ol.source.ImageVector.prototype.changed);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'dispatchEvent',
+ ol.source.ImageVector.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'getRevision',
+ ol.source.ImageVector.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'on',
+ ol.source.ImageVector.prototype.on);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'once',
+ ol.source.ImageVector.prototype.once);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'un',
+ ol.source.ImageVector.prototype.un);
+
+goog.exportProperty(
+ ol.source.ImageVector.prototype,
+ 'unByKey',
+ ol.source.ImageVector.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'getAttributions',
+ ol.source.ImageWMS.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'getLogo',
+ ol.source.ImageWMS.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'getProjection',
+ ol.source.ImageWMS.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'getState',
+ ol.source.ImageWMS.prototype.getState);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'setAttributions',
+ ol.source.ImageWMS.prototype.setAttributions);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'get',
+ ol.source.ImageWMS.prototype.get);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'getKeys',
+ ol.source.ImageWMS.prototype.getKeys);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'getProperties',
+ ol.source.ImageWMS.prototype.getProperties);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'set',
+ ol.source.ImageWMS.prototype.set);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'setProperties',
+ ol.source.ImageWMS.prototype.setProperties);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'unset',
+ ol.source.ImageWMS.prototype.unset);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'changed',
+ ol.source.ImageWMS.prototype.changed);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'dispatchEvent',
+ ol.source.ImageWMS.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'getRevision',
+ ol.source.ImageWMS.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'on',
+ ol.source.ImageWMS.prototype.on);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'once',
+ ol.source.ImageWMS.prototype.once);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'un',
+ ol.source.ImageWMS.prototype.un);
+
+goog.exportProperty(
+ ol.source.ImageWMS.prototype,
+ 'unByKey',
+ ol.source.ImageWMS.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'setRenderReprojectionEdges',
+ ol.source.XYZ.prototype.setRenderReprojectionEdges);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'setTileGridForProjection',
+ ol.source.XYZ.prototype.setTileGridForProjection);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'getTileLoadFunction',
+ ol.source.XYZ.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'getTileUrlFunction',
+ ol.source.XYZ.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'getUrls',
+ ol.source.XYZ.prototype.getUrls);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'setTileLoadFunction',
+ ol.source.XYZ.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'setTileUrlFunction',
+ ol.source.XYZ.prototype.setTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'setUrl',
+ ol.source.XYZ.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'setUrls',
+ ol.source.XYZ.prototype.setUrls);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'getTileGrid',
+ ol.source.XYZ.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'getAttributions',
+ ol.source.XYZ.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'getLogo',
+ ol.source.XYZ.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'getProjection',
+ ol.source.XYZ.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'getState',
+ ol.source.XYZ.prototype.getState);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'setAttributions',
+ ol.source.XYZ.prototype.setAttributions);
+
+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',
+ ol.source.XYZ.prototype.changed);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'dispatchEvent',
+ ol.source.XYZ.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'getRevision',
+ ol.source.XYZ.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'on',
+ ol.source.XYZ.prototype.on);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'once',
+ ol.source.XYZ.prototype.once);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'un',
+ ol.source.XYZ.prototype.un);
+
+goog.exportProperty(
+ ol.source.XYZ.prototype,
+ 'unByKey',
+ ol.source.XYZ.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'setRenderReprojectionEdges',
+ ol.source.MapQuest.prototype.setRenderReprojectionEdges);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'setTileGridForProjection',
+ ol.source.MapQuest.prototype.setTileGridForProjection);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'getTileLoadFunction',
+ ol.source.MapQuest.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'getTileUrlFunction',
+ ol.source.MapQuest.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'getUrls',
+ ol.source.MapQuest.prototype.getUrls);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'setTileLoadFunction',
+ ol.source.MapQuest.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'setTileUrlFunction',
+ ol.source.MapQuest.prototype.setTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'setUrl',
+ ol.source.MapQuest.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'setUrls',
+ ol.source.MapQuest.prototype.setUrls);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'getTileGrid',
+ ol.source.MapQuest.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'getAttributions',
+ ol.source.MapQuest.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'getLogo',
+ ol.source.MapQuest.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'getProjection',
+ ol.source.MapQuest.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'getState',
+ ol.source.MapQuest.prototype.getState);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'setAttributions',
+ ol.source.MapQuest.prototype.setAttributions);
+
+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',
+ ol.source.MapQuest.prototype.changed);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'dispatchEvent',
+ ol.source.MapQuest.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'getRevision',
+ ol.source.MapQuest.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'on',
+ ol.source.MapQuest.prototype.on);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'once',
+ ol.source.MapQuest.prototype.once);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'un',
+ ol.source.MapQuest.prototype.un);
+
+goog.exportProperty(
+ ol.source.MapQuest.prototype,
+ 'unByKey',
+ ol.source.MapQuest.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'setRenderReprojectionEdges',
+ ol.source.OSM.prototype.setRenderReprojectionEdges);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'setTileGridForProjection',
+ ol.source.OSM.prototype.setTileGridForProjection);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'getTileLoadFunction',
+ ol.source.OSM.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'getTileUrlFunction',
+ ol.source.OSM.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'getUrls',
+ ol.source.OSM.prototype.getUrls);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'setTileLoadFunction',
+ ol.source.OSM.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'setTileUrlFunction',
+ ol.source.OSM.prototype.setTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'setUrl',
+ ol.source.OSM.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'setUrls',
+ ol.source.OSM.prototype.setUrls);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'getTileGrid',
+ ol.source.OSM.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'getAttributions',
+ ol.source.OSM.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'getLogo',
+ ol.source.OSM.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'getProjection',
+ ol.source.OSM.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'getState',
+ ol.source.OSM.prototype.getState);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'setAttributions',
+ ol.source.OSM.prototype.setAttributions);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'get',
+ ol.source.OSM.prototype.get);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'getKeys',
+ ol.source.OSM.prototype.getKeys);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'getProperties',
+ ol.source.OSM.prototype.getProperties);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'set',
+ ol.source.OSM.prototype.set);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'setProperties',
+ ol.source.OSM.prototype.setProperties);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'unset',
+ ol.source.OSM.prototype.unset);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'changed',
+ ol.source.OSM.prototype.changed);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'dispatchEvent',
+ ol.source.OSM.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'getRevision',
+ ol.source.OSM.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'on',
+ ol.source.OSM.prototype.on);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'once',
+ ol.source.OSM.prototype.once);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'un',
+ ol.source.OSM.prototype.un);
+
+goog.exportProperty(
+ ol.source.OSM.prototype,
+ 'unByKey',
+ ol.source.OSM.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'getAttributions',
+ ol.source.Raster.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'getLogo',
+ ol.source.Raster.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'getProjection',
+ ol.source.Raster.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'getState',
+ ol.source.Raster.prototype.getState);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'setAttributions',
+ ol.source.Raster.prototype.setAttributions);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'get',
+ ol.source.Raster.prototype.get);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'getKeys',
+ ol.source.Raster.prototype.getKeys);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'getProperties',
+ ol.source.Raster.prototype.getProperties);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'set',
+ ol.source.Raster.prototype.set);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'setProperties',
+ ol.source.Raster.prototype.setProperties);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'unset',
+ ol.source.Raster.prototype.unset);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'changed',
+ ol.source.Raster.prototype.changed);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'dispatchEvent',
+ ol.source.Raster.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'getRevision',
+ ol.source.Raster.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'on',
+ ol.source.Raster.prototype.on);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'once',
+ ol.source.Raster.prototype.once);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'un',
+ ol.source.Raster.prototype.un);
+
+goog.exportProperty(
+ ol.source.Raster.prototype,
+ 'unByKey',
+ ol.source.Raster.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'setRenderReprojectionEdges',
+ ol.source.Stamen.prototype.setRenderReprojectionEdges);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'setTileGridForProjection',
+ ol.source.Stamen.prototype.setTileGridForProjection);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'getTileLoadFunction',
+ ol.source.Stamen.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'getTileUrlFunction',
+ ol.source.Stamen.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'getUrls',
+ ol.source.Stamen.prototype.getUrls);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'setTileLoadFunction',
+ ol.source.Stamen.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'setTileUrlFunction',
+ ol.source.Stamen.prototype.setTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'setUrl',
+ ol.source.Stamen.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'setUrls',
+ ol.source.Stamen.prototype.setUrls);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'getTileGrid',
+ ol.source.Stamen.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'getAttributions',
+ ol.source.Stamen.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'getLogo',
+ ol.source.Stamen.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'getProjection',
+ ol.source.Stamen.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'getState',
+ ol.source.Stamen.prototype.getState);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'setAttributions',
+ ol.source.Stamen.prototype.setAttributions);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'get',
+ ol.source.Stamen.prototype.get);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'getKeys',
+ ol.source.Stamen.prototype.getKeys);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'getProperties',
+ ol.source.Stamen.prototype.getProperties);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'set',
+ ol.source.Stamen.prototype.set);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'setProperties',
+ ol.source.Stamen.prototype.setProperties);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'unset',
+ ol.source.Stamen.prototype.unset);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'changed',
+ ol.source.Stamen.prototype.changed);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'dispatchEvent',
+ ol.source.Stamen.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'getRevision',
+ ol.source.Stamen.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'on',
+ ol.source.Stamen.prototype.on);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'once',
+ ol.source.Stamen.prototype.once);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'un',
+ ol.source.Stamen.prototype.un);
+
+goog.exportProperty(
+ ol.source.Stamen.prototype,
+ 'unByKey',
+ ol.source.Stamen.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'setRenderReprojectionEdges',
+ ol.source.TileArcGISRest.prototype.setRenderReprojectionEdges);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'setTileGridForProjection',
+ ol.source.TileArcGISRest.prototype.setTileGridForProjection);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'getTileLoadFunction',
+ ol.source.TileArcGISRest.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'getTileUrlFunction',
+ ol.source.TileArcGISRest.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'getUrls',
+ ol.source.TileArcGISRest.prototype.getUrls);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'setTileLoadFunction',
+ ol.source.TileArcGISRest.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'setTileUrlFunction',
+ ol.source.TileArcGISRest.prototype.setTileUrlFunction);
+
+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,
+ 'getTileGrid',
+ ol.source.TileArcGISRest.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'getAttributions',
+ ol.source.TileArcGISRest.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'getLogo',
+ ol.source.TileArcGISRest.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'getProjection',
+ ol.source.TileArcGISRest.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'getState',
+ ol.source.TileArcGISRest.prototype.getState);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'setAttributions',
+ ol.source.TileArcGISRest.prototype.setAttributions);
+
+goog.exportProperty(
+ 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.TileArcGISRest.prototype.changed);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'dispatchEvent',
+ ol.source.TileArcGISRest.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'getRevision',
+ ol.source.TileArcGISRest.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'on',
+ ol.source.TileArcGISRest.prototype.on);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'once',
+ ol.source.TileArcGISRest.prototype.once);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'un',
+ ol.source.TileArcGISRest.prototype.un);
+
+goog.exportProperty(
+ ol.source.TileArcGISRest.prototype,
+ 'unByKey',
+ ol.source.TileArcGISRest.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.TileDebug.prototype,
+ 'getTileGrid',
+ ol.source.TileDebug.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.TileDebug.prototype,
+ 'getAttributions',
+ ol.source.TileDebug.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.TileDebug.prototype,
+ 'getLogo',
+ ol.source.TileDebug.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.TileDebug.prototype,
+ 'getProjection',
+ ol.source.TileDebug.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.TileDebug.prototype,
+ 'getState',
+ ol.source.TileDebug.prototype.getState);
+
+goog.exportProperty(
+ ol.source.TileDebug.prototype,
+ 'setAttributions',
+ ol.source.TileDebug.prototype.setAttributions);
+
+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',
+ ol.source.TileDebug.prototype.changed);
+
+goog.exportProperty(
+ ol.source.TileDebug.prototype,
+ 'dispatchEvent',
+ ol.source.TileDebug.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.TileDebug.prototype,
+ 'getRevision',
+ ol.source.TileDebug.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.TileDebug.prototype,
+ 'on',
+ ol.source.TileDebug.prototype.on);
+
+goog.exportProperty(
+ ol.source.TileDebug.prototype,
+ 'once',
+ ol.source.TileDebug.prototype.once);
+
+goog.exportProperty(
+ ol.source.TileDebug.prototype,
+ 'un',
+ ol.source.TileDebug.prototype.un);
+
+goog.exportProperty(
+ ol.source.TileDebug.prototype,
+ 'unByKey',
+ ol.source.TileDebug.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'setRenderReprojectionEdges',
+ ol.source.TileJSON.prototype.setRenderReprojectionEdges);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'setTileGridForProjection',
+ ol.source.TileJSON.prototype.setTileGridForProjection);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'getTileLoadFunction',
+ ol.source.TileJSON.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'getTileUrlFunction',
+ ol.source.TileJSON.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'getUrls',
+ ol.source.TileJSON.prototype.getUrls);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'setTileLoadFunction',
+ ol.source.TileJSON.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'setTileUrlFunction',
+ ol.source.TileJSON.prototype.setTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'setUrl',
+ ol.source.TileJSON.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'setUrls',
+ ol.source.TileJSON.prototype.setUrls);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'getTileGrid',
+ ol.source.TileJSON.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'getAttributions',
+ ol.source.TileJSON.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'getLogo',
+ ol.source.TileJSON.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'getProjection',
+ ol.source.TileJSON.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'getState',
+ ol.source.TileJSON.prototype.getState);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'setAttributions',
+ ol.source.TileJSON.prototype.setAttributions);
+
+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',
+ ol.source.TileJSON.prototype.changed);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'dispatchEvent',
+ ol.source.TileJSON.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'getRevision',
+ ol.source.TileJSON.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'on',
+ ol.source.TileJSON.prototype.on);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'once',
+ ol.source.TileJSON.prototype.once);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'un',
+ ol.source.TileJSON.prototype.un);
+
+goog.exportProperty(
+ ol.source.TileJSON.prototype,
+ 'unByKey',
+ ol.source.TileJSON.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'getTileGrid',
+ ol.source.TileUTFGrid.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'getAttributions',
+ ol.source.TileUTFGrid.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'getLogo',
+ ol.source.TileUTFGrid.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'getProjection',
+ ol.source.TileUTFGrid.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'getState',
+ ol.source.TileUTFGrid.prototype.getState);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'setAttributions',
+ ol.source.TileUTFGrid.prototype.setAttributions);
+
+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',
+ ol.source.TileUTFGrid.prototype.changed);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'dispatchEvent',
+ ol.source.TileUTFGrid.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'getRevision',
+ ol.source.TileUTFGrid.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'on',
+ ol.source.TileUTFGrid.prototype.on);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'once',
+ ol.source.TileUTFGrid.prototype.once);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'un',
+ ol.source.TileUTFGrid.prototype.un);
+
+goog.exportProperty(
+ ol.source.TileUTFGrid.prototype,
+ 'unByKey',
+ ol.source.TileUTFGrid.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'setRenderReprojectionEdges',
+ ol.source.TileWMS.prototype.setRenderReprojectionEdges);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'setTileGridForProjection',
+ ol.source.TileWMS.prototype.setTileGridForProjection);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getTileLoadFunction',
+ ol.source.TileWMS.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getTileUrlFunction',
+ ol.source.TileWMS.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getUrls',
+ ol.source.TileWMS.prototype.getUrls);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'setTileLoadFunction',
+ ol.source.TileWMS.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'setTileUrlFunction',
+ ol.source.TileWMS.prototype.setTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'setUrl',
+ ol.source.TileWMS.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'setUrls',
+ ol.source.TileWMS.prototype.setUrls);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getTileGrid',
+ ol.source.TileWMS.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getAttributions',
+ ol.source.TileWMS.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getLogo',
+ ol.source.TileWMS.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getProjection',
+ ol.source.TileWMS.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getState',
+ ol.source.TileWMS.prototype.getState);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'setAttributions',
+ ol.source.TileWMS.prototype.setAttributions);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'get',
+ ol.source.TileWMS.prototype.get);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getKeys',
+ ol.source.TileWMS.prototype.getKeys);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getProperties',
+ ol.source.TileWMS.prototype.getProperties);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'set',
+ ol.source.TileWMS.prototype.set);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'setProperties',
+ ol.source.TileWMS.prototype.setProperties);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'unset',
+ ol.source.TileWMS.prototype.unset);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'changed',
+ ol.source.TileWMS.prototype.changed);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'dispatchEvent',
+ ol.source.TileWMS.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'getRevision',
+ ol.source.TileWMS.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'on',
+ ol.source.TileWMS.prototype.on);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'once',
+ ol.source.TileWMS.prototype.once);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'un',
+ ol.source.TileWMS.prototype.un);
+
+goog.exportProperty(
+ ol.source.TileWMS.prototype,
+ 'unByKey',
+ ol.source.TileWMS.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'getTileLoadFunction',
+ ol.source.VectorTile.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'getTileUrlFunction',
+ ol.source.VectorTile.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'getUrls',
+ ol.source.VectorTile.prototype.getUrls);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'setTileLoadFunction',
+ ol.source.VectorTile.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'setTileUrlFunction',
+ ol.source.VectorTile.prototype.setTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'setUrl',
+ ol.source.VectorTile.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'setUrls',
+ ol.source.VectorTile.prototype.setUrls);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'getTileGrid',
+ ol.source.VectorTile.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'getAttributions',
+ ol.source.VectorTile.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'getLogo',
+ ol.source.VectorTile.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'getProjection',
+ ol.source.VectorTile.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'getState',
+ ol.source.VectorTile.prototype.getState);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'setAttributions',
+ ol.source.VectorTile.prototype.setAttributions);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'get',
+ ol.source.VectorTile.prototype.get);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'getKeys',
+ ol.source.VectorTile.prototype.getKeys);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'getProperties',
+ ol.source.VectorTile.prototype.getProperties);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'set',
+ ol.source.VectorTile.prototype.set);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'setProperties',
+ ol.source.VectorTile.prototype.setProperties);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'unset',
+ ol.source.VectorTile.prototype.unset);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'changed',
+ ol.source.VectorTile.prototype.changed);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'dispatchEvent',
+ ol.source.VectorTile.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'getRevision',
+ ol.source.VectorTile.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'on',
+ ol.source.VectorTile.prototype.on);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'once',
+ ol.source.VectorTile.prototype.once);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'un',
+ ol.source.VectorTile.prototype.un);
+
+goog.exportProperty(
+ ol.source.VectorTile.prototype,
+ 'unByKey',
+ ol.source.VectorTile.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'setRenderReprojectionEdges',
+ ol.source.WMTS.prototype.setRenderReprojectionEdges);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'setTileGridForProjection',
+ ol.source.WMTS.prototype.setTileGridForProjection);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'getTileLoadFunction',
+ ol.source.WMTS.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'getTileUrlFunction',
+ ol.source.WMTS.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'getUrls',
+ ol.source.WMTS.prototype.getUrls);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'setTileLoadFunction',
+ ol.source.WMTS.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'setTileUrlFunction',
+ ol.source.WMTS.prototype.setTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'setUrl',
+ ol.source.WMTS.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'setUrls',
+ ol.source.WMTS.prototype.setUrls);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'getTileGrid',
+ ol.source.WMTS.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'getAttributions',
+ ol.source.WMTS.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'getLogo',
+ ol.source.WMTS.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'getProjection',
+ ol.source.WMTS.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'getState',
+ ol.source.WMTS.prototype.getState);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'setAttributions',
+ ol.source.WMTS.prototype.setAttributions);
+
+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',
+ ol.source.WMTS.prototype.changed);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'dispatchEvent',
+ ol.source.WMTS.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'getRevision',
+ ol.source.WMTS.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'on',
+ ol.source.WMTS.prototype.on);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'once',
+ ol.source.WMTS.prototype.once);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'un',
+ ol.source.WMTS.prototype.un);
+
+goog.exportProperty(
+ ol.source.WMTS.prototype,
+ 'unByKey',
+ ol.source.WMTS.prototype.unByKey);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'setRenderReprojectionEdges',
+ ol.source.Zoomify.prototype.setRenderReprojectionEdges);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'setTileGridForProjection',
+ ol.source.Zoomify.prototype.setTileGridForProjection);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'getTileLoadFunction',
+ ol.source.Zoomify.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'getTileUrlFunction',
+ ol.source.Zoomify.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'getUrls',
+ ol.source.Zoomify.prototype.getUrls);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'setTileLoadFunction',
+ ol.source.Zoomify.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'setTileUrlFunction',
+ ol.source.Zoomify.prototype.setTileUrlFunction);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'setUrl',
+ ol.source.Zoomify.prototype.setUrl);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'setUrls',
+ ol.source.Zoomify.prototype.setUrls);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'getTileGrid',
+ ol.source.Zoomify.prototype.getTileGrid);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'getAttributions',
+ ol.source.Zoomify.prototype.getAttributions);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'getLogo',
+ ol.source.Zoomify.prototype.getLogo);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'getProjection',
+ ol.source.Zoomify.prototype.getProjection);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'getState',
+ ol.source.Zoomify.prototype.getState);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'setAttributions',
+ ol.source.Zoomify.prototype.setAttributions);
+
+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',
+ ol.source.Zoomify.prototype.changed);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'dispatchEvent',
+ ol.source.Zoomify.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'getRevision',
+ ol.source.Zoomify.prototype.getRevision);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'on',
+ ol.source.Zoomify.prototype.on);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'once',
+ ol.source.Zoomify.prototype.once);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'un',
+ ol.source.Zoomify.prototype.un);
+
+goog.exportProperty(
+ ol.source.Zoomify.prototype,
+ 'unByKey',
+ ol.source.Zoomify.prototype.unByKey);
+
+goog.exportProperty(
+ ol.reproj.Tile.prototype,
+ 'getTileCoord',
+ ol.reproj.Tile.prototype.getTileCoord);
+
+goog.exportProperty(
+ ol.renderer.Layer.prototype,
+ 'changed',
+ ol.renderer.Layer.prototype.changed);
+
+goog.exportProperty(
+ ol.renderer.Layer.prototype,
+ 'dispatchEvent',
+ ol.renderer.Layer.prototype.dispatchEvent);
+
+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,
+ 'dispatchEvent',
+ ol.renderer.webgl.Layer.prototype.dispatchEvent);
+
+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,
+ 'dispatchEvent',
+ ol.renderer.webgl.ImageLayer.prototype.dispatchEvent);
+
+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,
+ 'dispatchEvent',
+ ol.renderer.webgl.TileLayer.prototype.dispatchEvent);
+
+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,
+ 'dispatchEvent',
+ ol.renderer.webgl.VectorLayer.prototype.dispatchEvent);
+
+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,
+ 'dispatchEvent',
+ ol.renderer.dom.Layer.prototype.dispatchEvent);
+
+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,
+ 'dispatchEvent',
+ ol.renderer.dom.ImageLayer.prototype.dispatchEvent);
+
+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,
+ 'dispatchEvent',
+ ol.renderer.dom.TileLayer.prototype.dispatchEvent);
+
+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,
+ 'dispatchEvent',
+ ol.renderer.dom.VectorLayer.prototype.dispatchEvent);
+
+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,
+ 'dispatchEvent',
+ ol.renderer.canvas.Layer.prototype.dispatchEvent);
+
+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,
+ 'dispatchEvent',
+ ol.renderer.canvas.ImageLayer.prototype.dispatchEvent);
+
+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,
+ 'dispatchEvent',
+ ol.renderer.canvas.TileLayer.prototype.dispatchEvent);
+
+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,
+ 'dispatchEvent',
+ ol.renderer.canvas.VectorLayer.prototype.dispatchEvent);
+
+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.renderer.canvas.VectorTileLayer.prototype,
+ 'changed',
+ ol.renderer.canvas.VectorTileLayer.prototype.changed);
+
+goog.exportProperty(
+ ol.renderer.canvas.VectorTileLayer.prototype,
+ 'dispatchEvent',
+ ol.renderer.canvas.VectorTileLayer.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.renderer.canvas.VectorTileLayer.prototype,
+ 'getRevision',
+ ol.renderer.canvas.VectorTileLayer.prototype.getRevision);
+
+goog.exportProperty(
+ ol.renderer.canvas.VectorTileLayer.prototype,
+ 'on',
+ ol.renderer.canvas.VectorTileLayer.prototype.on);
+
+goog.exportProperty(
+ ol.renderer.canvas.VectorTileLayer.prototype,
+ 'once',
+ ol.renderer.canvas.VectorTileLayer.prototype.once);
+
+goog.exportProperty(
+ ol.renderer.canvas.VectorTileLayer.prototype,
+ 'un',
+ ol.renderer.canvas.VectorTileLayer.prototype.un);
+
+goog.exportProperty(
+ ol.renderer.canvas.VectorTileLayer.prototype,
+ 'unByKey',
+ ol.renderer.canvas.VectorTileLayer.prototype.unByKey);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'get',
+ ol.layer.Base.prototype.get);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'getKeys',
+ ol.layer.Base.prototype.getKeys);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'getProperties',
+ ol.layer.Base.prototype.getProperties);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'set',
+ ol.layer.Base.prototype.set);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'setProperties',
+ ol.layer.Base.prototype.setProperties);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'unset',
+ ol.layer.Base.prototype.unset);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'changed',
+ ol.layer.Base.prototype.changed);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'dispatchEvent',
+ ol.layer.Base.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'getRevision',
+ ol.layer.Base.prototype.getRevision);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'on',
+ ol.layer.Base.prototype.on);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'once',
+ ol.layer.Base.prototype.once);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'un',
+ ol.layer.Base.prototype.un);
+
+goog.exportProperty(
+ ol.layer.Base.prototype,
+ 'unByKey',
+ ol.layer.Base.prototype.unByKey);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'getExtent',
+ ol.layer.Layer.prototype.getExtent);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'getMaxResolution',
+ ol.layer.Layer.prototype.getMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'getMinResolution',
+ ol.layer.Layer.prototype.getMinResolution);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'getOpacity',
+ ol.layer.Layer.prototype.getOpacity);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'getVisible',
+ ol.layer.Layer.prototype.getVisible);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'getZIndex',
+ ol.layer.Layer.prototype.getZIndex);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'setExtent',
+ ol.layer.Layer.prototype.setExtent);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'setMaxResolution',
+ ol.layer.Layer.prototype.setMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'setMinResolution',
+ ol.layer.Layer.prototype.setMinResolution);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'setOpacity',
+ ol.layer.Layer.prototype.setOpacity);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'setVisible',
+ ol.layer.Layer.prototype.setVisible);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'setZIndex',
+ ol.layer.Layer.prototype.setZIndex);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'get',
+ ol.layer.Layer.prototype.get);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'getKeys',
+ ol.layer.Layer.prototype.getKeys);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'getProperties',
+ ol.layer.Layer.prototype.getProperties);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'set',
+ ol.layer.Layer.prototype.set);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'setProperties',
+ ol.layer.Layer.prototype.setProperties);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'unset',
+ ol.layer.Layer.prototype.unset);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'changed',
+ ol.layer.Layer.prototype.changed);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'dispatchEvent',
+ ol.layer.Layer.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'getRevision',
+ ol.layer.Layer.prototype.getRevision);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'on',
+ ol.layer.Layer.prototype.on);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'once',
+ ol.layer.Layer.prototype.once);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ 'un',
+ ol.layer.Layer.prototype.un);
+
+goog.exportProperty(
+ ol.layer.Layer.prototype,
+ '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',
+ ol.layer.Vector.prototype.setSource);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'getExtent',
+ ol.layer.Vector.prototype.getExtent);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'getMaxResolution',
+ ol.layer.Vector.prototype.getMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'getMinResolution',
+ ol.layer.Vector.prototype.getMinResolution);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'getOpacity',
+ ol.layer.Vector.prototype.getOpacity);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'getVisible',
+ ol.layer.Vector.prototype.getVisible);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'getZIndex',
+ ol.layer.Vector.prototype.getZIndex);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'setExtent',
+ ol.layer.Vector.prototype.setExtent);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'setMaxResolution',
+ ol.layer.Vector.prototype.setMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'setMinResolution',
+ ol.layer.Vector.prototype.setMinResolution);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'setOpacity',
+ ol.layer.Vector.prototype.setOpacity);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'setVisible',
+ ol.layer.Vector.prototype.setVisible);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'setZIndex',
+ ol.layer.Vector.prototype.setZIndex);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'get',
+ ol.layer.Vector.prototype.get);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'getKeys',
+ ol.layer.Vector.prototype.getKeys);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'getProperties',
+ ol.layer.Vector.prototype.getProperties);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'set',
+ ol.layer.Vector.prototype.set);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'setProperties',
+ ol.layer.Vector.prototype.setProperties);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'unset',
+ ol.layer.Vector.prototype.unset);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'changed',
+ ol.layer.Vector.prototype.changed);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'dispatchEvent',
+ ol.layer.Vector.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'getRevision',
+ ol.layer.Vector.prototype.getRevision);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'on',
+ ol.layer.Vector.prototype.on);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'once',
+ ol.layer.Vector.prototype.once);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'un',
+ ol.layer.Vector.prototype.un);
+
+goog.exportProperty(
+ ol.layer.Vector.prototype,
+ 'unByKey',
+ ol.layer.Vector.prototype.unByKey);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'getSource',
+ ol.layer.Heatmap.prototype.getSource);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'getStyle',
+ ol.layer.Heatmap.prototype.getStyle);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'getStyleFunction',
+ ol.layer.Heatmap.prototype.getStyleFunction);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ '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',
+ ol.layer.Heatmap.prototype.setSource);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'getExtent',
+ ol.layer.Heatmap.prototype.getExtent);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'getMaxResolution',
+ ol.layer.Heatmap.prototype.getMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'getMinResolution',
+ ol.layer.Heatmap.prototype.getMinResolution);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'getOpacity',
+ ol.layer.Heatmap.prototype.getOpacity);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'getVisible',
+ ol.layer.Heatmap.prototype.getVisible);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'getZIndex',
+ ol.layer.Heatmap.prototype.getZIndex);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'setExtent',
+ ol.layer.Heatmap.prototype.setExtent);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'setMaxResolution',
+ ol.layer.Heatmap.prototype.setMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'setMinResolution',
+ ol.layer.Heatmap.prototype.setMinResolution);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'setOpacity',
+ ol.layer.Heatmap.prototype.setOpacity);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'setVisible',
+ ol.layer.Heatmap.prototype.setVisible);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'setZIndex',
+ ol.layer.Heatmap.prototype.setZIndex);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'get',
+ ol.layer.Heatmap.prototype.get);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'getKeys',
+ ol.layer.Heatmap.prototype.getKeys);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'getProperties',
+ ol.layer.Heatmap.prototype.getProperties);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'set',
+ ol.layer.Heatmap.prototype.set);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'setProperties',
+ ol.layer.Heatmap.prototype.setProperties);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'unset',
+ ol.layer.Heatmap.prototype.unset);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'changed',
+ ol.layer.Heatmap.prototype.changed);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'dispatchEvent',
+ ol.layer.Heatmap.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'getRevision',
+ ol.layer.Heatmap.prototype.getRevision);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'on',
+ ol.layer.Heatmap.prototype.on);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'once',
+ ol.layer.Heatmap.prototype.once);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ 'un',
+ ol.layer.Heatmap.prototype.un);
+
+goog.exportProperty(
+ ol.layer.Heatmap.prototype,
+ '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',
+ ol.layer.Image.prototype.setSource);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'getExtent',
+ ol.layer.Image.prototype.getExtent);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'getMaxResolution',
+ ol.layer.Image.prototype.getMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'getMinResolution',
+ ol.layer.Image.prototype.getMinResolution);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'getOpacity',
+ ol.layer.Image.prototype.getOpacity);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'getVisible',
+ ol.layer.Image.prototype.getVisible);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'getZIndex',
+ ol.layer.Image.prototype.getZIndex);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'setExtent',
+ ol.layer.Image.prototype.setExtent);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'setMaxResolution',
+ ol.layer.Image.prototype.setMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'setMinResolution',
+ ol.layer.Image.prototype.setMinResolution);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'setOpacity',
+ ol.layer.Image.prototype.setOpacity);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'setVisible',
+ ol.layer.Image.prototype.setVisible);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'setZIndex',
+ ol.layer.Image.prototype.setZIndex);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'get',
+ ol.layer.Image.prototype.get);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'getKeys',
+ ol.layer.Image.prototype.getKeys);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'getProperties',
+ ol.layer.Image.prototype.getProperties);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'set',
+ ol.layer.Image.prototype.set);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'setProperties',
+ ol.layer.Image.prototype.setProperties);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'unset',
+ ol.layer.Image.prototype.unset);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'changed',
+ ol.layer.Image.prototype.changed);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'dispatchEvent',
+ ol.layer.Image.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'getRevision',
+ ol.layer.Image.prototype.getRevision);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'on',
+ ol.layer.Image.prototype.on);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'once',
+ ol.layer.Image.prototype.once);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'un',
+ ol.layer.Image.prototype.un);
+
+goog.exportProperty(
+ ol.layer.Image.prototype,
+ 'unByKey',
+ ol.layer.Image.prototype.unByKey);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'getExtent',
+ ol.layer.Group.prototype.getExtent);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'getMaxResolution',
+ ol.layer.Group.prototype.getMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'getMinResolution',
+ ol.layer.Group.prototype.getMinResolution);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'getOpacity',
+ ol.layer.Group.prototype.getOpacity);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'getVisible',
+ ol.layer.Group.prototype.getVisible);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'getZIndex',
+ ol.layer.Group.prototype.getZIndex);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'setExtent',
+ ol.layer.Group.prototype.setExtent);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'setMaxResolution',
+ ol.layer.Group.prototype.setMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'setMinResolution',
+ ol.layer.Group.prototype.setMinResolution);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'setOpacity',
+ ol.layer.Group.prototype.setOpacity);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'setVisible',
+ ol.layer.Group.prototype.setVisible);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'setZIndex',
+ ol.layer.Group.prototype.setZIndex);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'get',
+ ol.layer.Group.prototype.get);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'getKeys',
+ ol.layer.Group.prototype.getKeys);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'getProperties',
+ ol.layer.Group.prototype.getProperties);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'set',
+ ol.layer.Group.prototype.set);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'setProperties',
+ ol.layer.Group.prototype.setProperties);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'unset',
+ ol.layer.Group.prototype.unset);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'changed',
+ ol.layer.Group.prototype.changed);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'dispatchEvent',
+ ol.layer.Group.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'getRevision',
+ ol.layer.Group.prototype.getRevision);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'on',
+ ol.layer.Group.prototype.on);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'once',
+ ol.layer.Group.prototype.once);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ 'un',
+ ol.layer.Group.prototype.un);
+
+goog.exportProperty(
+ ol.layer.Group.prototype,
+ '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',
+ ol.layer.Tile.prototype.setSource);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'getExtent',
+ ol.layer.Tile.prototype.getExtent);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'getMaxResolution',
+ ol.layer.Tile.prototype.getMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'getMinResolution',
+ ol.layer.Tile.prototype.getMinResolution);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'getOpacity',
+ ol.layer.Tile.prototype.getOpacity);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'getVisible',
+ ol.layer.Tile.prototype.getVisible);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'getZIndex',
+ ol.layer.Tile.prototype.getZIndex);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'setExtent',
+ ol.layer.Tile.prototype.setExtent);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'setMaxResolution',
+ ol.layer.Tile.prototype.setMaxResolution);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'setMinResolution',
+ ol.layer.Tile.prototype.setMinResolution);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'setOpacity',
+ ol.layer.Tile.prototype.setOpacity);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'setVisible',
+ ol.layer.Tile.prototype.setVisible);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'setZIndex',
+ ol.layer.Tile.prototype.setZIndex);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'get',
+ ol.layer.Tile.prototype.get);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'getKeys',
+ ol.layer.Tile.prototype.getKeys);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'getProperties',
+ ol.layer.Tile.prototype.getProperties);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'set',
+ ol.layer.Tile.prototype.set);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'setProperties',
+ ol.layer.Tile.prototype.setProperties);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'unset',
+ ol.layer.Tile.prototype.unset);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'changed',
+ ol.layer.Tile.prototype.changed);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'dispatchEvent',
+ ol.layer.Tile.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'getRevision',
+ ol.layer.Tile.prototype.getRevision);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'on',
+ ol.layer.Tile.prototype.on);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'once',
+ ol.layer.Tile.prototype.once);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'un',
+ ol.layer.Tile.prototype.un);
+
+goog.exportProperty(
+ ol.layer.Tile.prototype,
+ 'unByKey',
+ ol.layer.Tile.prototype.unByKey);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getStyle',
+ ol.layer.VectorTile.prototype.getStyle);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getStyleFunction',
+ ol.layer.VectorTile.prototype.getStyleFunction);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'setStyle',
+ ol.layer.VectorTile.prototype.setStyle);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'setMap',
+ ol.layer.VectorTile.prototype.setMap);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'setSource',
+ ol.layer.VectorTile.prototype.setSource);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getExtent',
+ ol.layer.VectorTile.prototype.getExtent);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getMaxResolution',
+ ol.layer.VectorTile.prototype.getMaxResolution);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getMinResolution',
+ ol.layer.VectorTile.prototype.getMinResolution);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getOpacity',
+ ol.layer.VectorTile.prototype.getOpacity);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getVisible',
+ ol.layer.VectorTile.prototype.getVisible);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getZIndex',
+ ol.layer.VectorTile.prototype.getZIndex);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'setExtent',
+ ol.layer.VectorTile.prototype.setExtent);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'setMaxResolution',
+ ol.layer.VectorTile.prototype.setMaxResolution);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'setMinResolution',
+ ol.layer.VectorTile.prototype.setMinResolution);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'setOpacity',
+ ol.layer.VectorTile.prototype.setOpacity);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'setVisible',
+ ol.layer.VectorTile.prototype.setVisible);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'setZIndex',
+ ol.layer.VectorTile.prototype.setZIndex);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'get',
+ ol.layer.VectorTile.prototype.get);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getKeys',
+ ol.layer.VectorTile.prototype.getKeys);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getProperties',
+ ol.layer.VectorTile.prototype.getProperties);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'set',
+ ol.layer.VectorTile.prototype.set);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'setProperties',
+ ol.layer.VectorTile.prototype.setProperties);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'unset',
+ ol.layer.VectorTile.prototype.unset);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'changed',
+ ol.layer.VectorTile.prototype.changed);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'dispatchEvent',
+ ol.layer.VectorTile.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'getRevision',
+ ol.layer.VectorTile.prototype.getRevision);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'on',
+ ol.layer.VectorTile.prototype.on);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'once',
+ ol.layer.VectorTile.prototype.once);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'un',
+ ol.layer.VectorTile.prototype.un);
+
+goog.exportProperty(
+ ol.layer.VectorTile.prototype,
+ 'unByKey',
+ ol.layer.VectorTile.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'get',
+ ol.interaction.Interaction.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'getKeys',
+ ol.interaction.Interaction.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'getProperties',
+ ol.interaction.Interaction.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'set',
+ ol.interaction.Interaction.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'setProperties',
+ ol.interaction.Interaction.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'unset',
+ ol.interaction.Interaction.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'changed',
+ ol.interaction.Interaction.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'dispatchEvent',
+ ol.interaction.Interaction.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'getRevision',
+ ol.interaction.Interaction.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'on',
+ ol.interaction.Interaction.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'once',
+ ol.interaction.Interaction.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'un',
+ ol.interaction.Interaction.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.Interaction.prototype,
+ 'unByKey',
+ ol.interaction.Interaction.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'getActive',
+ ol.interaction.DoubleClickZoom.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'getMap',
+ ol.interaction.DoubleClickZoom.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'setActive',
+ ol.interaction.DoubleClickZoom.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'get',
+ ol.interaction.DoubleClickZoom.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'getKeys',
+ ol.interaction.DoubleClickZoom.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'getProperties',
+ ol.interaction.DoubleClickZoom.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'set',
+ ol.interaction.DoubleClickZoom.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'setProperties',
+ ol.interaction.DoubleClickZoom.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'unset',
+ ol.interaction.DoubleClickZoom.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'changed',
+ ol.interaction.DoubleClickZoom.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'dispatchEvent',
+ ol.interaction.DoubleClickZoom.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'getRevision',
+ ol.interaction.DoubleClickZoom.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'on',
+ ol.interaction.DoubleClickZoom.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'once',
+ ol.interaction.DoubleClickZoom.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'un',
+ ol.interaction.DoubleClickZoom.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.DoubleClickZoom.prototype,
+ 'unByKey',
+ ol.interaction.DoubleClickZoom.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'getActive',
+ ol.interaction.DragAndDrop.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'getMap',
+ ol.interaction.DragAndDrop.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'setActive',
+ ol.interaction.DragAndDrop.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'get',
+ ol.interaction.DragAndDrop.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'getKeys',
+ ol.interaction.DragAndDrop.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'getProperties',
+ ol.interaction.DragAndDrop.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'set',
+ ol.interaction.DragAndDrop.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'setProperties',
+ ol.interaction.DragAndDrop.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'unset',
+ ol.interaction.DragAndDrop.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'changed',
+ ol.interaction.DragAndDrop.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'dispatchEvent',
+ ol.interaction.DragAndDrop.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'getRevision',
+ ol.interaction.DragAndDrop.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'on',
+ ol.interaction.DragAndDrop.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'once',
+ ol.interaction.DragAndDrop.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'un',
+ ol.interaction.DragAndDrop.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.DragAndDrop.prototype,
+ 'unByKey',
+ ol.interaction.DragAndDrop.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'getActive',
+ ol.interaction.Pointer.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'getMap',
+ ol.interaction.Pointer.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'setActive',
+ ol.interaction.Pointer.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'get',
+ ol.interaction.Pointer.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'getKeys',
+ ol.interaction.Pointer.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'getProperties',
+ ol.interaction.Pointer.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'set',
+ ol.interaction.Pointer.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'setProperties',
+ ol.interaction.Pointer.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'unset',
+ ol.interaction.Pointer.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'changed',
+ ol.interaction.Pointer.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'dispatchEvent',
+ ol.interaction.Pointer.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'getRevision',
+ ol.interaction.Pointer.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'on',
+ ol.interaction.Pointer.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'once',
+ ol.interaction.Pointer.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'un',
+ ol.interaction.Pointer.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.Pointer.prototype,
+ 'unByKey',
+ ol.interaction.Pointer.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'getActive',
+ ol.interaction.DragBox.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'getMap',
+ ol.interaction.DragBox.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'setActive',
+ ol.interaction.DragBox.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'get',
+ ol.interaction.DragBox.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'getKeys',
+ ol.interaction.DragBox.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'getProperties',
+ ol.interaction.DragBox.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'set',
+ ol.interaction.DragBox.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'setProperties',
+ ol.interaction.DragBox.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'unset',
+ ol.interaction.DragBox.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'changed',
+ ol.interaction.DragBox.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'dispatchEvent',
+ ol.interaction.DragBox.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'getRevision',
+ ol.interaction.DragBox.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'on',
+ ol.interaction.DragBox.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'once',
+ ol.interaction.DragBox.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'un',
+ ol.interaction.DragBox.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.DragBox.prototype,
+ 'unByKey',
+ ol.interaction.DragBox.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'getActive',
+ ol.interaction.DragPan.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'getMap',
+ ol.interaction.DragPan.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'setActive',
+ ol.interaction.DragPan.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'get',
+ ol.interaction.DragPan.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'getKeys',
+ ol.interaction.DragPan.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'getProperties',
+ ol.interaction.DragPan.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'set',
+ ol.interaction.DragPan.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'setProperties',
+ ol.interaction.DragPan.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'unset',
+ ol.interaction.DragPan.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'changed',
+ ol.interaction.DragPan.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'dispatchEvent',
+ ol.interaction.DragPan.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'getRevision',
+ ol.interaction.DragPan.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'on',
+ ol.interaction.DragPan.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'once',
+ ol.interaction.DragPan.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'un',
+ ol.interaction.DragPan.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.DragPan.prototype,
+ 'unByKey',
+ ol.interaction.DragPan.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'getActive',
+ ol.interaction.DragRotateAndZoom.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'getMap',
+ ol.interaction.DragRotateAndZoom.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'setActive',
+ ol.interaction.DragRotateAndZoom.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'get',
+ ol.interaction.DragRotateAndZoom.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'getKeys',
+ ol.interaction.DragRotateAndZoom.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'getProperties',
+ ol.interaction.DragRotateAndZoom.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'set',
+ ol.interaction.DragRotateAndZoom.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'setProperties',
+ ol.interaction.DragRotateAndZoom.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'unset',
+ ol.interaction.DragRotateAndZoom.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'changed',
+ ol.interaction.DragRotateAndZoom.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'dispatchEvent',
+ ol.interaction.DragRotateAndZoom.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'getRevision',
+ ol.interaction.DragRotateAndZoom.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'on',
+ ol.interaction.DragRotateAndZoom.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'once',
+ ol.interaction.DragRotateAndZoom.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'un',
+ ol.interaction.DragRotateAndZoom.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.DragRotateAndZoom.prototype,
+ 'unByKey',
+ ol.interaction.DragRotateAndZoom.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'getActive',
+ ol.interaction.DragRotate.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'getMap',
+ ol.interaction.DragRotate.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'setActive',
+ ol.interaction.DragRotate.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'get',
+ ol.interaction.DragRotate.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'getKeys',
+ ol.interaction.DragRotate.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'getProperties',
+ ol.interaction.DragRotate.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'set',
+ ol.interaction.DragRotate.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'setProperties',
+ ol.interaction.DragRotate.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'unset',
+ ol.interaction.DragRotate.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'changed',
+ ol.interaction.DragRotate.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'dispatchEvent',
+ ol.interaction.DragRotate.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'getRevision',
+ ol.interaction.DragRotate.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'on',
+ ol.interaction.DragRotate.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'once',
+ ol.interaction.DragRotate.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'un',
+ ol.interaction.DragRotate.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.DragRotate.prototype,
+ 'unByKey',
+ ol.interaction.DragRotate.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'getGeometry',
+ ol.interaction.DragZoom.prototype.getGeometry);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'getActive',
+ ol.interaction.DragZoom.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'getMap',
+ ol.interaction.DragZoom.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'setActive',
+ ol.interaction.DragZoom.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'get',
+ ol.interaction.DragZoom.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'getKeys',
+ ol.interaction.DragZoom.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'getProperties',
+ ol.interaction.DragZoom.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'set',
+ ol.interaction.DragZoom.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'setProperties',
+ ol.interaction.DragZoom.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'unset',
+ ol.interaction.DragZoom.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'changed',
+ ol.interaction.DragZoom.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'dispatchEvent',
+ ol.interaction.DragZoom.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'getRevision',
+ ol.interaction.DragZoom.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'on',
+ ol.interaction.DragZoom.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'once',
+ ol.interaction.DragZoom.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'un',
+ ol.interaction.DragZoom.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.DragZoom.prototype,
+ 'unByKey',
+ ol.interaction.DragZoom.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'getActive',
+ ol.interaction.Draw.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'getMap',
+ ol.interaction.Draw.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'setActive',
+ ol.interaction.Draw.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'get',
+ ol.interaction.Draw.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'getKeys',
+ ol.interaction.Draw.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'getProperties',
+ ol.interaction.Draw.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'set',
+ ol.interaction.Draw.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'setProperties',
+ ol.interaction.Draw.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'unset',
+ ol.interaction.Draw.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'changed',
+ ol.interaction.Draw.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'dispatchEvent',
+ ol.interaction.Draw.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'getRevision',
+ ol.interaction.Draw.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'on',
+ ol.interaction.Draw.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'once',
+ ol.interaction.Draw.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'un',
+ ol.interaction.Draw.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.Draw.prototype,
+ 'unByKey',
+ ol.interaction.Draw.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'getActive',
+ ol.interaction.KeyboardPan.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'getMap',
+ ol.interaction.KeyboardPan.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'setActive',
+ ol.interaction.KeyboardPan.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'get',
+ ol.interaction.KeyboardPan.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'getKeys',
+ ol.interaction.KeyboardPan.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'getProperties',
+ ol.interaction.KeyboardPan.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'set',
+ ol.interaction.KeyboardPan.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'setProperties',
+ ol.interaction.KeyboardPan.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'unset',
+ ol.interaction.KeyboardPan.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'changed',
+ ol.interaction.KeyboardPan.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'dispatchEvent',
+ ol.interaction.KeyboardPan.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'getRevision',
+ ol.interaction.KeyboardPan.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'on',
+ ol.interaction.KeyboardPan.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'once',
+ ol.interaction.KeyboardPan.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'un',
+ ol.interaction.KeyboardPan.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.KeyboardPan.prototype,
+ 'unByKey',
+ ol.interaction.KeyboardPan.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'getActive',
+ ol.interaction.KeyboardZoom.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'getMap',
+ ol.interaction.KeyboardZoom.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'setActive',
+ ol.interaction.KeyboardZoom.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'get',
+ ol.interaction.KeyboardZoom.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'getKeys',
+ ol.interaction.KeyboardZoom.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'getProperties',
+ ol.interaction.KeyboardZoom.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'set',
+ ol.interaction.KeyboardZoom.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'setProperties',
+ ol.interaction.KeyboardZoom.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'unset',
+ ol.interaction.KeyboardZoom.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'changed',
+ ol.interaction.KeyboardZoom.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'dispatchEvent',
+ ol.interaction.KeyboardZoom.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'getRevision',
+ ol.interaction.KeyboardZoom.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'on',
+ ol.interaction.KeyboardZoom.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'once',
+ ol.interaction.KeyboardZoom.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'un',
+ ol.interaction.KeyboardZoom.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.KeyboardZoom.prototype,
+ 'unByKey',
+ ol.interaction.KeyboardZoom.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'getActive',
+ ol.interaction.Modify.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'getMap',
+ ol.interaction.Modify.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'setActive',
+ ol.interaction.Modify.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'get',
+ ol.interaction.Modify.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'getKeys',
+ ol.interaction.Modify.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'getProperties',
+ ol.interaction.Modify.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'set',
+ ol.interaction.Modify.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'setProperties',
+ ol.interaction.Modify.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'unset',
+ ol.interaction.Modify.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'changed',
+ ol.interaction.Modify.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'dispatchEvent',
+ ol.interaction.Modify.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'getRevision',
+ ol.interaction.Modify.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'on',
+ ol.interaction.Modify.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'once',
+ ol.interaction.Modify.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'un',
+ ol.interaction.Modify.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.Modify.prototype,
+ 'unByKey',
+ ol.interaction.Modify.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'getActive',
+ ol.interaction.MouseWheelZoom.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'getMap',
+ ol.interaction.MouseWheelZoom.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'setActive',
+ ol.interaction.MouseWheelZoom.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'get',
+ ol.interaction.MouseWheelZoom.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'getKeys',
+ ol.interaction.MouseWheelZoom.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'getProperties',
+ ol.interaction.MouseWheelZoom.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'set',
+ ol.interaction.MouseWheelZoom.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'setProperties',
+ ol.interaction.MouseWheelZoom.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'unset',
+ ol.interaction.MouseWheelZoom.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'changed',
+ ol.interaction.MouseWheelZoom.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'dispatchEvent',
+ ol.interaction.MouseWheelZoom.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'getRevision',
+ ol.interaction.MouseWheelZoom.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'on',
+ ol.interaction.MouseWheelZoom.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'once',
+ ol.interaction.MouseWheelZoom.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'un',
+ ol.interaction.MouseWheelZoom.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.MouseWheelZoom.prototype,
+ 'unByKey',
+ ol.interaction.MouseWheelZoom.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'getActive',
+ ol.interaction.PinchRotate.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'getMap',
+ ol.interaction.PinchRotate.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'setActive',
+ ol.interaction.PinchRotate.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'get',
+ ol.interaction.PinchRotate.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'getKeys',
+ ol.interaction.PinchRotate.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'getProperties',
+ ol.interaction.PinchRotate.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'set',
+ ol.interaction.PinchRotate.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'setProperties',
+ ol.interaction.PinchRotate.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'unset',
+ ol.interaction.PinchRotate.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'changed',
+ ol.interaction.PinchRotate.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'dispatchEvent',
+ ol.interaction.PinchRotate.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'getRevision',
+ ol.interaction.PinchRotate.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'on',
+ ol.interaction.PinchRotate.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'once',
+ ol.interaction.PinchRotate.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'un',
+ ol.interaction.PinchRotate.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.PinchRotate.prototype,
+ 'unByKey',
+ ol.interaction.PinchRotate.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'getActive',
+ ol.interaction.PinchZoom.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'getMap',
+ ol.interaction.PinchZoom.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'setActive',
+ ol.interaction.PinchZoom.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'get',
+ ol.interaction.PinchZoom.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'getKeys',
+ ol.interaction.PinchZoom.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'getProperties',
+ ol.interaction.PinchZoom.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'set',
+ ol.interaction.PinchZoom.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'setProperties',
+ ol.interaction.PinchZoom.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'unset',
+ ol.interaction.PinchZoom.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'changed',
+ ol.interaction.PinchZoom.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'dispatchEvent',
+ ol.interaction.PinchZoom.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'getRevision',
+ ol.interaction.PinchZoom.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'on',
+ ol.interaction.PinchZoom.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'once',
+ ol.interaction.PinchZoom.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'un',
+ ol.interaction.PinchZoom.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.PinchZoom.prototype,
+ 'unByKey',
+ ol.interaction.PinchZoom.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'getActive',
+ ol.interaction.Select.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'getMap',
+ ol.interaction.Select.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'setActive',
+ ol.interaction.Select.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'get',
+ ol.interaction.Select.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'getKeys',
+ ol.interaction.Select.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'getProperties',
+ ol.interaction.Select.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'set',
+ ol.interaction.Select.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'setProperties',
+ ol.interaction.Select.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'unset',
+ ol.interaction.Select.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'changed',
+ ol.interaction.Select.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'dispatchEvent',
+ ol.interaction.Select.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'getRevision',
+ ol.interaction.Select.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'on',
+ ol.interaction.Select.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'once',
+ ol.interaction.Select.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'un',
+ ol.interaction.Select.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.Select.prototype,
+ 'unByKey',
+ ol.interaction.Select.prototype.unByKey);
+
+goog.exportProperty(
+ ol.interaction.Snap.prototype,
+ 'getActive',
+ ol.interaction.Snap.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.Snap.prototype,
+ 'getMap',
+ ol.interaction.Snap.prototype.getMap);
+
+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,
+ 'dispatchEvent',
+ ol.interaction.Snap.prototype.dispatchEvent);
+
+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.interaction.Translate.prototype,
+ 'getActive',
+ ol.interaction.Translate.prototype.getActive);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'getMap',
+ ol.interaction.Translate.prototype.getMap);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'setActive',
+ ol.interaction.Translate.prototype.setActive);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'get',
+ ol.interaction.Translate.prototype.get);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'getKeys',
+ ol.interaction.Translate.prototype.getKeys);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'getProperties',
+ ol.interaction.Translate.prototype.getProperties);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'set',
+ ol.interaction.Translate.prototype.set);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'setProperties',
+ ol.interaction.Translate.prototype.setProperties);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'unset',
+ ol.interaction.Translate.prototype.unset);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'changed',
+ ol.interaction.Translate.prototype.changed);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'dispatchEvent',
+ ol.interaction.Translate.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'getRevision',
+ ol.interaction.Translate.prototype.getRevision);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'on',
+ ol.interaction.Translate.prototype.on);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'once',
+ ol.interaction.Translate.prototype.once);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'un',
+ ol.interaction.Translate.prototype.un);
+
+goog.exportProperty(
+ ol.interaction.Translate.prototype,
+ 'unByKey',
+ ol.interaction.Translate.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',
+ ol.geom.Geometry.prototype.changed);
+
+goog.exportProperty(
+ ol.geom.Geometry.prototype,
+ 'dispatchEvent',
+ ol.geom.Geometry.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.geom.Geometry.prototype,
+ 'getRevision',
+ ol.geom.Geometry.prototype.getRevision);
+
+goog.exportProperty(
+ ol.geom.Geometry.prototype,
+ 'on',
+ ol.geom.Geometry.prototype.on);
+
+goog.exportProperty(
+ ol.geom.Geometry.prototype,
+ 'once',
+ ol.geom.Geometry.prototype.once);
+
+goog.exportProperty(
+ ol.geom.Geometry.prototype,
+ 'un',
+ ol.geom.Geometry.prototype.un);
+
+goog.exportProperty(
+ ol.geom.Geometry.prototype,
+ 'unByKey',
+ ol.geom.Geometry.prototype.unByKey);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'getClosestPoint',
+ ol.geom.SimpleGeometry.prototype.getClosestPoint);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'getExtent',
+ ol.geom.SimpleGeometry.prototype.getExtent);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'simplify',
+ ol.geom.SimpleGeometry.prototype.simplify);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'transform',
+ ol.geom.SimpleGeometry.prototype.transform);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'get',
+ ol.geom.SimpleGeometry.prototype.get);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ '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,
+ 'changed',
+ ol.geom.SimpleGeometry.prototype.changed);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'dispatchEvent',
+ ol.geom.SimpleGeometry.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'getRevision',
+ ol.geom.SimpleGeometry.prototype.getRevision);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'on',
+ ol.geom.SimpleGeometry.prototype.on);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'once',
+ ol.geom.SimpleGeometry.prototype.once);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'un',
+ ol.geom.SimpleGeometry.prototype.un);
+
+goog.exportProperty(
+ ol.geom.SimpleGeometry.prototype,
+ 'unByKey',
+ ol.geom.SimpleGeometry.prototype.unByKey);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'getFirstCoordinate',
+ ol.geom.Circle.prototype.getFirstCoordinate);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'getLastCoordinate',
+ ol.geom.Circle.prototype.getLastCoordinate);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'getLayout',
+ ol.geom.Circle.prototype.getLayout);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'getClosestPoint',
+ ol.geom.Circle.prototype.getClosestPoint);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'getExtent',
+ ol.geom.Circle.prototype.getExtent);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'simplify',
+ ol.geom.Circle.prototype.simplify);
+
+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,
+ 'changed',
+ ol.geom.Circle.prototype.changed);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'dispatchEvent',
+ ol.geom.Circle.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'getRevision',
+ ol.geom.Circle.prototype.getRevision);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'on',
+ ol.geom.Circle.prototype.on);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'once',
+ ol.geom.Circle.prototype.once);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'un',
+ ol.geom.Circle.prototype.un);
+
+goog.exportProperty(
+ ol.geom.Circle.prototype,
+ 'unByKey',
+ ol.geom.Circle.prototype.unByKey);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'getClosestPoint',
+ ol.geom.GeometryCollection.prototype.getClosestPoint);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'getExtent',
+ ol.geom.GeometryCollection.prototype.getExtent);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'simplify',
+ ol.geom.GeometryCollection.prototype.simplify);
+
+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',
+ ol.geom.GeometryCollection.prototype.changed);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'dispatchEvent',
+ ol.geom.GeometryCollection.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'getRevision',
+ ol.geom.GeometryCollection.prototype.getRevision);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'on',
+ ol.geom.GeometryCollection.prototype.on);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'once',
+ ol.geom.GeometryCollection.prototype.once);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'un',
+ ol.geom.GeometryCollection.prototype.un);
+
+goog.exportProperty(
+ ol.geom.GeometryCollection.prototype,
+ 'unByKey',
+ ol.geom.GeometryCollection.prototype.unByKey);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'getFirstCoordinate',
+ ol.geom.LinearRing.prototype.getFirstCoordinate);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'getLastCoordinate',
+ ol.geom.LinearRing.prototype.getLastCoordinate);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'getLayout',
+ ol.geom.LinearRing.prototype.getLayout);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'getClosestPoint',
+ ol.geom.LinearRing.prototype.getClosestPoint);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'getExtent',
+ ol.geom.LinearRing.prototype.getExtent);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'simplify',
+ ol.geom.LinearRing.prototype.simplify);
+
+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',
+ ol.geom.LinearRing.prototype.changed);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'dispatchEvent',
+ ol.geom.LinearRing.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'getRevision',
+ ol.geom.LinearRing.prototype.getRevision);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'on',
+ ol.geom.LinearRing.prototype.on);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'once',
+ ol.geom.LinearRing.prototype.once);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'un',
+ ol.geom.LinearRing.prototype.un);
+
+goog.exportProperty(
+ ol.geom.LinearRing.prototype,
+ 'unByKey',
+ ol.geom.LinearRing.prototype.unByKey);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'getFirstCoordinate',
+ ol.geom.LineString.prototype.getFirstCoordinate);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'getLastCoordinate',
+ ol.geom.LineString.prototype.getLastCoordinate);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'getLayout',
+ ol.geom.LineString.prototype.getLayout);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'getClosestPoint',
+ ol.geom.LineString.prototype.getClosestPoint);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'getExtent',
+ ol.geom.LineString.prototype.getExtent);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'simplify',
+ ol.geom.LineString.prototype.simplify);
+
+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',
+ ol.geom.LineString.prototype.changed);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'dispatchEvent',
+ ol.geom.LineString.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'getRevision',
+ ol.geom.LineString.prototype.getRevision);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'on',
+ ol.geom.LineString.prototype.on);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'once',
+ ol.geom.LineString.prototype.once);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'un',
+ ol.geom.LineString.prototype.un);
+
+goog.exportProperty(
+ ol.geom.LineString.prototype,
+ 'unByKey',
+ ol.geom.LineString.prototype.unByKey);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'getFirstCoordinate',
+ ol.geom.MultiLineString.prototype.getFirstCoordinate);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'getLastCoordinate',
+ ol.geom.MultiLineString.prototype.getLastCoordinate);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'getLayout',
+ ol.geom.MultiLineString.prototype.getLayout);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'getClosestPoint',
+ ol.geom.MultiLineString.prototype.getClosestPoint);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'getExtent',
+ ol.geom.MultiLineString.prototype.getExtent);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'simplify',
+ ol.geom.MultiLineString.prototype.simplify);
+
+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',
+ ol.geom.MultiLineString.prototype.changed);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'dispatchEvent',
+ ol.geom.MultiLineString.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'getRevision',
+ ol.geom.MultiLineString.prototype.getRevision);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'on',
+ ol.geom.MultiLineString.prototype.on);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'once',
+ ol.geom.MultiLineString.prototype.once);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'un',
+ ol.geom.MultiLineString.prototype.un);
+
+goog.exportProperty(
+ ol.geom.MultiLineString.prototype,
+ 'unByKey',
+ ol.geom.MultiLineString.prototype.unByKey);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'getFirstCoordinate',
+ ol.geom.MultiPoint.prototype.getFirstCoordinate);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'getLastCoordinate',
+ ol.geom.MultiPoint.prototype.getLastCoordinate);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'getLayout',
+ ol.geom.MultiPoint.prototype.getLayout);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'getClosestPoint',
+ ol.geom.MultiPoint.prototype.getClosestPoint);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'getExtent',
+ ol.geom.MultiPoint.prototype.getExtent);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'simplify',
+ ol.geom.MultiPoint.prototype.simplify);
+
+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',
+ ol.geom.MultiPoint.prototype.changed);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'dispatchEvent',
+ ol.geom.MultiPoint.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'getRevision',
+ ol.geom.MultiPoint.prototype.getRevision);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'on',
+ ol.geom.MultiPoint.prototype.on);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'once',
+ ol.geom.MultiPoint.prototype.once);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'un',
+ ol.geom.MultiPoint.prototype.un);
+
+goog.exportProperty(
+ ol.geom.MultiPoint.prototype,
+ 'unByKey',
+ ol.geom.MultiPoint.prototype.unByKey);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'getFirstCoordinate',
+ ol.geom.MultiPolygon.prototype.getFirstCoordinate);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'getLastCoordinate',
+ ol.geom.MultiPolygon.prototype.getLastCoordinate);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'getLayout',
+ ol.geom.MultiPolygon.prototype.getLayout);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'getClosestPoint',
+ ol.geom.MultiPolygon.prototype.getClosestPoint);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'getExtent',
+ ol.geom.MultiPolygon.prototype.getExtent);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'simplify',
+ ol.geom.MultiPolygon.prototype.simplify);
+
+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',
+ ol.geom.MultiPolygon.prototype.changed);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'dispatchEvent',
+ ol.geom.MultiPolygon.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'getRevision',
+ ol.geom.MultiPolygon.prototype.getRevision);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'on',
+ ol.geom.MultiPolygon.prototype.on);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'once',
+ ol.geom.MultiPolygon.prototype.once);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'un',
+ ol.geom.MultiPolygon.prototype.un);
+
+goog.exportProperty(
+ ol.geom.MultiPolygon.prototype,
+ 'unByKey',
+ ol.geom.MultiPolygon.prototype.unByKey);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'getFirstCoordinate',
+ ol.geom.Point.prototype.getFirstCoordinate);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'getLastCoordinate',
+ ol.geom.Point.prototype.getLastCoordinate);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'getLayout',
+ ol.geom.Point.prototype.getLayout);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'getClosestPoint',
+ ol.geom.Point.prototype.getClosestPoint);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'getExtent',
+ ol.geom.Point.prototype.getExtent);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'simplify',
+ ol.geom.Point.prototype.simplify);
+
+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',
+ ol.geom.Point.prototype.changed);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'dispatchEvent',
+ ol.geom.Point.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'getRevision',
+ ol.geom.Point.prototype.getRevision);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'on',
+ ol.geom.Point.prototype.on);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'once',
+ ol.geom.Point.prototype.once);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'un',
+ ol.geom.Point.prototype.un);
+
+goog.exportProperty(
+ ol.geom.Point.prototype,
+ 'unByKey',
+ ol.geom.Point.prototype.unByKey);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getFirstCoordinate',
+ ol.geom.Polygon.prototype.getFirstCoordinate);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getLastCoordinate',
+ ol.geom.Polygon.prototype.getLastCoordinate);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getLayout',
+ ol.geom.Polygon.prototype.getLayout);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getClosestPoint',
+ ol.geom.Polygon.prototype.getClosestPoint);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getExtent',
+ ol.geom.Polygon.prototype.getExtent);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'simplify',
+ ol.geom.Polygon.prototype.simplify);
+
+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',
+ ol.geom.Polygon.prototype.changed);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'dispatchEvent',
+ ol.geom.Polygon.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'getRevision',
+ ol.geom.Polygon.prototype.getRevision);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'on',
+ ol.geom.Polygon.prototype.on);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'once',
+ ol.geom.Polygon.prototype.once);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'un',
+ ol.geom.Polygon.prototype.un);
+
+goog.exportProperty(
+ ol.geom.Polygon.prototype,
+ 'unByKey',
+ ol.geom.Polygon.prototype.unByKey);
+
+goog.exportProperty(
+ ol.format.GML2.prototype,
+ 'readFeatures',
+ ol.format.GML2.prototype.readFeatures);
+
+goog.exportProperty(
+ ol.format.GML3.prototype,
+ 'readFeatures',
+ ol.format.GML3.prototype.readFeatures);
+
+goog.exportProperty(
+ ol.format.GML.prototype,
+ 'readFeatures',
+ ol.format.GML.prototype.readFeatures);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'get',
+ ol.control.Control.prototype.get);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'getKeys',
+ ol.control.Control.prototype.getKeys);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'getProperties',
+ ol.control.Control.prototype.getProperties);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'set',
+ ol.control.Control.prototype.set);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'setProperties',
+ ol.control.Control.prototype.setProperties);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'unset',
+ ol.control.Control.prototype.unset);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'changed',
+ ol.control.Control.prototype.changed);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'dispatchEvent',
+ ol.control.Control.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'getRevision',
+ ol.control.Control.prototype.getRevision);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'on',
+ ol.control.Control.prototype.on);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'once',
+ ol.control.Control.prototype.once);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'un',
+ ol.control.Control.prototype.un);
+
+goog.exportProperty(
+ ol.control.Control.prototype,
+ 'unByKey',
+ ol.control.Control.prototype.unByKey);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'getMap',
+ ol.control.Attribution.prototype.getMap);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'setMap',
+ ol.control.Attribution.prototype.setMap);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'setTarget',
+ ol.control.Attribution.prototype.setTarget);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'get',
+ ol.control.Attribution.prototype.get);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'getKeys',
+ ol.control.Attribution.prototype.getKeys);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'getProperties',
+ ol.control.Attribution.prototype.getProperties);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'set',
+ ol.control.Attribution.prototype.set);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'setProperties',
+ ol.control.Attribution.prototype.setProperties);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'unset',
+ ol.control.Attribution.prototype.unset);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'changed',
+ ol.control.Attribution.prototype.changed);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'dispatchEvent',
+ ol.control.Attribution.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'getRevision',
+ ol.control.Attribution.prototype.getRevision);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'on',
+ ol.control.Attribution.prototype.on);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'once',
+ ol.control.Attribution.prototype.once);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'un',
+ ol.control.Attribution.prototype.un);
+
+goog.exportProperty(
+ ol.control.Attribution.prototype,
+ 'unByKey',
+ ol.control.Attribution.prototype.unByKey);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'getMap',
+ ol.control.FullScreen.prototype.getMap);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'setMap',
+ ol.control.FullScreen.prototype.setMap);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'setTarget',
+ ol.control.FullScreen.prototype.setTarget);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'get',
+ ol.control.FullScreen.prototype.get);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'getKeys',
+ ol.control.FullScreen.prototype.getKeys);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'getProperties',
+ ol.control.FullScreen.prototype.getProperties);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'set',
+ ol.control.FullScreen.prototype.set);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'setProperties',
+ ol.control.FullScreen.prototype.setProperties);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'unset',
+ ol.control.FullScreen.prototype.unset);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'changed',
+ ol.control.FullScreen.prototype.changed);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'dispatchEvent',
+ ol.control.FullScreen.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'getRevision',
+ ol.control.FullScreen.prototype.getRevision);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'on',
+ ol.control.FullScreen.prototype.on);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'once',
+ ol.control.FullScreen.prototype.once);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'un',
+ ol.control.FullScreen.prototype.un);
+
+goog.exportProperty(
+ ol.control.FullScreen.prototype,
+ 'unByKey',
+ ol.control.FullScreen.prototype.unByKey);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'getMap',
+ ol.control.MousePosition.prototype.getMap);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'setMap',
+ ol.control.MousePosition.prototype.setMap);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'setTarget',
+ ol.control.MousePosition.prototype.setTarget);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'get',
+ ol.control.MousePosition.prototype.get);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'getKeys',
+ ol.control.MousePosition.prototype.getKeys);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'getProperties',
+ ol.control.MousePosition.prototype.getProperties);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'set',
+ ol.control.MousePosition.prototype.set);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'setProperties',
+ ol.control.MousePosition.prototype.setProperties);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'unset',
+ ol.control.MousePosition.prototype.unset);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'changed',
+ ol.control.MousePosition.prototype.changed);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'dispatchEvent',
+ ol.control.MousePosition.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'getRevision',
+ ol.control.MousePosition.prototype.getRevision);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'on',
+ ol.control.MousePosition.prototype.on);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'once',
+ ol.control.MousePosition.prototype.once);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'un',
+ ol.control.MousePosition.prototype.un);
+
+goog.exportProperty(
+ ol.control.MousePosition.prototype,
+ 'unByKey',
+ ol.control.MousePosition.prototype.unByKey);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'getMap',
+ ol.control.OverviewMap.prototype.getMap);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'setMap',
+ ol.control.OverviewMap.prototype.setMap);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'setTarget',
+ ol.control.OverviewMap.prototype.setTarget);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'get',
+ ol.control.OverviewMap.prototype.get);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'getKeys',
+ ol.control.OverviewMap.prototype.getKeys);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'getProperties',
+ ol.control.OverviewMap.prototype.getProperties);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'set',
+ ol.control.OverviewMap.prototype.set);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'setProperties',
+ ol.control.OverviewMap.prototype.setProperties);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'unset',
+ ol.control.OverviewMap.prototype.unset);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'changed',
+ ol.control.OverviewMap.prototype.changed);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'dispatchEvent',
+ ol.control.OverviewMap.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'getRevision',
+ ol.control.OverviewMap.prototype.getRevision);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'on',
+ ol.control.OverviewMap.prototype.on);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'once',
+ ol.control.OverviewMap.prototype.once);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'un',
+ ol.control.OverviewMap.prototype.un);
+
+goog.exportProperty(
+ ol.control.OverviewMap.prototype,
+ 'unByKey',
+ ol.control.OverviewMap.prototype.unByKey);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'getMap',
+ ol.control.Rotate.prototype.getMap);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'setMap',
+ ol.control.Rotate.prototype.setMap);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'setTarget',
+ ol.control.Rotate.prototype.setTarget);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'get',
+ ol.control.Rotate.prototype.get);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'getKeys',
+ ol.control.Rotate.prototype.getKeys);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'getProperties',
+ ol.control.Rotate.prototype.getProperties);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'set',
+ ol.control.Rotate.prototype.set);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'setProperties',
+ ol.control.Rotate.prototype.setProperties);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'unset',
+ ol.control.Rotate.prototype.unset);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'changed',
+ ol.control.Rotate.prototype.changed);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'dispatchEvent',
+ ol.control.Rotate.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'getRevision',
+ ol.control.Rotate.prototype.getRevision);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'on',
+ ol.control.Rotate.prototype.on);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'once',
+ ol.control.Rotate.prototype.once);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'un',
+ ol.control.Rotate.prototype.un);
+
+goog.exportProperty(
+ ol.control.Rotate.prototype,
+ 'unByKey',
+ ol.control.Rotate.prototype.unByKey);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'getMap',
+ ol.control.ScaleLine.prototype.getMap);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'setMap',
+ ol.control.ScaleLine.prototype.setMap);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'setTarget',
+ ol.control.ScaleLine.prototype.setTarget);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'get',
+ ol.control.ScaleLine.prototype.get);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'getKeys',
+ ol.control.ScaleLine.prototype.getKeys);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'getProperties',
+ ol.control.ScaleLine.prototype.getProperties);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'set',
+ ol.control.ScaleLine.prototype.set);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'setProperties',
+ ol.control.ScaleLine.prototype.setProperties);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'unset',
+ ol.control.ScaleLine.prototype.unset);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'changed',
+ ol.control.ScaleLine.prototype.changed);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'dispatchEvent',
+ ol.control.ScaleLine.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'getRevision',
+ ol.control.ScaleLine.prototype.getRevision);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'on',
+ ol.control.ScaleLine.prototype.on);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'once',
+ ol.control.ScaleLine.prototype.once);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'un',
+ ol.control.ScaleLine.prototype.un);
+
+goog.exportProperty(
+ ol.control.ScaleLine.prototype,
+ 'unByKey',
+ ol.control.ScaleLine.prototype.unByKey);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'getMap',
+ ol.control.Zoom.prototype.getMap);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'setMap',
+ ol.control.Zoom.prototype.setMap);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'setTarget',
+ ol.control.Zoom.prototype.setTarget);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'get',
+ ol.control.Zoom.prototype.get);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'getKeys',
+ ol.control.Zoom.prototype.getKeys);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'getProperties',
+ ol.control.Zoom.prototype.getProperties);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'set',
+ ol.control.Zoom.prototype.set);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'setProperties',
+ ol.control.Zoom.prototype.setProperties);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'unset',
+ ol.control.Zoom.prototype.unset);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'changed',
+ ol.control.Zoom.prototype.changed);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'dispatchEvent',
+ ol.control.Zoom.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'getRevision',
+ ol.control.Zoom.prototype.getRevision);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'on',
+ ol.control.Zoom.prototype.on);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'once',
+ ol.control.Zoom.prototype.once);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'un',
+ ol.control.Zoom.prototype.un);
+
+goog.exportProperty(
+ ol.control.Zoom.prototype,
+ 'unByKey',
+ ol.control.Zoom.prototype.unByKey);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'getMap',
+ ol.control.ZoomSlider.prototype.getMap);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'setMap',
+ ol.control.ZoomSlider.prototype.setMap);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'setTarget',
+ ol.control.ZoomSlider.prototype.setTarget);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'get',
+ ol.control.ZoomSlider.prototype.get);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'getKeys',
+ ol.control.ZoomSlider.prototype.getKeys);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'getProperties',
+ ol.control.ZoomSlider.prototype.getProperties);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'set',
+ ol.control.ZoomSlider.prototype.set);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'setProperties',
+ ol.control.ZoomSlider.prototype.setProperties);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'unset',
+ ol.control.ZoomSlider.prototype.unset);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'changed',
+ ol.control.ZoomSlider.prototype.changed);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'dispatchEvent',
+ ol.control.ZoomSlider.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'getRevision',
+ ol.control.ZoomSlider.prototype.getRevision);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'on',
+ ol.control.ZoomSlider.prototype.on);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'once',
+ ol.control.ZoomSlider.prototype.once);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'un',
+ ol.control.ZoomSlider.prototype.un);
+
+goog.exportProperty(
+ ol.control.ZoomSlider.prototype,
+ 'unByKey',
+ ol.control.ZoomSlider.prototype.unByKey);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'getMap',
+ ol.control.ZoomToExtent.prototype.getMap);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'setMap',
+ ol.control.ZoomToExtent.prototype.setMap);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'setTarget',
+ ol.control.ZoomToExtent.prototype.setTarget);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'get',
+ ol.control.ZoomToExtent.prototype.get);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'getKeys',
+ ol.control.ZoomToExtent.prototype.getKeys);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'getProperties',
+ ol.control.ZoomToExtent.prototype.getProperties);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'set',
+ ol.control.ZoomToExtent.prototype.set);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'setProperties',
+ ol.control.ZoomToExtent.prototype.setProperties);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'unset',
+ ol.control.ZoomToExtent.prototype.unset);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'changed',
+ ol.control.ZoomToExtent.prototype.changed);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'dispatchEvent',
+ ol.control.ZoomToExtent.prototype.dispatchEvent);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'getRevision',
+ ol.control.ZoomToExtent.prototype.getRevision);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'on',
+ ol.control.ZoomToExtent.prototype.on);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'once',
+ ol.control.ZoomToExtent.prototype.once);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'un',
+ ol.control.ZoomToExtent.prototype.un);
+
+goog.exportProperty(
+ ol.control.ZoomToExtent.prototype,
+ 'unByKey',
+ ol.control.ZoomToExtent.prototype.unByKey);
+OPENLAYERS.ol = ol;
+
+ return OPENLAYERS.ol;
+}));
diff --git a/chimere/static/ol3/ol.css b/chimere/static/ol3/ol.css
new file mode 100644
index 0000000..ea50e7e
--- /dev/null
+++ b/chimere/static/ol3/ol.css
@@ -0,0 +1 @@
+.ol-control,.ol-scale-line{position:absolute;padding:2px}.ol-box{box-sizing:border-box;border-radius:2px;border:2px solid #00f}.ol-mouse-position{top:8px;right:8px;position:absolute}.ol-scale-line{background:rgba(0,60,136,.3);border-radius:4px;bottom:8px;left:8px}.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{background-color:rgba(255,255,255,.4);border-radius:4px}.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: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: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;vertical-align:middle}.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;height:200px}.ol-zoomslider button{position:relative;height:10px}.ol-touch .ol-zoomslider{top:5.5em}.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/chimere/static/ol3/ol.js b/chimere/static/ol3/ol.js
new file mode 100644
index 0000000..49d2e32
--- /dev/null
+++ b/chimere/static/ol3/ol.js
@@ -0,0 +1,1024 @@
+// OpenLayers 3. See http://openlayers.org/
+// License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md
+// Version: v3.12.1
+
+(function (root, factory) {
+ 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 ca(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&&ca(c)?d[e]=c:d[e]?d=d[e]:d=d[e]={}}function da(){}function ea(b){b.Yb=function(){return b.Pg?b.Pg:b.Pg=new b}}
+function fa(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 ga(b){return"array"==fa(b)}function ha(b){var c=fa(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"==fa(b)}function oa(b){var c=typeof b;return"object"==c&&null!=b||"function"==c}function w(b){return b[pa]||(b[pa]=++qa)}var pa="closure_uid_"+(1E9*Math.random()>>>0),qa=0;
+function ra(b,c,d){return b.call.apply(b.bind,arguments)}function ta(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 ua(b,c,d){ua=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?ra:ta;return ua.apply(null,arguments)}
+function va(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 wa=Date.now||function(){return+new Date};function y(b,c){function d(){}d.prototype=c.prototype;b.ca=c.prototype;b.prototype=new d;b.prototype.constructor=b;b.zp=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 xa,ya;function za(){};function Aa(b){if(Error.captureStackTrace)Error.captureStackTrace(this,Aa);else{var c=Error().stack;c&&(this.stack=c)}b&&(this.message=String(b))}y(Aa,Error);Aa.prototype.name="CustomError";var Ba;function Ca(b,c){var d=b.length-c.length;return 0<=d&&b.indexOf(c,d)==d}function Da(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 Ea=String.prototype.trim?function(b){return b.trim()}:function(b){return b.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")};
+function Ga(b){if(!Ha.test(b))return b;-1!=b.indexOf("&")&&(b=b.replace(Ia,"&amp;"));-1!=b.indexOf("<")&&(b=b.replace(Ja,"&lt;"));-1!=b.indexOf(">")&&(b=b.replace(Ka,"&gt;"));-1!=b.indexOf('"')&&(b=b.replace(La,"&quot;"));-1!=b.indexOf("'")&&(b=b.replace(Ma,"&#39;"));-1!=b.indexOf("\x00")&&(b=b.replace(Na,"&#0;"));return b}var Ia=/&/g,Ja=/</g,Ka=/>/g,La=/"/g,Ma=/'/g,Na=/\x00/g,Ha=/[\x00&<>"']/,Oa=String.prototype.repeat?function(b,c){return b.repeat(c)}:function(b,c){return Array(c+1).join(b)};
+function Pa(b){b=ca(void 0)?b.toFixed(void 0):String(b);var c=b.indexOf(".");-1==c&&(c=b.length);return Oa("0",Math.max(0,2-c))+b}
+function Qa(b,c){for(var d=0,e=Ea(String(b)).split("."),f=Ea(String(c)).split("."),g=Math.max(e.length,f.length),h=0;0==d&&h<g;h++){var k=e[h]||"",m=f[h]||"",n=RegExp("(\\d*)(\\D*)","g"),p=RegExp("(\\d*)(\\D*)","g");do{var q=n.exec(k)||["","",""],r=p.exec(m)||["","",""];if(0==q[0].length&&0==r[0].length)break;d=Ra(0==q[1].length?0:parseInt(q[1],10),0==r[1].length?0:parseInt(r[1],10))||Ra(0==q[2].length,0==r[2].length)||Ra(q[2],r[2])}while(0==d)}return d}function Ra(b,c){return b<c?-1:b>c?1:0};function Sa(b,c,d){return Math.min(Math.max(b,c),d)}var Ta=function(){var b;"cosh"in Math?b=Math.cosh:b=function(b){b=Math.exp(b);return(b+1/b)/2};return b}();function Va(b,c,d,e,f,g){var h=f-d,k=g-e;if(0!==h||0!==k){var m=((b-d)*h+(c-e)*k)/(h*h+k*k);1<m?(d=f,e=g):0<m&&(d+=h*m,e+=k*m)}return Wa(b,c,d,e)}function Wa(b,c,d,e){b=d-b;c=e-c;return b*b+c*c}function Xa(b){return b*Math.PI/180};function Ya(b){return function(c){if(c)return[Sa(c[0],b[0],b[2]),Sa(c[1],b[1],b[3])]}}function Za(b){return b};var $a=Array.prototype;function ab(b,c){return $a.indexOf.call(b,c,void 0)}function bb(b,c){$a.forEach.call(b,c,void 0)}function cb(b,c){return $a.filter.call(b,c,void 0)}function db(b,c){return $a.map.call(b,c,void 0)}function eb(b,c){return $a.some.call(b,c,void 0)}function fb(b,c){var d=gb(b,c,void 0);return 0>d?null:ia(b)?b.charAt(d):b[d]}function gb(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 hb(b,c){var d=ab(b,c),e;(e=0<=d)&&$a.splice.call(b,d,1);return e}function ib(b){return $a.concat.apply($a,arguments)}function jb(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 kb(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 lb(b,c,d,e){$a.splice.apply(b,mb(arguments,1))}
+function mb(b,c,d){return 2>=arguments.length?$a.slice.call(b,c):$a.slice.call(b,c,d)}function nb(b,c){b.sort(c||ob)}function pb(b){for(var c=qb,d=0;d<b.length;d++)b[d]={index:d,value:b[d]};var e=c||ob;nb(b,function(b,c){return e(b.value,c.value)||b.index-c.index});for(d=0;d<b.length;d++)b[d]=b[d].value}function rb(b,c){if(!ha(b)||!ha(c)||b.length!=c.length)return!1;for(var d=b.length,e=sb,f=0;f<d;f++)if(!e(b[f],c[f]))return!1;return!0}function ob(b,c){return b>c?1:b<c?-1:0}
+function sb(b,c){return b===c}function tb(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=mb(e,f,f+8192),g=tb.apply(null,g),h=0;h<g.length;h++)c.push(g[h]);else c.push(e)}return c};function ub(b,c){return b>c?1:b<c?-1:0}function vb(b,c){return 0<=b.indexOf(c)}function wb(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 xb(b){return function(c,d,e){if(void 0!==c)return c=wb(b,c,e),c=Sa(c+d,0,b.length-1),b[c]}}function yb(b,c,d){return function(e,f,g){if(void 0!==e)return e=Math.max(Math.floor(Math.log(c/e)/Math.log(b)+(0<g?0:0>g?1:.5))+f,0),void 0!==d&&(e=Math.min(e,d)),c/Math.pow(b,e)}};function zb(b){if(void 0!==b)return 0}function Ab(b,c){if(void 0!==b)return b+c}function Bb(b){var c=2*Math.PI/b;return function(b,e){if(void 0!==b)return b=Math.floor((b+e)/c+.5)*c}}function Cb(){var b=Xa(5);return function(c,d){if(void 0!==c)return Math.abs(c+d)<=b?0:c+d}};function Db(b,c,d){this.center=b;this.resolution=c;this.rotation=d};var Eb;a:{var Fb=ba.navigator;if(Fb){var Gb=Fb.userAgent;if(Gb){Eb=Gb;break a}}Eb=""}function Hb(b){return-1!=Eb.indexOf(b)};function Ib(b,c,d){for(var e in b)c.call(d,b[e],e,b)}function Jb(b,c){for(var d in b)if(c.call(void 0,b[d],d,b))return!0;return!1}function Kb(b){var c=0,d;for(d in b)c++;return c}function Lb(b){var c=[],d=0,e;for(e in b)c[d++]=b[e];return c}function Mb(b,c){for(var d in b)if(b[d]==c)return!0;return!1}function Ob(b,c){for(var d in b)if(c.call(void 0,b[d],d,b))return d}function Pb(b){for(var c in b)return!1;return!0}function Qb(b){for(var c in b)delete b[c]}function Rb(b,c,d){return c in b?b[c]:d}
+function Sb(b,c){var d=[];return c in b?b[c]:b[c]=d}function Tb(b){var c={},d;for(d in b)c[d]=b[d];return c}function Ub(b){var c=fa(b);if("object"==c||"array"==c){if(ka(b.clone))return b.clone();var c="array"==c?[]:{},d;for(d in b)c[d]=Ub(b[d]);return c}return b}var Vb="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" ");
+function Wb(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<Vb.length;g++)d=Vb[g],Object.prototype.hasOwnProperty.call(e,d)&&(b[d]=e[d])}};var Xb=Hb("Opera")||Hb("OPR"),Yb=Hb("Trident")||Hb("MSIE"),Zb=Hb("Edge"),$b=Hb("Gecko")&&!(-1!=Eb.toLowerCase().indexOf("webkit")&&!Hb("Edge"))&&!(Hb("Trident")||Hb("MSIE"))&&!Hb("Edge"),ac=-1!=Eb.toLowerCase().indexOf("webkit")&&!Hb("Edge"),bc=Hb("Macintosh"),cc=Hb("Windows"),dc=Hb("Linux")||Hb("CrOS");function ec(){var b=Eb;if($b)return/rv\:([^\);]+)(\)|;)/.exec(b);if(Zb)return/Edge\/([\d\.]+)/.exec(b);if(Yb)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(b);if(ac)return/WebKit\/(\S+)/.exec(b)}
+function fc(){var b=ba.document;return b?b.documentMode:void 0}var gc=function(){if(Xb&&ba.opera){var b;var c=ba.opera.version;try{b=c()}catch(d){b=c}return b}b="";(c=ec())&&(b=c?c[1]:"");return Yb&&(c=fc(),c>parseFloat(b))?String(c):b}(),hc={};function ic(b){return hc[b]||(hc[b]=0<=Qa(gc,b))}var jc=ba.document,kc=jc&&Yb?fc()||("CSS1Compat"==jc.compatMode?parseInt(gc,10):5):void 0;var lc=!Yb||9<=kc,mc=!Yb||9<=kc,nc=Yb&&!ic("9");!ac||ic("528");$b&&ic("1.9b")||Yb&&ic("8")||Xb&&ic("9.5")||ac&&ic("528");$b&&!ic("8")||Yb&&ic("9");function oc(){0!=pc&&(qc[w(this)]=this);this.na=this.na;this.va=this.va}var pc=0,qc={};oc.prototype.na=!1;oc.prototype.Fc=function(){if(!this.na&&(this.na=!0,this.Y(),0!=pc)){var b=w(this);delete qc[b]}};function rc(b,c){var d=va(sc,c);b.na?d.call(void 0):(b.va||(b.va=[]),b.va.push(ca(void 0)?ua(d,void 0):d))}oc.prototype.Y=function(){if(this.va)for(;this.va.length;)this.va.shift()()};function sc(b){b&&"function"==typeof b.Fc&&b.Fc()};function tc(b,c){this.type=b;this.g=this.target=c;this.j=!1;this.Th=!0}tc.prototype.b=function(){this.j=!0};tc.prototype.preventDefault=function(){this.Th=!1};function uc(b){b.b()}function vc(b){b.preventDefault()};function wc(b){wc[" "](b);return b}wc[" "]=da;function xc(b,c){tc.call(this,b?b.type:"");this.relatedTarget=this.g=this.target=null;this.G=this.i=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.B=this.c=this.f=this.o=!1;this.state=null;this.l=!1;this.a=null;if(b){var d=this.type=b.type,e=b.changedTouches?b.changedTouches[0]:null;this.target=b.target||b.srcElement;this.g=c;var f=b.relatedTarget;if(f){if($b){var g;a:{try{wc(f.nodeName);g=!0;break a}catch(h){}g=!1}g||(f=null)}}else"mouseover"==d?
+f=b.fromElement:"mouseout"==d&&(f=b.toElement);this.relatedTarget=f;null===e?(this.offsetX=ac||void 0!==b.offsetX?b.offsetX:b.layerX,this.offsetY=ac||void 0!==b.offsetY?b.offsetY:b.layerY,this.clientX=void 0!==b.clientX?b.clientX:b.pageX,this.clientY=void 0!==b.clientY?b.clientY:b.pageY,this.screenX=b.screenX||0,this.screenY=b.screenY||0):(this.clientX=void 0!==e.clientX?e.clientX:e.pageX,this.clientY=void 0!==e.clientY?e.clientY:e.pageY,this.screenX=e.screenX||0,this.screenY=e.screenY||0);this.button=
+b.button;this.i=b.keyCode||0;this.G=b.charCode||("keypress"==d?b.keyCode:0);this.o=b.ctrlKey;this.f=b.altKey;this.c=b.shiftKey;this.B=b.metaKey;this.l=bc?b.metaKey:b.ctrlKey;this.state=b.state;this.a=b;b.defaultPrevented&&this.preventDefault()}}y(xc,tc);var yc=[1,4,2];function zc(b){return(lc?0==b.a.button:"click"==b.type?!0:!!(b.a.button&yc[0]))&&!(ac&&bc&&b.o)}xc.prototype.b=function(){xc.ca.b.call(this);this.a.stopPropagation?this.a.stopPropagation():this.a.cancelBubble=!0};
+xc.prototype.preventDefault=function(){xc.ca.preventDefault.call(this);var b=this.a;if(b.preventDefault)b.preventDefault();else if(b.returnValue=!1,nc)try{if(b.ctrlKey||112<=b.keyCode&&123>=b.keyCode)b.keyCode=-1}catch(c){}};var Ac="closure_listenable_"+(1E6*Math.random()|0);function Bc(b){return!(!b||!b[Ac])}var Cc=0;function Dc(b,c,d,e,f){this.listener=b;this.a=null;this.src=c;this.type=d;this.bd=!!e;this.ke=f;this.key=++Cc;this.Uc=this.Vd=!1}function Ec(b){b.Uc=!0;b.listener=null;b.a=null;b.src=null;b.ke=null};function Fc(b){this.src=b;this.a={};this.f=0}Fc.prototype.add=function(b,c,d,e,f){var g=b.toString();b=this.a[g];b||(b=this.a[g]=[],this.f++);var h=Gc(b,c,e,f);-1<h?(c=b[h],d||(c.Vd=!1)):(c=new Dc(c,this.src,g,!!e,f),c.Vd=d,b.push(c));return c};Fc.prototype.remove=function(b,c,d,e){b=b.toString();if(!(b in this.a))return!1;var f=this.a[b];c=Gc(f,c,d,e);return-1<c?(Ec(f[c]),$a.splice.call(f,c,1),0==f.length&&(delete this.a[b],this.f--),!0):!1};
+function Hc(b,c){var d=c.type;if(!(d in b.a))return!1;var e=hb(b.a[d],c);e&&(Ec(c),0==b.a[d].length&&(delete b.a[d],b.f--));return e}function Ic(b,c,d,e,f){b=b.a[c.toString()];c=-1;b&&(c=Gc(b,d,e,f));return-1<c?b[c]:null}function Jc(b,c,d){var e=ca(c),f=e?c.toString():"",g=ca(d);return Jb(b.a,function(b){for(var c=0;c<b.length;++c)if(!(e&&b[c].type!=f||g&&b[c].bd!=d))return!0;return!1})}
+function Gc(b,c,d,e){for(var f=0;f<b.length;++f){var g=b[f];if(!g.Uc&&g.listener==c&&g.bd==!!d&&g.ke==e)return f}return-1};var Kc="closure_lm_"+(1E6*Math.random()|0),Lc={},Mc=0;function D(b,c,d,e,f){if(ga(c)){for(var g=0;g<c.length;g++)D(b,c[g],d,e,f);return null}d=Nc(d);return Bc(b)?b.Ra(c,d,e,f):Oc(b,c,d,!1,e,f)}
+function Oc(b,c,d,e,f,g){if(!c)throw Error("Invalid event type");var h=!!f,k=Pc(b);k||(b[Kc]=k=new Fc(b));d=k.add(c,d,e,f,g);if(d.a)return d;e=Rc();d.a=e;e.src=b;e.listener=d;if(b.addEventListener)b.addEventListener(c.toString(),e,h);else if(b.attachEvent)b.attachEvent(Sc(c.toString()),e);else throw Error("addEventListener and attachEvent are unavailable.");Mc++;return d}
+function Rc(){var b=Tc,c=mc?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 Uc(b,c,d,e,f){if(ga(c)){for(var g=0;g<c.length;g++)Uc(b,c[g],d,e,f);return null}d=Nc(d);return Bc(b)?b.yb.add(String(c),d,!0,e,f):Oc(b,c,d,!0,e,f)}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);else d=Nc(d),Bc(b)?b.Wf(c,d,e,f):b&&(b=Pc(b))&&(c=Ic(b,c,d,!!e,f))&&Wc(c)}
+function Wc(b){if(ja(b)||!b||b.Uc)return!1;var c=b.src;if(Bc(c))return Hc(c.yb,b);var d=b.type,e=b.a;c.removeEventListener?c.removeEventListener(d,e,b.bd):c.detachEvent&&c.detachEvent(Sc(d),e);Mc--;(d=Pc(c))?(Hc(d,b),0==d.f&&(d.src=null,c[Kc]=null)):Ec(b);return!0}function Sc(b){return b in Lc?Lc[b]:Lc[b]="on"+b}function Xc(b,c,d,e){var f=!0;if(b=Pc(b))if(c=b.a[c.toString()])for(c=c.concat(),b=0;b<c.length;b++){var g=c[b];g&&g.bd==d&&!g.Uc&&(g=Yc(g,e),f=f&&!1!==g)}return f}
+function Yc(b,c){var d=b.listener,e=b.ke||b.src;b.Vd&&Wc(b);return d.call(e,c)}
+function Tc(b,c){if(b.Uc)return!0;if(!mc){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(m){g=!0}if(g||void 0==f.returnValue)f.returnValue=!0}f=[];for(g=d.g;g;g=g.parentNode)f.push(g);for(var g=b.type,h=f.length-1;!d.j&&0<=h;h--){d.g=f[h];var k=Xc(f[h],g,!0,d),e=e&&k}for(h=0;!d.j&&h<f.length;h++)d.g=f[h],k=
+Xc(f[h],g,!1,d),e=e&&k}return e}return Yc(b,new xc(c,this))}function Pc(b){b=b[Kc];return b instanceof Fc?b:null}var Zc="__closure_events_fn_"+(1E9*Math.random()>>>0);function Nc(b){if(ka(b))return b;b[Zc]||(b[Zc]=function(c){return b.handleEvent(c)});return b[Zc]};function $c(){oc.call(this);this.yb=new Fc(this);this.Od=this;this.gb=null}y($c,oc);$c.prototype[Ac]=!0;l=$c.prototype;l.addEventListener=function(b,c,d,e){D(this,b,c,d,e)};l.removeEventListener=function(b,c,d,e){Vc(this,b,c,d,e)};
+l.s=function(b){var c,d=this.gb;if(d)for(c=[];d;d=d.gb)c.push(d);var d=this.Od,e=b.type||b;if(ia(b))b=new tc(b,d);else if(b instanceof tc)b.target=b.target||d;else{var f=b;b=new tc(e,d);Wb(b,f)}var f=!0,g;if(c)for(var h=c.length-1;!b.j&&0<=h;h--)g=b.g=c[h],f=bd(g,e,!0,b)&&f;b.j||(g=b.g=d,f=bd(g,e,!0,b)&&f,b.j||(f=bd(g,e,!1,b)&&f));if(c)for(h=0;!b.j&&h<c.length;h++)g=b.g=c[h],f=bd(g,e,!1,b)&&f;return f};
+l.Y=function(){$c.ca.Y.call(this);if(this.yb){var b=this.yb,c=0,d;for(d in b.a){for(var e=b.a[d],f=0;f<e.length;f++)++c,Ec(e[f]);delete b.a[d];b.f--}}this.gb=null};l.Ra=function(b,c,d,e){return this.yb.add(String(b),c,!1,d,e)};l.Wf=function(b,c,d,e){return this.yb.remove(String(b),c,d,e)};
+function bd(b,c,d,e){c=b.yb.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.bd==d){var k=h.listener,m=h.ke||h.src;h.Vd&&Hc(b.yb,h);f=!1!==k.call(m,e)&&f}}return f&&0!=e.Th}function cd(b,c,d){return Jc(b.yb,ca(c)?String(c):void 0,d)};function dd(){$c.call(this);this.f=0}y(dd,$c);function ed(b){Wc(b)}l=dd.prototype;l.u=function(){++this.f;this.s("change")};l.L=function(){return this.f};l.H=function(b,c,d){return D(this,b,c,!1,d)};l.M=function(b,c,d){return Uc(this,b,c,!1,d)};l.K=function(b,c,d){Vc(this,b,c,!1,d)};l.N=ed;function fd(b,c,d){tc.call(this,b);this.key=c;this.oldValue=d}y(fd,tc);function gd(b){dd.call(this);w(this);this.G={};void 0!==b&&this.I(b)}y(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.G.hasOwnProperty(b)&&(c=this.G[b]);return c};l.P=function(){return Object.keys(this.G)};l.R=function(){var b={},c;for(c in this.G)b[c]=this.G[c];return b};
+function jd(b,c,d){var e;e=id(c);b.s(new fd(e,c,d));b.s(new fd("propertychange",c,d))}l.set=function(b,c,d){d?this.G[b]=c:(d=this.G[b],this.G[b]=c,d!==c&&jd(this,b,d))};l.I=function(b,c){for(var d in b)this.set(d,b[d],c)};l.S=function(b,c){if(b in this.G){var d=this.G[b];delete this.G[b];c||jd(this,b,d)}};function kd(b,c,d){void 0===d&&(d=[0,0]);d[0]=b[0]+2*c;d[1]=b[1]+2*c;return d}function ld(b,c,d){void 0===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;void 0===c?c=[b,b]:(c[0]=b,c[1]=b);return c};function nd(b,c){var d=b%c;return 0>d*c?d+c:d}function od(b,c,d){return b+d*(c-b)};function pd(b,c){b[0]+=c[0];b[1]+=c[1];return b}function qd(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],m=k-h,n=g-f,d=0===m&&0===n?0:(m*(d-h)+n*(e-f))/(m*m+n*n||0);0>=d||(1<=d?(h=k,f=g):(h+=d*m,f+=d*n));return[h,f]}function rd(b,c){var d=nd(b+180,360)-180,e=Math.abs(Math.round(3600*d));return Math.floor(e/3600)+"\u00b0 "+Pa(Math.floor(e/60%60))+"\u2032 "+Pa(Math.floor(e%60))+"\u2033 "+c.charAt(0>d?1:0)}
+function sd(b,c,d){return b?c.replace("{x}",b[0].toFixed(d)).replace("{y}",b[1].toFixed(d)):""}function td(b,c){for(var d=!0,e=b.length-1;0<=e;--e)if(b[e]!=c[e]){d=!1;break}return d}function ud(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 vd(b,c){var d=b[0]-c[0],e=b[1]-c[1];return d*d+e*e}function wd(b,c){return vd(b,qd(b,c))}function xd(b,c){return sd(b,"{x}, {y}",c)};function yd(b){this.length=b.length||b;for(var c=0;c<this.length;c++)this[c]=b[c]||0}yd.prototype.a=4;yd.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]};yd.prototype.toString=Array.prototype.join;"undefined"==typeof Float32Array&&(yd.BYTES_PER_ELEMENT=4,yd.prototype.BYTES_PER_ELEMENT=yd.prototype.a,yd.prototype.set=yd.prototype.set,yd.prototype.toString=yd.prototype.toString,u("Float32Array",yd,void 0));function zd(b){this.length=b.length||b;for(var c=0;c<this.length;c++)this[c]=b[c]||0}zd.prototype.a=8;zd.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]};zd.prototype.toString=Array.prototype.join;if("undefined"==typeof Float64Array){try{zd.BYTES_PER_ELEMENT=8}catch(b){}zd.prototype.BYTES_PER_ELEMENT=zd.prototype.a;zd.prototype.set=zd.prototype.set;zd.prototype.toString=zd.prototype.toString;u("Float64Array",zd,void 0)};function Ad(b,c,d,e,f){b[0]=c;b[1]=d;b[2]=e;b[3]=f};function Bd(){var b=Array(16);Cd(b,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);return b}function Dd(){var b=Array(16);Cd(b,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);return b}function Cd(b,c,d,e,f,g,h,k,m,n,p,q,r,t,x,z,A){b[0]=c;b[1]=d;b[2]=e;b[3]=f;b[4]=g;b[5]=h;b[6]=k;b[7]=m;b[8]=n;b[9]=p;b[10]=q;b[11]=r;b[12]=t;b[13]=x;b[14]=z;b[15]=A}
+function Ed(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 Fd(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 Gd(b,c,d){var e=b[0],f=b[1],g=b[2],h=b[3],k=b[4],m=b[5],n=b[6],p=b[7],q=b[8],r=b[9],t=b[10],x=b[11],z=b[12],A=b[13],B=b[14];b=b[15];var v=c[0],L=c[1],M=c[2],J=c[3],C=c[4],sa=c[5],la=c[6],K=c[7],ma=c[8],Ua=c[9],Nb=c[10],na=c[11],Fa=c[12],ad=c[13],Qc=c[14];c=c[15];d[0]=e*v+k*L+q*M+z*J;d[1]=f*v+m*L+r*M+A*J;d[2]=g*v+n*L+t*M+B*J;d[3]=h*v+p*L+x*M+b*J;d[4]=e*C+k*sa+q*la+z*K;d[5]=f*C+m*sa+r*la+A*K;d[6]=g*C+n*sa+t*la+B*K;d[7]=h*C+p*sa+x*la+b*K;d[8]=e*ma+k*Ua+q*Nb+z*na;d[9]=f*ma+m*Ua+r*Nb+A*na;d[10]=
+g*ma+n*Ua+t*Nb+B*na;d[11]=h*ma+p*Ua+x*Nb+b*na;d[12]=e*Fa+k*ad+q*Qc+z*c;d[13]=f*Fa+m*ad+r*Qc+A*c;d[14]=g*Fa+n*ad+t*Qc+B*c;d[15]=h*Fa+p*ad+x*Qc+b*c}
+function Hd(b,c){var d=b[0],e=b[1],f=b[2],g=b[3],h=b[4],k=b[5],m=b[6],n=b[7],p=b[8],q=b[9],r=b[10],t=b[11],x=b[12],z=b[13],A=b[14],B=b[15],v=d*k-e*h,L=d*m-f*h,M=d*n-g*h,J=e*m-f*k,C=e*n-g*k,sa=f*n-g*m,la=p*z-q*x,K=p*A-r*x,ma=p*B-t*x,Ua=q*A-r*z,Nb=q*B-t*z,na=r*B-t*A,Fa=v*na-L*Nb+M*Ua+J*ma-C*K+sa*la;0!=Fa&&(Fa=1/Fa,c[0]=(k*na-m*Nb+n*Ua)*Fa,c[1]=(-e*na+f*Nb-g*Ua)*Fa,c[2]=(z*sa-A*C+B*J)*Fa,c[3]=(-q*sa+r*C-t*J)*Fa,c[4]=(-h*na+m*ma-n*K)*Fa,c[5]=(d*na-f*ma+g*K)*Fa,c[6]=(-x*sa+A*M-B*L)*Fa,c[7]=(p*sa-r*M+t*
+L)*Fa,c[8]=(h*Nb-k*ma+n*la)*Fa,c[9]=(-d*Nb+e*ma-g*la)*Fa,c[10]=(x*C-z*M+B*v)*Fa,c[11]=(-p*C+q*M-t*v)*Fa,c[12]=(-h*Ua+k*K-m*la)*Fa,c[13]=(d*Ua-e*K+f*la)*Fa,c[14]=(-x*J+z*L-A*v)*Fa,c[15]=(p*J-q*L+r*v)*Fa)}function Id(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 Jd(b,c,d){Cd(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 Kd(b,c){var d=b[0],e=b[1],f=b[2],g=b[3],h=b[4],k=b[5],m=b[6],n=b[7],p=Math.cos(c),q=Math.sin(c);b[0]=d*p+h*q;b[1]=e*p+k*q;b[2]=f*p+m*q;b[3]=g*p+n*q;b[4]=d*-q+h*p;b[5]=e*-q+k*p;b[6]=f*-q+m*p;b[7]=g*-q+n*p}new Float64Array(3);new Float64Array(3);new Float64Array(4);new Float64Array(4);new Float64Array(4);new Float64Array(16);function Ld(b){for(var c=Md(),d=0,e=b.length;d<e;++d)Nd(c,b[d]);return c}function Od(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 Pd(e,f,b,c,d)}function Qd(b,c,d){return 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 Rd(b,c){return c?(c[0]=b[0],c[1]=b[1],c[2]=b[2],c[3]=b[3],c):b.slice()}
+function Sd(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 Td(b,c){return Ud(b,c[0],c[1])}function Vd(b,c){return b[0]<=c[0]&&c[2]<=b[2]&&b[1]<=c[1]&&c[3]<=b[3]}function Ud(b,c,d){return b[0]<=c&&c<=b[2]&&b[1]<=d&&d<=b[3]}function Wd(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 Md(){return[Infinity,Infinity,-Infinity,-Infinity]}
+function Pd(b,c,d,e,f){return f?(f[0]=b,f[1]=c,f[2]=d,f[3]=e,f):[b,c,d,e]}function Xd(b,c){var d=b[0],e=b[1];return Pd(d,e,d,e,c)}function Yd(b,c,d,e,f){f=Pd(Infinity,Infinity,-Infinity,-Infinity,f);return Zd(f,b,c,d,e)}function ae(b,c){return b[0]==c[0]&&b[2]==c[2]&&b[1]==c[1]&&b[3]==c[3]}function be(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 Nd(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 ce(b,c,d){var e;return(e=c.call(d,de(b)))||(e=c.call(d,ee(b)))||(e=c.call(d,fe(b)))?e:(e=c.call(d,ge(b)))?e:!1}function he(b){var c=0;ie(b)||(c=je(b)*ke(b));return c}function de(b){return[b[0],b[1]]}
+function ee(b){return[b[2],b[1]]}function le(b){return[(b[0]+b[2])/2,(b[1]+b[3])/2]}function me(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 Od(f,e,void 0)}function ke(b){return b[3]-b[1]}function ne(b,c,d){d=d?d:Md();oe(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 ge(b){return[b[0],b[3]]}
+function fe(b){return[b[2],b[3]]}function je(b){return b[2]-b[0]}function oe(b,c){return b[0]<=c[2]&&b[2]>=c[0]&&b[1]<=c[3]&&b[3]>=c[1]}function ie(b){return b[2]<b[0]||b[3]<b[1]}function pe(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 qe(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 Od([b[0],b[2],b[4],b[6]],[b[1],b[3],b[5],b[7]],d)};function re(b){return function(){return b}}var se=re(!1),te=re(!0),ue=re(null);function ve(b){return b}function we(b){var c;c=c||0;return function(){return b.apply(this,Array.prototype.slice.call(arguments,0,c))}}function xe(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 ye(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
+ Licensed under CC-BY-3.0.
+*/
+function ze(b){this.radius=b}ze.prototype.f=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+Xa(h-e)*(2+Math.sin(Xa(f))+Math.sin(Xa(k))),e=h,f=k;return c*this.radius*this.radius/2};ze.prototype.a=function(b,c){var d=Xa(b[1]),e=Xa(c[1]),f=(e-d)/2,g=Xa(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))};
+ze.prototype.offset=function(b,c,d){var e=Xa(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*(Xa(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 Ae=new ze(6370997);var Be={};Be.degrees=2*Math.PI*Ae.radius/360;Be.ft=.3048;Be.m=1;Be["us-ft"]=1200/3937;
+function Ce(b){this.a=b.code;this.f=b.units;this.i=void 0!==b.extent?b.extent:null;this.j=void 0!==b.worldExtent?b.worldExtent:null;this.g=void 0!==b.axisOrientation?b.axisOrientation:"enu";this.c=void 0!==b.global?b.global:!1;this.b=!(!this.c||!this.i);this.o=void 0!==b.getPointResolution?b.getPointResolution:this.hk;this.l=null;var c=De,d=b.code;if("function"==typeof proj4&&void 0===c[d]){var e=proj4.defs(d);if(void 0!==e){void 0!==e.axis&&void 0===b.axisOrientation&&(this.g=e.axis);void 0===b.units&&
+(b=e.units,void 0===e.to_meter||void 0!==b&&void 0!==Be[b]||(b=e.to_meter.toString(),Be[b]=e.to_meter),this.f=b);for(var f in c)b=proj4.defs(f),void 0!==b&&(c=Ee(f),b===e?Fe([c,this]):(b=proj4(f,d),Ge(c,this,b.forward,b.inverse)))}}}l=Ce.prototype;l.Jj=function(){return this.a};l.J=function(){return this.i};l.Am=function(){return this.f};l.Kc=function(){return Be[this.f]};l.tk=function(){return this.j};function He(b){return b.g}l.gl=function(){return this.c};
+l.No=function(b){this.c=b;this.b=!(!b||!this.i)};l.Bm=function(b){this.i=b;this.b=!(!this.c||!b)};l.Vo=function(b){this.j=b};l.Mo=function(b){this.o=b};l.hk=function(b,c){if("degrees"==this.f)return b;var d=Ie(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=Ae.a(e.slice(0,2),e.slice(2,4)),e=Ae.a(e.slice(4,6),e.slice(6,8)),e=(d+e)/2,d=this.Kc();void 0!==d&&(e/=d);return e};l.getPointResolution=function(b,c){return this.o(b,c)};var De={},Je={};
+function Fe(b){Ke(b);b.forEach(function(c){b.forEach(function(b){c!==b&&Le(c,b,Me)})})}function Ne(){var b=Oe,c=Pe,d=Qe;Re.forEach(function(e){b.forEach(function(b){Le(e,b,c);Le(b,e,d)})})}function Se(b){De[b.a]=b;Le(b,b,Me)}function Ke(b){var c=[];b.forEach(function(b){c.push(Se(b))})}function Te(b){return b?ia(b)?Ee(b):b:Ee("EPSG:3857")}function Le(b,c,d){b=b.a;c=c.a;b in Je||(Je[b]={});Je[b][c]=d}function Ge(b,c,d,e){b=Ee(b);c=Ee(c);Le(b,c,Ue(d));Le(c,b,Ue(e))}
+function Ue(b){return function(c,d,e){var f=c.length;e=void 0!==e?e:2;d=void 0!==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;b instanceof Ce?c=b:ia(b)?(c=De[b],void 0===c&&"function"==typeof proj4&&void 0!==proj4.defs(b)&&(c=new Ce({code:b}),Se(c))):c=null;return c}function Ve(b,c){if(b===c)return!0;var d=b.f===c.f;return b.a===c.a?d:Ie(b,c)===Me&&d}
+function We(b,c){var d=Ee(b),e=Ee(c);return Ie(d,e)}function Ie(b,c){var d=b.a,e=c.a,f;d in Je&&e in Je[d]&&(f=Je[d][e]);void 0===f&&(f=Xe);return f}function Xe(b,c){if(void 0!==c&&b!==c){for(var d=0,e=b.length;d<e;++d)c[d]=b[d];b=c}return b}function Me(b,c){var d;if(void 0!==c){d=0;for(var e=b.length;d<e;++d)c[d]=b[d];d=c}else d=b.slice();return d}function Ye(b,c,d){return We(c,d)(b,void 0,b.length)}function Ze(b,c,d){c=We(c,d);return qe(b,c)};function $e(){gd.call(this);this.B=Md();this.v=-1;this.i={};this.o=this.j=0}y($e,gd);l=$e.prototype;l.qb=function(b,c){var d=c?c:[NaN,NaN];this.nb(b[0],b[1],d,Infinity);return d};l.og=function(b){return this.uc(b[0],b[1])};l.uc=se;l.J=function(b){this.v!=this.f&&(this.B=this.Wd(this.B),this.v=this.f);var c=this.B;b?(b[0]=c[0],b[1]=c[1],b[2]=c[2],b[3]=c[3]):b=c;return b};l.xb=function(b){return this.td(b*b)};l.lb=function(b,c){this.pc(We(b,c));return this};function af(b,c,d,e,f,g){var h=f[0],k=f[1],m=f[4],n=f[5],p=f[12];f=f[13];for(var q=g?g:[],r=0;c<d;c+=e){var t=b[c],x=b[c+1];q[r++]=h*t+m*x+p;q[r++]=k*t+n*x+f}g&&q.length!=r&&(q.length=r);return q};function bf(){$e.call(this);this.b="XY";this.a=2;this.A=null}y(bf,$e);function cf(b){if("XY"==b)return 2;if("XYZ"==b||"XYM"==b)return 3;if("XYZM"==b)return 4}l=bf.prototype;l.uc=se;l.Wd=function(b){return Yd(this.A,0,this.A.length,this.a,b)};l.Kb=function(){return this.A.slice(0,this.a)};l.ia=function(){return this.A};l.Lb=function(){return this.A.slice(this.A.length-this.a)};l.Mb=function(){return this.b};
+l.td=function(b){this.o!=this.f&&(Qb(this.i),this.j=0,this.o=this.f);if(0>b||0!==this.j&&b<=this.j)return this;var c=b.toString();if(this.i.hasOwnProperty(c))return this.i[c];var d=this.Lc(b);if(d.ia().length<this.A.length)return this.i[c]=d;this.j=b;return this};l.Lc=function(){return this};l.ra=function(){return this.a};function df(b,c,d){b.a=cf(c);b.b=c;b.A=d}
+function ef(b,c,d,e){if(c)d=cf(c);else{for(c=0;c<e;++c){if(0===d.length){b.b="XY";b.a=2;return}d=d[0]}d=d.length;c=2==d?"XY":3==d?"XYZ":4==d?"XYZM":void 0}b.b=c;b.a=d}l.pc=function(b){this.A&&(b(this.A,this.A,this.a),this.u())};l.Pc=function(b,c){var d=this.ia();if(d){var e=d.length,f=this.ra(),g=d?d:[],h=0,k,m;for(k=0;k<e;k+=f)for(g[h++]=d[k]+b,g[h++]=d[k+1]+c,m=k+2;m<k+f;++m)g[h++]=d[m];d&&g.length!=h&&(g.length=h);this.u()}};function ff(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],m=b[c+1],f=f+(h*k-g*m),g=k,h=m;return f/2}function gf(b,c,d,e){var f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g],f=f+ff(b,c,k,e);c=k}return f};function hf(b,c,d,e,f,g,h){var k=b[c],m=b[c+1],n=b[d]-k,p=b[d+1]-m;if(0!==n||0!==p)if(g=((f-k)*n+(g-m)*p)/(n*n+p*p),1<g)c=d;else if(0<g){for(f=0;f<e;++f)h[f]=od(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 jf(b,c,d,e,f){var g=b[c],h=b[c+1];for(c+=e;c<d;c+=e){var k=b[c],m=b[c+1],g=Wa(g,h,k,m);g>f&&(f=g);g=k;h=m}return f}function kf(b,c,d,e,f){var g,h;g=0;for(h=d.length;g<h;++g){var k=d[g];f=jf(b,c,k,e,f);c=k}return f}
+function lf(b,c,d,e,f,g,h,k,m,n,p){if(c==d)return n;var q;if(0===f){q=Wa(h,k,b[c],b[c+1]);if(q<n){for(p=0;p<e;++p)m[p]=b[c+p];m.length=e;return q}return n}for(var r=p?p:[NaN,NaN],t=c+e;t<d;)if(hf(b,t-e,t,e,h,k,r),q=Wa(h,k,r[0],r[1]),q<n){n=q;for(p=0;p<e;++p)m[p]=r[p];m.length=e;t+=e}else t+=e*Math.max((Math.sqrt(q)-Math.sqrt(n))/f|0,1);if(g&&(hf(b,d-e,c,e,h,k,r),q=Wa(h,k,r[0],r[1]),q<n)){n=q;for(p=0;p<e;++p)m[p]=r[p];m.length=e}return n}
+function mf(b,c,d,e,f,g,h,k,m,n,p){p=p?p:[NaN,NaN];var q,r;q=0;for(r=d.length;q<r;++q){var t=d[q];n=lf(b,c,t,e,f,g,h,k,m,n,p);c=t}return n};function nf(b,c){var d=0,e,f;e=0;for(f=c.length;e<f;++e)b[d++]=c[e];return d}function of(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 pf(b,c,d,e,f){f=f?f:[];var g=0,h,k;h=0;for(k=d.length;h<k;++h)c=of(b,c,d[h],e),f[g++]=c;f.length=g;return f};function qf(b,c,d,e,f){f=void 0!==f?f:[];for(var g=0;c<d;c+=e)f[g++]=b.slice(c,c+e);f.length=g;return f}function rf(b,c,d,e,f){f=void 0!==f?f:[];var g=0,h,k;h=0;for(k=d.length;h<k;++h){var m=d[h];f[g++]=qf(b,c,m,e,f[g]);c=m}f.length=g;return f};function tf(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 m=Array(k);m[0]=1;m[k-1]=1;d=[c,d-e];for(var n=0,p;0<d.length;){var q=d.pop(),r=d.pop(),t=0,x=b[r],z=b[r+1],A=b[q],B=b[q+1];for(p=r+e;p<q;p+=e){var v=Va(b[p],b[p+1],x,z,A,B);v>t&&(n=p,t=v)}t>f&&(m[(n-c)/e]=1,r+e<n&&d.push(r,n),n+e<q&&d.push(n,q))}for(p=0;p<k;++p)m[p]&&(g[h++]=b[c+p*e],g[h++]=b[c+p*e+1]);return h}
+function uf(b,c,d,e,f,g,h,k){var m,n;m=0;for(n=d.length;m<n;++m){var p=d[m];a:{var q=b,r=p,t=e,x=f,z=g;if(c!=r){var A=x*Math.round(q[c]/x),B=x*Math.round(q[c+1]/x);c+=t;z[h++]=A;z[h++]=B;var v=void 0,L=void 0;do if(v=x*Math.round(q[c]/x),L=x*Math.round(q[c+1]/x),c+=t,c==r){z[h++]=v;z[h++]=L;break a}while(v==A&&L==B);for(;c<r;){var M,J;M=x*Math.round(q[c]/x);J=x*Math.round(q[c+1]/x);c+=t;if(M!=v||J!=L){var C=v-A,sa=L-B,la=M-A,K=J-B;C*K==sa*la&&(0>C&&la<C||C==la||0<C&&la>C)&&(0>sa&&K<sa||sa==K||0<sa&&
+K>sa)||(z[h++]=v,z[h++]=L,A=v,B=L);v=M;L=J}}z[h++]=v;z[h++]=L}}k.push(h);c=p}return h};function vf(b,c){bf.call(this);this.g=this.l=-1;this.la(b,c)}y(vf,bf);l=vf.prototype;l.clone=function(){var b=new vf(null);wf(b,this.b,this.A.slice());return b};l.nb=function(b,c,d,e){if(e<Sd(this.J(),b,c))return e;this.g!=this.f&&(this.l=Math.sqrt(jf(this.A,0,this.A.length,this.a,0)),this.g=this.f);return lf(this.A,0,this.A.length,this.a,this.l,!0,b,c,d,e)};l.bm=function(){return ff(this.A,0,this.A.length,this.a)};l.Z=function(){return qf(this.A,0,this.A.length,this.a)};
+l.Lc=function(b){var c=[];c.length=tf(this.A,0,this.A.length,this.a,b,c,0);b=new vf(null);wf(b,"XY",c);return b};l.W=function(){return"LinearRing"};l.la=function(b,c){b?(ef(this,c,b,1),this.A||(this.A=[]),this.A.length=of(this.A,0,b,this.a),this.u()):wf(this,"XY",null)};function wf(b,c,d){df(b,c,d);b.u()};function E(b,c){bf.call(this);this.la(b,c)}y(E,bf);l=E.prototype;l.clone=function(){var b=new E(null);b.ba(this.b,this.A.slice());return b};l.nb=function(b,c,d,e){var f=this.A;b=Wa(b,c,f[0],f[1]);if(b<e){e=this.a;for(c=0;c<e;++c)d[c]=f[c];d.length=e;return b}return e};l.Z=function(){return this.A?this.A.slice():[]};l.Wd=function(b){return Xd(this.A,b)};l.W=function(){return"Point"};l.Ea=function(b){return Ud(b,this.A[0],this.A[1])};
+l.la=function(b,c){b?(ef(this,c,b,0),this.A||(this.A=[]),this.A.length=nf(this.A,b),this.u()):this.ba("XY",null)};l.ba=function(b,c){df(this,b,c);this.u()};function xf(b,c,d,e,f){return!ce(f,function(f){return!yf(b,c,d,e,f[0],f[1])})}function yf(b,c,d,e,f,g){for(var h=!1,k=b[d-e],m=b[d-e+1];c<d;c+=e){var n=b[c],p=b[c+1];m>g!=p>g&&f<(n-k)*(g-m)/(p-m)+k&&(h=!h);k=n;m=p}return h}function zf(b,c,d,e,f,g){if(0===d.length||!yf(b,c,d[0],e,f,g))return!1;var h;c=1;for(h=d.length;c<h;++c)if(yf(b,d[c-1],d[c],e,f,g))return!1;return!0};function Af(b,c,d,e,f,g,h){var k,m,n,p,q,r=f[g+1],t=[],x=d[0];n=b[x-e];q=b[x-e+1];for(k=c;k<x;k+=e){p=b[k];m=b[k+1];if(r<=q&&m<=r||q<=r&&r<=m)n=(r-q)/(m-q)*(p-n)+n,t.push(n);n=p;q=m}x=NaN;q=-Infinity;t.sort(ub);n=t[0];k=1;for(m=t.length;k<m;++k){p=t[k];var z=Math.abs(p-n);z>q&&(n=(n+p)/2,zf(b,c,d,e,n,r)&&(x=n,q=z));n=p}isNaN(x)&&(x=f[g]);return h?(h.push(x,r),h):[x,r]};function Bf(b,c,d,e,f,g){for(var h=[b[c],b[c+1]],k=[],m;c+e<d;c+=e){k[0]=b[c+e];k[1]=b[c+e+1];if(m=f.call(g,h,k))return m;h[0]=k[0];h[1]=k[1]}return!1};function Cf(b,c,d,e,f){var g=Zd(Md(),b,c,d,e);return oe(f,g)?Vd(f,g)||g[0]>=f[0]&&g[2]<=f[2]||g[1]>=f[1]&&g[3]<=f[3]?!0:Bf(b,c,d,e,function(b,c){var d=!1,e=Wd(f,b),g=Wd(f,c);if(1===e||1===g)d=!0;else{var q=f[0],r=f[1],t=f[2],x=f[3],z=c[0],A=c[1],B=(A-b[1])/(z-b[0]);g&2&&!(e&2)&&(d=z-(A-x)/B,d=d>=q&&d<=t);d||!(g&4)||e&4||(d=A-(z-t)*B,d=d>=r&&d<=x);d||!(g&8)||e&8||(d=z-(A-r)/B,d=d>=q&&d<=t);d||!(g&16)||e&16||(d=A-(z-q)*B,d=d>=r&&d<=x)}return d}):!1}
+function Df(b,c,d,e,f){var g=d[0];if(!(Cf(b,c,g,e,f)||yf(b,c,g,e,f[0],f[1])||yf(b,c,g,e,f[0],f[3])||yf(b,c,g,e,f[2],f[1])||yf(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(xf(b,d[c-1],d[c],e,f))return!1;return!0};function Ef(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],m=b[c+1],f=f+(k-g)*(m+h),g=k,h=m;return 0<f}function Ff(b,c,d,e){var f=0;e=void 0!==e?e:!1;var g,h;g=0;for(h=c.length;g<h;++g){var k=c[g],f=Ef(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 Gf(b,c,d,e,f){f=void 0!==f?f:!1;var g,h;g=0;for(h=d.length;g<h;++g){var k=d[g],m=Ef(b,c,k,e);if(0===g?f&&m||!f&&!m:f&&!m||!f&&m)for(var m=b,n=k,p=e;c<n-p;){var q;for(q=0;q<p;++q){var r=m[c+q];m[c+q]=m[n-p+q];m[n-p+q]=r}c+=p;n-=p}c=k}return c}function Hf(b,c,d,e){var f=0,g,h;g=0;for(h=c.length;g<h;++g)f=Gf(b,f,c[g],d,e);return f};function F(b,c){bf.call(this);this.g=[];this.C=-1;this.D=null;this.T=this.O=this.U=-1;this.l=null;this.la(b,c)}y(F,bf);l=F.prototype;l.oj=function(b){this.A?kb(this.A,b.ia()):this.A=b.ia().slice();this.g.push(this.A.length);this.u()};l.clone=function(){var b=new F(null);b.ba(this.b,this.A.slice(),this.g.slice());return b};
+l.nb=function(b,c,d,e){if(e<Sd(this.J(),b,c))return e;this.O!=this.f&&(this.U=Math.sqrt(kf(this.A,0,this.g,this.a,0)),this.O=this.f);return mf(this.A,0,this.g,this.a,this.U,!0,b,c,d,e)};l.uc=function(b,c){return zf(this.bc(),0,this.g,this.a,b,c)};l.em=function(){return gf(this.bc(),0,this.g,this.a)};l.Z=function(b){var c;void 0!==b?(c=this.bc().slice(),Gf(c,0,this.g,this.a,b)):c=this.A;return rf(c,0,this.g,this.a)};l.zb=function(){return this.g};
+function If(b){if(b.C!=b.f){var c=le(b.J());b.D=Af(b.bc(),0,b.g,b.a,c,0);b.C=b.f}return b.D}l.Sj=function(){return new E(If(this))};l.Xj=function(){return this.g.length};l.Dg=function(b){if(0>b||this.g.length<=b)return null;var c=new vf(null);wf(c,this.b,this.A.slice(0===b?0:this.g[b-1],this.g[b]));return c};l.be=function(){var b=this.b,c=this.A,d=this.g,e=[],f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g],m=new vf(null);wf(m,b,c.slice(f,k));e.push(m);f=k}return e};
+l.bc=function(){if(this.T!=this.f){var b=this.A;Ff(b,this.g,this.a)?this.l=b:(this.l=b.slice(),this.l.length=Gf(this.l,0,this.g,this.a));this.T=this.f}return this.l};l.Lc=function(b){var c=[],d=[];c.length=uf(this.A,0,this.g,this.a,Math.sqrt(b),c,0,d);b=new F(null);b.ba("XY",c,d);return b};l.W=function(){return"Polygon"};l.Ea=function(b){return Df(this.bc(),0,this.g,this.a,b)};
+l.la=function(b,c){if(b){ef(this,c,b,2);this.A||(this.A=[]);var d=pf(this.A,0,b,this.a,this.g);this.A.length=0===d.length?0:d[d.length-1];this.u()}else this.ba("XY",null,this.g)};l.ba=function(b,c,d){df(this,b,c);this.g=d;this.u()};function Jf(b,c,d,e){var f=e?e:32;e=[];var g;for(g=0;g<f;++g)kb(e,b.offset(c,d,2*Math.PI*g/f));e.push(e[0],e[1]);b=new F(null);b.ba("XY",e,[e.length]);return b}
+function Kf(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);d.ba("XY",c,[c.length]);return d}function Lf(b,c,d){var e=c?c:32,f=b.ra();c=b.b;for(var g=new F(null,c),e=f*(e+1),f=[],h=0;h<e;h++)f[h]=0;g.ba(c,f,[f.length]);Mf(g,b.wd(),b.zf(),d);return g}function Mf(b,c,d,e){var f=b.ia(),g=b.b,h=b.ra(),k=b.zb(),m=f.length/h-1;e=e?e:0;for(var n,p,q=0;q<=m;++q)p=q*h,n=e+2*nd(q,m)*Math.PI/m,f[p]=c[0]+d*Math.cos(n),f[p+1]=c[1]+d*Math.sin(n);b.ba(g,f,k)};function Nf(b){gd.call(this);b=b||{};this.b=[0,0];var c={};c.center=void 0!==b.center?b.center:null;this.g=Te(b.projection);var d,e,f,g=void 0!==b.minZoom?b.minZoom:0;d=void 0!==b.maxZoom?b.maxZoom:28;var h=void 0!==b.zoomFactor?b.zoomFactor:2;if(void 0!==b.resolutions)d=b.resolutions,e=d[0],f=d[d.length-1],d=xb(d);else{e=Te(b.projection);f=e.J();var k=(f?Math.max(je(f),ke(f)):360*Be.degrees/Be[e.f])/256/Math.pow(2,0),m=k/Math.pow(2,28);e=b.maxResolution;void 0!==e?g=0:e=k/Math.pow(h,g);f=b.minResolution;
+void 0===f&&(f=void 0!==b.maxZoom?void 0!==b.maxResolution?e/Math.pow(h,d):k/Math.pow(h,d):m);d=g+Math.floor(Math.log(e/f)/Math.log(h));f=e/Math.pow(h,d-g);d=yb(h,e,d-g)}this.a=e;this.j=f;this.c=g;g=void 0!==b.extent?Ya(b.extent):Za;(void 0!==b.enableRotation?b.enableRotation:1)?(e=b.constrainRotation,e=void 0===e||!0===e?Cb():!1===e?Ab:ja(e)?Bb(e):Ab):e=zb;this.i=new Db(g,d,e);void 0!==b.resolution?c.resolution=b.resolution:void 0!==b.zoom&&(c.resolution=this.constrainResolution(this.a,b.zoom-this.c));
+c.rotation=void 0!==b.rotation?b.rotation:0;this.I(c)}y(Nf,gd);l=Nf.prototype;l.Xd=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.Ua=function(){return this.get("center")};l.$c=function(b){var c=this.Ua(),d=this.$(),e=this.Fa();return me(c,d,e,b)};l.Ml=function(){return this.g};l.$=function(){return this.get("resolution")};
+function Of(b){var c=b.a,d=Math.log(c/b.j)/Math.log(2);return function(b){return c/Math.pow(2,b*d)}}l.Fa=function(){return this.get("rotation")};function Pf(b){var c=b.a,d=Math.log(c/b.j)/Math.log(2);return function(b){return Math.log(c/b)/Math.log(2)/d}}function Qf(b){var c=b.Ua(),d=b.g,e=b.$();b=b.Fa();return{center:[Math.round(c[0]/e)*e,Math.round(c[1]/e)*e],projection:void 0!==d?d:null,resolution:e,rotation:b}}
+l.uk=function(){var b,c=this.$();if(void 0!==c){var d,e=0;do{d=this.constrainResolution(this.a,e);if(d==c){b=e;break}++e}while(d>this.j)}return void 0!==b?this.c+b:b};
+l.kf=function(b,c,d){b instanceof bf||(b=Kf(b));var e=d||{};d=void 0!==e.padding?e.padding:[0,0,0,0];var f=void 0!==e.constrainResolution?e.constrainResolution:!0,g=void 0!==e.nearest?e.nearest:!1,h;void 0!==e.minResolution?h=e.minResolution:void 0!==e.maxZoom?h=this.constrainResolution(this.a,e.maxZoom-this.c,0):h=0;var k=b.ia(),m=this.Fa(),e=Math.cos(-m),m=Math.sin(-m),n=Infinity,p=Infinity,q=-Infinity,r=-Infinity;b=b.ra();for(var t=0,x=k.length;t<x;t+=b)var z=k[t]*e-k[t+1]*m,A=k[t]*m+k[t+1]*e,
+n=Math.min(n,z),p=Math.min(p,A),q=Math.max(q,z),r=Math.max(r,A);k=[n,p,q,r];c=[c[0]-d[1]-d[3],c[1]-d[0]-d[2]];c=Math.max(je(k)/c[0],ke(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.Ub(c);m=-m;g=(n+q)/2+(d[1]-d[3])/2*c;d=(p+r)/2+(d[0]-d[2])/2*c;this.kb([g*e-d*m,d*e+g*m])};
+l.uj=function(b,c,d){var e=this.Fa(),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.$(),g=g+(c[0]/2-d[0])*h;b+=(d[1]-c[1]/2)*h;e=-e;this.kb([g*f-b*e,b*f+g*e])};function Rf(b){return!!b.Ua()&&void 0!==b.$()}l.rotate=function(b,c){if(void 0!==c){var d,e=this.Ua();void 0!==e&&(d=[e[0]-c[0],e[1]-c[1]],ud(d,b-this.Fa()),pd(d,c));this.kb(d)}this.ue(b)};l.kb=function(b){this.set("center",b)};function Sf(b,c){b.b[1]+=c}l.Ub=function(b){this.set("resolution",b)};
+l.ue=function(b){this.set("rotation",b)};l.Wo=function(b){b=this.constrainResolution(this.a,b-this.c,0);this.Ub(b)};function Tf(b){return Math.pow(b,3)}function Uf(b){return 1-Tf(1-b)}function Vf(b){return 3*b*b-2*b*b*b}function Wf(b){return b}function Xf(b){return.5>b?Vf(2*b):1-Vf(2*(b-.5))};function Yf(b){var c=b.source,d=b.start?b.start:Date.now(),e=c[0],f=c[1],g=void 0!==b.duration?b.duration:1E3,h=b.easing?b.easing:Vf;return function(b,c){if(c.time<d)return c.animate=!0,c.viewHints[0]+=1,!0;if(c.time<d+g){var n=1-h((c.time-d)/g),p=e-c.viewState.center[0],q=f-c.viewState.center[1];c.animate=!0;c.viewState.center[0]+=n*p;c.viewState.center[1]+=n*q;c.viewHints[0]+=1;return!0}return!1}}
+function Zf(b){var c=b.rotation?b.rotation:0,d=b.start?b.start:Date.now(),e=void 0!==b.duration?b.duration:1E3,f=b.easing?b.easing:Vf,g=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 m=1-f((k.time-d)/e),m=(c-k.viewState.rotation)*m;k.animate=!0;k.viewState.rotation+=m;if(g){var n=k.viewState.center;n[0]-=g[0];n[1]-=g[1];ud(n,m);pd(n,g)}k.viewHints[0]+=1;return!0}return!1}}
+function $f(b){var c=b.resolution,d=b.start?b.start:Date.now(),e=void 0!==b.duration?b.duration:1E3,f=b.easing?b.easing:Vf;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),m=c-h.viewState.resolution;h.animate=!0;h.viewState.resolution+=k*m;h.viewHints[0]+=1;return!0}return!1}};function ag(b,c,d,e){return void 0!==e?(e[0]=b,e[1]=c,e[2]=d,e):[b,c,d]}function bg(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 bg(b[0],b[1],b[2])};function fg(b,c,d,e){this.a=b;this.c=c;this.f=d;this.b=e}fg.prototype.contains=function(b){return gg(this,b[1],b[2])};function hg(b,c){return b.a<=c.a&&c.c<=b.c&&b.f<=c.f&&c.b<=b.b}function gg(b,c,d){return b.a<=c&&c<=b.c&&b.f<=d&&d<=b.b}function ig(b,c){return b.a==c.a&&b.f==c.f&&b.c==c.c&&b.b==c.b}function jg(b){return b.b-b.f+1}function kg(b){return b.c-b.a+1}function lg(b,c){return b.a<=c.c&&b.c>=c.a&&b.f<=c.b&&b.b>=c.f};function mg(b){this.f=b.html;this.a=b.tileRanges?b.tileRanges:null}mg.prototype.b=function(){return this.f};function ng(b,c,d){tc.call(this,b,d);this.element=c}y(ng,tc);function og(b){gd.call(this);this.a=b?b:[];pg(this)}y(og,gd);l=og.prototype;l.clear=function(){for(;0<this.$b();)this.pop()};l.uf=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){this.a.forEach(b,c)};l.wl=function(){return this.a};l.item=function(b){return this.a[b]};l.$b=function(){return this.get("length")};l.le=function(b,c){lb(this.a,b,0,c);pg(this);this.s(new ng("add",c,this))};
+l.pop=function(){return this.Sf(this.$b()-1)};l.push=function(b){var c=this.a.length;this.le(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.Sf(d)};l.Sf=function(b){var c=this.a[b];$a.splice.call(this.a,b,1);pg(this);this.s(new ng("remove",c,this));return c};l.Jo=function(b,c){var d=this.$b();if(b<d)d=this.a[b],this.a[b]=c,this.s(new ng("remove",d,this)),this.s(new ng("add",c,this));else{for(;d<b;++d)this.le(d,void 0);this.le(b,c)}};
+function pg(b){b.set("length",b.a.length)};var qg=/^#(?:[0-9a-f]{3}){1,2}$/i,rg=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i,sg=/^(?: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 tg(b){return ga(b)?b:ug(b)}function vg(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 ug=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;qg.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=sg.exec(d))?(e=Number(h[1]),f=Number(h[2]),g=Number(h[3]),h=Number(h[4]),e=[e,f,g,h],e=wg(e,e)):(h=rg.exec(d))?(e=Number(h[1]),f=Number(h[2]),g=Number(h[3]),
+e=[e,f,g,1],e=wg(e,e)):e=void 0;b[d]=e;++c}return e}}();function wg(b,c){var d=c||[];d[0]=Sa(b[0]+.5|0,0,255);d[1]=Sa(b[1]+.5|0,0,255);d[2]=Sa(b[2]+.5|0,0,255);d[3]=Sa(b[3],0,1);return d};var xg=!Yb||9<=kc;!$b&&!Yb||Yb&&9<=kc||$b&&ic("1.9.1");Yb&&ic("9");function yg(b,c){this.x=ca(b)?b:0;this.y=ca(c)?c:0}l=yg.prototype;l.clone=function(){return new yg(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 zg(b,c){this.width=b;this.height=c}l=zg.prototype;l.clone=function(){return new zg(this.width,this.height)};l.rj=function(){return this.width*this.height};l.La=function(){return!this.rj()};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 Ag(b){return b?new Bg(Cg(b)):Ba||(Ba=new Bg)}function Dg(b){var c=document;return ia(b)?c.getElementById(b):b}function Eg(b,c){Ib(c,function(c,e){"style"==e?b.style.cssText=c:"class"==e?b.className=c:"for"==e?b.htmlFor=c:Fg.hasOwnProperty(e)?b.setAttribute(Fg[e],c):0==e.lastIndexOf("aria-",0)||0==e.lastIndexOf("data-",0)?b.setAttribute(e,c):b[e]=c})}
+var Fg={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 Gg(b){b=b.document.documentElement;return new zg(b.clientWidth,b.clientHeight)}
+function Hg(b,c,d){var e=arguments,f=document,g=e[0],h=e[1];if(!xg&&h&&(h.name||h.type)){g=["<",g];h.name&&g.push(' name="',Ga(h.name),'"');if(h.type){g.push(' type="',Ga(h.type),'"');var k={};Wb(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(" "):Eg(g,h));2<e.length&&Ig(f,g,e);return g}
+function Ig(b,c,d){function e(d){d&&c.appendChild(ia(d)?b.createTextNode(d):d)}for(var f=2;f<d.length;f++){var g=d[f];!ha(g)||oa(g)&&0<g.nodeType?e(g):bb(Jg(g)?jb(g):g,e)}}function Kg(b){for(var c;c=b.firstChild;)b.removeChild(c)}function Lg(b,c,d){b.insertBefore(c,b.childNodes[d]||null)}function Mg(b){b&&b.parentNode&&b.parentNode.removeChild(b)}function Ng(b,c){var d=c.parentNode;d&&d.replaceChild(b,c)}
+function Og(b){if(ca(b.firstElementChild))b=b.firstElementChild;else for(b=b.firstChild;b&&1!=b.nodeType;)b=b.nextSibling;return b}function Pg(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 Cg(b){return 9==b.nodeType?b:b.ownerDocument||b.document}
+function Jg(b){if(b&&"number"==typeof b.length){if(oa(b))return"function"==typeof b.item||"string"==typeof b.item;if(ka(b))return"function"==typeof b.item}return!1}function Bg(b){this.a=b||ba.document||document}Bg.prototype.I=Eg;function Qg(){return!0}
+function Rg(b){var c=b.a;b=c.scrollingElement?c.scrollingElement:ac?c.body||c.documentElement:c.documentElement;c=c.parentWindow||c.defaultView;return Yb&&ic("10")&&c.pageYOffset!=b.scrollTop?new yg(b.scrollLeft,b.scrollTop):new yg(c.pageXOffset||b.scrollLeft,c.pageYOffset||b.scrollTop)}Bg.prototype.appendChild=function(b,c){b.appendChild(c)};Bg.prototype.contains=Pg;function Sg(b){if(b.classList)return b.classList;b=b.className;return ia(b)&&b.match(/\S+/g)||[]}function Tg(b,c){var d;b.classList?d=b.classList.contains(c):(d=Sg(b),d=0<=ab(d,c));return d}function Ug(b,c){b.classList?b.classList.add(c):Tg(b,c)||(b.className+=0<b.className.length?" "+c:c)}function Vg(b,c){b.classList?b.classList.remove(c):Tg(b,c)&&(b.className=cb(Sg(b),function(b){return b!=c}).join(" "))}function Wg(b,c){Tg(b,c)?Vg(b,c):Ug(b,c)};function Xg(b,c,d,e){this.top=b;this.right=c;this.bottom=d;this.left=e}l=Xg.prototype;l.clone=function(){return new Xg(this.top,this.right,this.bottom,this.left)};l.contains=function(b){return this&&b?b instanceof Xg?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 Yg(b,c,d,e){this.left=b;this.top=c;this.width=d;this.height=e}l=Yg.prototype;l.clone=function(){return new Yg(this.left,this.top,this.width,this.height)};l.contains=function(b){return b instanceof Yg?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};
+l.distance=function(b){var c=b.x<this.left?this.left-b.x:Math.max(b.x-(this.left+this.width),0);b=b.y<this.top?this.top-b.y:Math.max(b.y-(this.top+this.height),0);return Math.sqrt(c*c+b*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 Zg(b,c){var d=Cg(b);return d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(b,null))?d[c]||d.getPropertyValue(c)||"":""}function $g(b,c){return Zg(b,c)||(b.currentStyle?b.currentStyle[c]:null)||b.style&&b.style[c]}function ah(b,c,d){var e;c instanceof yg?(e=c.x,c=c.y):(e=c,c=d);b.style.left=bh(e);b.style.top=bh(c)}
+function ch(b){var c;try{c=b.getBoundingClientRect()}catch(d){return{left:0,top:0,right:0,bottom:0}}Yb&&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 dh(b){if(1==b.nodeType)return b=ch(b),new yg(b.left,b.top);b=b.changedTouches?b.changedTouches[0]:b;return new yg(b.clientX,b.clientY)}function bh(b){"number"==typeof b&&(b=b+"px");return b}
+function eh(b){var c=fh;if("none"!=$g(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 fh(b){var c=b.offsetWidth,d=b.offsetHeight,e=ac&&!c&&!d;return ca(c)&&!e||!b.getBoundingClientRect?new zg(c,d):(b=ch(b),new zg(b.right-b.left,b.bottom-b.top))}function gh(b,c){b.style.display=c?"":"none"}
+function hh(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 ih(b,c){var d=b.currentStyle?b.currentStyle[c]:null;return d?hh(b,d,"left","pixelLeft"):0}
+function jh(b,c){if(Yb){var d=ih(b,c+"Left"),e=ih(b,c+"Right"),f=ih(b,c+"Top"),g=ih(b,c+"Bottom");return new Xg(f,e,g,d)}d=Zg(b,c+"Left");e=Zg(b,c+"Right");f=Zg(b,c+"Top");g=Zg(b,c+"Bottom");return new Xg(parseFloat(f),parseFloat(e),parseFloat(g),parseFloat(d))}var kh={thin:2,medium:4,thick:6};function lh(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 kh?kh[d]:hh(b,d,"left","pixelLeft")}
+function mh(b){if(Yb&&!(9<=kc)){var c=lh(b,"borderLeft"),d=lh(b,"borderRight"),e=lh(b,"borderTop");b=lh(b,"borderBottom");return new Xg(e,d,b,c)}c=Zg(b,"borderLeftWidth");d=Zg(b,"borderRightWidth");e=Zg(b,"borderTopWidth");b=Zg(b,"borderBottomWidth");return new Xg(parseFloat(e),parseFloat(d),parseFloat(b),parseFloat(c))};function nh(b,c,d){tc.call(this,b);this.map=c;this.frameState=void 0!==d?d:null}y(nh,tc);function oh(b){gd.call(this);this.element=b.element?b.element:null;this.a=this.U=null;this.o=[];this.render=b.render?b.render:za;b.target&&this.c(b.target)}y(oh,gd);oh.prototype.Y=function(){Mg(this.element);oh.ca.Y.call(this)};oh.prototype.g=function(){return this.a};
+oh.prototype.setMap=function(b){this.a&&Mg(this.element);0<this.o.length&&(this.o.forEach(Wc),this.o.length=0);if(this.a=b)(this.U?this.U:b.j).appendChild(this.element),this.render!==za&&this.o.push(D(b,"postrender",this.render,!1,this)),b.render()};oh.prototype.c=function(b){this.U=Dg(b)};function ph(){this.b=0;this.c={};this.f=this.a=null}l=ph.prototype;l.clear=function(){this.b=0;this.c={};this.f=this.a=null};function qh(b,c){return b.c.hasOwnProperty(c)}l.forEach=function(b,c){for(var d=this.a;d;)b.call(c,d.mc,d.oe,this),d=d.sb};l.get=function(b){b=this.c[b];if(b===this.f)return b.mc;b===this.a?(this.a=this.a.sb,this.a.hc=null):(b.sb.hc=b.hc,b.hc.sb=b.sb);b.sb=null;b.hc=this.f;this.f=this.f.sb=b;return b.mc};l.qc=function(){return this.b};
+l.P=function(){var b=Array(this.b),c=0,d;for(d=this.f;d;d=d.hc)b[c++]=d.oe;return b};l.sc=function(){var b=Array(this.b),c=0,d;for(d=this.f;d;d=d.hc)b[c++]=d.mc;return b};l.pop=function(){var b=this.a;delete this.c[b.oe];b.sb&&(b.sb.hc=null);this.a=b.sb;this.a||(this.f=null);--this.b;return b.mc};l.replace=function(b,c){this.get(b);this.c[b].mc=c};l.set=function(b,c){var d={oe:b,sb:null,hc:this.f,mc:c};this.f?this.f.sb=d:this.a=d;this.f=d;this.c[b]=d;++this.b};function rh(b){ph.call(this);this.g=void 0!==b?b:2048}y(rh,ph);function sh(b){return b.qc()>b.g}function th(b,c){for(var d,e;sh(b)&&!(d=b.a.mc,e=d.a[0].toString(),e in c&&c[e].contains(d.a));)b.pop().Fc()};function uh(b,c){$c.call(this);this.a=b;this.state=c;this.f=null;this.key=""}y(uh,$c);function vh(b){b.s("change")}uh.prototype.$a=function(){return w(this).toString()};uh.prototype.g=function(){return this.a};function wh(b){gd.call(this);this.b=Ee(b.projection);this.i=void 0!==b.attributions?b.attributions:null;this.U=b.logo;this.B=void 0!==b.state?b.state:"ready";this.O=void 0!==b.wrapX?b.wrapX:!1}y(wh,gd);l=wh.prototype;l.ye=za;l.sa=function(){return this.i};l.qa=function(){return this.U};l.ta=function(){return this.b};l.ua=function(){return this.B};function xh(b){return b.O}l.ma=function(b){this.i=b;this.u()};function yh(b,c){b.B=c;b.u()};function zh(b){this.minZoom=void 0!==b.minZoom?b.minZoom:0;this.a=b.resolutions;this.maxZoom=this.a.length-1;this.b=void 0!==b.origin?b.origin:null;this.g=null;void 0!==b.origins&&(this.g=b.origins);var c=b.extent;void 0===c||this.b||this.g||(this.b=ge(c));this.i=null;void 0!==b.tileSizes&&(this.i=b.tileSizes);this.l=void 0!==b.tileSize?b.tileSize:this.i?null:256;this.G=void 0!==c?c:null;this.f=null;void 0!==b.sizes?this.f=b.sizes.map(function(b){return new fg(Math.min(0,b[0]),Math.max(b[0]-1,-1),
+Math.min(0,b[1]),Math.max(b[1]-1,-1))},this):c&&Ah(this,c);this.c=[0,0]}var Bh=[0,0,0];function Ch(b,c,d,e,f){f=b.Aa(c,f);for(c=c[0]-1;c>=b.minZoom;){if(d.call(null,c,Dh(b,f,c,e)))return!0;--c}return!1}l=zh.prototype;l.J=function(){return this.G};l.Eg=function(){return this.maxZoom};l.Fg=function(){return this.minZoom};l.Da=function(b){return this.b?this.b:this.g[b]};l.$=function(b){return this.a[b]};l.zh=function(){return this.a};
+function Eh(b,c,d,e){return c[0]<b.maxZoom?(e=b.Aa(c,e),Dh(b,e,c[0]+1,d)):null}function Fh(b,c,d,e){Gh(b,c[0],c[1],d,!1,Bh);var f=Bh[1],g=Bh[2];Gh(b,c[2],c[3],d,!0,Bh);b=Bh[1];c=Bh[2];void 0!==e?(e.a=f,e.c=b,e.f=g,e.b=c):e=new fg(f,b,g,c);return e}function Dh(b,c,d,e){d=b.$(d);return Fh(b,c,d,e)}function Hh(b,c){var d=b.Da(c[0]),e=b.$(c[0]),f=md(b.Ka(c[0]),b.c);return[d[0]+(c[1]+.5)*f[0]*e,d[1]+(c[2]+.5)*f[1]*e]}
+l.Aa=function(b,c){var d=this.Da(b[0]),e=this.$(b[0]),f=md(this.Ka(b[0]),this.c),g=d[0]+b[1]*f[0]*e,d=d[1]+b[2]*f[1]*e;return Pd(g,d,g+f[0]*e,d+f[1]*e,c)};l.fe=function(b,c,d){return Gh(this,b[0],b[1],c,!1,d)};function Gh(b,c,d,e,f,g){var h=Ih(b,e),k=e/b.$(h),m=b.Da(h);b=md(b.Ka(h),b.c);c=k*Math.floor((c-m[0])/e+(f?.5:0))/b[0];d=k*Math.floor((d-m[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 ag(h,c,d,g)}
+l.ge=function(b,c,d){c=this.$(c);return Gh(this,b[0],b[1],c,!1,d)};l.Ka=function(b){return this.l?this.l:this.i[b]};function Ih(b,c){var d=wb(b.a,c,0);return Sa(d,b.minZoom,b.maxZoom)}function Ah(b,c){for(var d=b.a.length,e=Array(d),f=b.minZoom;f<d;++f)e[f]=Dh(b,c,f);b.f=e}function Jh(b){var c=b.l;if(!c){var c=Kh(b),d=Lh(c,void 0,void 0),c=new zh({extent:c,origin:ge(c),resolutions:d,tileSize:void 0});b.l=c}return c}
+function Mh(b){var c={};Wb(c,void 0!==b?b:{});void 0===c.extent&&(c.extent=Ee("EPSG:3857").J());c.resolutions=Lh(c.extent,c.maxZoom,c.tileSize);delete c.maxZoom;return new zh(c)}function Lh(b,c,d){c=void 0!==c?c:42;var e=ke(b);b=je(b);d=md(void 0!==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 Kh(b){b=Ee(b);var c=b.J();c||(b=180*Be.degrees/b.Kc(),c=Pd(-b,-b,b,b));return c};function Nh(b){wh.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,projection:b.projection,state:b.state,wrapX:b.wrapX});this.pa=void 0!==b.opaque?b.opaque:!1;this.v=void 0!==b.tilePixelRatio?b.tilePixelRatio:1;this.tileGrid=void 0!==b.tileGrid?b.tileGrid:null;this.a=new rh(b.df);this.c=[0,0]}y(Nh,wh);l=Nh.prototype;l.qh=function(){return sh(this.a)};l.rh=function(b,c){var d=this.ud(b);d&&th(d,c)};
+function Oh(b,c,d,e,f){c=b.ud(c);if(!c)return!1;for(var g=!0,h,k,m=e.a;m<=e.c;++m)for(var n=e.f;n<=e.b;++n)h=b.Ab(d,m,n),k=!1,qh(c,h)&&(h=c.get(h),(k=2===h.state)&&(k=!1!==f(h))),k||(g=!1);return g}l.ae=function(){return 0};l.Cg=function(){return""};l.Ab=bg;l.Ha=function(){return this.tileGrid};l.ib=function(b){return this.tileGrid?this.tileGrid:Jh(b)};l.ud=function(b){var c=this.b;return c&&!Ve(c,b)?null:this.a};l.Qb=function(b,c,d){c=this.ib(d);return ld(md(c.Ka(b),this.c),this.v,this.c)};
+function Ph(b,c,d){var e=void 0!==d?d:b.b;d=b.ib(e);if(b.O&&e.c){var f=c;c=f[0];b=Hh(d,f);e=Kh(e);Td(e,b)?c=f:(f=je(e),b[0]+=f*Math.ceil((e[0]-b[0])/f),c=d.ge(b,c))}f=c[0];e=c[1];b=c[2];if(d.minZoom>f||f>d.maxZoom)d=!1;else{var g=d.J();d=(d=g?Dh(d,g,f):d.f?d.f[f]:null)?gg(d,e,b):!0}return d?c:null}l.Yf=za;function Qh(b,c){tc.call(this,b);this.tile=c}y(Qh,tc);function Rh(b){b=b?b:{};this.C=document.createElement("UL");this.B=document.createElement("LI");this.C.appendChild(this.B);gh(this.B,!1);this.b=void 0!==b.collapsed?b.collapsed:!0;this.j=void 0!==b.collapsible?b.collapsible:!0;this.j||(this.b=!1);var c=b.className?b.className:"ol-attribution",d=b.tipLabel?b.tipLabel:"Attributions",e=b.collapseLabel?b.collapseLabel:"\u00bb";this.D=ia(e)?Hg("SPAN",{},e):e;e=b.label?b.label:"i";this.O=ia(e)?Hg("SPAN",{},e):e;d=Hg("BUTTON",{type:"button",title:d},this.j&&
+!this.b?this.D:this.O);D(d,"click",this.Pl,!1,this);c=Hg("DIV",c+" ol-unselectable ol-control"+(this.b&&this.j?" ol-collapsed":"")+(this.j?"":" ol-uncollapsible"),this.C,d);oh.call(this,{element:c,render:b.render?b.render:Sh,target:b.target});this.v=!0;this.l={};this.i={};this.T={}}y(Rh,oh);
+function Sh(b){if(b=b.frameState){var c,d,e,f,g,h,k,m,n,p,q,r=b.layerStatesArray,t=Tb(b.attributions),x={},z=b.viewState.projection;d=0;for(c=r.length;d<c;d++)if(h=r[d].layer.ea())if(p=w(h).toString(),n=h.i)for(e=0,f=n.length;e<f;e++)if(k=n[e],m=w(k).toString(),!(m in t)){if(g=b.usedTiles[p]){var A=h.ib(z);a:{q=k;var B=z;if(q.a){var v=void 0,L=void 0,M=void 0,J=void 0;for(J in g)if(J in q.a)for(var M=g[J],C,v=0,L=q.a[J].length;v<L;++v){C=q.a[J][v];if(lg(C,M)){q=!0;break a}var sa=Dh(A,B.J(),parseInt(J,
+10)),la=kg(sa);if(M.a<sa.a||M.c>sa.c)if(lg(C,new fg(nd(M.a,la),nd(M.c,la),M.f,M.b))||kg(M)>la&&lg(C,sa)){q=!0;break a}}q=!1}else q=!0}}else q=!1;q?(m in x&&delete x[m],t[m]=k):x[m]=k}c=[t,x];d=c[0];c=c[1];for(var K in this.l)K in d?(this.i[K]||(gh(this.l[K],!0),this.i[K]=!0),delete d[K]):K in c?(this.i[K]&&(gh(this.l[K],!1),delete this.i[K]),delete c[K]):(Mg(this.l[K]),delete this.l[K],delete this.i[K]);for(K in d)e=document.createElement("LI"),e.innerHTML=d[K].f,this.C.appendChild(e),this.l[K]=e,
+this.i[K]=!0;for(K in c)e=document.createElement("LI"),e.innerHTML=c[K].f,gh(e,!1),this.C.appendChild(e),this.l[K]=e;K=!Pb(this.i)||!Pb(b.logos);this.v!=K&&(gh(this.element,K),this.v=K);K&&Pb(this.i)?Ug(this.element,"ol-logo-only"):Vg(this.element,"ol-logo-only");var ma;b=b.logos;K=this.T;for(ma in K)ma in b||(Mg(K[ma]),delete K[ma]);for(var Ua in b)Ua in K||(ma=new Image,ma.src=Ua,d=b[Ua],""===d?d=ma:(d=Hg("A",{href:d}),d.appendChild(ma)),this.B.appendChild(d),K[Ua]=d);gh(this.B,!Pb(b))}else this.v&&
+(gh(this.element,!1),this.v=!1)}l=Rh.prototype;l.Pl=function(b){b.preventDefault();Th(this)};function Th(b){Wg(b.element,"ol-collapsed");b.b?Ng(b.D,b.O):Ng(b.O,b.D);b.b=!b.b}l.Ol=function(){return this.j};l.Rl=function(b){this.j!==b&&(this.j=b,Wg(this.element,"ol-uncollapsible"),!b&&this.b&&Th(this))};l.Ql=function(b){this.j&&this.b!==b&&Th(this)};l.Nl=function(){return this.b};function Uh(b){b=b?b:{};var c=b.className?b.className:"ol-rotate",d=b.label?b.label:"\u21e7";this.b=null;ia(d)?this.b=Hg("SPAN","ol-compass",d):(this.b=d,Ug(this.b,"ol-compass"));d=Hg("BUTTON",{"class":c+"-reset",type:"button",title:b.tipLabel?b.tipLabel:"Reset rotation"},this.b);D(d,"click",Uh.prototype.v,!1,this);c=Hg("DIV",c+" ol-unselectable ol-control",d);d=b.render?b.render:Vh;this.j=b.resetNorth?b.resetNorth:void 0;oh.call(this,{element:c,render:d,target:b.target});this.l=void 0!==b.duration?
+b.duration:250;this.i=void 0!==b.autoHide?b.autoHide:!0;this.B=void 0;this.i&&Ug(this.element,"ol-hidden")}y(Uh,oh);Uh.prototype.v=function(b){b.preventDefault();if(void 0!==this.j)this.j();else{b=this.a;var c=b.aa();if(c){var d=c.Fa();void 0!==d&&(0<this.l&&(d%=2*Math.PI,d<-Math.PI&&(d+=2*Math.PI),d>Math.PI&&(d-=2*Math.PI),b.Na(Zf({rotation:d,duration:this.l,easing:Uf}))),c.ue(0))}}};
+function Vh(b){if(b=b.frameState){b=b.viewState.rotation;if(b!=this.B){var c="rotate("+b+"rad)";if(this.i){var d=this.element;0===b?Ug(d,"ol-hidden"):Vg(d,"ol-hidden")}this.b.style.msTransform=c;this.b.style.webkitTransform=c;this.b.style.transform=c}this.B=b}};function Wh(b){b=b?b:{};var c=b.className?b.className:"ol-zoom",d=b.delta?b.delta:1,e=b.zoomOutLabel?b.zoomOutLabel:"\u2212",f=b.zoomOutTipLabel?b.zoomOutTipLabel:"Zoom out",g=Hg("BUTTON",{"class":c+"-in",type:"button",title:b.zoomInTipLabel?b.zoomInTipLabel:"Zoom in"},b.zoomInLabel?b.zoomInLabel:"+");D(g,"click",va(Wh.prototype.i,d),!1,this);e=Hg("BUTTON",{"class":c+"-out",type:"button",title:f},e);D(e,"click",va(Wh.prototype.i,-d),!1,this);c=Hg("DIV",c+" ol-unselectable ol-control",g,e);oh.call(this,
+{element:c,target:b.target});this.b=void 0!==b.duration?b.duration:250}y(Wh,oh);Wh.prototype.i=function(b,c){c.preventDefault();var d=this.a,e=d.aa();if(e){var f=e.$();f&&(0<this.b&&d.Na($f({resolution:f,duration:this.b,easing:Uf})),d=e.constrainResolution(f,b),e.Ub(d))}};function Xh(b){b=b?b:{};var c=new og;(void 0!==b.zoom?b.zoom:1)&&c.push(new Wh(b.zoomOptions));(void 0!==b.rotate?b.rotate:1)&&c.push(new Uh(b.rotateOptions));(void 0!==b.attribution?b.attribution:1)&&c.push(new Rh(b.attributionOptions));return c};var Yh=ac?"webkitfullscreenchange":$b?"mozfullscreenchange":Yb?"MSFullscreenChange":"fullscreenchange";function Zh(){var b=Ag().a,c=b.body;return!!(c.webkitRequestFullscreen||c.mozRequestFullScreen&&b.mozFullScreenEnabled||c.msRequestFullscreen&&b.msFullscreenEnabled||c.requestFullscreen&&b.fullscreenEnabled)}
+function $h(b){b.webkitRequestFullscreen?b.webkitRequestFullscreen():b.mozRequestFullScreen?b.mozRequestFullScreen():b.msRequestFullscreen?b.msRequestFullscreen():b.requestFullscreen&&b.requestFullscreen()}function ai(){var b=Ag().a;return!!(b.webkitIsFullScreen||b.mozFullScreen||b.msFullscreenElement||b.fullscreenElement)};function bi(b){b=b?b:{};this.b=b.className?b.className:"ol-full-screen";var c=b.label?b.label:"\u2922";this.i=ia(c)?document.createTextNode(c):c;c=b.labelActive?b.labelActive:"\u00d7";this.j=ia(c)?document.createTextNode(c):c;c=b.tipLabel?b.tipLabel:"Toggle full-screen";c=Hg("BUTTON",{"class":this.b+"-"+ai(),type:"button",title:c},this.i);D(c,"click",this.v,!1,this);D(ba.document,Yh,this.l,!1,this);var d=this.b+" ol-unselectable ol-control "+(Zh()?"":"ol-unsupported"),c=Hg("DIV",d,c);oh.call(this,
+{element:c,target:b.target});this.B=void 0!==b.keys?b.keys:!1}y(bi,oh);bi.prototype.v=function(b){b.preventDefault();Zh()&&(b=this.a)&&(ai()?(b=Ag().a,b.webkitCancelFullScreen?b.webkitCancelFullScreen():b.mozCancelFullScreen?b.mozCancelFullScreen():b.msExitFullscreen?b.msExitFullscreen():b.exitFullscreen&&b.exitFullscreen()):(b=b.xf(),b=Dg(b),this.B?b.mozRequestFullScreenWithKeys?b.mozRequestFullScreenWithKeys():b.webkitRequestFullscreen?b.webkitRequestFullscreen():$h(b):$h(b)))};
+bi.prototype.l=function(){var b=this.b+"-true",c=this.b+"-false",d=Og(this.element),e=this.a;ai()?(Tg(d,c)&&(Vg(d,c),Ug(d,b)),Ng(this.j,this.i)):(Tg(d,b)&&(Vg(d,b),Ug(d,c)),Ng(this.i,this.j));e&&e.Vc()};function di(b){b=b?b:{};var c=Hg("DIV",b.className?b.className:"ol-mouse-position");oh.call(this,{element:c,render:b.render?b.render:ei,target:b.target});D(this,id("projection"),this.Sl,!1,this);b.coordinateFormat&&this.Wh(b.coordinateFormat);b.projection&&this.bh(Ee(b.projection));this.B=b.undefinedHTML?b.undefinedHTML:"";this.l=c.innerHTML;this.j=this.i=this.b=null}y(di,oh);
+function ei(b){b=b.frameState;b?this.b!=b.viewState.projection&&(this.b=b.viewState.projection,this.i=null):this.b=null;fi(this,this.j)}l=di.prototype;l.Sl=function(){this.i=null};l.wg=function(){return this.get("coordinateFormat")};l.ah=function(){return this.get("projection")};l.Nk=function(b){this.j=this.a.$d(b.a);fi(this,this.j)};l.Ok=function(){fi(this,null);this.j=null};
+l.setMap=function(b){di.ca.setMap.call(this,b);b&&(b=b.a,this.o.push(D(b,"mousemove",this.Nk,!1,this),D(b,"mouseout",this.Ok,!1,this)))};l.Wh=function(b){this.set("coordinateFormat",b)};l.bh=function(b){this.set("projection",b)};function fi(b,c){var d=b.B;if(c&&b.b){if(!b.i){var e=b.ah();b.i=e?Ie(b.b,e):Xe}if(e=b.a.Ga(c))b.i(e,e),d=(d=b.wg())?d(e):e.toString()}b.l&&d==b.l||(b.element.innerHTML=d,b.l=d)};function gi(b,c,d){oc.call(this);this.xa=null;this.b=!1;this.i=b;this.g=d;this.a=c||window;this.f=ua(this.c,this)}y(gi,oc);gi.prototype.start=function(){hi(this);this.b=!1;var b=ii(this),c=ji(this);b&&!c&&this.a.mozRequestAnimationFrame?(this.xa=D(this.a,"MozBeforePaint",this.f),this.a.mozRequestAnimationFrame(null),this.b=!0):this.xa=b&&c?b.call(this.a,this.f):this.a.setTimeout(we(this.f),20)};
+function hi(b){if(null!=b.xa){var c=ii(b),d=ji(b);c&&!d&&b.a.mozRequestAnimationFrame?Wc(b.xa):c&&d?d.call(b.a,b.xa):b.a.clearTimeout(b.xa)}b.xa=null}gi.prototype.c=function(){this.b&&this.xa&&Wc(this.xa);this.xa=null;this.i.call(this.g,wa())};gi.prototype.Y=function(){hi(this);gi.ca.Y.call(this)};function ii(b){b=b.a;return b.requestAnimationFrame||b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame||b.oRequestAnimationFrame||b.msRequestAnimationFrame||null}
+function ji(b){b=b.a;return b.cancelAnimationFrame||b.cancelRequestAnimationFrame||b.webkitCancelRequestAnimationFrame||b.mozCancelRequestAnimationFrame||b.oCancelRequestAnimationFrame||b.msCancelRequestAnimationFrame||null};function ki(b){ba.setTimeout(function(){throw b;},0)}function li(b,c){var d=b;c&&(d=ua(b,c));d=mi(d);!ka(ba.setImmediate)||ba.Window&&ba.Window.prototype&&ba.Window.prototype.setImmediate==ba.setImmediate?(ni||(ni=oi()),ni(d)):ba.setImmediate(d)}var ni;
+function oi(){var b=ba.MessageChannel;"undefined"===typeof b&&"undefined"!==typeof window&&window.postMessage&&window.addEventListener&&!Hb("Presto")&&(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=ua(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&&!Hb("Trident")&&!Hb("MSIE")){var c=new b,d={},e=d;c.port1.onmessage=function(){if(ca(d.next)){d=d.next;var b=d.ng;d.ng=null;b()}};return function(b){e.next={ng: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 mi=ve;function pi(b,c){this.f={};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){if(b instanceof pi)e=b.P(),d=b.sc();else{var d=[],f=0;for(e in b)d[f++]=e;e=d;d=Lb(b)}for(f=0;f<e.length;f++)this.set(e[f],d[f])}}l=pi.prototype;l.qc=function(){return this.b};l.sc=function(){qi(this);for(var b=[],c=0;c<this.a.length;c++)b.push(this.f[this.a[c]]);return b};l.P=function(){qi(this);return this.a.concat()};
+l.La=function(){return 0==this.b};l.clear=function(){this.f={};this.b=this.a.length=0};l.remove=function(b){return ri(this.f,b)?(delete this.f[b],this.b--,this.a.length>2*this.b&&qi(this),!0):!1};function qi(b){if(b.b!=b.a.length){for(var c=0,d=0;c<b.a.length;){var e=b.a[c];ri(b.f,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],ri(f,e)||(b.a[d++]=e,f[e]=1),c++;b.a.length=d}}l.get=function(b,c){return ri(this.f,b)?this.f[b]:c};
+l.set=function(b,c){ri(this.f,b)||(this.b++,this.a.push(b));this.f[b]=c};l.forEach=function(b,c){for(var d=this.P(),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 pi(this)};function ri(b,c){return Object.prototype.hasOwnProperty.call(b,c)};function si(){this.a=wa()}new si;si.prototype.set=function(b){this.a=b};si.prototype.reset=function(){this.set(wa())};si.prototype.get=function(){return this.a};function ti(b){$c.call(this);this.a=b||window;this.f=D(this.a,"resize",this.c,!1,this);this.b=Gg(this.a||window)}y(ti,$c);ti.prototype.Y=function(){ti.ca.Y.call(this);this.f&&(Wc(this.f),this.f=null);this.b=this.a=null};ti.prototype.c=function(){var b=Gg(this.a||window),c=this.b;b==c||b&&c&&b.width==c.width&&b.height==c.height||(this.b=b,this.s("resize"))};function ui(b,c,d,e,f){if(!(Yb||Zb||ac&&ic("525")))return!0;if(bc&&f)return vi(b);if(f&&!e)return!1;ja(c)&&(c=wi(c));if(!d&&(17==c||18==c||bc&&91==c))return!1;if((ac||Zb)&&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(Yb&&e&&c==b)return!1;switch(b){case 13:return!0;case 27:return!(ac||Zb)}return vi(b)}
+function vi(b){if(48<=b&&57>=b||96<=b&&106>=b||65<=b&&90>=b||(ac||Zb)&&0==b)return!0;switch(b){case 32:case 43:case 63:case 64: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 wi(b){if($b)b=xi(b);else if(bc&&ac)a:switch(b){case 93:b=91;break a}return b}
+function xi(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 yi(b,c){$c.call(this);b&&zi(this,b,c)}y(yi,$c);l=yi.prototype;l.vd=null;l.me=null;l.rf=null;l.ne=null;l.jb=-1;l.Zb=-1;l.cf=!1;
+var Ai={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},Bi={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},Ci=Yb||Zb||ac&&ic("525"),Di=bc&&$b;
+yi.prototype.a=function(b){if(ac||Zb)if(17==this.jb&&!b.o||18==this.jb&&!b.f||bc&&91==this.jb&&!b.B)this.Zb=this.jb=-1;-1==this.jb&&(b.o&&17!=b.i?this.jb=17:b.f&&18!=b.i?this.jb=18:b.B&&91!=b.i&&(this.jb=91));Ci&&!ui(b.i,this.jb,b.c,b.o,b.f)?this.handleEvent(b):(this.Zb=wi(b.i),Di&&(this.cf=b.f))};yi.prototype.f=function(b){this.Zb=this.jb=-1;this.cf=b.f};
+yi.prototype.handleEvent=function(b){var c=b.a,d,e,f=c.altKey;Yb&&"keypress"==b.type?(d=this.Zb,e=13!=d&&27!=d?c.keyCode:0):(ac||Zb)&&"keypress"==b.type?(d=this.Zb,e=0<=c.charCode&&63232>c.charCode&&vi(d)?c.charCode:0):Xb&&!ac?(d=this.Zb,e=vi(d)?c.keyCode:0):(d=c.keyCode||this.Zb,e=c.charCode||0,Di&&(f=this.cf),bc&&63==e&&224==d&&(d=191));var g=d=wi(d),h=c.keyIdentifier;d?63232<=d&&d in Ai?g=Ai[d]:25==d&&b.c&&(g=9):h&&h in Bi&&(g=Bi[h]);this.jb=g;b=new Ei(g,e,0,c);b.f=f;this.s(b)};
+function zi(b,c,d){b.ne&&Fi(b);b.vd=c;b.me=D(b.vd,"keypress",b,d);b.rf=D(b.vd,"keydown",b.a,d,b);b.ne=D(b.vd,"keyup",b.f,d,b)}function Fi(b){b.me&&(Wc(b.me),Wc(b.rf),Wc(b.ne),b.me=null,b.rf=null,b.ne=null);b.vd=null;b.jb=-1;b.Zb=-1}yi.prototype.Y=function(){yi.ca.Y.call(this);Fi(this)};function Ei(b,c,d,e){xc.call(this,e);this.type="key";this.i=b;this.G=c}y(Ei,xc);function Gi(b,c){$c.call(this);var d=this.a=b;(d=oa(d)&&1==d.nodeType?this.a:this.a?this.a.body:null)&&$g(d,"direction");this.f=D(this.a,$b?"DOMMouseScroll":"mousewheel",this,c)}y(Gi,$c);
+Gi.prototype.handleEvent=function(b){var c=0,d=0;b=b.a;if("mousewheel"==b.type){c=1;if(Yb||ac&&(cc||ic("532.0")))c=40;d=Hi(-b.wheelDelta,c);c=ca(b.wheelDeltaX)?Hi(-b.wheelDeltaY,c):d}else d=b.detail,100<d?d=3:-100>d&&(d=-3),ca(b.axis)&&b.axis===b.HORIZONTAL_AXIS||(c=d);ja(this.b)&&(c=Math.min(Math.max(c,-this.b),this.b));d=new Ii(d,b,0,c);this.s(d)};function Hi(b,c){return ac&&(bc||dc)&&0!=b%c?b:b/c}Gi.prototype.Y=function(){Gi.ca.Y.call(this);Wc(this.f);this.f=null};
+function Ii(b,c,d,e){xc.call(this,c);this.type="mousewheel";this.detail=b;this.v=e}y(Ii,xc);function Ji(b,c,d){tc.call(this,b);this.a=c;b=d?d:{};this.buttons=Ki(b);this.pressure=Li(b,this.buttons);this.bubbles="bubbles"in b?b.bubbles:!1;this.cancelable="cancelable"in b?b.cancelable:!1;this.view="view"in b?b.view:null;this.detail="detail"in b?b.detail:null;this.screenX="screenX"in b?b.screenX:0;this.screenY="screenY"in b?b.screenY:0;this.clientX="clientX"in b?b.clientX:0;this.clientY="clientY"in b?b.clientY:0;this.button="button"in b?b.button:0;this.relatedTarget="relatedTarget"in b?b.relatedTarget:
+null;this.pointerId="pointerId"in b?b.pointerId:0;this.width="width"in b?b.width:0;this.height="height"in b?b.height:0;this.pointerType="pointerType"in b?b.pointerType:"";this.isPrimary="isPrimary"in b?b.isPrimary:!1;c.preventDefault&&(this.preventDefault=function(){c.preventDefault()})}y(Ji,tc);function Ki(b){if(b.buttons||Mi)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 Li(b,c){var d=0;b.pressure?d=b.pressure:d=c?.5:0;return d}var Mi=!1;try{Mi=1===(new MouseEvent("click",{buttons:1})).buttons}catch(b){};function Ni(b,c){var d=document.createElement("CANVAS");b&&(d.width=b);c&&(d.height=c);return d.getContext("2d")}
+var Oi=function(){var b;return function(){if(void 0===b)if(ba.getComputedStyle){var c=document.createElement("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]));Mg(c);b=d&&"none"!==d}else b=!1;return b}}(),Pi=function(){var b;return function(){if(void 0===b)if(ba.getComputedStyle){var c=
+document.createElement("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]));Mg(c);b=d&&"none"!==d}else b=!1;return b}}();
+function Qi(b,c){var d=b.style;d.WebkitTransform=c;d.MozTransform=c;d.a=c;d.msTransform=c;d.transform=c;Yb&&ic("9.0")&&(b.style.transformOrigin="0 0")}function Ri(b,c){var d;if(Pi()){var e=Array(16);for(d=0;16>d;++d)e[d]=c[d].toFixed(6);Qi(b,"matrix3d("+e.join(",")+")")}else if(Oi()){var e=[c[0],c[1],c[4],c[5],c[12],c[13]],f=Array(6);for(d=0;6>d;++d)f[d]=e[d].toFixed(6);Qi(b,"matrix("+f.join(",")+")")}else b.style.left=Math.round(c[12])+"px",b.style.top=Math.round(c[13])+"px"};var Si=["experimental-webgl","webgl","webkit-3d","moz-webgl"];function Ti(b,c){var d,e,f=Si.length;for(e=0;e<f;++e)try{if(d=b.getContext(Si[e],c))return d}catch(g){}return null};var Ui,Vi=ba.devicePixelRatio||1,Wi=!1,Xi=function(){if(!("HTMLCanvasElement"in ba))return!1;try{var b=Ni();return b?(void 0!==b.setLineDash&&(Wi=!0),!0):!1}catch(c){return!1}}(),Yi="DeviceOrientationEvent"in ba,Zi="geolocation"in ba.navigator,$i="ontouchstart"in ba,aj="PointerEvent"in ba,bj=!!ba.navigator.msPointerEnabled,cj=!1,dj,ej=[];
+if("WebGLRenderingContext"in ba)try{var fj=Ti(document.createElement("CANVAS"),{failIfMajorPerformanceCaveat:!0});fj&&(cj=!0,dj=fj.getParameter(fj.MAX_TEXTURE_SIZE),ej=fj.getSupportedExtensions())}catch(b){}Ui=cj;ya=ej;xa=dj;function gj(b,c){this.a=b;this.g=c};function hj(b){gj.call(this,b,{mousedown:this.il,mousemove:this.jl,mouseup:this.ml,mouseover:this.ll,mouseout:this.kl});this.f=b.f;this.b=[]}y(hj,gj);function ij(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 m=Math.abs(f-k[1]);if(25>=Math.abs(e-k[0])&&25>=m)return!0}return!1}function jj(b){var c=kj(b,b.a),d=c.preventDefault;c.preventDefault=function(){b.preventDefault();d()};c.pointerId=1;c.isPrimary=!0;c.pointerType="mouse";return c}l=hj.prototype;
+l.il=function(b){if(!ij(this,b)){(1).toString()in this.f&&this.cancel(b);var c=jj(b);this.f[(1).toString()]=b;lj(this.a,mj,c,b)}};l.jl=function(b){if(!ij(this,b)){var c=jj(b);lj(this.a,nj,c,b)}};l.ml=function(b){if(!ij(this,b)){var c=this.f[(1).toString()];c&&c.button===b.button&&(c=jj(b),lj(this.a,oj,c,b),delete this.f[(1).toString()])}};l.ll=function(b){if(!ij(this,b)){var c=jj(b);pj(this.a,c,b)}};l.kl=function(b){if(!ij(this,b)){var c=jj(b);qj(this.a,c,b)}};
+l.cancel=function(b){var c=jj(b);this.a.cancel(c,b);delete this.f[(1).toString()]};function rj(b){gj.call(this,b,{MSPointerDown:this.rl,MSPointerMove:this.sl,MSPointerUp:this.vl,MSPointerOut:this.tl,MSPointerOver:this.ul,MSPointerCancel:this.ql,MSGotPointerCapture:this.ol,MSLostPointerCapture:this.pl});this.f=b.f;this.b=["","unavailable","touch","pen","mouse"]}y(rj,gj);function sj(b,c){var d=c;ja(c.a.pointerType)&&(d=kj(c,c.a),d.pointerType=b.b[c.a.pointerType]);return d}l=rj.prototype;l.rl=function(b){this.f[b.a.pointerId.toString()]=b;var c=sj(this,b);lj(this.a,mj,c,b)};
+l.sl=function(b){var c=sj(this,b);lj(this.a,nj,c,b)};l.vl=function(b){var c=sj(this,b);lj(this.a,oj,c,b);delete this.f[b.a.pointerId.toString()]};l.tl=function(b){var c=sj(this,b);qj(this.a,c,b)};l.ul=function(b){var c=sj(this,b);pj(this.a,c,b)};l.ql=function(b){var c=sj(this,b);this.a.cancel(c,b);delete this.f[b.a.pointerId.toString()]};l.pl=function(b){this.a.s(new Ji("lostpointercapture",b,b.a))};l.ol=function(b){this.a.s(new Ji("gotpointercapture",b,b.a))};function tj(b){gj.call(this,b,{pointerdown:this.Vn,pointermove:this.Wn,pointerup:this.Zn,pointerout:this.Xn,pointerover:this.Yn,pointercancel:this.Un,gotpointercapture:this.vk,lostpointercapture:this.hl})}y(tj,gj);l=tj.prototype;l.Vn=function(b){uj(this.a,b)};l.Wn=function(b){uj(this.a,b)};l.Zn=function(b){uj(this.a,b)};l.Xn=function(b){uj(this.a,b)};l.Yn=function(b){uj(this.a,b)};l.Un=function(b){uj(this.a,b)};l.hl=function(b){uj(this.a,b)};l.vk=function(b){uj(this.a,b)};function vj(b,c){gj.call(this,b,{touchstart:this.ap,touchmove:this.$o,touchend:this.Zo,touchcancel:this.Yo});this.f=b.f;this.j=c;this.b=void 0;this.i=0;this.c=void 0}y(vj,gj);l=vj.prototype;l.Sh=function(){this.i=0;this.c=void 0};
+function wj(b,c,d){c=kj(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.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 xj(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=wj(b,c,f[h]),k.preventDefault=e,d.call(b,c,k)}
+l.ap=function(b){var c=b.a.touches,d=Object.keys(this.f),e=d.length;if(e>=c.length){var f=[],g,h,k;for(g=0;g<e;++g){h=d[g];k=this.f[h];var m;if(!(m=1==h))a:{m=c.length;for(var n=void 0,p=0;p<m;p++)if(n=c[p],n.identifier===h-2){m=!0;break a}m=!1}m||f.push(k.wc)}for(g=0;g<f.length;++g)this.ef(b,f[g])}c=Kb(this.f);if(0===c||1===c&&(1).toString()in this.f)this.b=b.a.changedTouches[0].identifier,void 0!==this.c&&ba.clearTimeout(this.c);yj(this,b);this.i++;xj(this,b,this.Qn)};
+l.Qn=function(b,c){this.f[c.pointerId]={target:c.target,wc:c,Ah:c.target};var d=this.a;c.bubbles=!0;lj(d,Aj,c,b);d=this.a;c.bubbles=!1;lj(d,Bj,c,b);lj(this.a,mj,c,b)};l.$o=function(b){b.preventDefault();xj(this,b,this.nl)};l.nl=function(b,c){var d=this.f[c.pointerId];if(d){var e=d.wc,f=d.Ah;lj(this.a,nj,c,b);e&&f!==c.target&&(e.relatedTarget=c.target,c.relatedTarget=f,e.target=f,c.target?(qj(this.a,e,b),pj(this.a,c,b)):(c.target=f,c.relatedTarget=null,this.ef(b,c)));d.wc=c;d.Ah=c.target}};
+l.Zo=function(b){yj(this,b);xj(this,b,this.bp)};l.bp=function(b,c){lj(this.a,oj,c,b);this.a.wc(c,b);var d=this.a;c.bubbles=!1;lj(d,Cj,c,b);delete this.f[c.pointerId];c.isPrimary&&(this.b=void 0,this.c=ba.setTimeout(ua(this.Sh,this),200))};l.Yo=function(b){xj(this,b,this.ef)};l.ef=function(b,c){this.a.cancel(c,b);this.a.wc(c,b);var d=this.a;c.bubbles=!1;lj(d,Cj,c,b);delete this.f[c.pointerId];c.isPrimary&&(this.b=void 0,this.c=ba.setTimeout(ua(this.Sh,this),200))};
+function yj(b,c){var d=b.j.b,e=c.a.changedTouches[0];if(b.b===e.identifier){var f=[e.clientX,e.clientY];d.push(f);ba.setTimeout(function(){hb(d,f)},2500)}};function Dj(b){$c.call(this);this.c=b;this.f={};this.b={};this.a=[];aj?Ej(this,new tj(this)):bj?Ej(this,new rj(this)):(b=new hj(this),Ej(this,b),$i&&Ej(this,new vj(this,b)));b=this.a.length;for(var c,d=0;d<b;d++)c=this.a[d],Fj(this,Object.keys(c.g))}y(Dj,$c);function Ej(b,c){var d=Object.keys(c.g);d&&(d.forEach(function(b){var d=c.g[b];d&&(this.b[b]=ua(d,c))},b),b.a.push(c))}Dj.prototype.g=function(b){var c=this.b[b.type];c&&c(b)};
+function Fj(b,c){c.forEach(function(b){D(this.c,b,this.g,!1,this)},b)}function Gj(b,c){c.forEach(function(b){Vc(this.c,b,this.g,!1,this)},b)}function kj(b,c){for(var d={},e,f=0,g=Hj.length;f<g;f++)e=Hj[f][0],d[e]=b[e]||c[e]||Hj[f][1];return d}Dj.prototype.wc=function(b,c){b.bubbles=!0;lj(this,Ij,b,c)};Dj.prototype.cancel=function(b,c){lj(this,Jj,b,c)};function qj(b,c,d){b.wc(c,d);var e=c.relatedTarget;e&&Pg(c.target,e)||(c.bubbles=!1,lj(b,Cj,c,d))}
+function pj(b,c,d){c.bubbles=!0;lj(b,Aj,c,d);var e=c.relatedTarget;e&&Pg(c.target,e)||(c.bubbles=!1,lj(b,Bj,c,d))}function lj(b,c,d,e){b.s(new Ji(c,e,d))}function uj(b,c){b.s(new Ji(c.type,c,c.a))}Dj.prototype.Y=function(){for(var b=this.a.length,c,d=0;d<b;d++)c=this.a[d],Gj(this,Object.keys(c.g));Dj.ca.Y.call(this)};
+var nj="pointermove",mj="pointerdown",oj="pointerup",Aj="pointerover",Ij="pointerout",Bj="pointerenter",Cj="pointerleave",Jj="pointercancel",Hj=[["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 Kj(b,c,d,e,f){nh.call(this,b,c,f);this.a=d;this.originalEvent=d.a;this.pixel=c.$d(this.originalEvent);this.coordinate=c.Ga(this.pixel);this.dragging=void 0!==e?e:!1}y(Kj,nh);Kj.prototype.preventDefault=function(){Kj.ca.preventDefault.call(this);this.a.preventDefault()};Kj.prototype.b=function(){Kj.ca.b.call(this);this.a.b()};function Lj(b,c,d,e,f){Kj.call(this,b,c,d.a,e,f);this.f=d}y(Lj,Kj);
+function Mj(b){$c.call(this);this.b=b;this.i=0;this.j=!1;this.f=this.l=this.c=null;b=this.b.a;this.B=0;this.G={};this.g=new Dj(b);this.a=null;this.l=D(this.g,mj,this.Rk,!1,this);this.o=D(this.g,nj,this.xo,!1,this)}y(Mj,$c);function Nj(b,c){var d;d=new Lj(Oj,b.b,c);b.s(d);0!==b.i?(ba.clearTimeout(b.i),b.i=0,d=new Lj(Pj,b.b,c),b.s(d)):b.i=ba.setTimeout(ua(function(){this.i=0;var b=new Lj(Qj,this.b,c);this.s(b)},b),250)}
+function Rj(b,c){c.type==Sj||c.type==Tj?delete b.G[c.pointerId]:c.type==Uj&&(b.G[c.pointerId]=!0);b.B=Kb(b.G)}l=Mj.prototype;l.Lg=function(b){Rj(this,b);var c=new Lj(Sj,this.b,b);this.s(c);!this.j&&0===b.button&&Nj(this,this.f);0===this.B&&(this.c.forEach(Wc),this.c=null,this.j=!1,this.f=null,sc(this.a),this.a=null)};
+l.Rk=function(b){Rj(this,b);var c=new Lj(Uj,this.b,b);this.s(c);this.f=b;this.c||(this.a=new Dj(document),this.c=[D(this.a,Vj,this.Il,!1,this),D(this.a,Sj,this.Lg,!1,this),D(this.g,Tj,this.Lg,!1,this)])};l.Il=function(b){if(b.clientX!=this.f.clientX||b.clientY!=this.f.clientY){this.j=!0;var c=new Lj(Wj,this.b,b,this.j);this.s(c)}b.preventDefault()};l.xo=function(b){this.s(new Lj(b.type,this.b,b,!(!this.f||b.clientX==this.f.clientX&&b.clientY==this.f.clientY)))};
+l.Y=function(){this.o&&(Wc(this.o),this.o=null);this.l&&(Wc(this.l),this.l=null);this.c&&(this.c.forEach(Wc),this.c=null);this.a&&(sc(this.a),this.a=null);this.g&&(sc(this.g),this.g=null);Mj.ca.Y.call(this)};var Qj="singleclick",Oj="click",Pj="dblclick",Wj="pointerdrag",Vj="pointermove",Uj="pointerdown",Sj="pointerup",Tj="pointercancel",Xj={up:Qj,jp:Oj,kp:Pj,np:Wj,qp:Vj,mp:Uj,tp:Sj,sp:"pointerover",rp:"pointerout",op:"pointerenter",pp:"pointerleave",lp:Tj};function Yj(b){gd.call(this);var c=Tb(b);c.opacity=void 0!==b.opacity?b.opacity:1;c.visible=void 0!==b.visible?b.visible:!0;c.zIndex=void 0!==b.zIndex?b.zIndex:0;c.maxResolution=void 0!==b.maxResolution?b.maxResolution:Infinity;c.minResolution=void 0!==b.minResolution?b.minResolution:0;this.I(c)}y(Yj,gd);
+function Zj(b){var c=b.Rb(),d=b.pf(),e=b.rb(),f=b.J(),g=b.Sb(),h=b.Nb(),k=b.Ob();return{layer:b,opacity:Sa(c,0,1),O:d,visible:e,Bb:!0,extent:f,zIndex:g,maxResolution:h,minResolution:Math.max(k,0)}}l=Yj.prototype;l.J=function(){return this.get("extent")};l.Nb=function(){return this.get("maxResolution")};l.Ob=function(){return this.get("minResolution")};l.Rb=function(){return this.get("opacity")};l.rb=function(){return this.get("visible")};l.Sb=function(){return this.get("zIndex")};
+l.cc=function(b){this.set("extent",b)};l.kc=function(b){this.set("maxResolution",b)};l.lc=function(b){this.set("minResolution",b)};l.dc=function(b){this.set("opacity",b)};l.ec=function(b){this.set("visible",b)};l.fc=function(b){this.set("zIndex",b)};function ak(){};function bk(b,c,d,e,f,g){tc.call(this,b,c);this.vectorContext=d;this.frameState=e;this.context=f;this.glContext=g}y(bk,tc);function ck(b){var c=Tb(b);delete c.source;Yj.call(this,c);this.i=this.B=this.o=null;b.map&&this.setMap(b.map);D(this,id("source"),this.Xk,!1,this);this.zc(b.source?b.source:null)}y(ck,Yj);function dk(b,c){return b.visible&&c>=b.minResolution&&c<b.maxResolution}l=ck.prototype;l.of=function(b){b=b?b:[];b.push(Zj(this));return b};l.ea=function(){return this.get("source")||null};l.pf=function(){var b=this.ea();return b?b.B:"undefined"};l.zm=function(){this.u()};
+l.Xk=function(){this.i&&(Wc(this.i),this.i=null);var b=this.ea();b&&(this.i=D(b,"change",this.zm,!1,this));this.u()};l.setMap=function(b){Wc(this.o);this.o=null;b||this.u();Wc(this.B);this.B=null;b&&(this.o=D(b,"precompose",function(b){var d=Zj(this);d.Bb=!1;d.zIndex=Infinity;b.frameState.layerStatesArray.push(d);b.frameState.layerStates[w(this)]=d},!1,this),this.B=D(this,"change",b.render,!1,b),this.u())};l.zc=function(b){this.set("source",b)};function ek(b,c,d,e,f){$c.call(this);this.i=f;this.extent=b;this.b=d;this.resolution=c;this.state=e}y(ek,$c);function fk(b){b.s("change")}ek.prototype.J=function(){return this.extent};ek.prototype.$=function(){return this.resolution};function gk(b,c,d,e,f,g,h,k){Fd(b);0===c&&0===d||Id(b,c,d);1==e&&1==f||Jd(b,e,f);0!==g&&Kd(b,g);0===h&&0===k||Id(b,h,k);return b}function hk(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 ik(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 jk(b){dd.call(this);this.a=b}y(jk,dd);l=jk.prototype;l.ab=za;l.vc=function(b,c,d,e){b=b.slice();ik(c.pixelToCoordinateMatrix,b,b);if(this.ab(b,c,te,this))return d.call(e,this.a)};l.xe=se;l.cd=function(b,c,d){return function(e,f){return Oh(b,c,e,f,function(b){d[e]||(d[e]={});d[e][b.a.toString()]=b})}};l.Dm=function(b){2===b.target.state&&kk(this)};function lk(b,c){var d=c.state;2!=d&&3!=d&&D(c,"change",b.Dm,!1,b);0==d&&(c.load(),d=c.state);return 2==d}
+function kk(b){var c=b.a;c.rb()&&"ready"==c.pf()&&b.u()}function mk(b,c){c.qh()&&b.postRenderFunctions.push(va(function(b,c,f){c=w(b).toString();b.rh(f.viewState.projection,f.usedTiles[c])},c))}function nk(b,c){if(c){var d,e,f;e=0;for(f=c.length;e<f;++e)d=c[e],b[w(d).toString()]=d}}function ok(b,c){var d=c.U;void 0!==d&&(ia(d)?b.logos[d]="":oa(d)&&(b.logos[d.src]=d.href))}
+function pk(b,c,d,e){c=w(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.c>b.c&&(b.c=e.c),e.f<b.f&&(b.f=e.f),e.b>b.b&&(b.b=e.b)):b[c][d]=e:(b[c]={},b[c][d]=e)}function qk(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 rk(b,c,d,e,f,g,h,k,m,n){var p=w(c).toString();p in b.wantedTiles||(b.wantedTiles[p]={});var q=b.wantedTiles[p];b=b.tileQueue;var r=d.minZoom,t,x,z,A,B,v;for(v=h;v>=r;--v)for(x=Dh(d,g,v,x),z=d.$(v),A=x.a;A<=x.c;++A)for(B=x.f;B<=x.b;++B)h-v<=k?(t=c.Pb(v,A,B,e,f),0==t.state&&(q[eg(t.a)]=!0,t.$a()in b.b||b.c([t,p,Hh(d,t.a),z])),void 0!==m&&m.call(n,t)):c.Yf(v,A,B,f)};function sk(b){this.B=b.opacity;this.C=b.rotateWithView;this.G=b.rotation;this.j=b.scale;this.D=b.snapToPixel}l=sk.prototype;l.Be=function(){return this.B};l.de=function(){return this.C};l.Ce=function(){return this.G};l.De=function(){return this.j};l.ee=function(){return this.D};l.Ee=function(b){this.B=b};l.Fe=function(b){this.G=b};l.Ge=function(b){this.j=b};function tk(b){b=b||{};this.g=void 0!==b.anchor?b.anchor:[.5,.5];this.c=null;this.f=void 0!==b.anchorOrigin?b.anchorOrigin:"top-left";this.l=void 0!==b.anchorXUnits?b.anchorXUnits:"fraction";this.o=void 0!==b.anchorYUnits?b.anchorYUnits:"fraction";var c=void 0!==b.crossOrigin?b.crossOrigin:null,d=void 0!==b.img?b.img:null,e=void 0!==b.imgSize?b.imgSize:null,f=b.src;void 0!==f&&0!==f.length||!d||(f=d.src||w(d).toString());var g=void 0!==b.src?0:2,h=uk.Yb(),k=h.get(f,c);k||(k=new vk(d,f,e,c,g),h.set(f,
+c,k));this.a=k;this.O=void 0!==b.offset?b.offset:[0,0];this.b=void 0!==b.offsetOrigin?b.offsetOrigin:"top-left";this.i=null;this.v=void 0!==b.size?b.size:null;sk.call(this,{opacity:void 0!==b.opacity?b.opacity:1,rotation:void 0!==b.rotation?b.rotation:0,scale:void 0!==b.scale?b.scale:1,snapToPixel:void 0!==b.snapToPixel?b.snapToPixel:!0,rotateWithView:void 0!==b.rotateWithView?b.rotateWithView:!1})}y(tk,sk);l=tk.prototype;
+l.Xb=function(){if(this.c)return this.c;var b=this.g,c=this.Cb();if("fraction"==this.l||"fraction"==this.o){if(!c)return null;b=this.g.slice();"fraction"==this.l&&(b[0]*=c[0]);"fraction"==this.o&&(b[1]*=c[1])}if("top-left"!=this.f){if(!c)return null;b===this.g&&(b=this.g.slice());if("top-right"==this.f||"bottom-right"==this.f)b[0]=-b[0]+c[0];if("bottom-left"==this.f||"bottom-right"==this.f)b[1]=-b[1]+c[1]}return this.c=b};l.gc=function(){return this.a.a};l.rd=function(){return this.a.b};l.Cd=function(){return this.a.f};
+l.Ae=function(){var b=this.a;if(!b.g)if(b.l){var c=b.b[0],d=b.b[1],e=Ni(c,d);e.fillRect(0,0,c,d);b.g=e.canvas}else b.g=b.a;return b.g};l.Da=function(){if(this.i)return this.i;var b=this.O;if("top-left"!=this.b){var c=this.Cb(),d=this.a.b;if(!c||!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.i=b};l.hn=function(){return this.a.i};l.Cb=function(){return this.v?this.v:this.a.b};
+l.tf=function(b,c){return D(this.a,"change",b,!1,c)};l.load=function(){this.a.load()};l.Xf=function(b,c){Vc(this.a,"change",b,!1,c)};function vk(b,c,d,e,f){$c.call(this);this.g=null;this.a=b?b:new Image;null!==e&&(this.a.crossOrigin=e);this.c=null;this.f=f;this.b=d;this.i=c;this.l=!1;2==this.f&&wk(this)}y(vk,$c);function wk(b){var c=Ni(1,1);try{c.drawImage(b.a,0,0),c.getImageData(0,0,1,1)}catch(d){b.l=!0}}vk.prototype.j=function(){this.f=3;this.c.forEach(Wc);this.c=null;this.s("change")};
+vk.prototype.o=function(){this.f=2;this.b=[this.a.width,this.a.height];this.c.forEach(Wc);this.c=null;wk(this);this.s("change")};vk.prototype.load=function(){if(0==this.f){this.f=1;this.c=[Uc(this.a,"error",this.j,!1,this),Uc(this.a,"load",this.o,!1,this)];try{this.a.src=this.i}catch(b){this.j()}}};function uk(){this.a={};this.f=0}ea(uk);uk.prototype.clear=function(){this.a={};this.f=0};uk.prototype.get=function(b,c){var d=c+":"+b;return d in this.a?this.a[d]:null};
+uk.prototype.set=function(b,c,d){this.a[c+":"+b]=d;++this.f};function xk(b,c){oc.call(this);this.i=c;this.c={};this.G={}}y(xk,oc);function yk(b){var c=b.viewState,d=b.coordinateToPixelMatrix;gk(d,b.size[0]/2,b.size[1]/2,1/c.resolution,-1/c.resolution,-c.rotation,-c.center[0],-c.center[1]);Hd(d,b.pixelToCoordinateMatrix)}l=xk.prototype;l.Y=function(){Ib(this.c,sc);xk.ca.Y.call(this)};
+function zk(){var b=uk.Yb();if(32<b.f){var c=0,d,e;for(d in b.a){e=b.a[d];var f;if(f=0===(c++&3))Bc(e)?e=cd(e,void 0,void 0):(e=Pc(e),e=!!e&&Jc(e,void 0,void 0)),f=!e;f&&(delete b.a[d],--b.f)}}}
+l.Bf=function(b,c,d,e,f,g){function h(b){var c=w(b).toString();if(!(c in p))return p[c]=!0,d.call(e,b,null)}var k,m=c.viewState,n=m.resolution,p={},q=m.projection,m=b;if(q.b){var q=q.J(),r=je(q),t=b[0];if(t<q[0]||t>q[2])m=[t+r*Math.ceil((q[0]-t)/r),b[1]]}q=c.layerStatesArray;for(r=q.length-1;0<=r;--r){var t=q[r],x=t.layer;if(dk(t,n)&&f.call(g,x)){var z=Ak(this,x);x.ea()&&(k=z.ab(xh(x.ea())?m:b,c,t.Bb?d:h,e));if(k)return k}}};
+l.lh=function(b,c,d,e,f,g){var h,k=c.viewState.resolution,m=c.layerStatesArray,n;for(n=m.length-1;0<=n;--n){h=m[n];var p=h.layer;if(dk(h,k)&&f.call(g,p)&&(h=Ak(this,p).vc(b,c,d,e)))return h}};l.mh=function(b,c,d,e){return void 0!==this.Bf(b,c,te,this,d,e)};function Ak(b,c){var d=w(c).toString();if(d in b.c)return b.c[d];var e=b.hf(c);b.c[d]=e;b.G[d]=D(e,"change",b.Hk,!1,b);return e}l.Hk=function(){this.i.render()};l.Ne=za;
+l.Do=function(b,c){for(var d in this.c)if(!(c&&d in c.layerStates)){var e=d,f=this.c[e];delete this.c[e];Wc(this.G[e]);delete this.G[e];sc(f)}};function Bk(b,c){for(var d in b.c)if(!(d in c.layerStates)){c.postRenderFunctions.push(ua(b.Do,b));break}}function qb(b,c){return b.zIndex-c.zIndex};function Ck(b,c){this.o=b;this.i=c;this.a=[];this.f=[];this.b={}}Ck.prototype.clear=function(){this.a.length=0;this.f.length=0;Qb(this.b)};function Dk(b){var c=b.a,d=b.f,e=c[0];1==c.length?(c.length=0,d.length=0):(c[0]=c.pop(),d[0]=d.pop(),Ek(b,0));c=b.i(e);delete b.b[c];return e}Ck.prototype.c=function(b){var c=this.o(b);return Infinity!=c?(this.a.push(b),this.f.push(c),this.b[this.i(b)]=!0,Fk(this,0,this.a.length-1),!0):!1};Ck.prototype.qc=function(){return this.a.length};
+Ck.prototype.La=function(){return 0===this.a.length};function Ek(b,c){for(var d=b.a,e=b.f,f=d.length,g=d[c],h=e[c],k=c;c<f>>1;){var m=2*c+1,n=2*c+2,m=n<f&&e[n]<e[m]?n:m;d[c]=d[m];e[c]=e[m];c=m}d[c]=g;e[c]=h;Fk(b,k,c)}function Fk(b,c,d){var e=b.a;b=b.f;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 Gk(b){var c=b.o,d=b.a,e=b.f,f=0,g=d.length,h,k,m;for(k=0;k<g;++k)h=d[k],m=c(h),Infinity==m?delete b.b[b.i(h)]:(e[f]=m,d[f++]=h);d.length=f;e.length=f;for(c=(b.a.length>>1)-1;0<=c;c--)Ek(b,c)};function Hk(b,c){Ck.call(this,function(c){return b.apply(null,c)},function(b){return b[0].$a()});this.G=c;this.g=0;this.j={}}y(Hk,Ck);Hk.prototype.c=function(b){var c=Hk.ca.c.call(this,b);c&&D(b[0],"change",this.l,!1,this);return c};Hk.prototype.l=function(b){b=b.target;var c=b.state;if(2===c||3===c||4===c)Vc(b,"change",this.l,!1,this),b=b.$a(),b in this.j&&(delete this.j[b],--this.g),this.G()};
+function Ik(b,c,d){for(var e=0,f;b.g<c&&e<d&&0<b.qc();)f=Dk(b)[0],0===f.state&&(f.load(),b.j[f.$a()]=!0,++b.g,++e)};function Jk(b,c,d){this.c=b;this.b=c;this.i=d;this.a=[];this.f=this.g=0}function Kk(b,c){var d=b.c,e=b.f,f=b.b-e,g=Math.log(b.b/b.f)/b.c;return Yf({source:c,duration:g,easing:function(b){return e*(Math.exp(d*b*g)-1)/f}})};function Lk(b){gd.call(this);this.B=null;this.g(!0);this.handleEvent=b.handleEvent}y(Lk,gd);Lk.prototype.b=function(){return this.get("active")};Lk.prototype.i=function(){return this.B};Lk.prototype.g=function(b){this.set("active",b)};Lk.prototype.setMap=function(b){this.B=b};function Mk(b,c,d,e,f){if(void 0!==d){var g=c.Fa(),h=c.Ua();void 0!==g&&h&&f&&0<f&&(b.Na(Zf({rotation:g,duration:f,easing:Uf})),e&&b.Na(Yf({source:h,duration:f,easing:Uf})));c.rotate(d,e)}}
+function Nk(b,c,d,e,f){var g=c.$();d=c.constrainResolution(g,d,0);Ok(b,c,d,e,f)}function Ok(b,c,d,e,f){if(d){var g=c.$(),h=c.Ua();void 0!==g&&h&&d!==g&&f&&0<f&&(b.Na($f({resolution:g,duration:f,easing:Uf})),e&&b.Na(Yf({source:h,duration:f,easing:Uf})));if(e){var k;b=c.Ua();f=c.$();void 0!==b&&void 0!==f&&(k=[e[0]-d*(e[0]-b[0])/f,e[1]-d*(e[1]-b[1])/f]);c.kb(k)}c.Ub(d)}};function Pk(b){b=b?b:{};this.a=b.delta?b.delta:1;Lk.call(this,{handleEvent:Qk});this.c=void 0!==b.duration?b.duration:250}y(Pk,Lk);function Qk(b){var c=!1,d=b.a;if(b.type==Pj){var c=b.map,e=b.coordinate,d=d.c?-this.a:this.a,f=c.aa();Nk(c,f,d,e,this.c);b.preventDefault();c=!0}return!c};function Rk(b){b=b.a;return b.f&&!b.l&&b.c}function Sk(b){return"pointermove"==b.type}function Tk(b){return b.type==Qj}function Uk(b){b=b.a;return!b.f&&!b.l&&!b.c}function Vk(b){b=b.a;return!b.f&&!b.l&&b.c}function Wk(b){b=b.a.target.tagName;return"INPUT"!==b&&"SELECT"!==b&&"TEXTAREA"!==b}function Xk(b){return"mouse"==b.f.pointerType};function Yk(b){b=b?b:{};Lk.call(this,{handleEvent:b.handleEvent?b.handleEvent:Zk});this.Cc=b.handleDownEvent?b.handleDownEvent:se;this.Yc=b.handleDragEvent?b.handleDragEvent:za;this.Xe=b.handleMoveEvent?b.handleMoveEvent:za;this.Ye=b.handleUpEvent?b.handleUpEvent:se;this.C=!1;this.fa={};this.j=[]}y(Yk,Lk);function $k(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 Zk(b){if(!(b instanceof Lj))return!0;var c=!1,d=b.type;if(d===Uj||d===Wj||d===Sj)d=b.f,b.type==Sj?delete this.fa[d.pointerId]:b.type==Uj?this.fa[d.pointerId]=d:d.pointerId in this.fa&&(this.fa[d.pointerId]=d),this.j=Lb(this.fa);this.C&&(b.type==Wj?this.Yc(b):b.type==Sj&&(this.C=this.Ye(b)));b.type==Uj?(this.C=b=this.Cc(b),c=this.Ac(b)):b.type==Vj&&this.Xe(b);return!c}Yk.prototype.Ac=ve;function al(b){Yk.call(this,{handleDownEvent:bl,handleDragEvent:cl,handleUpEvent:dl});b=b?b:{};this.a=b.kinetic;this.c=this.l=null;this.v=b.condition?b.condition:Uk;this.o=!1}y(al,Yk);function cl(b){var c=$k(this.j);this.a&&this.a.a.push(c[0],c[1],Date.now());if(this.c){var d=this.c[0]-c[0],e=c[1]-this.c[1];b=b.map;var f=b.aa(),g=Qf(f),e=d=[d,e],h=g.resolution;e[0]*=h;e[1]*=h;ud(d,g.rotation);pd(d,g.center);d=f.Xd(d);b.render();f.kb(d)}this.c=c}
+function dl(b){b=b.map;var c=b.aa();if(0===this.j.length){var d;if(d=!this.o&&this.a)if(d=this.a,6>d.a.length)d=!1;else{var e=Date.now()-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.f=Math.sqrt(h*h+f*f)/e;d=d.f>d.b}}d&&(d=this.a,d=(d.b-d.f)/d.c,f=this.a.g,g=c.Ua(),this.l=Kk(this.a,g),b.Na(this.l),g=b.Pa(g),d=b.Ga([g[0]-d*Math.cos(f),g[1]-d*Math.sin(f)]),d=c.Xd(d),c.kb(d));Sf(c,-1);b.render();
+return!1}this.c=null;return!0}function bl(b){if(0<this.j.length&&this.v(b)){var c=b.map,d=c.aa();this.c=null;this.C||Sf(d,1);c.render();this.l&&hb(c.O,this.l)&&(d.kb(b.frameState.viewState.center),this.l=null);this.a&&(b=this.a,b.a.length=0,b.g=0,b.f=0);this.o=1<this.j.length;return!0}return!1}al.prototype.Ac=se;function el(b){b=b?b:{};Yk.call(this,{handleDownEvent:fl,handleDragEvent:gl,handleUpEvent:hl});this.c=b.condition?b.condition:Rk;this.a=void 0;this.l=void 0!==b.duration?b.duration:250}y(el,Yk);function gl(b){if(Xk(b)){var c=b.map,d=c.Sa();b=b.pixel;d=Math.atan2(d[1]/2-b[1],b[0]-d[0]/2);if(void 0!==this.a){b=d-this.a;var e=c.aa(),f=e.Fa();c.render();Mk(c,e,f-b)}this.a=d}}
+function hl(b){if(!Xk(b))return!0;b=b.map;var c=b.aa();Sf(c,-1);var d=c.Fa(),e=this.l,d=c.constrainRotation(d,0);Mk(b,c,d,void 0,e);return!1}function fl(b){return Xk(b)&&zc(b.a)&&this.c(b)?(b=b.map,Sf(b.aa(),1),b.render(),this.a=void 0,!0):!1}el.prototype.Ac=se;function il(b){this.c=null;this.f=document.createElement("div");this.f.style.position="absolute";this.f.className="ol-box "+b;this.b=this.g=this.a=null}y(il,oc);il.prototype.Y=function(){this.setMap(null);il.ca.Y.call(this)};function jl(b){var c=b.g,d=b.b;b=b.f.style;b.left=Math.min(c[0],d[0])+"px";b.top=Math.min(c[1],d[1])+"px";b.width=Math.abs(d[0]-c[0])+"px";b.height=Math.abs(d[1]-c[1])+"px"}
+il.prototype.setMap=function(b){if(this.a){this.a.l.removeChild(this.f);var c=this.f.style;c.left=c.top=c.width=c.height="inherit"}(this.a=b)&&this.a.l.appendChild(this.f)};function kl(b){var c=b.g,d=b.b,c=[c,[c[0],d[1]],d,[d[0],c[1]]].map(b.a.Ga,b.a);c[4]=c[0].slice();b.c?b.c.la([c]):b.c=new F([c])}il.prototype.X=function(){return this.c};function ll(b,c){tc.call(this,b);this.coordinate=c}y(ll,tc);function ml(b){Yk.call(this,{handleDownEvent:nl,handleDragEvent:pl,handleUpEvent:ql});b=b?b:{};this.c=new il(b.className||"ol-dragbox");this.a=null;this.v=b.condition?b.condition:te}y(ml,Yk);function pl(b){if(Xk(b)){var c=this.c;b=b.pixel;c.g=this.a;c.b=b;kl(c);jl(c)}}ml.prototype.X=function(){return this.c.X()};ml.prototype.o=za;
+function ql(b){if(!Xk(b))return!0;this.c.setMap(null);var c=b.pixel[0]-this.a[0],d=b.pixel[1]-this.a[1];64<=c*c+d*d&&(this.o(b),this.s(new ll("boxend",b.coordinate)));return!1}function nl(b){if(Xk(b)&&zc(b.a)&&this.v(b)){this.a=b.pixel;this.c.setMap(b.map);var c=this.c,d=this.a;c.g=this.a;c.b=d;kl(c);jl(c);this.s(new ll("boxstart",b.coordinate));return!0}return!1};function rl(b){b=b?b:{};var c=b.condition?b.condition:Vk;this.l=void 0!==b.duration?b.duration:200;ml.call(this,{condition:c,className:b.className||"ol-dragzoom"})}y(rl,ml);rl.prototype.o=function(){var b=this.B,c=b.aa(),d=b.Sa(),e=this.X().J(),d=c.constrainResolution(Math.max(je(e)/d[0],ke(e)/d[1])),f=c.$(),g=c.Ua();b.Na($f({resolution:f,duration:this.l,easing:Uf}));b.Na(Yf({source:g,duration:this.l,easing:Uf}));c.kb(le(e));c.Ub(d)};function sl(b){Lk.call(this,{handleEvent:tl});b=b||{};this.a=void 0!==b.condition?b.condition:ye(Uk,Wk);this.c=void 0!==b.duration?b.duration:100;this.j=void 0!==b.pixelDelta?b.pixelDelta:128}y(sl,Lk);
+function tl(b){var c=!1;if("key"==b.type){var d=b.a.i;if(this.a(b)&&(40==d||37==d||39==d||38==d)){var e=b.map,c=e.aa(),f=c.$()*this.j,g=0,h=0;40==d?h=-f:37==d?g=-f:39==d?g=f:h=f;d=[g,h];ud(d,c.Fa());f=this.c;if(g=c.Ua())f&&0<f&&e.Na(Yf({source:g,duration:f,easing:Wf})),e=c.Xd([g[0]+d[0],g[1]+d[1]]),c.kb(e);b.preventDefault();c=!0}}return!c};function ul(b){Lk.call(this,{handleEvent:vl});b=b?b:{};this.c=b.condition?b.condition:Wk;this.a=b.delta?b.delta:1;this.j=void 0!==b.duration?b.duration:100}y(ul,Lk);function vl(b){var c=!1;if("key"==b.type){var d=b.a.G;if(this.c(b)&&(43==d||45==d)){c=b.map;d=43==d?this.a:-this.a;c.render();var e=c.aa();Nk(c,e,d,void 0,this.j);b.preventDefault();c=!0}}return!c};function wl(b){Lk.call(this,{handleEvent:xl});b=b||{};this.c=0;this.C=void 0!==b.duration?b.duration:250;this.o=void 0!==b.useAnchor?b.useAnchor:!0;this.a=null;this.l=this.j=void 0}y(wl,Lk);function xl(b){var c=!1;if("mousewheel"==b.type){var c=b.map,d=b.a;this.o&&(this.a=b.coordinate);this.c+=d.v;void 0===this.j&&(this.j=Date.now());d=Math.max(80-(Date.now()-this.j),0);ba.clearTimeout(this.l);this.l=ba.setTimeout(ua(this.v,this,c),d);b.preventDefault();c=!0}return!c}
+wl.prototype.v=function(b){var c=Sa(this.c,-1,1),d=b.aa();b.render();Nk(b,d,-c,this.a,this.C);this.c=0;this.a=null;this.l=this.j=void 0};wl.prototype.D=function(b){this.o=b;b||(this.a=null)};function yl(b){Yk.call(this,{handleDownEvent:zl,handleDragEvent:Al,handleUpEvent:Bl});b=b||{};this.c=null;this.l=void 0;this.a=!1;this.o=0;this.D=void 0!==b.threshold?b.threshold:.3;this.v=void 0!==b.duration?b.duration:250}y(yl,Yk);
+function Al(b){var c=0,d=this.j[0],e=this.j[1],d=Math.atan2(e.clientY-d.clientY,e.clientX-d.clientX);void 0!==this.l&&(c=d-this.l,this.o+=c,!this.a&&Math.abs(this.o)>this.D&&(this.a=!0));this.l=d;b=b.map;d=dh(b.a);e=$k(this.j);e[0]-=d.x;e[1]-=d.y;this.c=b.Ga(e);this.a&&(d=b.aa(),e=d.Fa(),b.render(),Mk(b,d,e+c,this.c))}function Bl(b){if(2>this.j.length){b=b.map;var c=b.aa();Sf(c,-1);if(this.a){var d=c.Fa(),e=this.c,f=this.v,d=c.constrainRotation(d,0);Mk(b,c,d,e,f)}return!1}return!0}
+function zl(b){return 2<=this.j.length?(b=b.map,this.c=null,this.l=void 0,this.a=!1,this.o=0,this.C||Sf(b.aa(),1),b.render(),!0):!1}yl.prototype.Ac=se;function Cl(b){Yk.call(this,{handleDownEvent:Dl,handleDragEvent:El,handleUpEvent:Fl});b=b?b:{};this.c=null;this.o=void 0!==b.duration?b.duration:400;this.a=void 0;this.l=1}y(Cl,Yk);function El(b){var c=1,d=this.j[0],e=this.j[1],f=d.clientX-e.clientX,d=d.clientY-e.clientY,f=Math.sqrt(f*f+d*d);void 0!==this.a&&(c=this.a/f);this.a=f;1!=c&&(this.l=c);b=b.map;var f=b.aa(),d=f.$(),e=dh(b.a),g=$k(this.j);g[0]-=e.x;g[1]-=e.y;this.c=b.Ga(g);b.render();Ok(b,f,d*c,this.c)}
+function Fl(b){if(2>this.j.length){b=b.map;var c=b.aa();Sf(c,-1);var d=c.$(),e=this.c,f=this.o,d=c.constrainResolution(d,0,this.l-1);Ok(b,c,d,e,f);return!1}return!0}function Dl(b){return 2<=this.j.length?(b=b.map,this.c=null,this.a=void 0,this.l=1,this.C||Sf(b.aa(),1),b.render(),!0):!1}Cl.prototype.Ac=se;function Gl(b){b=b?b:{};var c=new og,d=new Jk(-.005,.05,100);(void 0!==b.altShiftDragRotate?b.altShiftDragRotate:1)&&c.push(new el);(void 0!==b.doubleClickZoom?b.doubleClickZoom:1)&&c.push(new Pk({delta:b.zoomDelta,duration:b.zoomDuration}));(void 0!==b.dragPan?b.dragPan:1)&&c.push(new al({kinetic:d}));(void 0!==b.pinchRotate?b.pinchRotate:1)&&c.push(new yl);(void 0!==b.pinchZoom?b.pinchZoom:1)&&c.push(new Cl({duration:b.zoomDuration}));if(void 0!==b.keyboard?b.keyboard:1)c.push(new sl),c.push(new ul({delta:b.zoomDelta,
+duration:b.zoomDuration}));(void 0!==b.mouseWheelZoom?b.mouseWheelZoom:1)&&c.push(new wl({duration:b.zoomDuration}));(void 0!==b.shiftDragZoom?b.shiftDragZoom:1)&&c.push(new rl({duration:b.zoomDuration}));return c};function Hl(b){var c=b||{};b=Tb(c);delete b.layers;c=c.layers;Yj.call(this,b);this.b=[];this.a={};D(this,id("layers"),this.Jk,!1,this);c?ga(c)&&(c=new og(c.slice())):c=new og;this.ih(c)}y(Hl,Yj);l=Hl.prototype;l.ie=function(){this.rb()&&this.u()};
+l.Jk=function(){this.b.forEach(Wc);this.b.length=0;var b=this.Qc();this.b.push(D(b,"add",this.Ik,!1,this),D(b,"remove",this.Kk,!1,this));Ib(this.a,function(b){b.forEach(Wc)});Qb(this.a);var b=b.a,c,d,e;c=0;for(d=b.length;c<d;c++)e=b[c],this.a[w(e).toString()]=[D(e,"propertychange",this.ie,!1,this),D(e,"change",this.ie,!1,this)];this.u()};l.Ik=function(b){b=b.element;var c=w(b).toString();this.a[c]=[D(b,"propertychange",this.ie,!1,this),D(b,"change",this.ie,!1,this)];this.u()};
+l.Kk=function(b){b=w(b.element).toString();this.a[b].forEach(Wc);delete this.a[b];this.u()};l.Qc=function(){return this.get("layers")};l.ih=function(b){this.set("layers",b)};
+l.of=function(b){var c=void 0!==b?b:[],d=c.length;this.Qc().forEach(function(b){b.of(c)});b=Zj(this);var e,f;for(e=c.length;d<e;d++)f=c[d],f.opacity*=b.opacity,f.visible=f.visible&&b.visible,f.maxResolution=Math.min(f.maxResolution,b.maxResolution),f.minResolution=Math.max(f.minResolution,b.minResolution),void 0!==b.extent&&(f.extent=void 0!==f.extent?ne(f.extent,b.extent):b.extent);return c};l.pf=function(){return"ready"};function Il(b){Ce.call(this,{code:b,units:"m",extent:Jl,global:!0,worldExtent:Kl})}y(Il,Ce);Il.prototype.getPointResolution=function(b,c){return b/Ta(c[1]/6378137)};var Ll=6378137*Math.PI,Jl=[-Ll,-Ll,Ll,Ll],Kl=[-180,-85,180,85],Oe="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(" ").map(function(b){return new Il(b)});
+function Pe(b,c,d){var e=b.length;d=1<d?d:2;void 0===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 Qe(b,c,d){var e=b.length;d=1<d?d:2;void 0===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 Ml(b,c){Ce.call(this,{code:b,units:"degrees",extent:Nl,axisOrientation:c,global:!0,worldExtent:Nl})}y(Ml,Ce);Ml.prototype.getPointResolution=function(b){return b};
+var Nl=[-180,-90,180,90],Re=[new Ml("CRS:84"),new Ml("EPSG:4326","neu"),new Ml("urn:ogc:def:crs:EPSG::4326","neu"),new Ml("urn:ogc:def:crs:EPSG:6.6:4326","neu"),new Ml("urn:ogc:def:crs:OGC:1.3:CRS84"),new Ml("urn:ogc:def:crs:OGC:2:84"),new Ml("http://www.opengis.net/gml/srs/epsg.xml#4326","neu"),new Ml("urn:x-ogc:def:crs:EPSG:4326","neu")];function Ol(){Fe(Oe);Fe(Re);Ne()};function Pl(b){ck.call(this,b?b:{})}y(Pl,ck);function G(b){b=b?b:{};var c=Tb(b);delete c.preload;delete c.useInterimTilesOnError;ck.call(this,c);this.c(void 0!==b.preload?b.preload:0);this.g(void 0!==b.useInterimTilesOnError?b.useInterimTilesOnError:!0)}y(G,ck);G.prototype.a=function(){return this.get("preload")};G.prototype.c=function(b){this.set("preload",b)};G.prototype.b=function(){return this.get("useInterimTilesOnError")};G.prototype.g=function(b){this.set("useInterimTilesOnError",b)};var Ql=[0,0,0,1],Rl=[],Sl=[0,0,0,1];function Tl(b){b=b||{};this.a=void 0!==b.color?b.color:null;this.f=void 0}Tl.prototype.b=function(){return this.a};Tl.prototype.c=function(b){this.a=b;this.f=void 0};Tl.prototype.Jb=function(){void 0===this.f&&(this.f="f"+(this.a?vg(this.a):"-"));return this.f};function Ul(){this.f=-1};function Vl(){this.f=-1;this.f=64;this.a=Array(4);this.g=Array(this.f);this.c=this.b=0;this.reset()}y(Vl,Ul);Vl.prototype.reset=function(){this.a[0]=1732584193;this.a[1]=4023233417;this.a[2]=2562383102;this.a[3]=271733878;this.c=this.b=0};
+function Wl(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|
+h>>>12);h=c+(f^g&(d^f))+e[9]+568446438&4294967295;c=d+(h<<5&4294967295|h>>>27);h=g+(d^f&(c^d))+e[14]+3275163606&4294967295;g=c+(h<<9&4294967295|h>>>23);h=f+(c^d&(g^c))+e[3]+4107603335&4294967295;f=g+(h<<14&4294967295|h>>>18);h=d+(g^c&(f^g))+e[8]+1163531501&4294967295;d=f+(h<<20&4294967295|h>>>12);h=c+(f^g&(d^f))+e[13]+2850285829&4294967295;c=d+(h<<5&4294967295|h>>>27);h=g+(d^f&(c^d))+e[2]+4243563512&4294967295;g=c+(h<<9&4294967295|h>>>23);h=f+(c^d&(g^c))+e[7]+1735328473&4294967295;f=g+(h<<14&4294967295|
+h>>>18);h=d+(g^c&(f^g))+e[12]+2368359562&4294967295;d=f+(h<<20&4294967295|h>>>12);h=c+(d^f^g)+e[5]+4294588738&4294967295;c=d+(h<<4&4294967295|h>>>28);h=g+(c^d^f)+e[8]+2272392833&4294967295;g=c+(h<<11&4294967295|h>>>21);h=f+(g^c^d)+e[11]+1839030562&4294967295;f=g+(h<<16&4294967295|h>>>16);h=d+(f^g^c)+e[14]+4259657740&4294967295;d=f+(h<<23&4294967295|h>>>9);h=c+(d^f^g)+e[1]+2763975236&4294967295;c=d+(h<<4&4294967295|h>>>28);h=g+(c^d^f)+e[4]+1272893353&4294967295;g=c+(h<<11&4294967295|h>>>21);h=f+(g^
+c^d)+e[7]+4139469664&4294967295;f=g+(h<<16&4294967295|h>>>16);h=d+(f^g^c)+e[10]+3200236656&4294967295;d=f+(h<<23&4294967295|h>>>9);h=c+(d^f^g)+e[13]+681279174&4294967295;c=d+(h<<4&4294967295|h>>>28);h=g+(c^d^f)+e[0]+3936430074&4294967295;g=c+(h<<11&4294967295|h>>>21);h=f+(g^c^d)+e[3]+3572445317&4294967295;f=g+(h<<16&4294967295|h>>>16);h=d+(f^g^c)+e[6]+76029189&4294967295;d=f+(h<<23&4294967295|h>>>9);h=c+(d^f^g)+e[9]+3654602809&4294967295;c=d+(h<<4&4294967295|h>>>28);h=g+(c^d^f)+e[12]+3873151461&4294967295;
+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}
+function Xl(b,c){var d;ca(d)||(d=c.length);for(var e=d-b.f,f=b.g,g=b.b,h=0;h<d;){if(0==g)for(;h<=e;)Wl(b,c,h),h+=b.f;if(ia(c))for(;h<d;){if(f[g++]=c.charCodeAt(h++),g==b.f){Wl(b,f);g=0;break}}else for(;h<d;)if(f[g++]=c[h++],g==b.f){Wl(b,f);g=0;break}}b.b=g;b.c+=d};function Yl(b){b=b||{};this.a=void 0!==b.color?b.color:null;this.c=b.lineCap;this.b=void 0!==b.lineDash?b.lineDash:null;this.g=b.lineJoin;this.i=b.miterLimit;this.f=b.width;this.j=void 0}l=Yl.prototype;l.pn=function(){return this.a};l.Uj=function(){return this.c};l.qn=function(){return this.b};l.Vj=function(){return this.g};l.$j=function(){return this.i};l.rn=function(){return this.f};l.sn=function(b){this.a=b;this.j=void 0};l.Oo=function(b){this.c=b;this.j=void 0};
+l.tn=function(b){this.b=b;this.j=void 0};l.Po=function(b){this.g=b;this.j=void 0};l.Qo=function(b){this.i=b;this.j=void 0};l.Uo=function(b){this.f=b;this.j=void 0};
+l.Jb=function(){if(void 0===this.j){var b="s"+(this.a?vg(this.a):"-")+","+(void 0!==this.c?this.c.toString():"-")+","+(this.b?this.b.toString():"-")+","+(void 0!==this.g?this.g:"-")+","+(void 0!==this.i?this.i.toString():"-")+","+(void 0!==this.f?this.f.toString():"-"),c=new Vl;Xl(c,b);b=Array((56>c.b?c.f:2*c.f)-c.b);b[0]=128;for(var d=1;d<b.length-8;++d)b[d]=0;for(var e=8*c.c,d=b.length-8;d<b.length;++d)b[d]=e&255,e/=256;Xl(c,b);b=Array(16);for(d=e=0;4>d;++d)for(var f=0;32>f;f+=8)b[e++]=c.a[d]>>>
+f&255;if(8192>=b.length)c=String.fromCharCode.apply(null,b);else for(c="",d=0;d<b.length;d+=8192)e=mb(b,d,d+8192),c+=String.fromCharCode.apply(null,e);this.j=c}return this.j};function Zl(b){b=b||{};this.i=this.a=this.g=null;this.c=void 0!==b.fill?b.fill:null;this.f=void 0!==b.stroke?b.stroke:null;this.b=b.radius;this.v=[0,0];this.o=this.O=this.l=null;var c=b.atlasManager,d,e=null,f,g=0;this.f&&(f=vg(this.f.a),g=this.f.f,void 0===g&&(g=1),e=this.f.b,Wi||(e=null));var h=2*(this.b+g)+1;f={strokeStyle:f,Id:g,size:h,lineDash:e};void 0===c?(this.a=document.createElement("CANVAS"),this.a.height=h,this.a.width=h,d=h=this.a.width,c=this.a.getContext("2d"),this.vh(f,c,0,0),this.c?
+this.i=this.a:(c=this.i=document.createElement("CANVAS"),c.height=f.size,c.width=f.size,c=c.getContext("2d"),this.uh(f,c,0,0))):(h=Math.round(h),(e=!this.c)&&(d=ua(this.uh,this,f)),g=this.Jb(),f=c.add(g,h,h,ua(this.vh,this,f),d),this.a=f.image,this.v=[f.offsetX,f.offsetY],d=f.image.width,this.i=e?f.Og:this.a);this.l=[h/2,h/2];this.O=[h,h];this.o=[d,d];sk.call(this,{opacity:1,rotateWithView:!1,rotation:0,scale:1,snapToPixel:void 0!==b.snapToPixel?b.snapToPixel:!0})}y(Zl,sk);l=Zl.prototype;l.Xb=function(){return this.l};
+l.en=function(){return this.c};l.Ae=function(){return this.i};l.gc=function(){return this.a};l.Cd=function(){return 2};l.rd=function(){return this.o};l.Da=function(){return this.v};l.fn=function(){return this.b};l.Cb=function(){return this.O};l.gn=function(){return this.f};l.tf=za;l.load=za;l.Xf=za;
+l.vh=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);this.c&&(c.fillStyle=vg(this.c.a),c.fill());this.f&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Id,b.lineDash&&c.setLineDash(b.lineDash),c.stroke());c.closePath()};
+l.uh=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=vg(Ql);c.fill();this.f&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Id,b.lineDash&&c.setLineDash(b.lineDash),c.stroke());c.closePath()};l.Jb=function(){var b=this.f?this.f.Jb():"-",c=this.c?this.c.Jb():"-";this.g&&b==this.g[1]&&c==this.g[2]&&this.b==this.g[3]||(this.g=["c"+b+c+(void 0!==this.b?this.b.toString():"-"),b,c,this.b]);return this.g[0]};function $l(b){b=b||{};this.i=null;this.c=am;void 0!==b.geometry&&this.yh(b.geometry);this.g=void 0!==b.fill?b.fill:null;this.f=void 0!==b.image?b.image:null;this.b=void 0!==b.stroke?b.stroke:null;this.j=void 0!==b.text?b.text:null;this.a=b.zIndex}l=$l.prototype;l.X=function(){return this.i};l.Pj=function(){return this.c};l.vn=function(){return this.g};l.wn=function(){return this.f};l.xn=function(){return this.b};l.Ca=function(){return this.j};l.yn=function(){return this.a};
+l.yh=function(b){ka(b)?this.c=b:ia(b)?this.c=function(c){return c.get(b)}:b?void 0!==b&&(this.c=function(){return b}):this.c=am;this.i=b};l.zn=function(b){this.a=b};function bm(b){if(!ka(b)){var c;c=ga(b)?b:[b];b=function(){return c}}return b}var cm=null;function dm(){if(!cm){var b=new Tl({color:"rgba(255,255,255,0.4)"}),c=new Yl({color:"#3399CC",width:1.25});cm=[new $l({image:new Zl({fill:b,stroke:c,radius:5}),fill:b,stroke:c})]}return cm}
+function em(){var b={},c=[255,255,255,1],d=[0,153,255,1];b.Polygon=[new $l({fill:new Tl({color:[255,255,255,.5]})})];b.MultiPolygon=b.Polygon;b.LineString=[new $l({stroke:new Yl({color:c,width:5})}),new $l({stroke:new Yl({color:d,width:3})})];b.MultiLineString=b.LineString;b.Circle=b.Polygon.concat(b.LineString);b.Point=[new $l({image:new Zl({radius:6,fill:new Tl({color:d}),stroke:new Yl({color:c,width:1.5})}),zIndex:Infinity})];b.MultiPoint=b.Point;b.GeometryCollection=b.Polygon.concat(b.LineString,
+b.Point);return b}function am(b){return b.X()};function H(b){b=b?b:{};var c=Tb(b);delete c.style;delete c.renderBuffer;delete c.updateWhileAnimating;delete c.updateWhileInteracting;ck.call(this,c);this.a=void 0!==b.renderBuffer?b.renderBuffer:100;this.v=null;this.b=void 0;this.c(b.style);this.j=void 0!==b.updateWhileAnimating?b.updateWhileAnimating:!1;this.l=void 0!==b.updateWhileInteracting?b.updateWhileInteracting:!1}y(H,ck);function fm(b){return b.get("renderOrder")}H.prototype.C=function(){return this.v};H.prototype.D=function(){return this.b};
+H.prototype.c=function(b){this.v=void 0!==b?b:dm;this.b=null===b?void 0:bm(this.v);this.u()};function I(b){b=b?b:{};var c=Tb(b);delete c.preload;delete c.useInterimTilesOnError;H.call(this,c);this.T(b.preload?b.preload:0);this.V(b.useInterimTilesOnError?b.useInterimTilesOnError:!0)}y(I,H);I.prototype.g=function(){return this.get("preload")};I.prototype.U=function(){return this.get("useInterimTilesOnError")};I.prototype.T=function(b){this.set("preload",b)};I.prototype.V=function(b){this.set("useInterimTilesOnError",b)};function gm(b,c,d,e,f){this.v={};this.b=b;this.D=c;this.g=d;this.na=e;this.Yc=f;this.i=this.a=this.f=this.gb=this.oa=this.ga=null;this.wa=this.pa=this.B=this.T=this.U=this.va=0;this.hb=!1;this.j=this.Db=0;this.Eb=!1;this.V=0;this.c="";this.o=this.O=this.Od=this.nc=0;this.fa=this.G=this.l=null;this.C=[];this.Cc=Bd()}
+function hm(b,c,d){if(b.i){c=af(c,0,d,2,b.na,b.C);d=b.b;var e=b.Cc,f=d.globalAlpha;1!=b.B&&(d.globalAlpha=f*b.B);var g=b.Db;b.hb&&(g+=b.Yc);var h,k;h=0;for(k=c.length;h<k;h+=2){var m=c[h]-b.va,n=c[h+1]-b.U;b.Eb&&(m=m+.5|0,n=n+.5|0);if(0!==g||1!=b.j){var p=m+b.va,q=n+b.U;gk(e,p,q,b.j,b.j,g,-p,-q);d.setTransform(e[0],e[1],e[4],e[5],e[12],e[13])}d.drawImage(b.i,b.pa,b.wa,b.V,b.T,m,n,b.V,b.T)}0===g&&1==b.j||d.setTransform(1,0,0,1,0,0);1!=b.B&&(d.globalAlpha=f)}}
+function im(b,c,d,e){var f=0;if(b.fa&&""!==b.c){b.l&&jm(b,b.l);b.G&&km(b,b.G);var g=b.fa,h=b.b,k=b.gb;k?(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)):(h.font=g.font,h.textAlign=g.textAlign,h.textBaseline=g.textBaseline,b.gb={font:g.font,textAlign:g.textAlign,textBaseline:g.textBaseline});c=af(c,f,d,e,b.na,b.C);for(g=b.b;f<d;f+=e){h=c[f]+b.nc;k=c[f+1]+b.Od;if(0!==
+b.O||1!=b.o){var m=gk(b.Cc,h,k,b.o,b.o,b.O,-h,-k);g.setTransform(m[0],m[1],m[4],m[5],m[12],m[13])}b.G&&g.strokeText(b.c,h,k);b.l&&g.fillText(b.c,h,k)}0===b.O&&1==b.o||g.setTransform(1,0,0,1,0,0)}}function lm(b,c,d,e,f,g){var h=b.b;b=af(c,d,e,f,b.na,b.C);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 mm(b,c,d,e,f){var g=b.b,h,k;h=0;for(k=e.length;h<k;++h)d=lm(b,c,d,e[h],f,!0),g.closePath();return d}l=gm.prototype;
+l.md=function(b,c){var d=b.toString(),e=this.v[d];void 0!==e?e.push(c):this.v[d]=[c]};l.Gc=function(b){if(oe(this.g,b.J())){if(this.f||this.a){this.f&&jm(this,this.f);this.a&&km(this,this.a);var c;c=this.na;var d=this.C,e=b.ia();c=e?af(e,0,e.length,b.ra(),c,d):null;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);this.f&&e.fill();this.a&&e.stroke()}""!==this.c&&im(this,b.wd(),2,2)}};
+l.jf=function(b,c){var d=(0,c.c)(b);if(d&&oe(this.g,d.J())){var e=c.a;void 0===e&&(e=0);this.md(e,function(b){b.bb(c.g,c.b);b.ub(c.f);b.cb(c.Ca());nm[d.W()].call(b,d,null)})}};l.Yd=function(b,c){var d=b.c,e,f;e=0;for(f=d.length;e<f;++e){var g=d[e];nm[g.W()].call(this,g,c)}};l.Hb=function(b){var c=b.ia();b=b.ra();this.i&&hm(this,c,c.length);""!==this.c&&im(this,c,c.length,b)};l.Gb=function(b){var c=b.ia();b=b.ra();this.i&&hm(this,c,c.length);""!==this.c&&im(this,c,c.length,b)};
+l.Wb=function(b){if(oe(this.g,b.J())){if(this.a){km(this,this.a);var c=this.b,d=b.ia();c.beginPath();lm(this,d,0,d.length,b.ra(),!1);c.stroke()}""!==this.c&&(b=om(b),im(this,b,2,2))}};l.Hc=function(b){var c=b.J();if(oe(this.g,c)){if(this.a){km(this,this.a);var c=this.b,d=b.ia(),e=0,f=b.zb(),g=b.ra();c.beginPath();var h,k;h=0;for(k=f.length;h<k;++h)e=lm(this,d,e,f[h],g,!1);c.stroke()}""!==this.c&&(b=pm(b),im(this,b,b.length,2))}};
+l.Jc=function(b){if(oe(this.g,b.J())){if(this.a||this.f){this.f&&jm(this,this.f);this.a&&km(this,this.a);var c=this.b;c.beginPath();mm(this,b.bc(),0,b.zb(),b.ra());this.f&&c.fill();this.a&&c.stroke()}""!==this.c&&(b=If(b),im(this,b,2,2))}};
+l.Ic=function(b){if(oe(this.g,b.J())){if(this.a||this.f){this.f&&jm(this,this.f);this.a&&km(this,this.a);var c=this.b,d=rm(b),e=0,f=b.g,g=b.ra(),h,k;h=0;for(k=f.length;h<k;++h){var m=f[h];c.beginPath();e=mm(this,d,e,m,g);this.f&&c.fill();this.a&&c.stroke()}}""!==this.c&&(b=sm(b),im(this,b,b.length,2))}};function tm(b){var c=Object.keys(b.v).map(Number);c.sort(ub);var d,e,f,g,h;d=0;for(e=c.length;d<e;++d)for(f=b.v[c[d].toString()],g=0,h=f.length;g<h;++g)f[g](b)}
+function jm(b,c){var d=b.b,e=b.ga;e?e.fillStyle!=c.fillStyle&&(e.fillStyle=d.fillStyle=c.fillStyle):(d.fillStyle=c.fillStyle,b.ga={fillStyle:c.fillStyle})}
+function km(b,c){var d=b.b,e=b.oa;e?(e.lineCap!=c.lineCap&&(e.lineCap=d.lineCap=c.lineCap),Wi&&!rb(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)):(d.lineCap=c.lineCap,Wi&&d.setLineDash(c.lineDash),d.lineJoin=c.lineJoin,d.lineWidth=
+c.lineWidth,d.miterLimit=c.miterLimit,d.strokeStyle=c.strokeStyle,b.oa={lineCap:c.lineCap,lineDash:c.lineDash,lineJoin:c.lineJoin,lineWidth:c.lineWidth,miterLimit:c.miterLimit,strokeStyle:c.strokeStyle})}
+l.bb=function(b,c){if(b){var d=b.a;this.f={fillStyle:vg(d?d:Ql)}}else this.f=null;if(c){var d=c.a,e=c.c,f=c.b,g=c.g,h=c.f,k=c.i;this.a={lineCap:void 0!==e?e:"round",lineDash:f?f:Rl,lineJoin:void 0!==g?g:"round",lineWidth:this.D*(void 0!==h?h:1),miterLimit:void 0!==k?k:10,strokeStyle:vg(d?d:Sl)}}else this.a=null};
+l.ub=function(b){if(b){var c=b.Xb(),d=b.gc(1),e=b.Da(),f=b.Cb();this.va=c[0];this.U=c[1];this.T=f[1];this.i=d;this.B=b.B;this.pa=e[0];this.wa=e[1];this.hb=b.C;this.Db=b.G;this.j=b.j;this.Eb=b.D;this.V=f[0]}else this.i=null};
+l.cb=function(b){if(b){var c=b.a;c?(c=c.a,this.l={fillStyle:vg(c?c:Ql)}):this.l=null;var d=b.j;if(d){var c=d.a,e=d.c,f=d.b,g=d.g,h=d.f,d=d.i;this.G={lineCap:void 0!==e?e:"round",lineDash:f?f:Rl,lineJoin:void 0!==g?g:"round",lineWidth:void 0!==h?h:1,miterLimit:void 0!==d?d:10,strokeStyle:vg(c?c:Sl)}}else this.G=null;var c=b.b,e=b.c,f=b.g,g=b.i,h=b.f,d=b.Ca(),k=b.l;b=b.o;this.fa={font:void 0!==c?c:"10px sans-serif",textAlign:void 0!==k?k:"center",textBaseline:void 0!==b?b:"middle"};this.c=void 0!==
+d?d:"";this.nc=void 0!==e?this.D*e:0;this.Od=void 0!==f?this.D*f:0;this.O=void 0!==g?g:0;this.o=this.D*(void 0!==h?h:1)}else this.c=""};var nm={Point:gm.prototype.Hb,LineString:gm.prototype.Wb,Polygon:gm.prototype.Jc,MultiPoint:gm.prototype.Gb,MultiLineString:gm.prototype.Hc,MultiPolygon:gm.prototype.Ic,GeometryCollection:gm.prototype.Yd,Circle:gm.prototype.Gc};function um(b){jk.call(this,b);this.O=Bd()}y(um,jk);
+um.prototype.G=function(b,c,d){vm(this,"precompose",d,b,void 0);var e=this.zd();if(e){var f=c.extent,g=void 0!==f;if(g){var h=b.pixelRatio,k=ge(f),m=fe(f),n=ee(f),f=de(f);ik(b.coordinateToPixelMatrix,k,k);ik(b.coordinateToPixelMatrix,m,m);ik(b.coordinateToPixelMatrix,n,n);ik(b.coordinateToPixelMatrix,f,f);d.save();d.beginPath();d.moveTo(k[0]*h,k[1]*h);d.lineTo(m[0]*h,m[1]*h);d.lineTo(n[0]*h,n[1]*h);d.lineTo(f[0]*h,f[1]*h);d.clip()}h=this.nf();k=d.globalAlpha;d.globalAlpha=c.opacity;0===b.viewState.rotation?
+d.drawImage(e,0,0,+e.width,+e.height,Math.round(h[12]),Math.round(h[13]),Math.round(e.width*h[0]),Math.round(e.height*h[5])):(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()}vm(this,"postcompose",d,b,void 0)};function vm(b,c,d,e,f){var g=b.a;cd(g,c)&&(b=void 0!==f?f:wm(b,e,0),b=new gm(d,e.pixelRatio,e.extent,b,e.viewState.rotation),g.s(new bk(c,g,b,e,d,null)),tm(b))}
+function wm(b,c,d){var e=c.viewState,f=c.pixelRatio;return gk(b.O,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 xm(b,c){var d=[0,0];ik(c,b,d);return d}
+var ym=function(){var b=null,c=null;return function(d){if(!b){b=Ni(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=rb(c.data,d.data));return f}}();var zm=["Polygon","LineString","Image","Text"];function Am(b,c,d){this.gb=b;this.V=c;this.c=null;this.g=0;this.resolution=d;this.U=this.va=null;this.f=[];this.coordinates=[];this.ga=Bd();this.a=[];this.fa=[];this.oa=Bd()}y(Am,ak);
+function Bm(b,c,d,e,f,g){var h=b.coordinates.length,k=b.lf(),m=[c[d],c[d+1]],n=[NaN,NaN],p=!0,q,r,t;for(q=d+f;q<e;q+=f)n[0]=c[q],n[1]=c[q+1],t=Wd(k,n),t!==r?(p&&(b.coordinates[h++]=m[0],b.coordinates[h++]=m[1]),b.coordinates[h++]=n[0],b.coordinates[h++]=n[1],p=!1):1===t?(b.coordinates[h++]=n[0],b.coordinates[h++]=n[1],p=!1):p=!0,m[0]=n[0],m[1]=n[1],r=t;q===d+f&&(b.coordinates[h++]=m[0],b.coordinates[h++]=m[1]);g&&(b.coordinates[h++]=c[d],b.coordinates[h++]=c[d+1]);return h}
+function Cm(b,c){b.va=[0,c,0];b.f.push(b.va);b.U=[0,c,0];b.a.push(b.U)}
+function Dm(b,c,d,e,f,g,h,k,m){var n;hk(e,b.ga)?n=b.fa:(n=af(b.coordinates,0,b.coordinates.length,2,e,b.fa),Ed(b.ga,e));e=!Pb(g);var p=0,q=h.length,r=0,t;b=b.oa;for(var x,z,A,B;p<q;){var v=h[p],L,M,J,C;switch(v[0]){case 0:r=v[1];e&&g[w(r).toString()]||!r.X()?p=v[2]:void 0===m||oe(m,r.X().J())?++p:p=v[2];break;case 1:c.beginPath();++p;break;case 2:r=v[1];t=n[r];v=n[r+1];A=n[r+2]-t;r=n[r+3]-v;c.arc(t,v,Math.sqrt(A*A+r*r),0,2*Math.PI,!0);++p;break;case 3:c.closePath();++p;break;case 4:r=v[1];t=v[2];
+L=v[3];J=v[4]*d;var sa=v[5]*d,la=v[6];M=v[7];var K=v[8],ma=v[9];A=v[11];B=v[12];var Ua=v[13],Nb=v[14];for(v[10]&&(A+=f);r<t;r+=2){v=n[r]-J;C=n[r+1]-sa;Ua&&(v=v+.5|0,C=C+.5|0);if(1!=B||0!==A){var na=v+J,Fa=C+sa;gk(b,na,Fa,B,B,A,-na,-Fa);c.setTransform(b[0],b[1],b[4],b[5],b[12],b[13])}na=c.globalAlpha;1!=M&&(c.globalAlpha=na*M);c.drawImage(L,K,ma,Nb,la,v,C,Nb*d,la*d);1!=M&&(c.globalAlpha=na);1==B&&0===A||c.setTransform(1,0,0,1,0,0)}++p;break;case 5:r=v[1];t=v[2];J=v[3];sa=v[4]*d;la=v[5]*d;A=v[6];B=
+v[7]*d;L=v[8];for(M=v[9];r<t;r+=2){v=n[r]+sa;C=n[r+1]+la;if(1!=B||0!==A)gk(b,v,C,B,B,A,-v,-C),c.setTransform(b[0],b[1],b[4],b[5],b[12],b[13]);K=J.split("\n");ma=K.length;1<ma?(Ua=Math.round(1.5*c.measureText("M").width),C-=(ma-1)/2*Ua):Ua=0;for(Nb=0;Nb<ma;Nb++)na=K[Nb],M&&c.strokeText(na,v,C),L&&c.fillText(na,v,C),C+=Ua;1==B&&0===A||c.setTransform(1,0,0,1,0,0)}++p;break;case 6:if(void 0!==k&&(r=v[1],r=k(r)))return r;++p;break;case 7:c.fill();++p;break;case 8:r=v[1];t=v[2];v=n[r];C=n[r+1];A=v+.5|0;
+B=C+.5|0;if(A!==x||B!==z)c.moveTo(v,C),x=A,z=B;for(r+=2;r<t;r+=2)if(v=n[r],C=n[r+1],A=v+.5|0,B=C+.5|0,A!==x||B!==z)c.lineTo(v,C),x=A,z=B;++p;break;case 9:c.fillStyle=v[1];++p;break;case 10:x=void 0!==v[7]?v[7]:!0;z=v[2];c.strokeStyle=v[1];c.lineWidth=x?z*d:z;c.lineCap=v[3];c.lineJoin=v[4];c.miterLimit=v[5];Wi&&c.setLineDash(v[6]);z=x=NaN;++p;break;case 11:c.font=v[1];c.textAlign=v[2];c.textBaseline=v[3];++p;break;case 12:c.stroke();++p;break;default:++p}}}
+function Em(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 Fm(b,c){b.va[2]=b.f.length;b.va=null;b.U[2]=b.a.length;b.U=null;var d=[6,c];b.f.push(d);b.a.push(d)}Am.prototype.we=za;Am.prototype.lf=function(){return this.V};
+function Gm(b,c,d){Am.call(this,b,c,d);this.l=this.T=null;this.na=this.O=this.D=this.C=this.v=this.B=this.G=this.o=this.j=this.i=this.b=void 0}y(Gm,Am);Gm.prototype.Hb=function(b,c){if(this.l){Cm(this,c);var d=b.ia(),e=this.coordinates.length,d=Bm(this,d,0,d.length,b.ra(),!1);this.f.push([4,e,d,this.l,this.b,this.i,this.j,this.o,this.G,this.B,this.v,this.C,this.D,this.O,this.na]);this.a.push([4,e,d,this.T,this.b,this.i,this.j,this.o,this.G,this.B,this.v,this.C,this.D,this.O,this.na]);Fm(this,c)}};
+Gm.prototype.Gb=function(b,c){if(this.l){Cm(this,c);var d=b.ia(),e=this.coordinates.length,d=Bm(this,d,0,d.length,b.ra(),!1);this.f.push([4,e,d,this.l,this.b,this.i,this.j,this.o,this.G,this.B,this.v,this.C,this.D,this.O,this.na]);this.a.push([4,e,d,this.T,this.b,this.i,this.j,this.o,this.G,this.B,this.v,this.C,this.D,this.O,this.na]);Fm(this,c)}};Gm.prototype.we=function(){Em(this);this.i=this.b=void 0;this.l=this.T=null;this.na=this.O=this.C=this.v=this.B=this.G=this.o=this.D=this.j=void 0};
+Gm.prototype.ub=function(b){var c=b.Xb(),d=b.Cb(),e=b.Ae(1),f=b.gc(1),g=b.Da();this.b=c[0];this.i=c[1];this.T=e;this.l=f;this.j=d[1];this.o=b.B;this.G=g[0];this.B=g[1];this.v=b.C;this.C=b.G;this.D=b.j;this.O=b.D;this.na=d[0]};function Hm(b,c,d){Am.call(this,b,c,d);this.b={jd:void 0,dd:void 0,ed:null,fd:void 0,gd:void 0,hd:void 0,sf:0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}y(Hm,Am);
+function Im(b,c,d,e,f){var g=b.coordinates.length;c=Bm(b,c,d,e,f,!1);g=[8,g,c];b.f.push(g);b.a.push(g);return e}l=Hm.prototype;l.lf=function(){this.c||(this.c=Rd(this.V),0<this.g&&Qd(this.c,this.resolution*(this.g+1)/2,this.c));return this.c};
+function Jm(b){var c=b.b,d=c.strokeStyle,e=c.lineCap,f=c.lineDash,g=c.lineJoin,h=c.lineWidth,k=c.miterLimit;c.jd==d&&c.dd==e&&rb(c.ed,f)&&c.fd==g&&c.gd==h&&c.hd==k||(c.sf!=b.coordinates.length&&(b.f.push([12]),c.sf=b.coordinates.length),b.f.push([10,d,h,e,g,k,f],[1]),c.jd=d,c.dd=e,c.ed=f,c.fd=g,c.gd=h,c.hd=k)}
+l.Wb=function(b,c){var d=this.b,e=d.lineWidth;void 0!==d.strokeStyle&&void 0!==e&&(Jm(this),Cm(this,c),this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash],[1]),d=b.ia(),Im(this,d,0,d.length,b.ra()),this.a.push([12]),Fm(this,c))};
+l.Hc=function(b,c){var d=this.b,e=d.lineWidth;if(void 0!==d.strokeStyle&&void 0!==e){Jm(this);Cm(this,c);this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash],[1]);var d=b.zb(),e=b.ia(),f=b.ra(),g=0,h,k;h=0;for(k=d.length;h<k;++h)g=Im(this,e,g,d[h],f);this.a.push([12]);Fm(this,c)}};l.we=function(){this.b.sf!=this.coordinates.length&&this.f.push([12]);Em(this);this.b=null};
+l.bb=function(b,c){var d=c.a;this.b.strokeStyle=vg(d?d:Sl);d=c.c;this.b.lineCap=void 0!==d?d:"round";d=c.b;this.b.lineDash=d?d:Rl;d=c.g;this.b.lineJoin=void 0!==d?d:"round";d=c.f;this.b.lineWidth=void 0!==d?d:1;d=c.i;this.b.miterLimit=void 0!==d?d:10;this.b.lineWidth>this.g&&(this.g=this.b.lineWidth,this.c=null)};
+function Km(b,c,d){Am.call(this,b,c,d);this.b={qg:void 0,jd:void 0,dd:void 0,ed:null,fd:void 0,gd:void 0,hd:void 0,fillStyle:void 0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}y(Km,Am);
+function Lm(b,c,d,e,f){var g=b.b,h=[1];b.f.push(h);b.a.push(h);var k,h=0;for(k=e.length;h<k;++h){var m=e[h],n=b.coordinates.length;d=Bm(b,c,d,m,f,!0);d=[8,n,d];n=[3];b.f.push(d,n);b.a.push(d,n);d=m}c=[7];b.a.push(c);void 0!==g.fillStyle&&b.f.push(c);void 0!==g.strokeStyle&&(g=[12],b.f.push(g),b.a.push(g));return d}l=Km.prototype;
+l.Gc=function(b,c){var d=this.b,e=d.strokeStyle;if(void 0!==d.fillStyle||void 0!==e){Mm(this);Cm(this,c);this.a.push([9,vg(Ql)]);void 0!==d.strokeStyle&&this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]);var f=b.ia(),e=this.coordinates.length;Bm(this,f,0,f.length,b.ra(),!1);f=[1];e=[2,e];this.f.push(f,e);this.a.push(f,e);e=[7];this.a.push(e);void 0!==d.fillStyle&&this.f.push(e);void 0!==d.strokeStyle&&(d=[12],this.f.push(d),this.a.push(d));Fm(this,c)}};
+l.Jc=function(b,c){var d=this.b,e=d.strokeStyle;if(void 0!==d.fillStyle||void 0!==e)Mm(this),Cm(this,c),this.a.push([9,vg(Ql)]),void 0!==d.strokeStyle&&this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]),d=b.zb(),e=b.bc(),Lm(this,e,0,d,b.ra()),Fm(this,c)};
+l.Ic=function(b,c){var d=this.b,e=d.strokeStyle;if(void 0!==d.fillStyle||void 0!==e){Mm(this);Cm(this,c);this.a.push([9,vg(Ql)]);void 0!==d.strokeStyle&&this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]);var d=b.g,e=rm(b),f=b.ra(),g=0,h,k;h=0;for(k=d.length;h<k;++h)g=Lm(this,e,g,d[h],f);Fm(this,c)}};l.we=function(){Em(this);this.b=null;var b=this.gb;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.lf=function(){this.c||(this.c=Rd(this.V),0<this.g&&Qd(this.c,this.resolution*(this.g+1)/2,this.c));return this.c};
+l.bb=function(b,c){var d=this.b;if(b){var e=b.a;d.fillStyle=vg(e?e:Ql)}else d.fillStyle=void 0;c?(e=c.a,d.strokeStyle=vg(e?e:Sl),e=c.c,d.lineCap=void 0!==e?e:"round",e=c.b,d.lineDash=e?e.slice():Rl,e=c.g,d.lineJoin=void 0!==e?e:"round",e=c.f,d.lineWidth=void 0!==e?e:1,e=c.i,d.miterLimit=void 0!==e?e:10,d.lineWidth>this.g&&(this.g=d.lineWidth,this.c=null)):(d.strokeStyle=void 0,d.lineCap=void 0,d.lineDash=null,d.lineJoin=void 0,d.lineWidth=void 0,d.miterLimit=void 0)};
+function Mm(b){var c=b.b,d=c.fillStyle,e=c.strokeStyle,f=c.lineCap,g=c.lineDash,h=c.lineJoin,k=c.lineWidth,m=c.miterLimit;void 0!==d&&c.qg!=d&&(b.f.push([9,d]),c.qg=c.fillStyle);void 0===e||c.jd==e&&c.dd==f&&c.ed==g&&c.fd==h&&c.gd==k&&c.hd==m||(b.f.push([10,e,k,f,h,m,g]),c.jd=e,c.dd=f,c.ed=g,c.fd=h,c.gd=k,c.hd=m)}function Nm(b,c,d){Am.call(this,b,c,d);this.O=this.D=this.C=null;this.l="";this.v=this.B=this.G=this.o=0;this.j=this.i=this.b=null}y(Nm,Am);
+Nm.prototype.Ib=function(b,c,d,e,f,g){if(""!==this.l&&this.j&&(this.b||this.i)){if(this.b){f=this.b;var h=this.C;if(!h||h.fillStyle!=f.fillStyle){var k=[9,f.fillStyle];this.f.push(k);this.a.push(k);h?h.fillStyle=f.fillStyle:this.C={fillStyle:f.fillStyle}}}this.i&&(f=this.i,h=this.D,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.f.push(k),this.a.push(k),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):this.D={lineCap:f.lineCap,lineDash:f.lineDash,lineJoin:f.lineJoin,lineWidth:f.lineWidth,miterLimit:f.miterLimit,strokeStyle:f.strokeStyle}));f=this.j;h=this.O;h&&h.font==f.font&&h.textAlign==f.textAlign&&h.textBaseline==f.textBaseline||(k=[11,f.font,f.textAlign,f.textBaseline],this.f.push(k),this.a.push(k),h?
+(h.font=f.font,h.textAlign=f.textAlign,h.textBaseline=f.textBaseline):this.O={font:f.font,textAlign:f.textAlign,textBaseline:f.textBaseline});Cm(this,g);f=this.coordinates.length;b=Bm(this,b,c,d,e,!1);b=[5,f,b,this.l,this.o,this.G,this.B,this.v,!!this.b,!!this.i];this.f.push(b);this.a.push(b);Fm(this,g)}};
+Nm.prototype.cb=function(b){if(b){var c=b.a;c?(c=c.a,c=vg(c?c:Ql),this.b?this.b.fillStyle=c:this.b={fillStyle:c}):this.b=null;var d=b.j;if(d){var c=d.a,e=d.c,f=d.b,g=d.g,h=d.f,d=d.i,e=void 0!==e?e:"round",f=f?f.slice():Rl,g=void 0!==g?g:"round",h=void 0!==h?h:1,d=void 0!==d?d:10,c=vg(c?c:Sl);if(this.i){var k=this.i;k.lineCap=e;k.lineDash=f;k.lineJoin=g;k.lineWidth=h;k.miterLimit=d;k.strokeStyle=c}else this.i={lineCap:e,lineDash:f,lineJoin:g,lineWidth:h,miterLimit:d,strokeStyle:c}}else this.i=null;
+var m=b.b,c=b.c,e=b.g,f=b.i,h=b.f,d=b.Ca(),g=b.l,k=b.o;b=void 0!==m?m:"10px sans-serif";g=void 0!==g?g:"center";k=void 0!==k?k:"middle";this.j?(m=this.j,m.font=b,m.textAlign=g,m.textBaseline=k):this.j={font:b,textAlign:g,textBaseline:k};this.l=void 0!==d?d:"";this.o=void 0!==c?c:0;this.G=void 0!==e?e:0;this.B=void 0!==f?f:0;this.v=void 0!==h?h:1}else this.l=""};function Om(b,c,d,e){this.G=b;this.g=c;this.o=d;this.i=e;this.f={};this.j=Ni(1,1);this.l=Bd()}
+function Pm(b){for(var c in b.f){var d=b.f[c],e;for(e in d)d[e].we()}}Om.prototype.c=function(b,c,d,e,f){var g=this.l;gk(g,.5,.5,1/c,-1/c,-d,-b[0],-b[1]);var h=this.j;h.clearRect(0,0,1,1);var k;void 0!==this.i&&(k=Md(),Nd(k,b),Qd(k,c*this.i,k));return Qm(this,h,g,d,e,function(b){if(0<h.getImageData(0,0,1,1).data[3]){if(b=f(b))return b;h.clearRect(0,0,1,1)}},k)};
+Om.prototype.a=function(b,c){var d=void 0!==b?b.toString():"0",e=this.f[d];void 0===e&&(e={},this.f[d]=e);d=e[c];void 0===d&&(d=new Rm[c](this.G,this.g,this.o),e[c]=d);return d};Om.prototype.La=function(){return Pb(this.f)};
+Om.prototype.b=function(b,c,d,e,f){var g=Object.keys(this.f).map(Number);g.sort(ub);var h=this.g,k=h[0],m=h[1],n=h[2],h=h[3],k=[k,m,k,h,n,h,n,m];af(k,0,8,2,d,k);b.save();b.beginPath();b.moveTo(k[0],k[1]);b.lineTo(k[2],k[3]);b.lineTo(k[4],k[5]);b.lineTo(k[6],k[7]);b.closePath();b.clip();for(var p,q,k=0,m=g.length;k<m;++k)for(p=this.f[g[k].toString()],n=0,h=zm.length;n<h;++n)q=p[zm[n]],void 0!==q&&Dm(q,b,c,d,e,f,q.f,void 0);b.restore()};
+function Qm(b,c,d,e,f,g,h){var k=Object.keys(b.f).map(Number);k.sort(function(b,c){return c-b});var m,n,p,q,r;m=0;for(n=k.length;m<n;++m)for(q=b.f[k[m].toString()],p=zm.length-1;0<=p;--p)if(r=q[zm[p]],void 0!==r&&(r=Dm(r,c,1,d,e,f,r.a,g,h)))return r}var Rm={Image:Gm,LineString:Hm,Polygon:Km,Text:Nm};function Sm(b,c,d,e){this.b=b;this.a=c;this.g=d;this.c=e}l=Sm.prototype;l.get=function(b){return this.c[b]};l.zb=function(){return this.g};l.J=function(){this.f||(this.f="Point"===this.b?Xd(this.a):Yd(this.a,0,this.a.length,2));return this.f};l.ia=Sm.prototype.bc=function(){return this.a};l.X=function(){return this};l.Cm=function(){return this.c};l.td=Sm.prototype.X;l.ra=function(){return 2};l.ac=za;l.W=function(){return this.b};function Tm(b,c){return w(b)-w(c)}function Um(b,c){var d=.5*b/c;return d*d}function Vm(b,c,d,e,f,g){var h=!1,k,m;if(k=d.f)m=k.Cd(),2==m||3==m?k.Xf(f,g):(0==m&&k.load(),k.tf(f,g),h=!0);if(f=(0,d.c)(c))e=f.td(e),(0,Wm[e.W()])(b,e,d,c);return h}
+var Wm={Point:function(b,c,d,e){var f=d.f;if(f){if(2!=f.Cd())return;var g=b.a(d.a,"Image");g.ub(f);g.Hb(c,e)}if(f=d.Ca())b=b.a(d.a,"Text"),b.cb(f),b.Ib(c.ia(),0,2,2,c,e)},LineString:function(b,c,d,e){var f=d.b;if(f){var g=b.a(d.a,"LineString");g.bb(null,f);g.Wb(c,e)}if(f=d.Ca())b=b.a(d.a,"Text"),b.cb(f),b.Ib(om(c),0,2,2,c,e)},Polygon:function(b,c,d,e){var f=d.g,g=d.b;if(f||g){var h=b.a(d.a,"Polygon");h.bb(f,g);h.Jc(c,e)}if(f=d.Ca())b=b.a(d.a,"Text"),b.cb(f),b.Ib(If(c),0,2,2,c,e)},MultiPoint:function(b,
+c,d,e){var f=d.f;if(f){if(2!=f.Cd())return;var g=b.a(d.a,"Image");g.ub(f);g.Gb(c,e)}if(f=d.Ca())b=b.a(d.a,"Text"),b.cb(f),d=c.ia(),b.Ib(d,0,d.length,c.ra(),c,e)},MultiLineString:function(b,c,d,e){var f=d.b;if(f){var g=b.a(d.a,"LineString");g.bb(null,f);g.Hc(c,e)}if(f=d.Ca())b=b.a(d.a,"Text"),b.cb(f),d=pm(c),b.Ib(d,0,d.length,2,c,e)},MultiPolygon:function(b,c,d,e){var f=d.g,g=d.b;if(g||f){var h=b.a(d.a,"Polygon");h.bb(f,g);h.Ic(c,e)}if(f=d.Ca())b=b.a(d.a,"Text"),b.cb(f),d=sm(c),b.Ib(d,0,d.length,2,
+c,e)},GeometryCollection:function(b,c,d,e){c=c.c;var f,g;f=0;for(g=c.length;f<g;++f)(0,Wm[c[f].W()])(b,c[f],d,e)},Circle:function(b,c,d,e){var f=d.g,g=d.b;if(f||g){var h=b.a(d.a,"Polygon");h.bb(f,g);h.Gc(c,e)}if(f=d.Ca())b=b.a(d.a,"Text"),b.cb(f),b.Ib(c.wd(),0,2,2,c,e)}};function Xm(b,c,d,e,f,g){this.g=void 0!==g?g:null;ek.call(this,b,c,d,void 0!==g?0:2,e);this.c=f;this.f=null}y(Xm,ek);Xm.prototype.getError=function(){return this.f};Xm.prototype.j=function(b){b?(this.f=b,this.state=3):this.state=2;fk(this)};Xm.prototype.load=function(){0==this.state&&(this.state=1,fk(this),this.g(ua(this.j,this)))};Xm.prototype.a=function(){return this.c};var Ym=!((Hb("Chrome")||Hb("CriOS"))&&!Hb("Opera")&&!Hb("OPR")&&!Hb("Edge"))||Hb("iPhone")&&!Hb("iPod")&&!Hb("iPad")||Hb("iPad")||Hb("iPod");function Zm(b,c,d,e){var f=Ye(d,c,b);d=c.getPointResolution(e,d);c=c.Kc();void 0!==c&&(d*=c);c=b.Kc();void 0!==c&&(d/=c);b=b.getPointResolution(d,f)/d;isFinite(b)&&!isNaN(b)&&0<b&&(d/=b);return d}function $m(b,c,d,e){b=d-b;c=e-c;var f=Math.sqrt(b*b+c*c);return[Math.round(d+b/f),Math.round(e+c/f)]}
+function an(b,c,d,e,f,g,h,k,m,n){var p=Ni(Math.round(d*b),Math.round(d*c));if(0===m.length)return p.canvas;p.scale(d,d);var q=Md();m.forEach(function(b){be(q,b.extent)});var r=Ni(Math.round(d*je(q)/e),Math.round(d*ke(q)/e));r.scale(d/e,d/e);r.translate(-q[0],q[3]);m.forEach(function(b){r.drawImage(b.image,b.extent[0],-b.extent[3],je(b.extent),ke(b.extent))});var t=ge(h);k.c.forEach(function(b){var c=b.source,f=b.target,h=c[1][0],k=c[1][1],m=c[2][0],n=c[2][1];b=(f[0][0]-t[0])/g;var J=-(f[0][1]-t[1])/
+g,C=(f[1][0]-t[0])/g,sa=-(f[1][1]-t[1])/g,la=(f[2][0]-t[0])/g,K=-(f[2][1]-t[1])/g,f=c[0][0],c=c[0][1],h=h-f,k=k-c,m=m-f,n=n-c;a:{h=[[h,k,0,0,C-b],[m,n,0,0,la-b],[0,0,h,k,sa-J],[0,0,m,n,K-J]];k=h.length;for(m=0;m<k;m++){for(var n=m,ma=Math.abs(h[m][m]),Ua=m+1;Ua<k;Ua++){var Nb=Math.abs(h[Ua][m]);Nb>ma&&(ma=Nb,n=Ua)}if(0===ma){h=null;break a}ma=h[n];h[n]=h[m];h[m]=ma;for(n=m+1;n<k;n++)for(ma=-h[n][m]/h[m][m],Ua=m;Ua<k+1;Ua++)h[n][Ua]=m==Ua?0:h[n][Ua]+ma*h[m][Ua]}m=Array(k);for(n=k-1;0<=n;n--)for(m[n]=
+h[n][k]/h[n][n],ma=n-1;0<=ma;ma--)h[ma][k]-=h[ma][n]*m[n];h=m}h&&(p.save(),p.beginPath(),Ym?(m=(b+C+la)/3,n=(J+sa+K)/3,k=$m(m,n,b,J),C=$m(m,n,C,sa),la=$m(m,n,la,K),p.moveTo(k[0],k[1]),p.lineTo(C[0],C[1]),p.lineTo(la[0],la[1])):(p.moveTo(b,J),p.lineTo(C,sa),p.lineTo(la,K)),p.closePath(),p.clip(),p.transform(h[0],h[2],h[1],h[3],b,J),p.translate(q[0]-f,q[3]-c),p.scale(e/d,-e/d),p.drawImage(r.canvas,0,0),p.restore())});n&&(p.save(),p.strokeStyle="black",p.lineWidth=1,k.c.forEach(function(b){var c=b.target;
+b=(c[0][0]-t[0])/g;var d=-(c[0][1]-t[1])/g,e=(c[1][0]-t[0])/g,f=-(c[1][1]-t[1])/g,h=(c[2][0]-t[0])/g,c=-(c[2][1]-t[1])/g;p.beginPath();p.moveTo(b,d);p.lineTo(e,f);p.lineTo(h,c);p.closePath();p.stroke()}),p.restore());return p.canvas};function bn(b,c,d,e,f){this.b=b;this.g=c;var g={},h=We(this.g,this.b);this.f=function(b){var c=b[0]+"/"+b[1];g[c]||(g[c]=h(b));return g[c]};this.i=e;this.G=f*f;this.c=[];this.l=!1;this.o=this.b.b&&!!e&&!!this.b.J()&&je(e)==je(this.b.J());this.a=this.b.J()?je(this.b.J()):null;this.j=this.g.J()?je(this.g.J()):null;b=ge(d);c=fe(d);e=ee(d);d=de(d);f=this.f(b);var k=this.f(c),m=this.f(e),n=this.f(d);cn(this,b,c,e,d,f,k,m,n,10);if(this.l){var p=Infinity;this.c.forEach(function(b){p=Math.min(p,b.source[0][0],
+b.source[1][0],b.source[2][0])});this.c.forEach(function(b){if(Math.max(b.source[0][0],b.source[1][0],b.source[2][0])-p>this.a/2){var c=[[b.source[0][0],b.source[0][1]],[b.source[1][0],b.source[1][1]],[b.source[2][0],b.source[2][1]]];c[0][0]-p>this.a/2&&(c[0][0]-=this.a);c[1][0]-p>this.a/2&&(c[1][0]-=this.a);c[2][0]-p>this.a/2&&(c[2][0]-=this.a);Math.max(c[0][0],c[1][0],c[2][0])-Math.min(c[0][0],c[1][0],c[2][0])<this.a/2&&(b.source=c)}},this)}g={}}
+function cn(b,c,d,e,f,g,h,k,m,n){var p=Ld([g,h,k,m]),q=b.a?je(p)/b.a:null,r=b.b.b&&.5<q&&1>q,t=!1;if(0<n){if(b.g.c&&b.j)var x=Ld([c,d,e,f]),t=t|.25<je(x)/b.j;!r&&b.b.c&&q&&(t|=.25<q)}if(t||!b.i||oe(p,b.i)){if(!(t||isFinite(g[0])&&isFinite(g[1])&&isFinite(h[0])&&isFinite(h[1])&&isFinite(k[0])&&isFinite(k[1])&&isFinite(m[0])&&isFinite(m[1])))if(0<n)t=!0;else return;if(0<n&&(t||(q=b.f([(c[0]+e[0])/2,(c[1]+e[1])/2]),p=r?(nd(g[0],b.a)+nd(k[0],b.a))/2-nd(q[0],b.a):(g[0]+k[0])/2-q[0],q=(g[1]+k[1])/2-q[1],
+t=p*p+q*q>b.G),t)){Math.abs(c[0]-e[0])<=Math.abs(c[1]-e[1])?(r=[(d[0]+e[0])/2,(d[1]+e[1])/2],p=b.f(r),q=[(f[0]+c[0])/2,(f[1]+c[1])/2],t=b.f(q),cn(b,c,d,r,q,g,h,p,t,n-1),cn(b,q,r,e,f,t,p,k,m,n-1)):(r=[(c[0]+d[0])/2,(c[1]+d[1])/2],p=b.f(r),q=[(e[0]+f[0])/2,(e[1]+f[1])/2],t=b.f(q),cn(b,c,r,q,f,g,p,t,m,n-1),cn(b,r,d,e,q,p,h,k,t,n-1));return}if(r){if(!b.o)return;b.l=!0}b.c.push({source:[g,k,m],target:[c,e,f]});b.c.push({source:[g,h,k],target:[c,d,e]})}}
+function dn(b){var c=Md();b.c.forEach(function(b){b=b.source;Nd(c,b[0]);Nd(c,b[1]);Nd(c,b[2])});return c};function en(b,c,d,e,f,g){this.v=c;this.B=b.J();var h=c.J(),k=h?ne(d,h):d,h=Zm(b,c,le(k),e);this.o=new bn(b,c,k,this.B,.5*h);this.j=e;this.g=d;b=dn(this.o);this.G=(this.f=g(b,h,f))?this.f.b:1;this.c=this.l=null;f=2;g=[];this.f&&(f=0,g=this.f.i);ek.call(this,d,e,this.G,f,g)}y(en,ek);en.prototype.Y=function(){1==this.state&&(Wc(this.c),this.c=null);en.ca.Y.call(this)};en.prototype.a=function(){return this.l};
+function fn(b){var c=b.f.state;2==c&&(b.l=an(je(b.g)/b.j,ke(b.g)/b.j,b.G,b.f.$(),0,b.j,b.g,b.o,[{extent:b.f.J(),image:b.f.a()}]));b.state=c;fk(b)}en.prototype.load=function(){if(0==this.state){this.state=1;fk(this);var b=this.f.state;2==b||3==b?fn(this):(this.c=this.f.Ra("change",function(){var b=this.f.state;if(2==b||3==b)Wc(this.c),this.c=null,fn(this)},!1,this),this.f.load())}};function gn(b){wh.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,projection:b.projection,state:b.state});this.D=void 0!==b.resolutions?b.resolutions:null;this.a=null;this.oa=0}y(gn,wh);function hn(b,c){if(b.D){var d=wb(b.D,c,0);c=b.D[d]}return c}
+gn.prototype.C=function(b,c,d,e){var f=this.b;if(f&&e&&!Ve(f,e)){if(this.a){if(this.oa==this.f&&Ve(this.a.v,e)&&this.a.$()==c&&this.a.b==d&&ae(this.a.J(),b))return this.a;this.a.Fc();this.a=null}this.a=new en(f,e,b,c,d,ua(function(b,c,d){return this.qd(b,c,d,f)},this));this.oa=this.f;return this.a}f&&(e=f);return this.qd(b,c,d,e)};gn.prototype.l=function(b){b=b.target;switch(b.state){case 1:this.s(new jn(kn,b));break;case 2:this.s(new jn(ln,b));break;case 3:this.s(new jn(mn,b))}};
+function nn(b,c){b.a().src=c}function jn(b,c){tc.call(this,b);this.image=c}y(jn,tc);var kn="imageloadstart",ln="imageloadend",mn="imageloaderror";function on(b){gn.call(this,{attributions:b.attributions,logo:b.logo,projection:b.projection,resolutions:b.resolutions,state:void 0!==b.state?b.state:void 0});this.ga=b.canvasFunction;this.V=null;this.fa=0;this.pa=void 0!==b.ratio?b.ratio:1.5}y(on,gn);on.prototype.qd=function(b,c,d,e){c=hn(this,c);var f=this.V;if(f&&this.fa==this.f&&f.$()==c&&f.b==d&&Vd(f.J(),b))return f;b=b.slice();pe(b,this.pa);(e=this.ga(b,c,d,[je(b)/c*d,ke(b)/c*d],e))&&(f=new Xm(b,c,d,this.i,e));this.V=f;this.fa=this.f;return f};function pn(b){gd.call(this);this.xa=void 0;this.a="geometry";this.c=null;this.g=void 0;this.b=null;D(this,id(this.a),this.he,!1,this);void 0!==b&&(b instanceof $e||!b?this.Ma(b):this.I(b))}y(pn,gd);l=pn.prototype;l.clone=function(){var b=new pn(this.R());b.yc(this.a);var c=this.X();c&&b.Ma(c.clone());(c=this.c)&&b.wf(c);return b};l.X=function(){return this.get(this.a)};l.Oa=function(){return this.xa};l.Qj=function(){return this.a};l.zl=function(){return this.c};l.ac=function(){return this.g};
+l.Al=function(){this.u()};l.he=function(){this.b&&(Wc(this.b),this.b=null);var b=this.X();b&&(this.b=D(b,"change",this.Al,!1,this));this.u()};l.Ma=function(b){this.set(this.a,b)};l.wf=function(b){this.g=(this.c=b)?qn(b):void 0;this.u()};l.jc=function(b){this.xa=b;this.u()};l.yc=function(b){Vc(this,id(this.a),this.he,!1,this);this.a=b;D(this,id(this.a),this.he,!1,this);this.he()};function qn(b){if(!ka(b)){var c;c=ga(b)?b:[b];b=function(){return c}}return b};function rn(b){b.prototype.then=b.prototype.then;b.prototype.$goog_Thenable=!0}function sn(b){if(!b)return!1;try{return!!b.$goog_Thenable}catch(c){return!1}};function tn(b,c,d){this.c=d;this.b=b;this.g=c;this.f=0;this.a=null}tn.prototype.get=function(){var b;0<this.f?(this.f--,b=this.a,this.a=b.next,b.next=null):b=this.b();return b};function un(b,c){b.g(c);b.f<b.c&&(b.f++,c.next=b.a,b.a=c)};function vn(){this.f=this.a=null}var xn=new tn(function(){return new wn},function(b){b.reset()},100);vn.prototype.add=function(b,c){var d=xn.get();d.set(b,c);this.f?this.f.next=d:this.a=d;this.f=d};vn.prototype.remove=function(){var b=null;this.a&&(b=this.a,this.a=this.a.next,this.a||(this.f=null),b.next=null);return b};function wn(){this.next=this.f=this.a=null}wn.prototype.set=function(b,c){this.a=b;this.f=c;this.next=null};wn.prototype.reset=function(){this.next=this.f=this.a=null};function yn(b,c){zn||An();Bn||(zn(),Bn=!0);Cn.add(b,c)}var zn;function An(){if(ba.Promise&&ba.Promise.resolve){var b=ba.Promise.resolve(void 0);zn=function(){b.then(Dn)}}else zn=function(){li(Dn)}}var Bn=!1,Cn=new vn;function Dn(){for(var b=null;b=Cn.remove();){try{b.a.call(b.f)}catch(c){ki(c)}un(xn,b)}Bn=!1};function En(b,c){this.a=Fn;this.j=void 0;this.c=this.f=this.b=null;this.g=this.i=!1;if(b!=da)try{var d=this;b.call(c,function(b){Gn(d,Hn,b)},function(b){Gn(d,In,b)})}catch(e){Gn(this,In,e)}}var Fn=0,Hn=2,In=3;function Jn(){this.next=this.b=this.f=this.c=this.a=null;this.g=!1}Jn.prototype.reset=function(){this.b=this.f=this.c=this.a=null;this.g=!1};var Kn=new tn(function(){return new Jn},function(b){b.reset()},100);function Ln(b,c,d){var e=Kn.get();e.c=b;e.f=c;e.b=d;return e}
+En.prototype.then=function(b,c,d){return Mn(this,ka(b)?b:null,ka(c)?c:null,d)};rn(En);En.prototype.cancel=function(b){this.a==Fn&&yn(function(){var c=new Nn(b);On(this,c)},this)};function On(b,c){if(b.a==Fn)if(b.b){var d=b.b;if(d.f){for(var e=0,f=null,g=null,h=d.f;h&&(h.g||(e++,h.a==b&&(f=h),!(f&&1<e)));h=h.next)f||(g=h);f&&(d.a==Fn&&1==e?On(d,c):(g?(e=g,e.next==d.c&&(d.c=e),e.next=e.next.next):Pn(d),Qn(d,f,In,c)))}b.b=null}else Gn(b,In,c)}
+function Rn(b,c){b.f||b.a!=Hn&&b.a!=In||Sn(b);b.c?b.c.next=c:b.f=c;b.c=c}function Mn(b,c,d,e){var f=Ln(null,null,null);f.a=new En(function(b,h){f.c=c?function(d){try{var f=c.call(e,d);b(f)}catch(n){h(n)}}:b;f.f=d?function(c){try{var f=d.call(e,c);!ca(f)&&c instanceof Nn?h(c):b(f)}catch(n){h(n)}}:h});f.a.b=b;Rn(b,f);return f.a}En.prototype.o=function(b){this.a=Fn;Gn(this,Hn,b)};En.prototype.G=function(b){this.a=Fn;Gn(this,In,b)};
+function Gn(b,c,d){if(b.a==Fn){b==d&&(c=In,d=new TypeError("Promise cannot resolve to itself"));b.a=1;var e;a:{var f=d,g=b.o,h=b.G;if(f instanceof En)Rn(f,Ln(g||da,h||null,b)),e=!0;else if(sn(f))f.then(g,h,b),e=!0;else{if(oa(f))try{var k=f.then;if(ka(k)){Tn(f,k,g,h,b);e=!0;break a}}catch(m){h.call(b,m);e=!0;break a}e=!1}}e||(b.j=d,b.a=c,b.b=null,Sn(b),c!=In||d instanceof Nn||Un(b,d))}}
+function Tn(b,c,d,e,f){function g(b){k||(k=!0,e.call(f,b))}function h(b){k||(k=!0,d.call(f,b))}var k=!1;try{c.call(b,h,g)}catch(m){g(m)}}function Sn(b){b.i||(b.i=!0,yn(b.l,b))}function Pn(b){var c=null;b.f&&(c=b.f,b.f=c.next,c.next=null);b.f||(b.c=null);return c}En.prototype.l=function(){for(var b=null;b=Pn(this);)Qn(this,b,this.a,this.j);this.i=!1};
+function Qn(b,c,d,e){if(d==In&&c.f&&!c.g)for(;b&&b.g;b=b.b)b.g=!1;if(c.a)c.a.b=null,Vn(c,d,e);else try{c.g?c.c.call(c.b):Vn(c,d,e)}catch(f){Wn.call(null,f)}un(Kn,c)}function Vn(b,c,d){c==Hn?b.c.call(b.b,d):b.f&&b.f.call(b.b,d)}function Un(b,c){b.g=!0;yn(function(){b.g&&Wn.call(null,c)})}var Wn=ki;function Nn(b){Aa.call(this,b)}y(Nn,Aa);Nn.prototype.name="cancel";function Xn(b,c,d){if(ka(b))d&&(b=ua(b,d));else if(b&&"function"==typeof b.handleEvent)b=ua(b.handleEvent,b);else throw Error("Invalid listener argument");return 2147483647<c?-1:ba.setTimeout(b,c||0)};var Yn=ba.JSON.parse,Zn=ba.JSON.stringify;function $n(){}$n.prototype.a=null;function ao(b){var c;(c=b.a)||(c={},bo(b)&&(c[0]=!0,c[1]=!0),c=b.a=c);return c};var co;function eo(){}y(eo,$n);function fo(b){return(b=bo(b))?new ActiveXObject(b):new XMLHttpRequest}function bo(b){if(!b.f&&"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.f=e}catch(f){}}throw Error("Could not create ActiveXObject. ActiveX might be disabled, or MSXML might not be installed");}return b.f}co=new eo;var go=/^(?:([^:/?#.]+):)?(?:\/\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\?([^#]*))?(?:#(.*))?$/;function ho(b,c){if(b)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 io(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 jo(b,c,d){if(ga(c))for(var e=0;e<c.length;e++)jo(b,String(c[e]),d);else null!=c&&d.push("&",b,""===c?"":"=",encodeURIComponent(String(c)))}function ko(b,c){for(var d in c)jo(d,c[d],b);return b};function lo(b){$c.call(this);this.O=new pi;this.o=b||null;this.a=!1;this.l=this.ha=null;this.g=this.U=this.v="";this.f=this.B=this.c=this.G=!1;this.j=0;this.b=null;this.i=mo;this.C=this.V=!1}y(lo,$c);var mo="",no=/^https?$/i,oo=["POST","PUT"];
+function po(b,c){if(b.ha)throw Error("[goog.net.XhrIo] Object is active with another request="+b.v+"; newUri="+c);b.v=c;b.g="";b.U="GET";b.G=!1;b.a=!0;b.ha=b.o?fo(b.o):fo(co);b.l=b.o?ao(b.o):ao(co);b.ha.onreadystatechange=ua(b.D,b);try{b.B=!0,b.ha.open("GET",String(c),!0),b.B=!1}catch(g){qo(b,g);return}var d=b.O.clone(),e=fb(d.P(),ro),f=ba.FormData&&!1;!(0<=ab(oo,"GET"))||e||f||d.set("Content-Type","application/x-www-form-urlencoded;charset=utf-8");d.forEach(function(b,c){this.ha.setRequestHeader(c,
+b)},b);b.i&&(b.ha.responseType=b.i);"withCredentials"in b.ha&&(b.ha.withCredentials=b.V);try{so(b),0<b.j&&(b.C=to(b.ha),b.C?(b.ha.timeout=b.j,b.ha.ontimeout=ua(b.Bc,b)):b.b=Xn(b.Bc,b.j,b)),b.c=!0,b.ha.send(""),b.c=!1}catch(g){qo(b,g)}}function to(b){return Yb&&ic(9)&&ja(b.timeout)&&ca(b.ontimeout)}function ro(b){return"content-type"==b.toLowerCase()}
+lo.prototype.Bc=function(){"undefined"!=typeof aa&&this.ha&&(this.g="Timed out after "+this.j+"ms, aborting",this.s("timeout"),this.ha&&this.a&&(this.a=!1,this.f=!0,this.ha.abort(),this.f=!1,this.s("complete"),this.s("abort"),uo(this)))};function qo(b,c){b.a=!1;b.ha&&(b.f=!0,b.ha.abort(),b.f=!1);b.g=c;vo(b);uo(b)}function vo(b){b.G||(b.G=!0,b.s("complete"),b.s("error"))}lo.prototype.Y=function(){this.ha&&(this.a&&(this.a=!1,this.f=!0,this.ha.abort(),this.f=!1),uo(this,!0));lo.ca.Y.call(this)};
+lo.prototype.D=function(){this.na||(this.B||this.c||this.f?wo(this):this.T())};lo.prototype.T=function(){wo(this)};function wo(b){if(b.a&&"undefined"!=typeof aa&&(!b.l[1]||4!=xo(b)||2!=yo(b)))if(b.c&&4==xo(b))Xn(b.D,0,b);else if(b.s("readystatechange"),4==xo(b)){b.a=!1;try{if(zo(b))b.s("complete"),b.s("success");else{var c;try{c=2<xo(b)?b.ha.statusText:""}catch(d){c=""}b.g=c+" ["+yo(b)+"]";vo(b)}}finally{uo(b)}}}
+function uo(b,c){if(b.ha){so(b);var d=b.ha,e=b.l[0]?da:null;b.ha=null;b.l=null;c||b.s("ready");try{d.onreadystatechange=e}catch(f){}}}function so(b){b.ha&&b.C&&(b.ha.ontimeout=null);ja(b.b)&&(ba.clearTimeout(b.b),b.b=null)}
+function zo(b){var c=yo(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=String(b.v).match(go)[1]||null,!b&&ba.self&&ba.self.location&&(b=ba.self.location.protocol,b=b.substr(0,b.length-1)),c=!no.test(b?b.toLowerCase():"");d=c}return d}function xo(b){return b.ha?b.ha.readyState:0}function yo(b){try{return 2<xo(b)?b.ha.status:-1}catch(c){return-1}}function Ao(b){try{return b.ha?b.ha.responseText:""}catch(c){return""}}
+function Bo(b){try{if(!b.ha)return null;if("response"in b.ha)return b.ha.response;switch(b.i){case mo:case "text":return b.ha.responseText;case "arraybuffer":if("mozResponseArrayBuffer"in b.ha)return b.ha.mozResponseArrayBuffer}return null}catch(c){return null}};function Co(b,c,d,e,f,g){uh.call(this,b,c);this.j=e;this.i=null;this.b=g;this.c={kd:!1,Tf:null,Rh:-1,Hd:null};this.G=f;this.l=d}y(Co,uh);l=Co.prototype;l.Y=function(){Co.ca.Y.call(this)};l.Ll=function(){return this.j};l.$a=function(){return this.l};l.load=function(){0==this.state&&(this.state=1,vh(this),this.G(this,this.l),this.o(null,NaN,this.b))};l.ai=function(b){this.o=b};function Do(){if(!Yb)return!1;try{return new ActiveXObject("MSXML2.DOMDocument"),!0}catch(b){return!1}}var Eo=Yb&&Do();function Fo(b){var c=b.xml;if(c)return c;if("undefined"!=typeof XMLSerializer)return(new XMLSerializer).serializeToString(b);throw Error("Your browser does not support serializing XML documents");};var Go;a:if(document.implementation&&document.implementation.createDocument)Go=document.implementation.createDocument("","",null);else{if(Eo){var Ho=new ActiveXObject("MSXML2.DOMDocument");if(Ho){Ho.resolveExternals=!1;Ho.validateOnParse=!1;try{Ho.setProperty("ProhibitDTD",!0),Ho.setProperty("MaxXMLSize",2048),Ho.setProperty("MaxElementDepth",256)}catch(b){}}if(Ho){Go=Ho;break a}}throw Error("Your browser does not support creating new documents");}var Io=Go;
+function Jo(b,c){return Io.createElementNS(b,c)}function Ko(b,c){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;b;b=b.nextSibling)No(b,c,d);return d}function Oo(b){return b.localName}
+function Po(b){var c=b.localName;return void 0!==c?c:b.baseName}var Qo=Yb?Po:Oo;function Ro(b){return b instanceof Document}function So(b){return oa(b)&&9==b.nodeType}var To=Yb?So:Ro;function Uo(b){return b instanceof Node}function Vo(b){return oa(b)&&void 0!==b.nodeType}var Wo=Yb?Vo:Uo;function Xo(b,c,d){return b.getAttributeNS(c,d)||""}function Yo(b,c,d){var e="";b=Zo(b,c,d);void 0!==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){c?(c=b.ownerDocument.createNode(2,d,c),c.nodeValue=e,b.setAttributeNode(c)):b.setAttribute(d,e)}
+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);void 0!==f&&kb(e[e.length-1],f)}}function hp(b,c){return function(d,e){var f=b.call(void 0!==c?c:this,d,e);void 0!==f&&e[e.length-1].push(f)}}function ip(b,c){return function(d,e){var f=b.call(void 0!==c?c:this,d,e);void 0!==f&&(e[e.length-1]=f)}}
+function jp(b){return function(c,d){var e=b.call(this,c,d);void 0!==e&&Sb(d[d.length-1],c.localName).push(e)}}function N(b,c){return function(d,e){var f=b.call(this,d,e);void 0!==f&&(e[e.length-1][void 0!==c?c:d.localName]=f)}}function O(b,c){return function(d,e,f){b.call(void 0!==c?c:this,d,e,f);f[f.length-1].node.appendChild(d)}}function kp(b){var c,d;return function(e,f,g){if(void 0===c){c={};var h={};h[e.localName]=b;c[e.namespaceURI]=h;d=lp(e.localName)}mp(c,d,f,g)}}
+function lp(b,c){return function(d,e,f){d=e[e.length-1].node;e=b;void 0===e&&(e=f);f=c;void 0===c&&(f=d.namespaceURI);return Lo(f,e)}}var np=lp();function op(b,c){for(var d=c.length,e=Array(d),f=0;f<d;++f)e[f]=b[c[f]];return e}function P(b,c,d){d=void 0!==d?d:{};var e,f;e=0;for(f=b.length;e<f;++e)d[b[e]]=c;return d}function pp(b,c,d,e){for(c=c.firstElementChild;c;c=c.nextElementSibling){var f=b[c.namespaceURI];void 0!==f&&(f=f[c.localName],void 0!==f&&f.call(e,c,d))}}
+function Q(b,c,d,e,f){e.push(b);pp(c,d,e,f);return e.pop()}function mp(b,c,d,e,f,g){for(var h=(void 0!==f?f:d).length,k,m,n=0;n<h;++n)k=d[n],void 0!==k&&(m=c.call(g,k,e,void 0!==f?f[n]:void 0),void 0!==m&&b[m.namespaceURI][m.localName].call(g,m,k,e))}function qp(b,c,d,e,f,g,h){f.push(b);mp(c,d,e,f,g,h);f.pop()};function rp(b,c,d,e){return function(f,g,h){var k=new lo;k.i="arraybuffer"==c.W()?"arraybuffer":"text";D(k,"complete",function(b){b=b.target;if(zo(b)){var f=c.W(),g;if("json"==f)g=Ao(b);else if("text"==f)g=Ao(b);else if("xml"==f){if(!Yb)try{g=b.ha?b.ha.responseXML:null}catch(k){g=null}g||(g=fp(Ao(b)))}else"arraybuffer"==f&&(g=Bo(b));g&&(this instanceof Co&&(f=c.Ia(g).f,"tile-pixels"===f&&(this.b=h=new Ce({code:this.b.a,units:f}))),d.call(this,c.Ba(g,{featureProjection:h})))}else e.call(this);sc(b)},
+!1,this);ka(b)?po(k,b(f,g,h)):po(k,b)}}function sp(b,c){return rp(b,c,function(b){this.i=b;this.state=2;vh(this)},function(){this.state=3;vh(this)})}function tp(b,c){return rp(b,c,function(b){this.Ec(b)},za)};function up(){return[[-Infinity,-Infinity,Infinity,Infinity]]};var vp,wp,xp,yp;
+(function(){var b={ja:{}};(function(){function c(b,d){if(!(this instanceof c))return new c(b,d);this.bf=Math.max(4,b||9);this.hg=Math.max(2,Math.ceil(.4*this.bf));d&&this.cj(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.Qa?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 m(b){return b[2]-b[0]+(b[3]-b[1])}function n(b,c){return b[0]<=c[0]&&b[1]<=c[1]&&c[2]<=b[2]&&c[3]<=b[3]}function p(b,c){return c[0]<=b[2]&&c[1]<=b[3]&&c[2]>=b[0]&&c[3]>=b[1]}function q(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,r(b,c,d,h,f),g.push(c,h,h,d))}function r(b,c,d,e,f){for(var g,h,k,m,n;d>c;){600<d-c&&(g=d-c+1,h=e-c+1,k=Math.log(g),
+m=.5*Math.exp(2*k/3),n=.5*Math.sqrt(k*m*(g-m)/g)*(0>h-g/2?-1:1),k=Math.max(c,Math.floor(e-h*m/g+n)),h=Math.min(d,Math.floor(e+(g-h)*m/g+n)),r(b,k,h,e,f));g=b[e];h=c;m=d;t(b,c,e);for(0<f(b[d],g)&&t(b,c,d);h<m;){t(b,h,m);h++;for(m--;0>f(b[h],g);)h++;for(;0<f(b[m],g);)m--}0===f(b[c],g)?t(b,c,m):(m++,t(b,m,d));m<=e&&(c=m+1);e<=m&&(d=m-1)}}function t(b,c,d){var e=b[c];b[c]=b[d];b[d]=e}c.prototype={all:function(){return this.cg(this.data,[])},search:function(b){var c=this.data,d=[],e=this.fb;if(!p(b,c.bbox))return d;
+for(var f=[],g,h,k,m;c;){g=0;for(h=c.children.length;g<h;g++)k=c.children[g],m=c.Qa?e(k):k.bbox,p(b,m)&&(c.Qa?d.push(k):n(b,m)?this.cg(k,d):f.push(k));c=f.pop()}return d},load:function(b){if(!b||!b.length)return this;if(b.length<this.hg){for(var c=0,d=b.length;c<d;c++)this.ya(b[c]);return this}b=this.eg(b.slice(),0,b.length-1,0);this.data.children.length?this.data.height===b.height?this.jg(this.data,b):(this.data.height<b.height&&(c=this.data,this.data=b,b=c),this.gg(b,this.data.height-b.height-1,
+!0)):this.data=b;return this},ya:function(b){b&&this.gg(b,this.data.height-1);return this},clear:function(){this.data={children:[],height:1,bbox:[Infinity,Infinity,-Infinity,-Infinity],Qa:!0};return this},remove:function(b){if(!b)return this;for(var c=this.data,d=this.fb(b),e=[],f=[],g,h,k,m;c||e.length;){c||(c=e.pop(),h=e[e.length-1],g=f.pop(),m=!0);if(c.Qa&&(k=c.children.indexOf(b),-1!==k)){c.children.splice(k,1);e.push(c);this.aj(e);break}m||c.Qa||!n(c.bbox,d)?h?(g++,c=h.children[g],m=!1):c=null:
+(e.push(c),f.push(g),g=0,h=c,c=c.children[0])}return this},fb:function(b){return b},ff:function(b,c){return b[0]-c[0]},gf:function(b,c){return b[1]-c[1]},toJSON:function(){return this.data},cg:function(b,c){for(var d=[];b;)b.Qa?c.push.apply(c,b.children):d.push.apply(d,b.children),b=d.pop();return c},eg:function(b,c,e,f){var g=e-c+1,h=this.bf,k;if(g<=h)return k={children:b.slice(c,e+1),height:1,bbox:null,Qa:!0},d(k,this.fb),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)),m,n,p;for(q(b,c,e,h,this.ff);c<=e;c+=h)for(n=Math.min(c+h-1,e),q(b,c,n,g,this.gf),m=c;m<=n;m+=g)p=Math.min(m+g-1,n),k.children.push(this.eg(b,m,p,f-1));d(k,this.fb);return k},$i:function(b,c,d,e){for(var f,g,h,m,n,p,q,r;;){e.push(c);if(c.Qa||e.length-1===d)break;q=r=Infinity;f=0;for(g=c.children.length;f<g;f++)h=c.children[f],n=k(h.bbox),p=h.bbox,p=(Math.max(p[2],b[2])-Math.min(p[0],b[0]))*(Math.max(p[3],b[3])-Math.min(p[1],
+b[1]))-n,p<r?(r=p,q=n<q?n:q,m=h):p===r&&n<q&&(q=n,m=h);c=m}return c},gg:function(b,c,d){var e=this.fb;d=d?b.bbox:e(b);var e=[],g=this.$i(d,this.data,c,e);g.children.push(b);for(f(g.bbox,d);0<=c;)if(e[c].children.length>this.bf)this.ij(e,c),c--;else break;this.Xi(d,e,c)},ij:function(b,c){var e=b[c],f=e.children.length,g=this.hg;this.Yi(e,g,f);f=this.Zi(e,g,f);f={children:e.children.splice(f,e.children.length-f),height:e.height};e.Qa&&(f.Qa=!0);d(e,this.fb);d(f,this.fb);c?b[c-1].children.push(f):this.jg(e,
+f)},jg:function(b,c){this.data={children:[b,c],height:b.height+1};d(this.data,this.fb)},Zi:function(b,c,d){var f,g,h,m,n,p,q;n=p=Infinity;for(f=c;f<=d-c;f++)g=e(b,0,f,this.fb),h=e(b,f,d,this.fb),m=Math.max(0,Math.min(g[2],h[2])-Math.max(g[0],h[0]))*Math.max(0,Math.min(g[3],h[3])-Math.max(g[1],h[1])),g=k(g)+k(h),m<n?(n=m,q=f,p=g<p?g:p):m===n&&g<p&&(p=g,q=f);return q},Yi:function(b,c,d){var e=b.Qa?this.ff:g,f=b.Qa?this.gf:h,k=this.dg(b,c,d,e);c=this.dg(b,c,d,f);k<c&&b.children.sort(e)},dg:function(b,
+c,d,g){b.children.sort(g);g=this.fb;var h=e(b,0,c,g),k=e(b,d-c,d,g),n=m(h)+m(k),p,q;for(p=c;p<d-c;p++)q=b.children[p],f(h,b.Qa?g(q):q.bbox),n+=m(h);for(p=d-c-1;p>=c;p--)q=b.children[p],f(k,b.Qa?g(q):q.bbox),n+=m(k);return n},Xi:function(b,c,d){for(;0<=d;d--)f(c[d].bbox,b)},aj: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.fb)},cj:function(b){var c=["return a"," - b",";"];this.ff=new Function("a","b",
+c.join(b[0]));this.gf=new Function("a","b",c.join(b[1]));this.fb=new Function("a","return [a"+b.join(", a")+"];")}};"undefined"!==typeof b?b.ja=c:"undefined"!==typeof self?self.a=c:window.a=c})();vp=b.ja})();function zp(b){this.f=vp(b);this.a={}}l=zp.prototype;l.ya=function(b,c){var d=[b[0],b[1],b[2],b[3],c];this.f.ya(d);this.a[w(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[w(h)]=g}this.f.load(d)};l.remove=function(b){b=w(b);var c=this.a[b];delete this.a[b];return null!==this.f.remove(c)};function Ap(b,c,d){var e=w(d);ae(b.a[e].slice(0,4),c)||(b.remove(d),b.ya(c,d))}
+function Bp(b){return b.f.all().map(function(b){return b[4]})}function Cp(b,c){return b.f.search(c).map(function(b){return b[4]})}l.forEach=function(b,c){return Dp(Bp(this),b,c)};function Ep(b,c,d,e){return Dp(Cp(b,c),d,e)}function Dp(b,c,d){for(var e,f=0,g=b.length;f<g&&!(e=c.call(d,b[f]));f++);return e}l.La=function(){return Pb(this.a)};l.clear=function(){this.f.clear();this.a={}};l.J=function(){return this.f.data.bbox};function R(b){b=b||{};wh.call(this,{attributions:b.attributions,logo:b.logo,projection:void 0,state:"ready",wrapX:void 0!==b.wrapX?b.wrapX:!0});this.T=za;void 0!==b.loader?this.T=b.loader:void 0!==b.url&&(this.T=tp(b.url,b.format));this.pa=void 0!==b.strategy?b.strategy:up;var c=void 0!==b.useSpatialIndex?b.useSpatialIndex:!0;this.a=c?new zp:null;this.V=new zp;this.g={};this.j={};this.l={};this.o={};this.c=null;var d,e;b.features instanceof og?(d=b.features,e=d.a):ga(b.features)&&(e=b.features);c||
+void 0!==d||(d=new og(e));void 0!==e&&Fp(this,e);void 0!==d&&Gp(this,d)}y(R,wh);l=R.prototype;l.Bd=function(b){var c=w(b).toString();if(Hp(this,c,b)){Ip(this,c,b);var d=b.X();d?(c=d.J(),this.a&&this.a.ya(c,b)):this.g[c]=b;this.s(new Jp("addfeature",b))}this.u()};function Ip(b,c,d){b.o[c]=[D(d,"change",b.th,!1,b),D(d,"propertychange",b.th,!1,b)]}function Hp(b,c,d){var e=!0,f=d.Oa();void 0!==f?f.toString()in b.j?e=!1:b.j[f.toString()]=d:b.l[c]=d;return e}l.Ec=function(b){Fp(this,b);this.u()};
+function Fp(b,c){var d,e,f,g,h=[],k=[],m=[];e=0;for(f=c.length;e<f;e++)g=c[e],d=w(g).toString(),Hp(b,d,g)&&k.push(g);e=0;for(f=k.length;e<f;e++){g=k[e];d=w(g).toString();Ip(b,d,g);var n=g.X();n?(d=n.J(),h.push(d),m.push(g)):b.g[d]=g}b.a&&b.a.load(h,m);e=0;for(f=k.length;e<f;e++)b.s(new Jp("addfeature",k[e]))}
+function Gp(b,c){var d=!1;D(b,"addfeature",function(b){d||(d=!0,c.push(b.feature),d=!1)});D(b,"removefeature",function(b){d||(d=!0,c.remove(b.feature),d=!1)});D(c,"add",function(b){d||(b=b.element,d=!0,this.Bd(b),d=!1)},!1,b);D(c,"remove",function(b){d||(b=b.element,d=!0,this.Rc(b),d=!1)},!1,b);b.c=c}
+l.clear=function(b){if(b){for(var c in this.o)this.o[c].forEach(Wc);this.c||(this.o={},this.j={},this.l={})}else b=this.Qh,this.a&&(this.a.forEach(b,this),Ib(this.g,b,this));this.c&&this.c.clear();this.a&&this.a.clear();this.V.clear();this.g={};this.s(new Jp("clear"));this.u()};l.sg=function(b,c){if(this.a)return this.a.forEach(b,c);if(this.c)return this.c.forEach(b,c)};function Kp(b,c,d){b.pb([c[0],c[1],c[0],c[1]],function(b){if(b.X().og(c))return d.call(void 0,b)})}
+l.pb=function(b,c,d){if(this.a)return Ep(this.a,b,c,d);if(this.c)return this.c.forEach(c,d)};l.tg=function(b,c,d){return this.pb(b,function(e){if(e.X().Ea(b)&&(e=c.call(d,e)))return e})};l.zg=function(){return this.c};l.ze=function(){var b;this.c?b=this.c.a:this.a&&(b=Bp(this.a),Pb(this.g)||kb(b,Lb(this.g)));return b};l.yg=function(b){var c=[];Kp(this,b,function(b){c.push(b)});return c};l.mf=function(b){return Cp(this.a,b)};
+l.vg=function(b){var c=b[0],d=b[1],e=null,f=[NaN,NaN],g=Infinity,h=[-Infinity,-Infinity,Infinity,Infinity];Ep(this.a,h,function(b){var m=b.X(),n=g;g=m.nb(c,d,f,g);g<n&&(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.J=function(){return this.a.J()};l.xg=function(b){b=this.j[b.toString()];return void 0!==b?b:null};
+l.th=function(b){b=b.target;var c=w(b).toString(),d=b.X();d?(d=d.J(),c in this.g?(delete this.g[c],this.a&&this.a.ya(d,b)):this.a&&Ap(this.a,d,b)):c in this.g||(this.a&&this.a.remove(b),this.g[c]=b);d=b.Oa();void 0!==d?(d=d.toString(),c in this.l?(delete this.l[c],this.j[d]=b):this.j[d]!==b&&(Lp(this,b),this.j[d]=b)):c in this.l||(Lp(this,b),this.l[c]=b);this.u();this.s(new Jp("changefeature",b))};l.La=function(){return this.a.La()&&Pb(this.g)};
+l.Nc=function(b,c,d){var e=this.V;b=this.pa(b,c);var f,g;f=0;for(g=b.length;f<g;++f){var h=b[f];Ep(e,h,function(b){return Vd(b.extent,h)})||(this.T.call(this,h,c,d),e.ya(h,{extent:h.slice()}))}};l.Rc=function(b){var c=w(b).toString();c in this.g?delete this.g[c]:this.a&&this.a.remove(b);this.Qh(b);this.u()};l.Qh=function(b){var c=w(b).toString();this.o[c].forEach(Wc);delete this.o[c];var d=b.Oa();void 0!==d?delete this.j[d.toString()]:delete this.l[c];this.s(new Jp("removefeature",b))};
+function Lp(b,c){for(var d in b.j)if(b.j[d]===c){delete b.j[d];break}}function Jp(b,c){tc.call(this,b);this.feature=c}y(Jp,tc);function Mp(b){this.c=b.source;this.wa=Bd();this.g=Ni();this.j=[0,0];this.v=null;on.call(this,{attributions:b.attributions,canvasFunction:ua(this.tj,this),logo:b.logo,projection:b.projection,ratio:b.ratio,resolutions:b.resolutions,state:this.c.B});this.T=null;this.o=void 0;this.ph(b.style);D(this.c,"change",this.Om,void 0,this)}y(Mp,on);l=Mp.prototype;
+l.tj=function(b,c,d,e,f){var g=new Om(.5*c/d,b,c);this.c.Nc(b,c,f);var h=!1;this.c.pb(b,function(b){var e;if(!(e=h)){var f;(e=b.ac())?f=e.call(b,c):this.o&&(f=this.o(b,c));if(f){var p,q=!1;e=0;for(p=f.length;e<p;++e)q=Vm(g,b,f[e],Um(c,d),this.Nm,this)||q;e=q}else e=!1}h=e},this);Pm(g);if(h)return null;this.j[0]!=e[0]||this.j[1]!=e[1]?(this.g.canvas.width=e[0],this.g.canvas.height=e[1],this.j[0]=e[0],this.j[1]=e[1]):this.g.clearRect(0,0,e[0],e[1]);b=Np(this,le(b),c,d,e);g.b(this.g,d,b,0,{});this.v=
+g;return this.g.canvas};l.ye=function(b,c,d,e,f){if(this.v){var g={};return this.v.c(b,c,0,e,function(b){var c=w(b).toString();if(!(c in g))return g[c]=!0,f(b)})}};l.Km=function(){return this.c};l.Lm=function(){return this.T};l.Mm=function(){return this.o};function Np(b,c,d,e,f){return gk(b.wa,f[0]/2,f[1]/2,e/d,-e/d,0,-c[0],-c[1])}l.Nm=function(){this.u()};l.Om=function(){yh(this,this.c.B)};l.ph=function(b){this.T=void 0!==b?b:dm;this.o=b?bm(this.T):void 0;this.u()};function Op(b){um.call(this,b);this.g=null;this.i=Bd();this.b=this.c=null}y(Op,um);l=Op.prototype;l.ab=function(b,c,d,e){var f=this.a;return f.ea().ye(b,c.viewState.resolution,c.viewState.rotation,c.skippedFeatureUids,function(b){return d.call(e,b,f)})};
+l.vc=function(b,c,d,e){if(this.zd())if(this.a.ea()instanceof Mp){if(b=b.slice(),ik(c.pixelToCoordinateMatrix,b,b),this.ab(b,c,te,this))return d.call(e,this.a)}else if(this.c||(this.c=Bd(),Hd(this.i,this.c)),c=xm(b,this.c),this.b||(this.b=Ni(1,1)),this.b.clearRect(0,0,1,1),this.b.drawImage(this.zd(),c[0],c[1],1,1,0,0,1,1),0<this.b.getImageData(0,0,1,1).data[3])return d.call(e,this.a)};l.zd=function(){return this.g?this.g.a():null};l.nf=function(){return this.i};
+l.Ad=function(b,c){var d=b.pixelRatio,e=b.viewState,f=e.center,g=e.resolution,h=e.rotation,k=this.a.ea(),m=b.viewHints,n=b.extent;void 0!==c.extent&&(n=ne(n,c.extent));m[0]||m[1]||ie(n)||(e=k.C(n,g,d,e.projection))&&lk(this,e)&&(this.g=e);if(this.g){var e=this.g,m=e.J(),n=e.$(),p=e.b,g=d*n/(g*p);gk(this.i,d*b.size[0]/2,d*b.size[1]/2,g,g,h,p*(m[0]-f[0])/n,p*(f[1]-m[3])/n);this.c=null;nk(b.attributions,e.i);ok(b,k)}return!0};function Pp(b){um.call(this,b);this.b=this.i=null;this.o=!1;this.j=null;this.B=Bd();this.g=null;this.C=this.D=this.v=NaN;this.l=this.c=null;this.U=[0,0]}y(Pp,um);Pp.prototype.zd=function(){return this.i};Pp.prototype.nf=function(){return this.B};
+Pp.prototype.Ad=function(b,c){function d(b){b=b.state;return 2==b||4==b||3==b&&!K}var e=b.pixelRatio,f=b.viewState,g=f.projection,h=this.a,k=h.ea(),m=k.ib(g),n=k.ae(),p=Ih(m,f.resolution),q=k.Qb(p,b.pixelRatio,g),r=q[0]/md(m.Ka(p),this.U)[0],t=m.$(p),r=t/r,x=f.center,z;t==f.resolution?(x=qk(x,t,b.size),z=me(x,t,f.rotation,b.size)):z=b.extent;void 0!==c.extent&&(z=ne(z,c.extent));if(ie(z))return!1;var A=Fh(m,z,t),B=q[0]*kg(A),v=q[1]*jg(A),L,M;this.i?(L=this.i,M=this.j,this.b[0]<B||this.b[1]<v||this.D!==
+q[0]||this.C!==q[1]||this.o&&(this.b[0]>B||this.b[1]>v)?(L.width=B,L.height=v,this.b=[B,v],this.o=!ym(this.b),this.c=null):(B=this.b[0],v=this.b[1],p==this.v&&hg(this.c,A)||(this.c=null))):(M=Ni(B,v),this.i=M.canvas,this.b=[B,v],this.j=M,this.o=!ym(this.b));var J,C;this.c?(v=this.c,B=kg(v)):(B/=q[0],v/=q[1],J=A.a-Math.floor((B-kg(A))/2),C=A.f-Math.floor((v-jg(A))/2),this.v=p,this.D=q[0],this.C=q[1],this.c=new fg(J,J+B-1,C,C+v-1),this.l=Array(B*v),v=this.c);L={};L[p]={};var sa=[],la=this.cd(k,g,L),
+K=h.b(),ma=Md(),Ua=new fg(0,0,0,0),Nb,na,Fa;for(C=A.a;C<=A.c;++C)for(Fa=A.f;Fa<=A.b;++Fa)na=k.Pb(p,C,Fa,e,g),!d(na)&&na.f&&(na=na.f),d(na)?L[p][eg(na.a)]=na:(Nb=Ch(m,na.a,la,Ua,ma),Nb||(sa.push(na),(Nb=Eh(m,na.a,Ua,ma))&&la(p+1,Nb)));la=0;for(Nb=sa.length;la<Nb;++la)na=sa[la],C=q[0]*(na.a[1]-v.a),Fa=q[1]*(v.b-na.a[2]),M.clearRect(C,Fa,q[0],q[1]);sa=Object.keys(L).map(Number);sa.sort(ub);var ad=k.pa,Qc=ge(m.Aa([p,v.a,v.b],ma)),sf,zj,$d,ci,cg,qm,la=0;for(Nb=sa.length;la<Nb;++la)if(sf=sa[la],q=k.Qb(sf,
+e,g),ci=L[sf],sf==p)for(zj in ci)na=ci[zj],J=(na.a[2]-v.f)*B+(na.a[1]-v.a),this.l[J]!=na&&(C=q[0]*(na.a[1]-v.a),Fa=q[1]*(v.b-na.a[2]),$d=na.state,4!=$d&&(3!=$d||K)&&ad||M.clearRect(C,Fa,q[0],q[1]),2==$d&&M.drawImage(na.Ta(),n,n,q[0],q[1],C,Fa,q[0],q[1]),this.l[J]=na);else for(zj in sf=m.$(sf)/t,ci)for(na=ci[zj],J=m.Aa(na.a,ma),C=(J[0]-Qc[0])/r,Fa=(Qc[1]-J[3])/r,qm=sf*q[0],cg=sf*q[1],$d=na.state,4!=$d&&ad||M.clearRect(C,Fa,qm,cg),2==$d&&M.drawImage(na.Ta(),n,n,q[0],q[1],C,Fa,qm,cg),na=Dh(m,J,p,Ua),
+J=Math.max(na.a,v.a),Fa=Math.min(na.c,v.c),C=Math.max(na.f,v.f),na=Math.min(na.b,v.b),$d=J;$d<=Fa;++$d)for(cg=C;cg<=na;++cg)J=(cg-v.f)*B+($d-v.a),this.l[J]=void 0;pk(b.usedTiles,k,p,A);rk(b,k,m,e,g,z,p,h.a());mk(b,k);ok(b,k);gk(this.B,e*b.size[0]/2,e*b.size[1]/2,e*r/f.resolution,e*r/f.resolution,f.rotation,(Qc[0]-x[0])/r,(x[1]-Qc[1])/r);this.g=null;return!0};
+Pp.prototype.vc=function(b,c,d,e){if(this.j&&(this.g||(this.g=Bd(),Hd(this.B,this.g)),b=xm(b,this.g),0<this.j.getImageData(b[0],b[1],1,1).data[3]))return d.call(e,this.a)};function Qp(b){um.call(this,b);this.c=!1;this.o=-1;this.l=NaN;this.i=Md();this.b=this.j=null;this.g=Ni()}y(Qp,um);
+Qp.prototype.G=function(b,c,d){var e=b.extent,f=b.pixelRatio,g=c.Bb?b.skippedFeatureUids:{},h=b.viewState,k=h.projection,h=h.rotation,m=k.J(),n=this.a.ea(),p=wm(this,b,0);vm(this,"precompose",d,b,p);var q=this.b;if(q&&!q.La()){var r;cd(this.a,"render")?(this.g.canvas.width=d.canvas.width,this.g.canvas.height=d.canvas.height,r=this.g):r=d;var t=r.globalAlpha;r.globalAlpha=c.opacity;q.b(r,f,p,h,g);if(n.O&&k.b&&!Vd(m,e)){c=e[0];k=je(m);for(n=0;c<m[0];)--n,p=k*n,p=wm(this,b,p),q.b(r,f,p,h,g),c+=k;n=0;
+for(c=e[2];c>m[2];)++n,p=k*n,p=wm(this,b,p),q.b(r,f,p,h,g),c-=k;p=wm(this,b,0)}r!=d&&(vm(this,"render",r,b,p),d.drawImage(r.canvas,0,0));r.globalAlpha=t}vm(this,"postcompose",d,b,p)};Qp.prototype.ab=function(b,c,d,e){if(this.b){var f=c.viewState.resolution,g=c.viewState.rotation,h=this.a,k=c.layerStates[w(h)],m={};return this.b.c(b,f,g,k.Bb?c.skippedFeatureUids:{},function(b){var c=w(b).toString();if(!(c in m))return m[c]=!0,d.call(e,b,h)})}};Qp.prototype.B=function(){kk(this)};
+Qp.prototype.Ad=function(b){function c(b){var c,e=b.ac();e?c=e.call(b,n):(e=d.b)&&(c=e(b,n));if(c){if(c){e=!1;if(ga(c))for(var f=0,g=c.length;f<g;++f)e=Vm(r,b,c[f],Um(n,p),this.B,this)||e;else e=Vm(r,b,c,Um(n,p),this.B,this)||e;b=e}else b=!1;this.c=this.c||b}}var d=this.a,e=d.ea();nk(b.attributions,e.i);ok(b,e);var f=b.viewHints[0],g=b.viewHints[1],h=d.j,k=d.l;if(!this.c&&!h&&f||!k&&g)return!0;var m=b.extent,k=b.viewState,f=k.projection,n=k.resolution,p=b.pixelRatio,g=d.f,q=d.a,h=fm(d);void 0===h&&
+(h=Tm);m=Qd(m,q*n);q=k.projection.J();e.O&&k.projection.b&&!Vd(q,b.extent)&&(b=Math.max(je(m)/2,je(q)),m[0]=q[0]-b,m[2]=q[2]+b);if(!this.c&&this.l==n&&this.o==g&&this.j==h&&Vd(this.i,m))return!0;sc(this.b);this.b=null;this.c=!1;var r=new Om(.5*n/p,m,n,d.a);e.Nc(m,n,f);if(h){var t=[];e.pb(m,function(b){t.push(b)},this);t.sort(h);t.forEach(c,this)}else e.pb(m,c,this);Pm(r);this.l=n;this.o=g;this.j=h;this.i=m;this.b=r;return!0};function Rp(b,c){var d=/\{z\}/g,e=/\{x\}/g,f=/\{y\}/g,g=/\{-y\}/g;return function(h){if(h)return b.replace(d,h[0].toString()).replace(e,h[1].toString()).replace(f,function(){return(-h[2]-1).toString()}).replace(g,function(){return(jg(c.f?c.f[h[0]]:null)+h[2]).toString()})}}function Sp(b,c){for(var d=b.length,e=Array(d),f=0;f<d;++f)e[f]=Rp(b[f],c);return Tp(e)}function Tp(b){return 1===b.length?b[0]:function(c,d,e){if(c)return b[nd((c[1]<<c[0])+c[2],b.length)](c,d,e)}}function Up(){}
+function Vp(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 Wp(b){Nh.call(this,{attributions:b.attributions,df:b.df,extent:b.extent,logo:b.logo,opaque:b.opaque,projection:b.projection,state:b.state?b.state:void 0,tileGrid:b.tileGrid,tilePixelRatio:b.tilePixelRatio,wrapX:b.wrapX});this.tileLoadFunction=b.tileLoadFunction;this.tileUrlFunction=b.tileUrlFunction?b.tileUrlFunction:Up;this.urls=null;b.urls?b.tileUrlFunction?this.urls=b.urls:this.Wa(b.urls):b.url&&this.Va(b.url);b.tileUrlFunction&&this.Ja(b.tileUrlFunction)}y(Wp,Nh);l=Wp.prototype;
+l.Xa=function(){return this.tileLoadFunction};l.Ya=function(){return this.tileUrlFunction};l.Za=function(){return this.urls};l.sh=function(b){b=b.target;switch(b.state){case 1:this.s(new Qh("tileloadstart",b));break;case 2:this.s(new Qh("tileloadend",b));break;case 3:this.s(new Qh("tileloaderror",b))}};l.eb=function(b){this.a.clear();this.tileLoadFunction=b;this.u()};l.Ja=function(b){this.a.clear();this.tileUrlFunction=b;this.u()};l.Va=function(b){this.Ja(Sp(Vp(b),this.tileGrid));this.urls=[b]};
+l.Wa=function(b){this.Ja(Sp(b,this.tileGrid));this.urls=b};l.Yf=function(b,c,d){b=this.Ab(b,c,d);qh(this.a,b)&&this.a.get(b)};function Xp(b){Wp.call(this,{attributions:b.attributions,df:128,extent:b.extent,logo:b.logo,opaque:b.opaque,projection:b.projection,state:b.state?b.state:void 0,tileGrid:b.tileGrid,tileLoadFunction:b.tileLoadFunction?b.tileLoadFunction:Yp,tileUrlFunction:b.tileUrlFunction,tilePixelRatio:b.tilePixelRatio,url:b.url,urls:b.urls,wrapX:void 0===b.wrapX?!0:b.wrapX});this.g=b.format?b.format:null;this.tileClass=b.tileClass?b.tileClass:Co}y(Xp,Wp);
+Xp.prototype.Pb=function(b,c,d,e,f){var g=this.Ab(b,c,d);if(qh(this.a,g))return this.a.get(g);b=[b,c,d];e=(c=Ph(this,b,f))?this.tileUrlFunction(c,e,f):void 0;f=new this.tileClass(b,void 0!==e?0:4,void 0!==e?e:"",this.g,this.tileLoadFunction,f);D(f,"change",this.sh,!1,this);this.a.set(g,f);return f};function Yp(b,c){b.ai(sp(c,b.j))};function Zp(b){um.call(this,b);this.c=Ni();this.b=!1;this.g=[];this.i=Md();this.l=[NaN,NaN];this.o=Bd()}y(Zp,um);
+Zp.prototype.G=function(b,c,d){var e=b.pixelRatio,f=c.Bb?b.skippedFeatureUids:{},g=b.viewState,h=g.center,k=g.projection,m=g.resolution,n=g.rotation,p=this.a,q=p.ea(),r=wm(this,b,0);vm(this,"precompose",d,b,r);cd(p,"render")?(this.c.canvas.width=d.canvas.width,this.c.canvas.height=d.canvas.height,p=this.c):p=d;var t=p.globalAlpha;p.globalAlpha=c.opacity;c=this.g;var x=q.tileGrid,z,A,B,v,L,M;A=0;for(B=c.length;A<B;++A)v=c[A],z=v.a[0],L=x.Ka(z),M=q.Qb(z,e,k),L=M[0]/md(L,this.l)[0],z=x.$(z),z/=L,"tile-pixels"==
+v.b.f&&(r=ge(x.Aa(v.a,this.i)),r=gk(this.o,e*b.size[0]/2,e*b.size[1]/2,e*z/m,e*z/m,g.rotation,(r[0]-h[0])/z,(h[1]-r[1])/z)),v.c.Hd.b(p,e,r,n,f);r=wm(this,b,0);p!=d&&(vm(this,"render",p,b,r),d.drawImage(p.canvas,0,0));p.globalAlpha=t;vm(this,"postcompose",d,b,r)};
+function $p(b,c,d,e){function f(b){var c,e=b.ac();e?c=e.call(b,t):(e=d.b)&&(c=e(b,t));if(c){ga(c)||(c=[c]);var e=z,f=x;if(c){var g=!1;if(ga(c))for(var h=0,m=c.length;h<m;++h)g=Vm(f,b,c[h],e,this.j,this)||g;else g=Vm(f,b,c,e,this.j,this)||g;b=g}else b=!1;this.b=this.b||b;k.kd=k.kd||b}}var g=d.f,h=fm(d)||null,k=c.c;if(k.kd||k.Rh!=g||k.Tf!=h){sc(k.Hd);k.Hd=null;k.kd=!1;var m=d.ea(),n=m.tileGrid,p=c.a,q="tile-pixels"==c.b.f,r;q?(r=m.Qb(p[0],e,c.b),r=[0,0,r[0],r[1]]):r=n.Aa(p);var t=n.$(p[0]),m=q?m.v:
+t;k.kd=!1;var x=new Om(0,r,m,d.a),z=Um(m,e);c=c.i;h&&h!==k.Tf&&c.sort(h);c.forEach(f,b);Pm(x);k.Rh=g;k.Tf=h;k.Hd=x}}
+Zp.prototype.ab=function(b,c,d,e){var f=c.viewState.resolution,g=c.viewState.rotation,h=this.a,k=c.layerStates[w(h)],m={},n=this.g,p=h.ea(),q=p.tileGrid,r,t,x,z,A,B;x=0;for(z=n.length;x<z;++x)B=n[x],t=B.a,A=p.tileGrid.Aa(t,this.i),Td(A,b)&&("tile-pixels"===B.b.f?(A=ge(A),f=p.v,t=q.$(t[0])/f,t=[(b[0]-A[0])/t,(A[1]-b[1])/t]):t=b,B=B.c.Hd,r=r||B.c(t,f,g,k.Bb?c.skippedFeatureUids:{},function(b){var c=w(b).toString();if(!(c in m))return m[c]=!0,d.call(e,b,h)}));return r};Zp.prototype.j=function(){kk(this)};
+Zp.prototype.Ad=function(b,c){var d=this.a,e=d.ea();nk(b.attributions,e.i);ok(b,e);var f=b.viewHints[0],g=b.viewHints[1],h=d.j,k=d.l;if(!this.b&&!h&&f||!k&&g)return!0;g=b.extent;c.extent&&(g=ne(g,c.extent));if(ie(g))return!1;for(var f=b.viewState,h=f.projection,k=f.resolution,f=b.pixelRatio,m=e.tileGrid,n=m.a,p=n.length-1;0<p&&n[p]<k;)--p;n=Dh(m,g,p);pk(b.usedTiles,e,p,n);rk(b,e,m,f,h,g,p,d.g());mk(b,e);g={};g[p]={};var q=this.cd(e,h,g),r=d.U(),t=this.i,x=new fg(0,0,0,0),z,A,B;for(A=n.a;A<=n.c;++A)for(B=
+n.f;B<=n.b;++B)k=e.Pb(p,A,B,f,h),z=k.state,2==z||4==z||3==z&&!r?g[p][eg(k.a)]=k:(z=Ch(m,k.a,q,x,t),z||(k=Eh(m,k.a,x,t))&&q(p+1,k));this.b=!1;e=Object.keys(g).map(Number);e.sort(ub);for(var h=[],v,m=0,p=e.length;m<p;++m)for(v in k=e[m],n=g[k],n)k=n[v],2==k.state&&(h.push(k),$p(this,k,d,f));this.g=h;return!0};function aq(b,c){xk.call(this,0,c);this.b=Ni();this.a=this.b.canvas;this.a.style.width="100%";this.a.style.height="100%";this.a.className="ol-unselectable";Lg(b,this.a,0);this.f=!0;this.g=Bd()}y(aq,xk);aq.prototype.hf=function(b){return b instanceof Pl?new Op(b):b instanceof G?new Pp(b):b instanceof I?new Zp(b):b instanceof H?new Qp(b):null};
+function bq(b,c,d){var e=b.i,f=b.b;if(cd(e,c)){var g=d.extent,h=d.pixelRatio,k=d.viewState.rotation,m=d.pixelRatio,n=d.viewState,p=n.resolution;b=gk(b.g,b.a.width/2,b.a.height/2,m/p,-m/p,-n.rotation,-n.center[0],-n.center[1]);g=new gm(f,h,g,b,k);e.s(new bk(c,e,g,d,f,null));tm(g)}}aq.prototype.W=function(){return"canvas"};
+aq.prototype.Ne=function(b){if(b){var c=this.b,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);yk(b);bq(this,"precompose",b);d=b.layerStatesArray;pb(d);var e=b.viewState.resolution,f,g,h,k;f=0;for(g=d.length;f<g;++f)k=d[f],h=k.layer,h=Ak(this,h),dk(k,e)&&"ready"==k.O&&h.Ad(b,k)&&h.G(b,k,c);bq(this,"postcompose",b);this.f||(gh(this.a,!0),this.f=!0);Bk(this,b);b.postRenderFunctions.push(zk)}else this.f&&
+(gh(this.a,!1),this.f=!1)};function cq(b,c){jk.call(this,b);this.target=c}y(cq,jk);cq.prototype.g=za;cq.prototype.l=za;function dq(b){var c=document.createElement("DIV");c.style.position="absolute";cq.call(this,b,c);this.b=null;this.c=Dd()}y(dq,cq);dq.prototype.ab=function(b,c,d,e){var f=this.a;return f.ea().ye(b,c.viewState.resolution,c.viewState.rotation,c.skippedFeatureUids,function(b){return d.call(e,b,f)})};dq.prototype.g=function(){Kg(this.target);this.b=null};
+dq.prototype.i=function(b,c){var d=b.viewState,e=d.center,f=d.resolution,g=d.rotation,h=this.b,k=this.a.ea(),m=b.viewHints,n=b.extent;void 0!==c.extent&&(n=ne(n,c.extent));m[0]||m[1]||ie(n)||(d=k.C(n,f,b.pixelRatio,d.projection))&&lk(this,d)&&(h=d);h&&(m=h.J(),n=h.$(),d=Bd(),gk(d,b.size[0]/2,b.size[1]/2,n/f,n/f,g,(m[0]-e[0])/n,(e[1]-m[3])/n),h!=this.b&&(e=h.a(this),e.style.maxWidth="none",e.style.position="absolute",Kg(this.target),this.target.appendChild(e),this.b=h),hk(d,this.c)||(Ri(this.target,
+d),Ed(this.c,d)),nk(b.attributions,h.i),ok(b,k));return!0};function eq(b){var c=document.createElement("DIV");c.style.position="absolute";cq.call(this,b,c);this.c=!0;this.o=1;this.j=0;this.b={}}y(eq,cq);eq.prototype.g=function(){Kg(this.target);this.j=0};
+eq.prototype.i=function(b,c){if(!c.visible)return this.c&&(gh(this.target,!1),this.c=!1),!0;var d=b.pixelRatio,e=b.viewState,f=e.projection,g=this.a,h=g.ea(),k=h.ib(f),m=h.ae(),n=Ih(k,e.resolution),p=k.$(n),q=e.center,r;p==e.resolution?(q=qk(q,p,b.size),r=me(q,p,e.rotation,b.size)):r=b.extent;void 0!==c.extent&&(r=ne(r,c.extent));var p=Fh(k,r,p),t={};t[n]={};var x=this.cd(h,f,t),z=g.b(),A=Md(),B=new fg(0,0,0,0),v,L,M,J;for(M=p.a;M<=p.c;++M)for(J=p.f;J<=p.b;++J)v=h.Pb(n,M,J,d,f),L=v.state,L=2==L||
+4==L||3==L&&!z,!L&&v.f&&(v=v.f),L=v.state,2==L?t[n][eg(v.a)]=v:4==L||3==L&&!z||(L=Ch(k,v.a,x,B,A),L||(v=Eh(k,v.a,B,A))&&x(n+1,v));var C;if(this.j!=h.f){for(C in this.b)z=this.b[+C],Mg(z.target);this.b={};this.j=h.f}A=Object.keys(t).map(Number);A.sort(ub);var x={},sa;M=0;for(J=A.length;M<J;++M){C=A[M];C in this.b?z=this.b[C]:(z=k.ge(q,C),z=new fq(k,z),x[C]=!0,this.b[C]=z);C=t[C];for(sa in C){v=z;L=C[sa];var la=m,K=L.a,ma=K[0],Ua=K[1],Nb=K[2],K=eg(K);if(!(K in v.f)){var ma=md(v.g.Ka(ma),v.l),na=L.Ta(v),
+Fa=na.style;Fa.maxWidth="none";var ad=void 0,Qc=void 0;0<la?(ad=document.createElement("DIV"),Qc=ad.style,Qc.overflow="hidden",Qc.width=ma[0]+"px",Qc.height=ma[1]+"px",Fa.position="absolute",Fa.left=-la+"px",Fa.top=-la+"px",Fa.width=ma[0]+2*la+"px",Fa.height=ma[1]+2*la+"px",ad.appendChild(na)):(Fa.width=ma[0]+"px",Fa.height=ma[1]+"px",ad=na,Qc=Fa);Qc.position="absolute";Qc.left=(Ua-v.b[1])*ma[0]+"px";Qc.top=(v.b[2]-Nb)*ma[1]+"px";v.a||(v.a=document.createDocumentFragment());v.a.appendChild(ad);v.f[K]=
+L}}z.a&&(z.target.appendChild(z.a),z.a=null)}m=Object.keys(this.b).map(Number);m.sort(ub);M=Bd();sa=0;for(A=m.length;sa<A;++sa)if(C=m[sa],z=this.b[C],C in t)if(v=z.$(),J=z.Da(),gk(M,b.size[0]/2,b.size[1]/2,v/e.resolution,v/e.resolution,e.rotation,(J[0]-q[0])/v,(q[1]-J[1])/v),z.setTransform(M),C in x){for(--C;0<=C;--C)if(C in this.b){J=this.b[C].target;J.parentNode&&J.parentNode.insertBefore(z.target,J.nextSibling);break}0>C&&Lg(this.target,z.target,0)}else{if(!b.viewHints[0]&&!b.viewHints[1]){L=Dh(z.g,
+r,z.b[0],B);C=[];v=J=void 0;for(v in z.f)J=z.f[v],L.contains(J.a)||C.push(J);la=L=void 0;L=0;for(la=C.length;L<la;++L)J=C[L],v=eg(J.a),Mg(J.Ta(z)),delete z.f[v]}}else Mg(z.target),delete this.b[C];c.opacity!=this.o&&(this.o=this.target.style.opacity=c.opacity);c.visible&&!this.c&&(gh(this.target,!0),this.c=!0);pk(b.usedTiles,h,n,p);rk(b,h,k,d,f,r,n,g.a());mk(b,h);ok(b,h);return!0};
+function fq(b,c){this.target=document.createElement("DIV");this.target.style.position="absolute";this.target.style.width="100%";this.target.style.height="100%";this.g=b;this.b=c;this.i=ge(b.Aa(c));this.j=b.$(c[0]);this.f={};this.a=null;this.c=Dd();this.l=[0,0]}fq.prototype.Da=function(){return this.i};fq.prototype.$=function(){return this.j};fq.prototype.setTransform=function(b){hk(b,this.c)||(Ri(this.target,b),Ed(this.c,b))};function gq(b){this.j=Ni();var c=this.j.canvas;c.style.maxWidth="none";c.style.position="absolute";cq.call(this,b,c);this.c=!1;this.v=-1;this.B=NaN;this.o=Md();this.b=this.G=null;this.O=Bd();this.D=Bd()}y(gq,cq);
+gq.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],m=h*d,n=k*d,e=gk(this.O,d*h/2,d*k/2,d/g,-d/g,-f,-e[0],-e[1]),g=this.j;g.canvas.width=m;g.canvas.height=n;h=gk(this.D,0,0,1/d,1/d,0,-(m-h)/2*d,-(n-k)/2*d);Ri(g.canvas,h);hq(this,"precompose",b,e);(h=this.b)&&!h.La()&&(g.globalAlpha=c.opacity,h.b(g,d,e,f,c.Bb?b.skippedFeatureUids:{}),hq(this,"render",b,e));hq(this,"postcompose",b,e)};
+function hq(b,c,d,e){var f=b.j;b=b.a;cd(b,c)&&(e=new gm(f,d.pixelRatio,d.extent,e,d.viewState.rotation),b.s(new bk(c,b,e,d,f,null)),tm(e))}gq.prototype.ab=function(b,c,d,e){if(this.b){var f=c.viewState.resolution,g=c.viewState.rotation,h=this.a,k=c.layerStates[w(h)],m={};return this.b.c(b,f,g,k.Bb?c.skippedFeatureUids:{},function(b){var c=w(b).toString();if(!(c in m))return m[c]=!0,d.call(e,b,h)})}};gq.prototype.C=function(){kk(this)};
+gq.prototype.i=function(b){function c(b){var c,e=b.ac();e?c=e.call(b,m):(e=d.b)&&(c=e(b,m));if(c){if(c){e=!1;if(ga(c))for(var f=0,g=c.length;f<g;++f)e=Vm(p,b,c[f],Um(m,n),this.C,this)||e;else e=Vm(p,b,c,Um(m,n),this.C,this)||e;b=e}else b=!1;this.c=this.c||b}}var d=this.a,e=d.ea();nk(b.attributions,e.i);ok(b,e);var f=b.viewHints[0],g=b.viewHints[1],h=d.j,k=d.l;if(!this.c&&!h&&f||!k&&g)return!0;var g=b.extent,h=b.viewState,f=h.projection,m=h.resolution,n=b.pixelRatio;b=d.f;k=d.a;h=fm(d);void 0===h&&
+(h=Tm);g=Qd(g,k*m);if(!this.c&&this.B==m&&this.v==b&&this.G==h&&Vd(this.o,g))return!0;sc(this.b);this.b=null;this.c=!1;var p=new Om(.5*m/n,g,m,d.a);e.Nc(g,m,f);if(h){var q=[];e.pb(g,function(b){q.push(b)},this);q.sort(h);q.forEach(c,this)}else e.pb(g,c,this);Pm(p);this.B=m;this.v=b;this.G=h;this.o=g;this.b=p;return!0};function iq(b,c){xk.call(this,0,c);this.b=Ni();var d=this.b.canvas;d.style.position="absolute";d.style.width="100%";d.style.height="100%";d.className="ol-unselectable";Lg(b,d,0);this.g=Bd();this.a=document.createElement("DIV");this.a.className="ol-unselectable";d=this.a.style;d.position="absolute";d.width="100%";d.height="100%";D(this.a,"touchstart",vc);Lg(b,this.a,0);this.f=!0}y(iq,xk);iq.prototype.Y=function(){Mg(this.a);iq.ca.Y.call(this)};
+iq.prototype.hf=function(b){if(b instanceof Pl)b=new dq(b);else if(b instanceof G)b=new eq(b);else if(b instanceof H)b=new gq(b);else return null;return b};function jq(b,c,d){var e=b.i;if(cd(e,c)){var f=d.extent,g=d.pixelRatio,h=d.viewState,k=h.rotation,m=b.b,n=m.canvas;gk(b.g,n.width/2,n.height/2,g/h.resolution,-g/h.resolution,-h.rotation,-h.center[0],-h.center[1]);b=new gm(m,g,f,b.g,k);e.s(new bk(c,e,b,d,m,null));tm(b)}}iq.prototype.W=function(){return"dom"};
+iq.prototype.Ne=function(b){if(b){var c=this.i;if(cd(c,"precompose")||cd(c,"postcompose")){var c=this.b.canvas,d=b.pixelRatio;c.width=b.size[0]*d;c.height=b.size[1]*d}jq(this,"precompose",b);c=b.layerStatesArray;pb(c);var d=b.viewState.resolution,e,f,g,h;e=0;for(f=c.length;e<f;++e)h=c[e],g=h.layer,g=Ak(this,g),Lg(this.a,g.target,e),dk(h,d)&&"ready"==h.O?g.i(b,h)&&g.l(b,h):g.g();var c=b.layerStates,k;for(k in this.c)k in c||(g=this.c[k],Mg(g.target));this.f||(gh(this.a,!0),this.f=!0);yk(b);Bk(this,
+b);b.postRenderFunctions.push(zk);jq(this,"postcompose",b)}else this.f&&(gh(this.a,!1),this.f=!1)};function kq(b){this.a=b}function lq(b){this.a=b}y(lq,kq);lq.prototype.W=function(){return 35632};function mq(b){this.a=b}y(mq,kq);mq.prototype.W=function(){return 35633};function nq(){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;}"}y(nq,lq);ea(nq);
+function oq(){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;}"}y(oq,mq);ea(oq);
+function pq(b,c){this.l=b.getUniformLocation(c,"j");this.o=b.getUniformLocation(c,"i");this.i=b.getUniformLocation(c,"k");this.j=b.getUniformLocation(c,"h");this.a=b.getAttribLocation(c,"e");this.f=b.getAttribLocation(c,"f");this.c=b.getAttribLocation(c,"c");this.b=b.getAttribLocation(c,"g");this.g=b.getAttribLocation(c,"d")};function qq(b){this.a=void 0!==b?b:[]};function rq(b,c){this.G=b;this.a=c;this.f={};this.i={};this.g={};this.l=this.o=this.c=this.j=null;(this.b=vb(ya,"OES_element_index_uint"))&&c.getExtension("OES_element_index_uint");D(this.G,"webglcontextlost",this.Hn,!1,this);D(this.G,"webglcontextrestored",this.In,!1,this)}
+function sq(b,c,d){var e=b.a,f=d.a,g=w(d);if(g in b.f)e.bindBuffer(c,b.f[g].buffer);else{var h=e.createBuffer();e.bindBuffer(c,h);var k;34962==c?k=new Float32Array(f):34963==c&&(k=b.b?new Uint32Array(f):new Uint16Array(f));e.bufferData(c,k,35044);b.f[g]={Fb:d,buffer:h}}}function tq(b,c){var d=b.a,e=w(c),f=b.f[e];d.isContextLost()||d.deleteBuffer(f.buffer);delete b.f[e]}l=rq.prototype;
+l.Y=function(){var b=this.a;b.isContextLost()||(Ib(this.f,function(c){b.deleteBuffer(c.buffer)}),Ib(this.g,function(c){b.deleteProgram(c)}),Ib(this.i,function(c){b.deleteShader(c)}),b.deleteFramebuffer(this.c),b.deleteRenderbuffer(this.l),b.deleteTexture(this.o))};l.Gn=function(){return this.a};
+function uq(b){if(!b.c){var c=b.a,d=c.createFramebuffer();c.bindFramebuffer(c.FRAMEBUFFER,d);var e=vq(c,1,1),f=c.createRenderbuffer();c.bindRenderbuffer(c.RENDERBUFFER,f);c.renderbufferStorage(c.RENDERBUFFER,c.DEPTH_COMPONENT16,1,1);c.framebufferTexture2D(c.FRAMEBUFFER,c.COLOR_ATTACHMENT0,c.TEXTURE_2D,e,0);c.framebufferRenderbuffer(c.FRAMEBUFFER,c.DEPTH_ATTACHMENT,c.RENDERBUFFER,f);c.bindTexture(c.TEXTURE_2D,null);c.bindRenderbuffer(c.RENDERBUFFER,null);c.bindFramebuffer(c.FRAMEBUFFER,null);b.c=d;
+b.o=e;b.l=f}return b.c}function wq(b,c){var d=w(c);if(d in b.i)return b.i[d];var e=b.a,f=e.createShader(c.W());e.shaderSource(f,c.a);e.compileShader(f);return b.i[d]=f}function xq(b,c,d){var e=w(c)+"/"+w(d);if(e in b.g)return b.g[e];var f=b.a,g=f.createProgram();f.attachShader(g,wq(b,c));f.attachShader(g,wq(b,d));f.linkProgram(g);return b.g[e]=g}l.Hn=function(){Qb(this.f);Qb(this.i);Qb(this.g);this.l=this.o=this.c=this.j=null};l.In=function(){};
+l.He=function(b){if(b==this.j)return!1;this.a.useProgram(b);this.j=b;return!0};function yq(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);void 0!==c&&b.texParameteri(3553,10242,c);void 0!==d&&b.texParameteri(3553,10243,d);return e}function vq(b,c,d){var e=yq(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 zq(b,c){var d=yq(b,33071,33071);b.texImage2D(b.TEXTURE_2D,0,b.RGBA,b.RGBA,b.UNSIGNED_BYTE,c);return d};function Aq(b,c){this.D=this.C=void 0;this.o=le(c);this.v=[];this.i=[];this.na=void 0;this.g=[];this.c=[];this.U=this.va=void 0;this.f=[];this.O=this.l=null;this.T=void 0;this.hb=Dd();this.Db=Dd();this.fa=this.V=void 0;this.Eb=Dd();this.gb=this.oa=this.ga=void 0;this.wa=[];this.j=[];this.a=[];this.B=null;this.b=[];this.G=[];this.pa=void 0}y(Aq,ak);
+function Bq(b,c){var d=b.B,e=b.l,f=b.wa,g=b.j,h=c.a;return function(){if(!h.isContextLost()){var b,m;b=0;for(m=f.length;b<m;++b)h.deleteTexture(f[b]);b=0;for(m=g.length;b<m;++b)h.deleteTexture(g[b])}tq(c,d);tq(c,e)}}
+function Cq(b,c,d,e){var f=b.C,g=b.D,h=b.na,k=b.va,m=b.U,n=b.T,p=b.V,q=b.fa,r=b.ga?1:0,t=b.oa,x=b.gb,z=b.pa,A=Math.cos(t),t=Math.sin(t),B=b.f.length,v=b.a.length,L,M,J,C,sa,la;for(L=0;L<d;L+=e)sa=c[L]-b.o[0],la=c[L+1]-b.o[1],M=v/8,J=-x*f,C=-x*(h-g),b.a[v++]=sa,b.a[v++]=la,b.a[v++]=J*A-C*t,b.a[v++]=J*t+C*A,b.a[v++]=p/m,b.a[v++]=(q+h)/k,b.a[v++]=n,b.a[v++]=r,J=x*(z-f),C=-x*(h-g),b.a[v++]=sa,b.a[v++]=la,b.a[v++]=J*A-C*t,b.a[v++]=J*t+C*A,b.a[v++]=(p+z)/m,b.a[v++]=(q+h)/k,b.a[v++]=n,b.a[v++]=r,J=x*(z-
+f),C=x*g,b.a[v++]=sa,b.a[v++]=la,b.a[v++]=J*A-C*t,b.a[v++]=J*t+C*A,b.a[v++]=(p+z)/m,b.a[v++]=q/k,b.a[v++]=n,b.a[v++]=r,J=-x*f,C=x*g,b.a[v++]=sa,b.a[v++]=la,b.a[v++]=J*A-C*t,b.a[v++]=J*t+C*A,b.a[v++]=p/m,b.a[v++]=q/k,b.a[v++]=n,b.a[v++]=r,b.f[B++]=M,b.f[B++]=M+1,b.f[B++]=M+2,b.f[B++]=M,b.f[B++]=M+2,b.f[B++]=M+3}Aq.prototype.Gb=function(b,c){this.b.push(this.f.length);this.G.push(c);var d=b.ia();Cq(this,d,d.length,b.ra())};
+Aq.prototype.Hb=function(b,c){this.b.push(this.f.length);this.G.push(c);var d=b.ia();Cq(this,d,d.length,b.ra())};function Dq(b,c){var d=c.a;b.v.push(b.f.length);b.i.push(b.f.length);b.B=new qq(b.a);sq(c,34962,b.B);b.l=new qq(b.f);sq(c,34963,b.l);var e={};Eq(b.wa,b.g,e,d);Eq(b.j,b.c,e,d);b.C=void 0;b.D=void 0;b.na=void 0;b.g=null;b.c=null;b.va=void 0;b.U=void 0;b.f=null;b.T=void 0;b.V=void 0;b.fa=void 0;b.ga=void 0;b.oa=void 0;b.gb=void 0;b.a=null;b.pa=void 0}
+function Eq(b,c,d,e){var f,g,h,k=c.length;for(h=0;h<k;++h)f=c[h],g=w(f).toString(),g in d?f=d[g]:(f=zq(e,f),d[g]=f),b[h]=f}
+function Fq(b,c,d,e,f,g,h,k,m,n,p){var q=c.a;sq(c,34962,b.B);sq(c,34963,b.l);var r=nq.Yb(),t=oq.Yb(),t=xq(c,r,t);b.O?r=b.O:(r=new pq(q,t),b.O=r);c.He(t);q.enableVertexAttribArray(r.c);q.vertexAttribPointer(r.c,2,5126,!1,32,0);q.enableVertexAttribArray(r.a);q.vertexAttribPointer(r.a,2,5126,!1,32,8);q.enableVertexAttribArray(r.g);q.vertexAttribPointer(r.g,2,5126,!1,32,16);q.enableVertexAttribArray(r.f);q.vertexAttribPointer(r.f,1,5126,!1,32,24);q.enableVertexAttribArray(r.b);q.vertexAttribPointer(r.b,
+1,5126,!1,32,28);t=b.Eb;gk(t,0,0,2/(e*g[0]),2/(e*g[1]),-f,-(d[0]-b.o[0]),-(d[1]-b.o[1]));d=b.Db;e=2/g[0];g=2/g[1];Fd(d);d[0]=e;d[5]=g;d[10]=1;d[15]=1;g=b.hb;Fd(g);0!==f&&Kd(g,-f);q.uniformMatrix4fv(r.j,!1,t);q.uniformMatrix4fv(r.o,!1,d);q.uniformMatrix4fv(r.l,!1,g);q.uniform1f(r.i,h);var x;if(void 0===m)Gq(b,q,c,k,b.wa,b.v);else{if(n)a:{f=c.b?5125:5123;c=c.b?4:2;g=b.b.length-1;for(h=b.j.length-1;0<=h;--h)for(q.bindTexture(3553,b.j[h]),n=0<h?b.i[h-1]:0,t=b.i[h];0<=g&&b.b[g]>=n;){x=b.b[g];d=b.G[g];
+e=w(d).toString();if(void 0===k[e]&&d.X()&&(void 0===p||oe(p,d.X().J()))&&(q.clear(q.COLOR_BUFFER_BIT|q.DEPTH_BUFFER_BIT),q.drawElements(4,t-x,f,x*c),t=m(d))){b=t;break a}t=x;g--}b=void 0}else q.clear(q.COLOR_BUFFER_BIT|q.DEPTH_BUFFER_BIT),Gq(b,q,c,k,b.j,b.i),b=(b=m(null))?b:void 0;x=b}q.disableVertexAttribArray(r.c);q.disableVertexAttribArray(r.a);q.disableVertexAttribArray(r.g);q.disableVertexAttribArray(r.f);q.disableVertexAttribArray(r.b);return x}
+function Gq(b,c,d,e,f,g){var h=d.b?5125:5123;d=d.b?4:2;if(Pb(e)){var k;b=0;e=f.length;for(k=0;b<e;++b){c.bindTexture(3553,f[b]);var m=g[b];c.drawElements(4,m-k,h,k*d);k=m}}else{k=0;var n,m=0;for(n=f.length;m<n;++m){c.bindTexture(3553,f[m]);for(var p=0<m?g[m-1]:0,q=g[m],r=p;k<b.b.length&&b.b[k]<=q;){var t=w(b.G[k]).toString();void 0!==e[t]?(r!==p&&c.drawElements(4,p-r,h,r*d),p=r=k===b.b.length-1?q:b.b[k+1]):p=k===b.b.length-1?q:b.b[k+1];k++}r!==p&&c.drawElements(4,p-r,h,r*d)}}}
+Aq.prototype.ub=function(b){var c=b.Xb(),d=b.gc(1),e=b.rd(),f=b.Ae(1),g=b.B,h=b.Da(),k=b.C,m=b.G,n=b.Cb();b=b.j;var p;0===this.g.length?this.g.push(d):(p=this.g[this.g.length-1],w(p)!=w(d)&&(this.v.push(this.f.length),this.g.push(d)));0===this.c.length?this.c.push(f):(p=this.c[this.c.length-1],w(p)!=w(f)&&(this.i.push(this.f.length),this.c.push(f)));this.C=c[0];this.D=c[1];this.na=n[1];this.va=e[1];this.U=e[0];this.T=g;this.V=h[0];this.fa=h[1];this.oa=m;this.ga=k;this.gb=b;this.pa=n[0]};
+function Hq(b,c,d){this.i=c;this.j=b;this.g=d;this.f={}}function Iq(b,c){var d=[],e;for(e in b.f)d.push(Bq(b.f[e],c));return xe.apply(null,d)}function Jq(b,c){for(var d in b.f)Dq(b.f[d],c)}Hq.prototype.a=function(b,c){var d=this.f[c];void 0===d&&(d=new Kq[c](this.j,this.i),this.f[c]=d);return d};Hq.prototype.La=function(){return Pb(this.f)};Hq.prototype.b=function(b,c,d,e,f,g,h,k){var m,n;g=0;for(m=zm.length;g<m;++g)n=this.f[zm[g]],void 0!==n&&Fq(n,b,c,d,e,f,h,k,void 0,!1)};
+function Lq(b,c,d,e,f,g,h,k,m,n){var p=Mq,q,r;for(q=zm.length-1;0<=q;--q)if(r=b.f[zm[q]],void 0!==r&&(r=Fq(r,c,d,e,f,p,g,h,k,m,n)))return r}Hq.prototype.c=function(b,c,d,e,f,g,h,k,m,n){var p=c.a;p.bindFramebuffer(p.FRAMEBUFFER,uq(c));var q;void 0!==this.g&&(q=Qd(Xd(b),e*this.g));return Lq(this,c,b,e,f,k,m,function(b){var c=new Uint8Array(4);p.readPixels(0,0,1,1,p.RGBA,p.UNSIGNED_BYTE,c);if(0<c[3]&&(b=n(b)))return b},!0,q)};
+function Nq(b,c,d,e,f,g,h){var k=d.a;k.bindFramebuffer(k.FRAMEBUFFER,uq(d));return void 0!==Lq(b,d,c,e,f,g,h,function(){var b=new Uint8Array(4);k.readPixels(0,0,1,1,k.RGBA,k.UNSIGNED_BYTE,b);return 0<b[3]},!1)}var Kq={Image:Aq},Mq=[1,1];function Oq(b,c,d,e,f,g){this.f=b;this.g=c;this.c=g;this.l=f;this.j=e;this.i=d;this.b=null;this.a={}}y(Oq,ak);l=Oq.prototype;l.md=function(b,c){var d=b.toString(),e=this.a[d];void 0!==e?e.push(c):this.a[d]=[c]};l.Gc=function(){};l.jf=function(b,c){var d=(0,c.c)(b);if(d&&oe(this.c,d.J())){var e=c.a;void 0===e&&(e=0);this.md(e,function(b){b.bb(c.g,c.b);b.ub(c.f);b.cb(c.Ca());var e=Pq[d.W()];e&&e.call(b,d,null)})}};
+l.Yd=function(b,c){var d=b.c,e,f;e=0;for(f=d.length;e<f;++e){var g=d[e],h=Pq[g.W()];h&&h.call(this,g,c)}};l.Hb=function(b,c){var d=this.f,e=(new Hq(1,this.c)).a(0,"Image");e.ub(this.b);e.Hb(b,c);Dq(e,d);Fq(e,this.f,this.g,this.i,this.j,this.l,1,{},void 0,!1);Bq(e,d)()};l.Wb=function(){};l.Hc=function(){};l.Gb=function(b,c){var d=this.f,e=(new Hq(1,this.c)).a(0,"Image");e.ub(this.b);e.Gb(b,c);Dq(e,d);Fq(e,this.f,this.g,this.i,this.j,this.l,1,{},void 0,!1);Bq(e,d)()};l.Ic=function(){};l.Jc=function(){};
+l.Ib=function(){};l.bb=function(){};l.ub=function(b){this.b=b};l.cb=function(){};var Pq={Point:Oq.prototype.Hb,MultiPoint:Oq.prototype.Gb,GeometryCollection:Oq.prototype.Yd};function Qq(){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;}"}y(Qq,lq);ea(Qq);function Rq(){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;}"}y(Rq,mq);ea(Rq);
+function Sq(b,c){this.b=b.getUniformLocation(c,"f");this.c=b.getUniformLocation(c,"e");this.i=b.getUniformLocation(c,"d");this.g=b.getUniformLocation(c,"g");this.a=b.getAttribLocation(c,"b");this.f=b.getAttribLocation(c,"c")};function Tq(b,c){jk.call(this,c);this.b=b;this.U=new qq([-1,-1,0,0,1,-1,1,0,-1,1,0,1,1,1,1,1]);this.g=this.mb=null;this.i=void 0;this.o=Bd();this.B=Dd();this.G=null}y(Tq,jk);
+function Uq(b,c,d){var e=b.b.b;if(void 0===b.i||b.i!=d){c.postRenderFunctions.push(va(function(b,c,d){b.isContextLost()||(b.deleteFramebuffer(c),b.deleteTexture(d))},e,b.g,b.mb));c=vq(e,d,d);var f=e.createFramebuffer();e.bindFramebuffer(36160,f);e.framebufferTexture2D(36160,36064,3553,c,0);b.mb=c;b.g=f;b.i=d}else e.bindFramebuffer(36160,b.g)}
+Tq.prototype.nh=function(b,c,d){Vq(this,"precompose",d,b);sq(d,34962,this.U);var e=d.a,f=Qq.Yb(),g=Rq.Yb(),f=xq(d,f,g);this.G?g=this.G:this.G=g=new Sq(e,f);d.He(f)&&(e.enableVertexAttribArray(g.a),e.vertexAttribPointer(g.a,2,5126,!1,16,0),e.enableVertexAttribArray(g.f),e.vertexAttribPointer(g.f,2,5126,!1,16,8),e.uniform1i(g.g,0));e.uniformMatrix4fv(g.i,!1,this.o);e.uniformMatrix4fv(g.c,!1,this.B);e.uniform1f(g.b,c.opacity);e.bindTexture(3553,this.mb);e.drawArrays(5,0,4);Vq(this,"postcompose",d,b)};
+function Vq(b,c,d,e){b=b.a;if(cd(b,c)){var f=e.viewState;b.s(new bk(c,b,new Oq(d,f.center,f.resolution,f.rotation,e.size,e.extent),e,null,d))}}Tq.prototype.Cf=function(){this.g=this.mb=null;this.i=void 0};function Wq(b,c){Tq.call(this,b,c);this.l=this.j=this.c=null}y(Wq,Tq);function Xq(b,c){var d=c.a();return zq(b.b.b,d)}Wq.prototype.ab=function(b,c,d,e){var f=this.a;return f.ea().ye(b,c.viewState.resolution,c.viewState.rotation,c.skippedFeatureUids,function(b){return d.call(e,b,f)})};
+Wq.prototype.Df=function(b,c){var d=this.b.b,e=b.pixelRatio,f=b.viewState,g=f.center,h=f.resolution,k=f.rotation,m=this.c,n=this.mb,p=this.a.ea(),q=b.viewHints,r=b.extent;void 0!==c.extent&&(r=ne(r,c.extent));q[0]||q[1]||ie(r)||(f=p.C(r,h,e,f.projection))&&lk(this,f)&&(m=f,n=Xq(this,f),this.mb&&b.postRenderFunctions.push(va(function(b,c){b.isContextLost()||b.deleteTexture(c)},d,this.mb)));m&&(d=this.b.g.G,Yq(this,d.width,d.height,e,g,h,k,m.J()),this.l=null,e=this.o,Fd(e),Jd(e,1,-1),Id(e,0,-1),this.c=
+m,this.mb=n,nk(b.attributions,m.i),ok(b,p));return!0};function Yq(b,c,d,e,f,g,h,k){c*=g;d*=g;b=b.B;Fd(b);Jd(b,2*e/c,2*e/d);Kd(b,-h);Id(b,k[0]-f[0],k[1]-f[1]);Jd(b,(k[2]-k[0])/2,(k[3]-k[1])/2);Id(b,1,1)}Wq.prototype.xe=function(b,c){return void 0!==this.ab(b,c,te,this)};
+Wq.prototype.vc=function(b,c,d,e){if(this.c&&this.c.a())if(this.a.ea()instanceof Mp){if(b=b.slice(),ik(c.pixelToCoordinateMatrix,b,b),this.ab(b,c,te,this))return d.call(e,this.a)}else{var f=[this.c.a().width,this.c.a().height];if(!this.l){var g=c.size;c=Bd();Fd(c);Id(c,-1,-1);Jd(c,2/g[0],2/g[1]);Id(c,0,g[1]);Jd(c,1,-1);g=Bd();Hd(this.B,g);var h=Bd();Fd(h);Id(h,0,f[1]);Jd(h,1,-1);Jd(h,f[0]/2,f[1]/2);Id(h,1,1);var k=Bd();Gd(h,g,k);Gd(k,c,k);this.l=k}c=[0,0];ik(this.l,b,c);if(!(0>c[0]||c[0]>f[0]||0>
+c[1]||c[1]>f[1])&&(this.j||(this.j=Ni(1,1)),this.j.clearRect(0,0,1,1),this.j.drawImage(this.c.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.a)}};function Zq(){this.a="precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}"}y(Zq,lq);ea(Zq);function $q(){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;}"}y($q,mq);ea($q);function ar(b,c){this.b=b.getUniformLocation(c,"e");this.c=b.getUniformLocation(c,"d");this.a=b.getAttribLocation(c,"b");this.f=b.getAttribLocation(c,"c")};function br(b,c){Tq.call(this,b,c);this.D=Zq.Yb();this.T=$q.Yb();this.c=null;this.C=new qq([0,0,0,1,1,0,1,1,0,1,0,0,1,1,1,0]);this.v=this.j=null;this.l=-1;this.O=[0,0]}y(br,Tq);l=br.prototype;l.Y=function(){tq(this.b.g,this.C);br.ca.Y.call(this)};l.cd=function(b,c,d){var e=this.b;return function(f,g){return Oh(b,c,f,g,function(b){var c=qh(e.f,b.$a());c&&(d[f]||(d[f]={}),d[f][b.a.toString()]=b);return c})}};l.Cf=function(){br.ca.Cf.call(this);this.c=null};
+l.Df=function(b,c,d){var e=this.b,f=d.a,g=b.viewState,h=g.projection,k=this.a,m=k.ea(),n=m.ib(h),p=Ih(n,g.resolution),q=n.$(p),r=m.Qb(p,b.pixelRatio,h),t=r[0]/md(n.Ka(p),this.O)[0],x=q/t,z=m.ae(),A=g.center,B;q==g.resolution?(A=qk(A,q,b.size),B=me(A,q,g.rotation,b.size)):B=b.extent;q=Fh(n,B,q);if(this.j&&ig(this.j,q)&&this.l==m.f)x=this.v;else{var v=[kg(q),jg(q)],L=Math.pow(2,Math.ceil(Math.log(Math.max(v[0]*r[0],v[1]*r[1]))/Math.LN2)),v=x*L,M=n.Da(p),J=M[0]+q.a*r[0]*x,x=M[1]+q.f*r[1]*x,x=[J,x,J+
+v,x+v];Uq(this,b,L);f.viewport(0,0,L,L);f.clearColor(0,0,0,0);f.clear(16384);f.disable(3042);L=xq(d,this.D,this.T);d.He(L);this.c||(this.c=new ar(f,L));sq(d,34962,this.C);f.enableVertexAttribArray(this.c.a);f.vertexAttribPointer(this.c.a,2,5126,!1,16,0);f.enableVertexAttribArray(this.c.f);f.vertexAttribPointer(this.c.f,2,5126,!1,16,8);f.uniform1i(this.c.b,0);d={};d[p]={};var C=this.cd(m,h,d),sa=k.b(),L=!0,J=Md(),la=new fg(0,0,0,0),K,ma,Ua;for(ma=q.a;ma<=q.c;++ma)for(Ua=q.f;Ua<=q.b;++Ua){M=m.Pb(p,
+ma,Ua,t,h);if(void 0!==c.extent&&(K=n.Aa(M.a,J),!oe(K,c.extent)))continue;K=M.state;K=2==K||4==K||3==K&&!sa;!K&&M.f&&(M=M.f);K=M.state;if(2==K){if(qh(e.f,M.$a())){d[p][eg(M.a)]=M;continue}}else if(4==K||3==K&&!sa)continue;L=!1;K=Ch(n,M.a,C,la,J);K||(M=Eh(n,M.a,la,J))&&C(p+1,M)}c=Object.keys(d).map(Number);c.sort(ub);for(var C=new Float32Array(4),Nb,na,Fa,sa=0,la=c.length;sa<la;++sa)for(Nb in na=d[c[sa]],na)M=na[Nb],K=n.Aa(M.a,J),ma=2*(K[2]-K[0])/v,Ua=2*(K[3]-K[1])/v,Fa=2*(K[0]-x[0])/v-1,K=2*(K[1]-
+x[1])/v-1,Ad(C,ma,Ua,Fa,K),f.uniform4fv(this.c.c,C),cr(e,M,r,z*t),f.drawArrays(5,0,4);L?(this.j=q,this.v=x,this.l=m.f):(this.v=this.j=null,this.l=-1,b.animate=!0)}pk(b.usedTiles,m,p,q);var ad=e.l;rk(b,m,n,t,h,B,p,k.a(),function(b){var c;(c=2!=b.state||qh(e.f,b.$a()))||(c=b.$a()in ad.b);c||ad.c([b,Hh(n,b.a),n.$(b.a[0]),r,z*t])},this);mk(b,m);ok(b,m);f=this.o;Fd(f);Id(f,(A[0]-x[0])/(x[2]-x[0]),(A[1]-x[1])/(x[3]-x[1]));0!==g.rotation&&Kd(f,g.rotation);Jd(f,b.size[0]*g.resolution/(x[2]-x[0]),b.size[1]*
+g.resolution/(x[3]-x[1]));Id(f,-.5,-.5);return!0};l.vc=function(b,c,d,e){if(this.g){var f=[0,0];ik(this.o,[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.b.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.a)}};function dr(b,c){Tq.call(this,b,c);this.l=!1;this.O=-1;this.D=NaN;this.v=Md();this.j=this.c=this.C=null}y(dr,Tq);l=dr.prototype;l.nh=function(b,c,d){this.j=c;var e=b.viewState,f=this.c;f&&!f.La()&&f.b(d,e.center,e.resolution,e.rotation,b.size,b.pixelRatio,c.opacity,c.Bb?b.skippedFeatureUids:{})};l.Y=function(){var b=this.c;b&&(Iq(b,this.b.g)(),this.c=null);dr.ca.Y.call(this)};
+l.ab=function(b,c,d,e){if(this.c&&this.j){var f=c.viewState,g=this.a,h=this.j,k={};return this.c.c(b,this.b.g,f.center,f.resolution,f.rotation,c.size,c.pixelRatio,h.opacity,h.Bb?c.skippedFeatureUids:{},function(b){var c=w(b).toString();if(!(c in k))return k[c]=!0,d.call(e,b,g)})}};l.xe=function(b,c){if(this.c&&this.j){var d=c.viewState;return Nq(this.c,b,this.b.g,d.resolution,d.rotation,this.j.opacity,c.skippedFeatureUids)}return!1};
+l.vc=function(b,c,d,e){b=b.slice();ik(c.pixelToCoordinateMatrix,b,b);if(this.xe(b,c))return d.call(e,this.a)};l.oh=function(){kk(this)};
+l.Df=function(b,c,d){function e(b){var c,d=b.ac();d?c=d.call(b,n):(d=f.b)&&(c=d(b,n));if(c){if(c){d=!1;if(ga(c))for(var e=0,g=c.length;e<g;++e)d=Vm(r,b,c[e],Um(n,p),this.oh,this)||d;else d=Vm(r,b,c,Um(n,p),this.oh,this)||d;b=d}else b=!1;this.l=this.l||b}}var f=this.a;c=f.ea();nk(b.attributions,c.i);ok(b,c);var g=b.viewHints[0],h=b.viewHints[1],k=f.j,m=f.l;if(!this.l&&!k&&g||!m&&h)return!0;var h=b.extent,k=b.viewState,g=k.projection,n=k.resolution,p=b.pixelRatio,k=f.f,q=f.a,m=fm(f);void 0===m&&(m=
+Tm);h=Qd(h,q*n);if(!this.l&&this.D==n&&this.O==k&&this.C==m&&Vd(this.v,h))return!0;this.c&&b.postRenderFunctions.push(Iq(this.c,d));this.l=!1;var r=new Hq(.5*n/p,h,f.a);c.Nc(h,n,g);if(m){var t=[];c.pb(h,function(b){t.push(b)},this);t.sort(m);t.forEach(e,this)}else c.pb(h,e,this);Jq(r,d);this.D=n;this.O=k;this.C=m;this.v=h;this.c=r;return!0};function er(b,c){xk.call(this,0,c);this.a=document.createElement("CANVAS");this.a.style.width="100%";this.a.style.height="100%";this.a.className="ol-unselectable";Lg(b,this.a,0);this.v=this.C=0;this.D=Ni();this.o=!0;this.b=Ti(this.a,{antialias:!0,depth:!1,failIfMajorPerformanceCaveat:!0,preserveDrawingBuffer:!1,stencil:!0});this.g=new rq(this.a,this.b);D(this.a,"webglcontextlost",this.Em,!1,this);D(this.a,"webglcontextrestored",this.Fm,!1,this);this.f=new ph;this.B=null;this.l=new Ck(ua(function(b){var c=
+b[1];b=b[2];var f=c[0]-this.B[0],c=c[1]-this.B[1];return 65536*Math.log(b)+Math.sqrt(f*f+c*c)/b},this),function(b){return b[0].$a()});this.O=ua(function(){if(!this.l.La()){Gk(this.l);var b=Dk(this.l);cr(this,b[0],b[3],b[4])}},this);this.j=0;fr(this)}y(er,xk);
+function cr(b,c,d,e){var f=b.b,g=c.$a();if(qh(b.f,g))b=b.f.get(g),f.bindTexture(3553,b.mb),9729!=b.Sg&&(f.texParameteri(3553,10240,9729),b.Sg=9729),9729!=b.Tg&&(f.texParameteri(3553,10240,9729),b.Tg=9729);else{var h=f.createTexture();f.bindTexture(3553,h);if(0<e){var k=b.D.canvas,m=b.D;b.C!==d[0]||b.v!==d[1]?(k.width=d[0],k.height=d[1],b.C=d[0],b.v=d[1]):m.clearRect(0,0,d[0],d[1]);m.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.f.set(g,{mb:h,Sg:9729,Tg:9729})}}l=er.prototype;l.hf=function(b){return b instanceof Pl?new Wq(this,b):b instanceof G?new br(this,b):b instanceof H?new dr(this,b):null};
+function gr(b,c,d){var e=b.i;if(cd(e,c)){var f=b.g;b=d.viewState;b=new Oq(f,b.center,b.resolution,b.rotation,d.size,d.extent);e.s(new bk(c,e,b,d,null,f));c=Object.keys(b.a).map(Number);c.sort(ub);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.Y=function(){var b=this.b;b.isContextLost()||this.f.forEach(function(c){c&&b.deleteTexture(c.mb)});sc(this.g);er.ca.Y.call(this)};
+l.xj=function(b,c){for(var d=this.b,e;1024<this.f.qc()-this.j;){if(e=this.f.a.mc)d.deleteTexture(e.mb);else if(+this.f.a.oe==c.index)break;else--this.j;this.f.pop()}};l.W=function(){return"webgl"};l.Em=function(b){b.preventDefault();this.f.clear();this.j=0;Ib(this.c,function(b){b.Cf()})};l.Fm=function(){fr(this);this.i.render()};function fr(b){b=b.b;b.activeTexture(33984);b.blendFuncSeparate(770,771,1,771);b.disable(2884);b.disable(2929);b.disable(3089);b.disable(2960)}
+l.Ne=function(b){var c=this.g,d=this.b;if(d.isContextLost())return!1;if(!b)return this.o&&(gh(this.a,!1),this.o=!1),!1;this.B=b.focus;this.f.set((-b.index).toString(),null);++this.j;gr(this,"precompose",b);var e=[],f=b.layerStatesArray;pb(f);var g=b.viewState.resolution,h,k,m,n;h=0;for(k=f.length;h<k;++h)n=f[h],dk(n,g)&&"ready"==n.O&&(m=Ak(this,n.layer),m.Df(b,n,c)&&e.push(n));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)n=e[h],m=Ak(this,n.layer),m.nh(b,n,c);this.o||(gh(this.a,!0),this.o=!0);yk(b);1024<this.f.qc()-this.j&&b.postRenderFunctions.push(ua(this.xj,this));this.l.La()||(b.postRenderFunctions.push(this.O),b.animate=!0);gr(this,"postcompose",b);Bk(this,b);b.postRenderFunctions.push(zk)};
+l.Bf=function(b,c,d,e,f,g){var h;if(this.b.isContextLost())return!1;var k=c.viewState,m=c.layerStatesArray,n;for(n=m.length-1;0<=n;--n){h=m[n];var p=h.layer;if(dk(h,k.resolution)&&f.call(g,p)&&(h=Ak(this,p).ab(b,c,d,e)))return h}};l.mh=function(b,c,d,e){var f=!1;if(this.b.isContextLost())return!1;var g=c.viewState,h=c.layerStatesArray,k;for(k=h.length-1;0<=k;--k){var m=h[k],n=m.layer;if(dk(m,g.resolution)&&d.call(e,n)&&(f=Ak(this,n).xe(b,c)))return!0}return f};
+l.lh=function(b,c,d,e,f){if(this.b.isContextLost())return!1;var g=c.viewState,h,k=c.layerStatesArray,m;for(m=k.length-1;0<=m;--m){h=k[m];var n=h.layer;if(dk(h,g.resolution)&&f.call(e,n)&&(h=Ak(this,n).vc(b,c,d,e)))return h}};var hr=["canvas","webgl","dom"];
+function S(b){gd.call(this);var c=ir(b);this.nc=void 0!==b.loadTilesWhileAnimating?b.loadTilesWhileAnimating:!1;this.Cc=void 0!==b.loadTilesWhileInteracting?b.loadTilesWhileInteracting:!1;this.Xe=void 0!==b.pixelRatio?b.pixelRatio:Vi;this.Yc=c.logos;this.v=new gi(this.Eo,void 0,this);rc(this,this.v);this.Db=Bd();this.Ye=Bd();this.Eb=0;this.b=null;this.wa=Md();this.D=this.U=null;this.a=document.createElement("DIV");this.a.className="ol-viewport";this.a.style.position="relative";this.a.style.overflow=
+"hidden";this.a.style.width="100%";this.a.style.height="100%";this.a.style.msTouchAction="none";this.a.style.touchAction="none";$i&&Ug(this.a,"ol-touch");this.l=document.createElement("DIV");this.l.className="ol-overlaycontainer";this.a.appendChild(this.l);this.j=document.createElement("DIV");this.j.className="ol-overlaycontainer-stopevent";D(this.j,["click","dblclick","mousedown","touchstart","MSPointerDown",Uj,$b?"DOMMouseScroll":"mousewheel"],uc);this.a.appendChild(this.j);b=new Mj(this);D(b,Lb(Xj),
+this.Kg,!1,this);rc(this,b);this.ga=c.keyboardEventTarget;this.C=new yi;D(this.C,"key",this.Jg,!1,this);rc(this,this.C);b=new Gi(this.a);D(b,"mousewheel",this.Jg,!1,this);rc(this,b);this.g=c.controls;this.c=c.interactions;this.i=c.overlays;this.V={};this.o=new c.Go(this.a,this);rc(this,this.o);this.hb=new ti;rc(this,this.hb);this.T=this.B=null;this.O=[];this.pa=[];this.oa=new Hk(ua(this.qk,this),ua(this.Zk,this));this.fa={};D(this,id("layergroup"),this.Ek,!1,this);D(this,id("view"),this.$k,!1,this);
+D(this,id("size"),this.Wk,!1,this);D(this,id("target"),this.Yk,!1,this);this.I(c.values);this.g.forEach(function(b){b.setMap(this)},this);D(this.g,"add",function(b){b.element.setMap(this)},!1,this);D(this.g,"remove",function(b){b.element.setMap(null)},!1,this);this.c.forEach(function(b){b.setMap(this)},this);D(this.c,"add",function(b){b.element.setMap(this)},!1,this);D(this.c,"remove",function(b){b.element.setMap(null)},!1,this);this.i.forEach(this.mg,this);D(this.i,"add",function(b){this.mg(b.element)},
+!1,this);D(this.i,"remove",function(b){var c=b.element.Oa();void 0!==c&&delete this.V[c.toString()];b.element.setMap(null)},!1,this)}y(S,gd);l=S.prototype;l.kj=function(b){this.g.push(b)};l.lj=function(b){this.c.push(b)};l.kg=function(b){this.rc().Qc().push(b)};l.lg=function(b){this.i.push(b)};l.mg=function(b){var c=b.Oa();void 0!==c&&(this.V[c.toString()]=b);b.setMap(this)};l.Na=function(b){this.render();Array.prototype.push.apply(this.O,arguments)};l.Y=function(){Mg(this.a);S.ca.Y.call(this)};
+l.pd=function(b,c,d,e,f){if(this.b)return b=this.Ga(b),this.o.Bf(b,this.b,c,void 0!==d?d:null,void 0!==e?e:te,void 0!==f?f:null)};l.Jl=function(b,c,d,e,f){if(this.b)return this.o.lh(b,this.b,c,void 0!==d?d:null,void 0!==e?e:te,void 0!==f?f:null)};l.bl=function(b,c,d){if(!this.b)return!1;b=this.Ga(b);return this.o.mh(b,this.b,void 0!==c?c:te,void 0!==d?d:null)};l.Mj=function(b){return this.Ga(this.$d(b))};l.$d=function(b){var c;c=this.a;b=dh(b);c=dh(c);c=new yg(b.x-c.x,b.y-c.y);return[c.x,c.y]};
+l.xf=function(){return this.get("target")};l.Mc=function(){var b=this.xf();return void 0!==b?Dg(b):null};l.Ga=function(b){var c=this.b;return c?(b=b.slice(),ik(c.pixelToCoordinateMatrix,b,b)):null};l.Kj=function(){return this.g};l.dk=function(){return this.i};l.ck=function(b){b=this.V[b.toString()];return void 0!==b?b:null};l.Rj=function(){return this.c};l.rc=function(){return this.get("layergroup")};l.Zg=function(){return this.rc().Qc()};
+l.Pa=function(b){var c=this.b;return c?(b=b.slice(0,2),ik(c.coordinateToPixelMatrix,b,b)):null};l.Sa=function(){return this.get("size")};l.aa=function(){return this.get("view")};l.sk=function(){return this.a};l.qk=function(b,c,d,e){var f=this.b;if(!(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.Jg=function(b,c){var d=new Kj(c||b.type,this,b);this.Kg(d)};
+l.Kg=function(b){if(this.b){this.T=b.coordinate;b.frameState=this.b;var c=this.c.a,d;if(!1!==this.s(b))for(d=c.length-1;0<=d;d--){var e=c[d];if(e.b()&&!e.handleEvent(b))break}}};l.Uk=function(){var b=this.b,c=this.oa;if(!c.La()){var d=16,e=d,f=0;b&&(f=b.viewHints,f[0]&&(d=this.nc?8:0,e=2),f[1]&&(d=this.Cc?8:0,e=2),f=Kb(b.wantedTiles));d*=f;e*=f;c.g<d&&(Gk(c),Ik(c,d,e))}c=this.pa;d=0;for(e=c.length;d<e;++d)c[d](this,b);c.length=0};l.Wk=function(){this.render()};
+l.Yk=function(){var b=this.Mc();Fi(this.C);b?(b.appendChild(this.a),zi(this.C,this.ga?this.ga:b),this.B||(this.B=D(this.hb,"resize",this.Vc,!1,this))):(Mg(this.a),this.B&&(Wc(this.B),this.B=null));this.Vc()};l.Zk=function(){this.render()};l.al=function(){this.render()};l.$k=function(){this.U&&(Wc(this.U),this.U=null);var b=this.aa();b&&(this.U=D(b,"propertychange",this.al,!1,this));this.render()};l.Fk=function(){this.render()};l.Gk=function(){this.render()};
+l.Ek=function(){this.D&&(this.D.forEach(Wc),this.D=null);var b=this.rc();b&&(this.D=[D(b,"propertychange",this.Gk,!1,this),D(b,"change",this.Fk,!1,this)]);this.render()};l.Fo=function(){var b=this.v;hi(b);b.c()};l.render=function(){null!=this.v.xa||this.v.start()};l.yo=function(b){return this.g.remove(b)};l.zo=function(b){return this.c.remove(b)};l.Bo=function(b){return this.rc().Qc().remove(b)};l.Co=function(b){return this.i.remove(b)};
+l.Eo=function(b){var c,d,e,f=this.Sa(),g=this.aa(),h=null;if(void 0!==f&&0<f[0]&&0<f[1]&&g&&Rf(g)){var h=g.b.slice(),k=this.rc().of(),m={};c=0;for(d=k.length;c<d;++c)m[w(k[c].layer)]=k[c];e=Qf(g);h={animate:!1,attributions:{},coordinateToPixelMatrix:this.Db,extent:null,focus:this.T?this.T:e.center,index:this.Eb++,layerStates:m,layerStatesArray:k,logos:Tb(this.Yc),pixelRatio:this.Xe,pixelToCoordinateMatrix:this.Ye,postRenderFunctions:[],size:f,skippedFeatureUids:this.fa,tileQueue:this.oa,time:b,usedTiles:{},
+viewState:e,viewHints:h,wantedTiles:{}}}if(h){b=this.O;c=f=0;for(d=b.length;c<d;++c)g=b[c],g(this,h)&&(b[f++]=g);b.length=f;h.extent=me(e.center,e.resolution,e.rotation,h.size)}this.b=h;this.o.Ne(h);h&&(h.animate&&this.render(),Array.prototype.push.apply(this.pa,h.postRenderFunctions),0!==this.O.length||h.viewHints[0]||h.viewHints[1]||ae(h.extent,this.wa)||(this.s(new nh("moveend",this,h)),Rd(h.extent,this.wa)));this.s(new nh("postrender",this,h));li(this.Uk,this)};
+l.$h=function(b){this.set("layergroup",b)};l.Vf=function(b){this.set("size",b)};l.Kl=function(b){this.set("target",b)};l.To=function(b){this.set("view",b)};l.ii=function(b){b=w(b).toString();this.fa[b]=!0;this.render()};
+l.Vc=function(){var b=this.Mc();if(b){var c=Cg(b),d=Yb&&b.currentStyle;d&&Qg(Ag(c))&&"auto"!=d.width&&"auto"!=d.height&&!d.boxSizing?(c=hh(b,d.width,"width","pixelWidth"),b=hh(b,d.height,"height","pixelHeight"),b=new zg(c,b)):(d=new zg(b.offsetWidth,b.offsetHeight),c=jh(b,"padding"),b=mh(b),b=new zg(d.width-b.left-c.left-c.right-b.right,d.height-b.top-c.top-c.bottom-b.bottom));this.Vf([b.width,b.height])}else this.Vf(void 0)};l.li=function(b){b=w(b).toString();delete this.fa[b];this.render()};
+function ir(b){var c=null;void 0!==b.keyboardEventTarget&&(c=ia(b.keyboardEventTarget)?document.getElementById(b.keyboardEventTarget):b.keyboardEventTarget);var d={},e={};if(void 0===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]="":oa(f)&&(e[f.src]=f.href)}f=b.layers instanceof Hl?b.layers:new Hl({layers:b.layers});d.layergroup=f;d.target=b.target;d.view=void 0!==b.view?b.view:new Nf;var f=xk,g;void 0!==b.renderer?ga(b.renderer)?g=b.renderer:ia(b.renderer)&&(g=[b.renderer]):g=hr;var h,k;h=0;for(k=g.length;h<k;++h){var m=g[h];if("canvas"==m){if(Xi){f=aq;break}}else if("dom"==m){f=iq;break}else if("webgl"==m&&Ui){f=er;break}}var n;void 0!==b.controls?n=ga(b.controls)?new og(b.controls.slice()):
+b.controls:n=Xh();var p;void 0!==b.interactions?p=ga(b.interactions)?new og(b.interactions.slice()):b.interactions:p=Gl();b=void 0!==b.overlays?ga(b.overlays)?new og(b.overlays.slice()):b.overlays:new og;return{controls:n,interactions:p,keyboardEventTarget:c,logos:e,overlays:b,Go:f,values:d}}Ol();function jr(b){gd.call(this);this.xa=b.id;this.j=void 0!==b.insertFirst?b.insertFirst:!0;this.l=void 0!==b.stopEvent?b.stopEvent:!0;this.b=document.createElement("DIV");this.b.className="ol-overlay-container";this.b.style.position="absolute";this.autoPan=void 0!==b.autoPan?b.autoPan:!1;this.g=void 0!==b.autoPanAnimation?b.autoPanAnimation:{};this.i=void 0!==b.autoPanMargin?b.autoPanMargin:20;this.a={Ud:"",pe:"",Oe:"",Pe:"",visible:!0};this.c=null;D(this,id("element"),this.Ak,!1,this);D(this,id("map"),
+this.Lk,!1,this);D(this,id("offset"),this.Qk,!1,this);D(this,id("position"),this.Sk,!1,this);D(this,id("positioning"),this.Tk,!1,this);void 0!==b.element&&this.Xh(b.element);this.bi(void 0!==b.offset?b.offset:[0,0]);this.ei(void 0!==b.positioning?b.positioning:"top-left");void 0!==b.position&&this.yf(b.position)}y(jr,gd);l=jr.prototype;l.se=function(){return this.get("element")};l.Oa=function(){return this.xa};l.te=function(){return this.get("map")};l.Gg=function(){return this.get("offset")};
+l.$g=function(){return this.get("position")};l.Hg=function(){return this.get("positioning")};l.Ak=function(){Kg(this.b);var b=this.se();b&&this.b.appendChild(b)};l.Lk=function(){this.c&&(Mg(this.b),Wc(this.c),this.c=null);var b=this.te();b&&(this.c=D(b,"postrender",this.render,!1,this),kr(this),b=this.l?b.j:b.l,this.j?Lg(b,this.b,0):b.appendChild(this.b))};l.render=function(){kr(this)};l.Qk=function(){kr(this)};
+l.Sk=function(){kr(this);if(void 0!==this.get("position")&&this.autoPan){var b=this.te();if(void 0!==b&&b.Mc()){var c=lr(b.Mc(),b.Sa()),d=this.se(),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=lr(d,[e,f]),d=this.i;Vd(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.aa().Ua(),e=b.Pa(d),c=[e[0]+c[0],e[1]+c[1]],this.g&&(this.g.source=d,b.Na(Yf(this.g))),b.aa().kb(b.Ga(c)))}}};l.Tk=function(){kr(this)};l.Xh=function(b){this.set("element",b)};l.setMap=function(b){this.set("map",b)};l.bi=function(b){this.set("offset",b)};l.yf=function(b){this.set("position",b)};
+function lr(b,c){var d=Cg(b),e=new yg(0,0),f;f=d?Cg(d):document;f=!Yb||9<=kc||Qg(Ag(f))?f.documentElement:f.body;b!=f&&(f=ch(b),d=Rg(Ag(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.ei=function(b){this.set("positioning",b)};function mr(b,c){b.a.visible!==c&&(gh(b.b,c),b.a.visible=c)}
+function kr(b){var c=b.te(),d=b.$g();if(void 0!==c&&c.b&&void 0!==d){var d=c.Pa(d),e=c.Sa(),c=b.b.style,f=b.Gg(),g=b.Hg(),h=f[0],f=f[1];if("bottom-right"==g||"center-right"==g||"top-right"==g)""!==b.a.pe&&(b.a.pe=c.left=""),h=Math.round(e[0]-d[0]-h)+"px",b.a.Oe!=h&&(b.a.Oe=c.right=h);else{""!==b.a.Oe&&(b.a.Oe=c.right="");if("bottom-center"==g||"center-center"==g||"top-center"==g)h-=eh(b.b).width/2;h=Math.round(d[0]+h)+"px";b.a.pe!=h&&(b.a.pe=c.left=h)}if("bottom-left"==g||"bottom-center"==g||"bottom-right"==
+g)""!==b.a.Pe&&(b.a.Pe=c.top=""),d=Math.round(e[1]-d[1]-f)+"px",b.a.Ud!=d&&(b.a.Ud=c.bottom=d);else{""!==b.a.Ud&&(b.a.Ud=c.bottom="");if("center-left"==g||"center-center"==g||"center-right"==g)f-=eh(b.b).height/2;d=Math.round(d[1]+f)+"px";b.a.Pe!=d&&(b.a.Pe=c.top=d)}mr(b,!0)}else mr(b,!1)};function nr(b){b=b?b:{};this.i=void 0!==b.collapsed?b.collapsed:!0;this.j=void 0!==b.collapsible?b.collapsible:!0;this.j||(this.i=!1);var c=b.className?b.className:"ol-overviewmap",d=b.tipLabel?b.tipLabel:"Overview map",e=b.collapseLabel?b.collapseLabel:"\u00ab";this.B=ia(e)?Hg("SPAN",{},e):e;e=b.label?b.label:"\u00bb";this.v=ia(e)?Hg("SPAN",{},e):e;d=Hg("BUTTON",{type:"button",title:d},this.j&&!this.i?this.B:this.v);D(d,"click",this.Vl,!1,this);var e=Hg("DIV","ol-overviewmap-map"),f=this.b=new S({controls:new og,
+interactions:new og,target:e,view:b.view});b.layers&&b.layers.forEach(function(b){f.kg(b)},this);var g=Hg("DIV","ol-overviewmap-box");this.l=new jr({position:[0,0],positioning:"bottom-left",element:g});this.b.lg(this.l);c=Hg("DIV",c+" ol-unselectable ol-control"+(this.i&&this.j?" ol-collapsed":"")+(this.j?"":" ol-uncollapsible"),e,d);oh.call(this,{element:c,render:b.render?b.render:or,target:b.target})}y(nr,oh);l=nr.prototype;
+l.setMap=function(b){var c=this.a;b!==c&&(c&&(c=c.aa())&&Vc(c,id("rotation"),this.je,!1,this),nr.ca.setMap.call(this,b),b&&(this.o.push(D(b,"propertychange",this.Mk,!1,this)),0===this.b.Zg().$b()&&this.b.$h(b.rc()),b=b.aa()))&&(D(b,id("rotation"),this.je,!1,this),Rf(b)&&(this.b.Vc(),pr(this)))};l.Mk=function(b){"view"===b.key&&((b=b.oldValue)&&Vc(b,id("rotation"),this.je,!1,this),b=this.a.aa(),D(b,id("rotation"),this.je,!1,this))};l.je=function(){this.b.aa().ue(this.a.aa().Fa())};
+function or(){var b=this.a,c=this.b;if(b.b&&c.b){var d=b.Sa(),b=b.aa().$c(d),e=c.Sa(),d=c.aa().$c(e),f=c.Pa(ge(b)),c=c.Pa(ee(b)),c=new zg(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?pr(this):Vd(d,b)||(b=this.b,d=this.a.aa(),b.aa().kb(d.Ua()))}qr(this)}function pr(b){var c=b.a;b=b.b;var d=c.Sa(),c=c.aa().$c(d),d=b.Sa();b=b.aa();pe(c,1/(.1*Math.pow(2,Math.log(7.5)/Math.LN2/2)));b.kf(c,d)}
+function qr(b){var c=b.a,d=b.b;if(c.b&&d.b){var e=c.Sa(),f=c.aa(),g=d.aa();d.Sa();var c=f.Fa(),h=b.l,d=b.l.se(),f=f.$c(e),e=g.$(),g=de(f),f=fe(f),k;if(b=b.a.aa().Ua())k=[g[0]-b[0],g[1]-b[1]],ud(k,c),pd(k,b);h.yf(k);d&&(k=new zg(Math.abs((g[0]-f[0])/e),Math.abs((f[1]-g[1])/e)),c=Qg(Ag(Cg(d))),!Yb||ic("10")||c&&ic("8")?(d=d.style,$b?d.MozBoxSizing="border-box":ac?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=jh(d,"padding"),d=mh(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.Vl=function(b){b.preventDefault();rr(this)};function rr(b){Wg(b.element,"ol-collapsed");b.i?Ng(b.B,b.v):Ng(b.v,b.B);b.i=!b.i;var c=b.b;b.i||c.b||(c.Vc(),pr(b),Uc(c,"postrender",function(){qr(this)},!1,b))}l.Ul=function(){return this.j};
+l.Xl=function(b){this.j!==b&&(this.j=b,Wg(this.element,"ol-uncollapsible"),!b&&this.i&&rr(this))};l.Wl=function(b){this.j&&this.i!==b&&rr(this)};l.Tl=function(){return this.i};l.ek=function(){return this.b};function sr(b){b=b?b:{};var c=b.className?b.className:"ol-scale-line";this.l=Hg("DIV",c+"-inner");this.j=Hg("DIV",c+" ol-unselectable",this.l);this.v=null;this.B=void 0!==b.minWidth?b.minWidth:64;this.b=!1;this.O=void 0;this.C="";this.i=null;oh.call(this,{element:this.j,render:b.render?b.render:tr,target:b.target});D(this,id("units"),this.V,!1,this);this.T(b.units||"metric")}y(sr,oh);var ur=[1,2,5];sr.prototype.D=function(){return this.get("units")};
+function tr(b){(b=b.frameState)?this.v=b.viewState:this.v=null;vr(this)}sr.prototype.V=function(){vr(this)};sr.prototype.T=function(b){this.set("units",b)};
+function vr(b){var c=b.v;if(c){var d=c.center,e=c.projection,c=e.getPointResolution(c.resolution,d),f=e.f,g=b.D();"degrees"!=f||"metric"!=g&&"imperial"!=g&&"us"!=g&&"nautical"!=g?"degrees"!=f&&"degrees"==g?(b.i||(b.i=Ie(e,Ee("EPSG:4326"))),d=Math.cos(Xa(b.i(d)[1])),e=Ae.radius,e/=Be[f],c*=180/(Math.PI*d*e)):b.i=null:(b.i=null,d=Math.cos(Xa(d[1])),c*=Math.PI*d*Ae.radius/180);d=b.B*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.B*c)/Math.log(10));;){e=ur[d%3]*Math.pow(10,Math.floor(d/3));g=Math.round(e/c);if(isNaN(g)){gh(b.j,!1);b.b=!1;return}if(g>=b.B)break;++d}c=e+" "+f;b.C!=c&&(b.l.innerHTML=c,b.C=c);b.O!=g&&(b.l.style.width=g+"px",b.O=g);b.b||(gh(b.j,!0),b.b=
+!0)}else b.b&&(gh(b.j,!1),b.b=!1)};function wr(b){oc.call(this);this.f=b;this.a={}}y(wr,oc);var xr=[];wr.prototype.Ra=function(b,c,d,e){ga(c)||(c&&(xr[0]=c.toString()),c=xr);for(var f=0;f<c.length;f++){var g=D(b,c[f],d||this.handleEvent,e||!1,this.f||this);if(!g)break;this.a[g.key]=g}return this};
+wr.prototype.Wf=function(b,c,d,e,f){if(ga(c))for(var g=0;g<c.length;g++)this.Wf(b,c[g],d,e,f);else d=d||this.handleEvent,f=f||this.f||this,d=Nc(d),e=!!e,c=Bc(b)?Ic(b.yb,String(c),d,e,f):b?(b=Pc(b))?Ic(b,c,d,e,f):null:null,c&&(Wc(c),delete this.a[c.key]);return this};function yr(b){Ib(b.a,function(b,d){this.a.hasOwnProperty(d)&&Wc(b)},b);b.a={}}wr.prototype.Y=function(){wr.ca.Y.call(this);yr(this)};wr.prototype.handleEvent=function(){throw Error("EventHandler.handleEvent not implemented");};function zr(b,c,d){$c.call(this);this.target=b;this.handle=c||b;this.a=d||new Yg(NaN,NaN,NaN,NaN);this.i=Cg(b);this.f=new wr(this);rc(this,this.f);this.g=this.c=this.o=this.l=this.screenY=this.screenX=this.clientY=this.clientX=0;this.b=!1;D(this.handle,["touchstart","mousedown"],this.ji,!1,this)}y(zr,$c);var Ar=Yb||$b&&ic("1.9.3");l=zr.prototype;
+l.Y=function(){zr.ca.Y.call(this);Vc(this.handle,["touchstart","mousedown"],this.ji,!1,this);yr(this.f);Ar&&this.i.releaseCapture();this.handle=this.target=null};
+l.ji=function(b){var c="mousedown"==b.type;if(this.b||c&&!zc(b))this.s("earlycancel");else if(this.s(new Br("start",this,b.clientX,b.clientY))){this.b=!0;b.preventDefault();var c=this.i,d=c.documentElement,e=!Ar;this.f.Ra(c,["touchmove","mousemove"],this.Pk,e);this.f.Ra(c,["touchend","mouseup"],this.Zd,e);Ar?(d.setCapture(!1),this.f.Ra(d,"losecapture",this.Zd)):this.f.Ra(c?c.parentWindow||c.defaultView:window,"blur",this.Zd);this.G&&this.f.Ra(this.G,"scroll",this.Nn,e);this.clientX=this.l=b.clientX;
+this.clientY=this.o=b.clientY;this.screenX=b.screenX;this.screenY=b.screenY;this.c=this.target.offsetLeft;this.g=this.target.offsetTop;this.j=Rg(Ag(this.i))}};l.Zd=function(b){yr(this.f);Ar&&this.i.releaseCapture();this.b?(this.b=!1,this.s(new Br("end",this,b.clientX,b.clientY,0,Cr(this,this.c),Dr(this,this.g)))):this.s("earlycancel")};
+l.Pk=function(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.b){var e=this.l-this.clientX,f=this.o-this.clientY;if(0<e*e+f*f)if(this.s(new Br("start",this,b.clientX,b.clientY)))this.b=!0;else{this.na||this.Zd(b);return}}d=Er(this,c,d);c=d.x;d=d.y;this.b&&this.s(new Br("beforedrag",this,b.clientX,b.clientY,0,c,d))&&(Fr(this,b,c,d),b.preventDefault())};
+function Er(b,c,d){var e=Rg(Ag(b.i));c+=e.x-b.j.x;d+=e.y-b.j.y;b.j=e;b.c+=c;b.g+=d;return new yg(Cr(b,b.c),Dr(b,b.g))}l.Nn=function(b){var c=Er(this,0,0);b.clientX=this.clientX;b.clientY=this.clientY;Fr(this,b,c.x,c.y)};function Fr(b,c,d,e){b.target.style.left=d+"px";b.target.style.top=e+"px";b.s(new Br("drag",b,c.clientX,c.clientY,0,d,e))}function Cr(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 Dr(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 Br(b,c,d,e,f,g,h){tc.call(this,b);this.clientX=d;this.clientY=e;this.left=ca(g)?g:c.c;this.top=ca(h)?h:c.g}y(Br,tc);function Gr(b){b=b?b:{};this.i=void 0;this.j=Hr;this.l=null;this.v=!1;this.B=void 0!==b.duration?b.duration:200;var c=b.className?b.className:"ol-zoomslider",d=Hg("BUTTON",{type:"button","class":c+"-thumb ol-unselectable"}),c=Hg("DIV",[c,"ol-unselectable","ol-control"],d);this.b=new zr(d);rc(this,this.b);D(this.b,"start",this.zk,!1,this);D(this.b,"drag",this.xk,!1,this);D(this.b,"end",this.yk,!1,this);D(c,"click",this.wk,!1,this);D(d,"click",uc);oh.call(this,{element:c,render:b.render?b.render:Ir})}
+y(Gr,oh);var Hr=0;l=Gr.prototype;l.setMap=function(b){Gr.ca.setMap.call(this,b);b&&b.render()};
+function Ir(b){if(b.frameState){if(!this.v){var c=this.element,d=eh(c),e=Og(c),c=jh(e,"margin"),f=new zg(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 Yg(0,0,e,0)):(this.j=Hr,d=new Yg(0,0,0,c));this.b.a=d||new Yg(NaN,NaN,NaN,NaN);this.v=!0}b=b.frameState.viewState.resolution;b!==this.i&&(this.i=b,b=1-Pf(this.a.aa())(b),d=this.b,c=Og(this.element),1==this.j?ah(c,d.a.left+d.a.width*b):ah(c,d.a.left,
+d.a.top+d.a.height*b))}}l.wk=function(b){var c=this.a,d=c.aa(),e=d.$();c.Na($f({resolution:e,duration:this.B,easing:Uf}));b=Jr(this,Kr(this,b.offsetX-this.l[0]/2,b.offsetY-this.l[1]/2));d.Ub(d.constrainResolution(b))};l.zk=function(){Sf(this.a.aa(),1)};l.xk=function(b){this.i=Jr(this,Kr(this,b.left,b.top));this.a.aa().Ub(this.i)};l.yk=function(){var b=this.a,c=b.aa();Sf(c,-1);b.Na($f({resolution:this.i,duration:this.B,easing:Uf}));b=c.constrainResolution(this.i);c.Ub(b)};
+function Kr(b,c,d){var e=b.b.a;return Sa(1===b.j?(c-e.left)/e.width:(d-e.top)/e.height,0,1)}function Jr(b,c){return Of(b.a.aa())(1-c)};function Lr(b){b=b?b:{};this.b=b.extent?b.extent:null;var c=b.className?b.className:"ol-zoom-extent",d=Hg("BUTTON",{type:"button",title:b.tipLabel?b.tipLabel:"Fit to extent"},b.label?b.label:"E");D(d,"click",this.i,!1,this);c=Hg("DIV",c+" ol-unselectable ol-control",d);oh.call(this,{element:c,target:b.target})}y(Lr,oh);Lr.prototype.i=function(b){b.preventDefault();var c=this.a;b=c.aa();var d=this.b?this.b:b.g.J(),c=c.Sa();b.kf(d,c)};function Mr(b){gd.call(this);b=b?b:{};this.a=null;D(this,id("tracking"),this.yl,!1,this);this.vf(void 0!==b.tracking?b.tracking:!1)}y(Mr,gd);l=Mr.prototype;l.Y=function(){this.vf(!1);Mr.ca.Y.call(this)};
+l.On=function(b){b=b.a;if(null!==b.alpha){var c=Xa(b.alpha);this.set("alpha",c);"boolean"==typeof b.absolute&&b.absolute?this.set("heading",c):ja(b.webkitCompassHeading)&&-1!=b.webkitCompassAccuracy&&this.set("heading",Xa(b.webkitCompassHeading))}null!==b.beta&&this.set("beta",Xa(b.beta));null!==b.gamma&&this.set("gamma",Xa(b.gamma));this.u()};l.Fj=function(){return this.get("alpha")};l.Ij=function(){return this.get("beta")};l.Oj=function(){return this.get("gamma")};l.xl=function(){return this.get("heading")};
+l.Vg=function(){return this.get("tracking")};l.yl=function(){if(Yi){var b=this.Vg();b&&!this.a?this.a=D(ba,"deviceorientation",this.On,!1,this):!b&&this.a&&(Wc(this.a),this.a=null)}};l.vf=function(b){this.set("tracking",b)};function Nr(){this.defaultDataProjection=null}function Or(b,c,d){var e;d&&(e={dataProjection:d.dataProjection?d.dataProjection:b.Ia(c),featureProjection:d.featureProjection});return Pr(b,e)}function Pr(b,c){var d;c&&(d={featureProjection:c.featureProjection,dataProjection:c.dataProjection?c.dataProjection:b.defaultDataProjection,rightHanded:c.rightHanded});return d}
+function Qr(b,c,d){var e=d?Ee(d.featureProjection):null;d=d?Ee(d.dataProjection):null;return e&&d&&!Ve(e,d)?b instanceof $e?(c?b.clone():b).lb(c?e:d,c?d:e):Ze(c?b.slice():b,c?e:d,c?d:e):b};function Rr(){this.defaultDataProjection=null}y(Rr,Nr);function Sr(b){return oa(b)?b:ia(b)?(b=Yn(b))?b:null:null}l=Rr.prototype;l.W=function(){return"json"};l.Tb=function(b,c){return this.Sc(Sr(b),Or(this,b,c))};l.Ba=function(b,c){return this.Kf(Sr(b),Or(this,b,c))};l.Tc=function(b,c){return this.Gh(Sr(b),Or(this,b,c))};l.Ia=function(b){return this.Nh(Sr(b))};l.Kd=function(b,c){return Zn(this.Wc(b,c))};l.Vb=function(b,c){return Zn(this.Se(b,c))};l.Xc=function(b,c){return Zn(this.Ue(b,c))};function Tr(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],m=0,g=[0],n;for(n=c+e;n<d;n+=e){var p=b[n],q=b[n+1],m=m+Math.sqrt((p-h)*(p-h)+(q-k)*(q-k));g.push(m);h=p;k=q}d=.5*m;for(var r,h=ob,k=0,m=g.length;k<m;)n=k+m>>1,p=h(d,g[n]),0<p?k=n+1:(m=n,r=!p);r=r?k:~k;0>r?(d=(d-g[-r-2])/(g[-r-1]-g[-r-2]),c+=(-r-2)*e,g=od(b[c],b[c+e],d),h=od(b[c+1],b[c+e+1],d)):(g=b[c+r*e],h=b[c+r*e+1])}return f?(f[0]=
+g,f[1]=h,f):[g,h]}function Ur(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(od(b[(c-1)*e+h],b[c*e+h],g));d.push(f);return d}
+function Vr(b,c,d,e,f,g){var h=0;if(g)return Ur(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 Ur(b,h,k,d,e,!1);h=k}}return null};function T(b,c){bf.call(this);this.g=null;this.C=this.D=this.l=-1;this.la(b,c)}y(T,bf);l=T.prototype;l.mj=function(b){this.A?kb(this.A,b):this.A=b.slice();this.u()};l.clone=function(){var b=new T(null);b.ba(this.b,this.A.slice());return b};l.nb=function(b,c,d,e){if(e<Sd(this.J(),b,c))return e;this.C!=this.f&&(this.D=Math.sqrt(jf(this.A,0,this.A.length,this.a,0)),this.C=this.f);return lf(this.A,0,this.A.length,this.a,this.D,!1,b,c,d,e)};
+l.Cj=function(b,c){return Bf(this.A,0,this.A.length,this.a,b,c)};l.$l=function(b,c){return"XYM"!=this.b&&"XYZM"!=this.b?null:Ur(this.A,0,this.A.length,this.a,b,void 0!==c?c:!1)};l.Z=function(){return qf(this.A,0,this.A.length,this.a)};l.am=function(){var b=this.A,c=this.a,d=b[0],e=b[1],f=0,g;for(g=0+c;g<this.A.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 om(b){b.l!=b.f&&(b.g=Tr(b.A,0,b.A.length,b.a,b.g),b.l=b.f);return b.g}
+l.Lc=function(b){var c=[];c.length=tf(this.A,0,this.A.length,this.a,b,c,0);b=new T(null);b.ba("XY",c);return b};l.W=function(){return"LineString"};l.Ea=function(b){return Cf(this.A,0,this.A.length,this.a,b)};l.la=function(b,c){b?(ef(this,c,b,1),this.A||(this.A=[]),this.A.length=of(this.A,0,b,this.a),this.u()):this.ba("XY",null)};l.ba=function(b,c){df(this,b,c);this.u()};function U(b,c){bf.call(this);this.g=[];this.l=this.C=-1;this.la(b,c)}y(U,bf);l=U.prototype;l.nj=function(b){this.A?kb(this.A,b.ia().slice()):this.A=b.ia().slice();this.g.push(this.A.length);this.u()};l.clone=function(){var b=new U(null);b.ba(this.b,this.A.slice(),this.g.slice());return b};l.nb=function(b,c,d,e){if(e<Sd(this.J(),b,c))return e;this.l!=this.f&&(this.C=Math.sqrt(kf(this.A,0,this.g,this.a,0)),this.l=this.f);return mf(this.A,0,this.g,this.a,this.C,!1,b,c,d,e)};
+l.cm=function(b,c,d){return"XYM"!=this.b&&"XYZM"!=this.b||0===this.A.length?null:Vr(this.A,this.g,this.a,b,void 0!==c?c:!1,void 0!==d?d:!1)};l.Z=function(){return rf(this.A,0,this.g,this.a)};l.zb=function(){return this.g};l.Wj=function(b){if(0>b||this.g.length<=b)return null;var c=new T(null);c.ba(this.b,this.A.slice(0===b?0:this.g[b-1],this.g[b]));return c};
+l.sd=function(){var b=this.A,c=this.g,d=this.b,e=[],f=0,g,h;g=0;for(h=c.length;g<h;++g){var k=c[g],m=new T(null);m.ba(d,b.slice(f,k));e.push(m);f=k}return e};function pm(b){var c=[],d=b.A,e=0,f=b.g;b=b.a;var g,h;g=0;for(h=f.length;g<h;++g){var k=f[g],e=Tr(d,e,k,b);kb(c,e);e=k}return c}l.Lc=function(b){var c=[],d=[],e=this.A,f=this.g,g=this.a,h=0,k=0,m,n;m=0;for(n=f.length;m<n;++m){var p=f[m],k=tf(e,h,p,g,b,c,k);d.push(k);h=p}c.length=k;b=new U(null);b.ba("XY",c,d);return b};l.W=function(){return"MultiLineString"};
+l.Ea=function(b){a:{var c=this.A,d=this.g,e=this.a,f=0,g,h;g=0;for(h=d.length;g<h;++g){if(Cf(c,f,d[g],e,b)){b=!0;break a}f=d[g]}b=!1}return b};l.la=function(b,c){if(b){ef(this,c,b,2);this.A||(this.A=[]);var d=pf(this.A,0,b,this.a,this.g);this.A.length=0===d.length?0:d[d.length-1];this.u()}else this.ba("XY",null,this.g)};l.ba=function(b,c,d){df(this,b,c);this.g=d;this.u()};
+function Wr(b,c){var d=b.b,e=[],f=[],g,h;g=0;for(h=c.length;g<h;++g){var k=c[g];0===g&&(d=k.b);kb(e,k.ia());f.push(e.length)}b.ba(d,e,f)};function Xr(b,c){bf.call(this);this.la(b,c)}y(Xr,bf);l=Xr.prototype;l.pj=function(b){this.A?kb(this.A,b.ia()):this.A=b.ia().slice();this.u()};l.clone=function(){var b=new Xr(null);b.ba(this.b,this.A.slice());return b};l.nb=function(b,c,d,e){if(e<Sd(this.J(),b,c))return e;var f=this.A,g=this.a,h,k,m;h=0;for(k=f.length;h<k;h+=g)if(m=Wa(b,c,f[h],f[h+1]),m<e){e=m;for(m=0;m<g;++m)d[m]=f[h+m];d.length=g}return e};l.Z=function(){return qf(this.A,0,this.A.length,this.a)};
+l.gk=function(b){var c=this.A?this.A.length/this.a:0;if(0>b||c<=b)return null;c=new E(null);c.ba(this.b,this.A.slice(b*this.a,(b+1)*this.a));return c};l.ve=function(){var b=this.A,c=this.b,d=this.a,e=[],f,g;f=0;for(g=b.length;f<g;f+=d){var h=new E(null);h.ba(c,b.slice(f,f+d));e.push(h)}return e};l.W=function(){return"MultiPoint"};l.Ea=function(b){var c=this.A,d=this.a,e,f,g,h;e=0;for(f=c.length;e<f;e+=d)if(g=c[e],h=c[e+1],Ud(b,g,h))return!0;return!1};
+l.la=function(b,c){b?(ef(this,c,b,1),this.A||(this.A=[]),this.A.length=of(this.A,0,b,this.a),this.u()):this.ba("XY",null)};l.ba=function(b,c){df(this,b,c);this.u()};function V(b,c){bf.call(this);this.g=[];this.C=-1;this.D=null;this.T=this.O=this.U=-1;this.l=null;this.la(b,c)}y(V,bf);l=V.prototype;l.qj=function(b){if(this.A){var c=this.A.length;kb(this.A,b.ia());b=b.zb().slice();var d,e;d=0;for(e=b.length;d<e;++d)b[d]+=c}else this.A=b.ia().slice(),b=b.zb().slice(),this.g.push();this.g.push(b);this.u()};l.clone=function(){var b=new V(null),c=Ub(this.g);Yr(b,this.b,this.A.slice(),c);return b};
+l.nb=function(b,c,d,e){if(e<Sd(this.J(),b,c))return e;if(this.O!=this.f){var f=this.g,g=0,h=0,k,m;k=0;for(m=f.length;k<m;++k)var n=f[k],h=kf(this.A,g,n,this.a,h),g=n[n.length-1];this.U=Math.sqrt(h);this.O=this.f}f=rm(this);g=this.g;h=this.a;k=this.U;m=0;var n=[NaN,NaN],p,q;p=0;for(q=g.length;p<q;++p){var r=g[p];e=mf(f,m,r,h,k,!0,b,c,d,e,n);m=r[r.length-1]}return e};
+l.uc=function(b,c){var d;a:{d=rm(this);var e=this.g,f=0;if(0!==e.length){var g,h;g=0;for(h=e.length;g<h;++g){var k=e[g];if(zf(d,f,k,this.a,b,c)){d=!0;break a}f=k[k.length-1]}}d=!1}return d};l.dm=function(){var b=rm(this),c=this.g,d=0,e=0,f,g;f=0;for(g=c.length;f<g;++f)var h=c[f],e=e+gf(b,d,h,this.a),d=h[h.length-1];return e};
+l.Z=function(b){var c;void 0!==b?(c=rm(this).slice(),Hf(c,this.g,this.a,b)):c=this.A;b=c;c=this.g;var d=this.a,e=0,f=[],g=0,h,k;h=0;for(k=c.length;h<k;++h){var m=c[h];f[g++]=rf(b,e,m,d,f[g]);e=m[m.length-1]}f.length=g;return f};
+function sm(b){if(b.C!=b.f){var c=b.A,d=b.g,e=b.a,f=0,g=[],h,k,m=Md();h=0;for(k=d.length;h<k;++h){var n=d[h],m=Yd(c,f,n[0],e);g.push((m[0]+m[2])/2,(m[1]+m[3])/2);f=n[n.length-1]}c=rm(b);d=b.g;e=b.a;f=0;h=[];k=0;for(m=d.length;k<m;++k)n=d[k],h=Af(c,f,n,e,g,2*k,h),f=n[n.length-1];b.D=h;b.C=b.f}return b.D}l.Tj=function(){var b=new Xr(null);b.ba("XY",sm(this).slice());return b};
+function rm(b){if(b.T!=b.f){var c=b.A,d;a:{d=b.g;var e,f;e=0;for(f=d.length;e<f;++e)if(!Ff(c,d[e],b.a,void 0)){d=!1;break a}d=!0}d?b.l=c:(b.l=c.slice(),b.l.length=Hf(b.l,b.g,b.a));b.T=b.f}return b.l}l.Lc=function(b){var c=[],d=[],e=this.A,f=this.g,g=this.a;b=Math.sqrt(b);var h=0,k=0,m,n;m=0;for(n=f.length;m<n;++m){var p=f[m],q=[],k=uf(e,h,p,g,b,c,k,q);d.push(q);h=p[p.length-1]}c.length=k;e=new V(null);Yr(e,"XY",c,d);return e};
+l.ik=function(b){if(0>b||this.g.length<=b)return null;var c;0===b?c=0:(c=this.g[b-1],c=c[c.length-1]);b=this.g[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);e.ba(this.b,this.A.slice(c,d),b);return e};l.ce=function(){var b=this.b,c=this.A,d=this.g,e=[],f=0,g,h,k,m;g=0;for(h=d.length;g<h;++g){var n=d[g].slice(),p=n[n.length-1];if(0!==f)for(k=0,m=n.length;k<m;++k)n[k]-=f;k=new F(null);k.ba(b,c.slice(f,p),n);e.push(k);f=p}return e};l.W=function(){return"MultiPolygon"};
+l.Ea=function(b){a:{var c=rm(this),d=this.g,e=this.a,f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g];if(Df(c,f,k,e,b)){b=!0;break a}f=k[k.length-1]}b=!1}return b};l.la=function(b,c){if(b){ef(this,c,b,3);this.A||(this.A=[]);var d=this.A,e=this.a,f=this.g,g=0,f=f?f:[],h=0,k,m;k=0;for(m=b.length;k<m;++k)g=pf(d,g,b[k],e,f[h]),f[h++]=g,g=g[g.length-1];f.length=h;0===f.length?this.A.length=0:(d=f[f.length-1],this.A.length=0===d.length?0:d[d.length-1]);this.u()}else Yr(this,"XY",null,this.g)};
+function Yr(b,c,d,e){df(b,c,d);b.g=e;b.u()}function Zr(b,c){var d=b.b,e=[],f=[],g,h,k;g=0;for(h=c.length;g<h;++g){var m=c[g];0===g&&(d=m.b);var n=e.length;k=m.zb();var p,q;p=0;for(q=k.length;p<q;++p)k[p]+=n;kb(e,m.ia());f.push(k)}Yr(b,d,e,f)};function $r(b){b=b?b:{};this.defaultDataProjection=null;this.a=b.geometryName}y($r,Rr);
+function as(b,c){if(!b)return null;var d;if(ja(b.x)&&ja(b.y))d="Point";else if(b.points)d="MultiPoint";else if(b.paths)d=1===b.paths.length?"LineString":"MultiLineString";else if(b.rings){var e=b.rings,f=bs(b),g=[];d=[];var h,k;h=0;for(k=e.length;h<k;++h){var m=tb(e[h]);Ef(m,0,m.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(Vd((new vf(g[h][0])).J(),(new vf(e)).J())){g[h].push(e);f=!0;break}f||g.push([e.reverse()])}b=Tb(b);1===g.length?(d=
+"Polygon",b.rings=g[0]):(d="MultiPolygon",b.rings=g)}return Qr((0,cs[d])(b),!1,c)}function bs(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 ds(b){b=b.b;return{hasZ:"XYZ"===b||"XYZM"===b,hasM:"XYM"===b||"XYZM"===b}}
+var cs={Point:function(b){return void 0!==b.m&&void 0!==b.z?new E([b.x,b.y,b.z,b.m],"XYZM"):void 0!==b.z?new E([b.x,b.y,b.z],"XYZ"):void 0!==b.m?new E([b.x,b.y,b.m],"XYM"):new E([b.x,b.y])},LineString:function(b){return new T(b.paths[0],bs(b))},Polygon:function(b){return new F(b.rings,bs(b))},MultiPoint:function(b){return new Xr(b.points,bs(b))},MultiLineString:function(b){return new U(b.paths,bs(b))},MultiPolygon:function(b){return new V(b.rings,bs(b))}},es={Point:function(b){var c=b.Z();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=ds(b);return{hasZ:c.hasZ,hasM:c.hasM,paths:[b.Z()]}},Polygon:function(b){var c=ds(b);return{hasZ:c.hasZ,hasM:c.hasM,rings:b.Z(!1)}},MultiPoint:function(b){var c=ds(b);return{hasZ:c.hasZ,hasM:c.hasM,points:b.Z()}},MultiLineString:function(b){var c=ds(b);return{hasZ:c.hasZ,hasM:c.hasM,paths:b.Z()}},MultiPolygon:function(b){var c=
+ds(b);b=b.Z(!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=$r.prototype;l.Sc=function(b,c){var d=as(b.geometry,c),e=new pn;this.a&&e.yc(this.a);e.Ma(d);c&&c.qf&&b.attributes[c.qf]&&e.jc(b.attributes[c.qf]);b.attributes&&e.I(b.attributes);return e};
+l.Kf=function(b,c){var d=c?c:{};if(b.features){var e=[],f=b.features,g,h;d.qf=b.objectIdFieldName;g=0;for(h=f.length;g<h;++g)e.push(this.Sc(f[g],d));return e}return[this.Sc(b,d)]};l.Gh=function(b,c){return as(b,c)};l.Nh=function(b){return b.spatialReference&&b.spatialReference.wkid?Ee("EPSG:"+b.spatialReference.wkid):null};function fs(b,c){return(0,es[b.W()])(Qr(b,!0,c),c)}l.Ue=function(b,c){return fs(b,Pr(this,c))};
+l.Wc=function(b,c){c=Pr(this,c);var d={},e=b.X();e&&(d.geometry=fs(e,c));e=b.R();delete e[b.a];d.attributes=Pb(e)?{}:e;c&&c.featureProjection&&(d.spatialReference={wkid:Ee(c.featureProjection).a.split(":").pop()});return d};l.Se=function(b,c){c=Pr(this,c);var d=[],e,f;e=0;for(f=b.length;e<f;++e)d.push(this.Wc(b[e],c));return{features:d}};function gs(b){$e.call(this);this.c=b?b:null;hs(this)}y(gs,$e);function is(b){var c=[],d,e;d=0;for(e=b.length;d<e;++d)c.push(b[d].clone());return c}function js(b){var c,d;if(b.c)for(c=0,d=b.c.length;c<d;++c)Vc(b.c[c],"change",b.u,!1,b)}function hs(b){var c,d;if(b.c)for(c=0,d=b.c.length;c<d;++c)D(b.c[c],"change",b.u,!1,b)}l=gs.prototype;l.clone=function(){var b=new gs(null);b.Yh(this.c);return b};
+l.nb=function(b,c,d,e){if(e<Sd(this.J(),b,c))return e;var f=this.c,g,h;g=0;for(h=f.length;g<h;++g)e=f[g].nb(b,c,d,e);return e};l.uc=function(b,c){var d=this.c,e,f;e=0;for(f=d.length;e<f;++e)if(d[e].uc(b,c))return!0;return!1};l.Wd=function(b){Pd(Infinity,Infinity,-Infinity,-Infinity,b);for(var c=this.c,d=0,e=c.length;d<e;++d)be(b,c[d].J());return b};l.Ag=function(){return is(this.c)};
+l.td=function(b){this.o!=this.f&&(Qb(this.i),this.j=0,this.o=this.f);if(0>b||0!==this.j&&b<this.j)return this;var c=b.toString();if(this.i.hasOwnProperty(c))return this.i[c];var d=[],e=this.c,f=!1,g,h;g=0;for(h=e.length;g<h;++g){var k=e[g],m=k.td(b);d.push(m);m!==k&&(f=!0)}if(f)return b=new gs(null),js(b),b.c=d,hs(b),b.u(),this.i[c]=b;this.j=b;return this};l.W=function(){return"GeometryCollection"};l.Ea=function(b){var c=this.c,d,e;d=0;for(e=c.length;d<e;++d)if(c[d].Ea(b))return!0;return!1};
+l.La=function(){return 0===this.c.length};l.Yh=function(b){b=is(b);js(this);this.c=b;hs(this);this.u()};l.pc=function(b){var c=this.c,d,e;d=0;for(e=c.length;d<e;++d)c[d].pc(b);this.u()};l.Pc=function(b,c){var d=this.c,e,f;e=0;for(f=d.length;e<f;++e)d[e].Pc(b,c);this.u()};l.Y=function(){js(this);gs.ca.Y.call(this)};function ks(b){b=b?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee(b.defaultDataProjection?b.defaultDataProjection:"EPSG:4326");this.a=b.geometryName}y(ks,Rr);function ls(b,c){return b?Qr((0,ms[b.type])(b),!1,c):null}function ns(b,c){return(0,os[b.W()])(Qr(b,!0,c),c)}
+var ms={Point:function(b){return new E(b.coordinates)},LineString:function(b){return new T(b.coordinates)},Polygon:function(b){return new F(b.coordinates)},MultiPoint:function(b){return new Xr(b.coordinates)},MultiLineString:function(b){return new U(b.coordinates)},MultiPolygon:function(b){return new V(b.coordinates)},GeometryCollection:function(b,c){var d=b.geometries.map(function(b){return ls(b,c)});return new gs(d)}},os={Point:function(b){return{type:"Point",coordinates:b.Z()}},LineString:function(b){return{type:"LineString",
+coordinates:b.Z()}},Polygon:function(b,c){var d;c&&(d=c.rightHanded);return{type:"Polygon",coordinates:b.Z(d)}},MultiPoint:function(b){return{type:"MultiPoint",coordinates:b.Z()}},MultiLineString:function(b){return{type:"MultiLineString",coordinates:b.Z()}},MultiPolygon:function(b,c){var d;c&&(d=c.rightHanded);return{type:"MultiPolygon",coordinates:b.Z(d)}},GeometryCollection:function(b,c){return{type:"GeometryCollection",geometries:b.c.map(function(b){return ns(b,c)})}},Circle:function(){return{type:"GeometryCollection",
+geometries:[]}}};l=ks.prototype;l.Sc=function(b,c){var d=ls(b.geometry,c),e=new pn;this.a&&e.yc(this.a);e.Ma(d);void 0!==b.id&&e.jc(b.id);b.properties&&e.I(b.properties);return e};l.Kf=function(b,c){if("Feature"==b.type)return[this.Sc(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.Sc(e[f],c));return d}return[]};l.Gh=function(b,c){return ls(b,c)};
+l.Nh=function(b){return(b=b.crs)?"name"==b.type?Ee(b.properties.name):"EPSG"==b.type?Ee("EPSG:"+b.properties.code):null:this.defaultDataProjection};l.Wc=function(b,c){c=Pr(this,c);var d={type:"Feature"},e=b.Oa();void 0!==e&&(d.id=e);e=b.X();d.geometry=e?ns(e,c):null;e=b.R();delete e[b.a];d.properties=Pb(e)?null:e;return d};l.Se=function(b,c){c=Pr(this,c);var d=[],e,f;e=0;for(f=b.length;e<f;++e)d.push(this.Wc(b[e],c));return{type:"FeatureCollection",features:d}};
+l.Ue=function(b,c){return ns(b,Pr(this,c))};function ps(){this.defaultDataProjection=null}y(ps,Nr);l=ps.prototype;l.W=function(){return"xml"};l.Tb=function(b,c){if(To(b))return qs(this,b,c);if(Wo(b))return this.Eh(b,c);if(ia(b)){var d=fp(b);return qs(this,d,c)}return null};function qs(b,c,d){b=rs(b,c,d);return 0<b.length?b[0]:null}l.Ba=function(b,c){if(To(b))return rs(this,b,c);if(Wo(b))return this.ic(b,c);if(ia(b)){var d=fp(b);return rs(this,d,c)}return[]};
+function rs(b,c,d){var e=[];for(c=c.firstChild;c;c=c.nextSibling)1==c.nodeType&&kb(e,b.ic(c,d));return e}l.Tc=function(b,c){if(To(b))return this.G(b,c);if(Wo(b)){var d=this.Je(b,[Or(this,b,c?c:{})]);return d?d:null}return ia(b)?(d=fp(b),this.G(d,c)):null};l.Ia=function(b){return To(b)?this.Qf(b):Wo(b)?this.Me(b):ia(b)?(b=fp(b),this.Qf(b)):null};l.Qf=function(){return this.defaultDataProjection};l.Me=function(){return this.defaultDataProjection};l.Kd=function(b,c){var d=this.v(b,c);return Fo(d)};
+l.Vb=function(b,c){var d=this.f(b,c);return Fo(d)};l.Xc=function(b,c){var d=this.o(b,c);return Fo(d)};function ss(b){b=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(ss.prototype.Ed),featureMembers:ip(ss.prototype.Ed)};this.defaultDataProjection=null}y(ss,ps);l=ss.prototype;
+l.Ed=function(b,c){var d=Qo(b),e;if("FeatureCollection"==d)"http://www.opengis.net/wfs"===b.namespaceURI?e=Q([],this.a,b,c,this):e=Q(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(!g&&b.childNodes){g=[];e={};h=0;for(k=b.childNodes.length;h<k;++h){var m=b.childNodes[h];if(1===m.nodeType){var n=m.nodeName.split(":").pop();if(-1===g.indexOf(n)){var p;Mb(e,m.namespaceURI)?p=Ob(e,function(b){return b===m.namespaceURI}):(p="p"+
+Kb(e),e[p]=m.namespaceURI);g.push(p+":"+n)}}}f.featureType=g;f.featureNS=e}ia(e)&&(h=e,e={},e.p0=h);var f={},g=ga(g)?g:[g],q;for(q in e){n={};h=0;for(k=g.length;h<k;++h)(-1===g[h].indexOf(":")?"p0":g[h].split(":")[0])===q&&(n[g[h].split(":").pop()]="featureMembers"==d?hp(this.Jf,this):ip(this.Jf,this));f[e[q]]=n}e=Q([],f,b,c)}e||(e=[]);return e};l.Je=function(b,c){var d=c[0];d.srsName=b.firstElementChild.getAttribute("srsName");var e=Q(null,this.bg,b,c,this);if(e)return Qr(e,!1,d)};
+l.Jf=function(b,c){var d,e=b.getAttribute("fid")||$o(b,"http://www.opengis.net/gml","id"),f={},g;for(d=b.firstElementChild;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.Je(d,c)}d=new pn(f);g&&d.yc(g);e&&d.jc(e);return d};l.Mh=function(b,c){var d=this.Ie(b,c);if(d){var e=new E(null);e.ba("XYZ",d);return e}};
+l.Kh=function(b,c){var d=Q([],this.Ki,b,c,this);if(d)return new Xr(d)};l.Jh=function(b,c){var d=Q([],this.Ji,b,c,this);if(d){var e=new U(null);Wr(e,d);return e}};l.Lh=function(b,c){var d=Q([],this.Li,b,c,this);if(d){var e=new V(null);Zr(e,d);return e}};l.Bh=function(b,c){pp(this.Oi,b,c,this)};l.Qg=function(b,c){pp(this.Hi,b,c,this)};l.Ch=function(b,c){pp(this.Pi,b,c,this)};l.Ke=function(b,c){var d=this.Ie(b,c);if(d){var e=new T(null);e.ba("XYZ",d);return e}};
+l.jo=function(b,c){var d=Q(null,this.Md,b,c,this);if(d)return d};l.Ih=function(b,c){var d=this.Ie(b,c);if(d){var e=new vf(null);wf(e,"XYZ",d);return e}};l.Le=function(b,c){var d=Q([null],this.We,b,c,this);if(d&&d[0]){var e=new F(null),f=d[0],g=[f.length],h,k;h=1;for(k=d.length;h<k;++h)kb(f,d[h]),g.push(f.length);e.ba("XYZ",f,g);return e}};l.Ie=function(b,c){return Q(null,this.Md,b,c,this)};l.Ki=Object({"http://www.opengis.net/gml":{pointMember:hp(ss.prototype.Bh),pointMembers:hp(ss.prototype.Bh)}});
+l.Ji=Object({"http://www.opengis.net/gml":{lineStringMember:hp(ss.prototype.Qg),lineStringMembers:hp(ss.prototype.Qg)}});l.Li=Object({"http://www.opengis.net/gml":{polygonMember:hp(ss.prototype.Ch),polygonMembers:hp(ss.prototype.Ch)}});l.Oi=Object({"http://www.opengis.net/gml":{Point:hp(ss.prototype.Ie)}});l.Hi=Object({"http://www.opengis.net/gml":{LineString:hp(ss.prototype.Ke)}});l.Pi=Object({"http://www.opengis.net/gml":{Polygon:hp(ss.prototype.Le)}});l.Nd=Object({"http://www.opengis.net/gml":{LinearRing:ip(ss.prototype.jo)}});
+l.ic=function(b,c){var d={featureType:this.featureType,featureNS:this.featureNS};c&&Wb(d,Or(this,b,c));return this.Ed(b,[d])};l.Me=function(b){return Ee(this.B?this.B:b.firstElementChild.getAttribute("srsName"))};function ts(b){b=Mo(b,!1);return us(b)}function us(b){if(b=/^\s*(true|1)|(false|0)\s*$/.exec(b))return void 0!==b[1]||!1}
+function vs(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);void 0!==b[10]&&(c+=3600*d*parseInt(b[10],10))}return c}}function ws(b){b=Mo(b,!1);return xs(b)}
+function xs(b){if(b=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(b))return parseFloat(b[1])}function ys(b){b=Mo(b,!1);return zs(b)}function zs(b){if(b=/^\s*(\d+)\s*$/.exec(b))return parseInt(b[1],10)}function W(b){return Mo(b,!1).trim()}function As(b,c){Bs(b,c?"1":"0")}function Cs(b,c){b.appendChild(Io.createTextNode(c.toPrecision()))}function Ds(b,c){b.appendChild(Io.createTextNode(c.toString()))}function Bs(b,c){b.appendChild(Io.createTextNode(c))};function Es(b){b=b?b:{};ss.call(this,b);this.a["http://www.opengis.net/gml"].featureMember=hp(ss.prototype.Ed);this.schemaLocation=b.schemaLocation?b.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd"}y(Es,ss);l=Es.prototype;
+l.Fh=function(b,c){var d=Mo(b,!1).replace(/^\s*|\s*$/g,""),e=c[0].srsName,f=b.parentNode.getAttribute("srsDimension"),g="enu";e&&(e=Ee(e))&&(g=e.g);d=d.split(/[\s,]+/);e=2;b.getAttribute("srsDimension")?e=zs(b.getAttribute("srsDimension")):b.getAttribute("dimension")?e=zs(b.getAttribute("dimension")):f&&(e=zs(f));for(var h,k,m=[],n=0,p=d.length;n<p;n+=e)f=parseFloat(d[n]),h=parseFloat(d[n+1]),k=3===e?parseFloat(d[n+2]):0,"en"===g.substr(0,2)?m.push(f,h,k):m.push(h,f,k);return m};
+l.fo=function(b,c){var d=Q([null],this.Di,b,c,this);return Pd(d[1][0],d[1][1],d[1][3],d[1][4])};l.dl=function(b,c){var d=Q(void 0,this.Nd,b,c,this);d&&c[c.length-1].push(d)};l.Pn=function(b,c){var d=Q(void 0,this.Nd,b,c,this);d&&(c[c.length-1][0]=d)};l.Md=Object({"http://www.opengis.net/gml":{coordinates:ip(Es.prototype.Fh)}});l.We=Object({"http://www.opengis.net/gml":{innerBoundaryIs:Es.prototype.dl,outerBoundaryIs:Es.prototype.Pn}});l.Di=Object({"http://www.opengis.net/gml":{coordinates:hp(Es.prototype.Fh)}});
+l.bg=Object({"http://www.opengis.net/gml":{Point:ip(ss.prototype.Mh),MultiPoint:ip(ss.prototype.Kh),LineString:ip(ss.prototype.Ke),MultiLineString:ip(ss.prototype.Jh),LinearRing:ip(ss.prototype.Ih),Polygon:ip(ss.prototype.Le),MultiPolygon:ip(ss.prototype.Lh),Box:ip(Es.prototype.fo)}});function Fs(b){b=b?b:{};ss.call(this,b);this.l=void 0!==b.surface?b.surface:!1;this.g=void 0!==b.curve?b.curve:!1;this.i=void 0!==b.multiCurve?b.multiCurve:!0;this.j=void 0!==b.multiSurface?b.multiSurface:!0;this.schemaLocation=b.schemaLocation?b.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd"}y(Fs,ss);l=Fs.prototype;l.no=function(b,c){var d=Q([],this.Ii,b,c,this);if(d){var e=new U(null);Wr(e,d);return e}};
+l.oo=function(b,c){var d=Q([],this.Mi,b,c,this);if(d){var e=new V(null);Zr(e,d);return e}};l.rg=function(b,c){pp(this.Ei,b,c,this)};l.ki=function(b,c){pp(this.Ti,b,c,this)};l.ro=function(b,c){return Q([null],this.Ni,b,c,this)};l.to=function(b,c){return Q([null],this.Si,b,c,this)};l.so=function(b,c){return Q([null],this.We,b,c,this)};l.mo=function(b,c){return Q([null],this.Md,b,c,this)};l.fl=function(b,c){var d=Q(void 0,this.Nd,b,c,this);d&&c[c.length-1].push(d)};
+l.yj=function(b,c){var d=Q(void 0,this.Nd,b,c,this);d&&(c[c.length-1][0]=d)};l.Oh=function(b,c){var d=Q([null],this.Ui,b,c,this);if(d&&d[0]){var e=new F(null),f=d[0],g=[f.length],h,k;h=1;for(k=d.length;h<k;++h)kb(f,d[h]),g.push(f.length);e.ba("XYZ",f,g);return e}};l.Dh=function(b,c){var d=Q([null],this.Fi,b,c,this);if(d){var e=new T(null);e.ba("XYZ",d);return e}};l.io=function(b,c){var d=Q([null],this.Gi,b,c,this);return Pd(d[1][0],d[1][1],d[2][0],d[2][1])};
+l.ko=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";d&&(e=He(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.Nf=function(b,c){var d=Mo(b,!1).replace(/^\s*|\s*$/g,""),e=c[0].srsName,f=b.parentNode.getAttribute("srsDimension"),g="enu";e&&(g=He(Ee(e)));d=d.split(/\s+/);e=2;b.getAttribute("srsDimension")?e=zs(b.getAttribute("srsDimension")):b.getAttribute("dimension")?e=zs(b.getAttribute("dimension")):f&&(e=zs(f));for(var h,k,m=[],n=0,p=d.length;n<p;n+=e)f=parseFloat(d[n]),h=parseFloat(d[n+1]),k=3===e?parseFloat(d[n+2]):0,"en"===g.substr(0,2)?m.push(f,h,k):m.push(h,f,k);return m};
+l.Md=Object({"http://www.opengis.net/gml":{pos:ip(Fs.prototype.ko),posList:ip(Fs.prototype.Nf)}});l.We=Object({"http://www.opengis.net/gml":{interior:Fs.prototype.fl,exterior:Fs.prototype.yj}});
+l.bg=Object({"http://www.opengis.net/gml":{Point:ip(ss.prototype.Mh),MultiPoint:ip(ss.prototype.Kh),LineString:ip(ss.prototype.Ke),MultiLineString:ip(ss.prototype.Jh),LinearRing:ip(ss.prototype.Ih),Polygon:ip(ss.prototype.Le),MultiPolygon:ip(ss.prototype.Lh),Surface:ip(Fs.prototype.Oh),MultiSurface:ip(Fs.prototype.oo),Curve:ip(Fs.prototype.Dh),MultiCurve:ip(Fs.prototype.no),Envelope:ip(Fs.prototype.io)}});l.Ii=Object({"http://www.opengis.net/gml":{curveMember:hp(Fs.prototype.rg),curveMembers:hp(Fs.prototype.rg)}});
+l.Mi=Object({"http://www.opengis.net/gml":{surfaceMember:hp(Fs.prototype.ki),surfaceMembers:hp(Fs.prototype.ki)}});l.Ei=Object({"http://www.opengis.net/gml":{LineString:hp(ss.prototype.Ke),Curve:hp(Fs.prototype.Dh)}});l.Ti=Object({"http://www.opengis.net/gml":{Polygon:hp(ss.prototype.Le),Surface:hp(Fs.prototype.Oh)}});l.Ui=Object({"http://www.opengis.net/gml":{patches:ip(Fs.prototype.ro)}});l.Fi=Object({"http://www.opengis.net/gml":{segments:ip(Fs.prototype.to)}});
+l.Gi=Object({"http://www.opengis.net/gml":{lowerCorner:hp(Fs.prototype.Nf),upperCorner:hp(Fs.prototype.Nf)}});l.Ni=Object({"http://www.opengis.net/gml":{PolygonPatch:ip(Fs.prototype.so)}});l.Si=Object({"http://www.opengis.net/gml":{LineStringSegment:ip(Fs.prototype.mo)}});function Gs(b,c,d){d=d[d.length-1].srsName;c=c.Z();for(var e=c.length,f=Array(e),g,h=0;h<e;++h){g=c[h];var k=h,m="enu";d&&(m=He(Ee(d)));f[k]="en"===m.substr(0,2)?g[0]+" "+g[1]:g[1]+" "+g[0]}Bs(b,f.join(" "))}
+l.zi=function(b,c,d){var e=d[d.length-1].srsName;e&&b.setAttribute("srsName",e);e=Lo(b.namespaceURI,"pos");b.appendChild(e);d=d[d.length-1].srsName;b="enu";d&&(b=He(Ee(d)));c=c.Z();Bs(e,"en"===b.substr(0,2)?c[0]+" "+c[1]:c[1]+" "+c[0])};var Hs={"http://www.opengis.net/gml":{lowerCorner:O(Bs),upperCorner:O(Bs)}};l=Fs.prototype;l.fp=function(b,c,d){var e=d[d.length-1].srsName;e&&b.setAttribute("srsName",e);qp({node:b},Hs,np,[c[0]+" "+c[1],c[2]+" "+c[3]],d,["lowerCorner","upperCorner"],this)};
+l.wi=function(b,c,d){var e=d[d.length-1].srsName;e&&b.setAttribute("srsName",e);e=Lo(b.namespaceURI,"posList");b.appendChild(e);Gs(e,c,d)};l.Ri=function(b,c){var d=c[c.length-1],e=d.node,f=d.exteriorWritten;void 0===f&&(d.exteriorWritten=!0);return Lo(e.namespaceURI,void 0!==f?"interior":"exterior")};
+l.Ve=function(b,c,d){var e=d[d.length-1].srsName;"PolygonPatch"!==b.nodeName&&e&&b.setAttribute("srsName",e);"Polygon"===b.nodeName||"PolygonPatch"===b.nodeName?(c=c.be(),qp({node:b,srsName:e},Is,this.Ri,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.Ve(b,c,d))};
+l.Qe=function(b,c,d){var e=d[d.length-1].srsName;"LineStringSegment"!==b.nodeName&&e&&b.setAttribute("srsName",e);"LineString"===b.nodeName||"LineStringSegment"===b.nodeName?(e=Lo(b.namespaceURI,"posList"),b.appendChild(e),Gs(e,c,d)):"Curve"===b.nodeName&&(e=Lo(b.namespaceURI,"segments"),b.appendChild(e),b=Lo(e.namespaceURI,"LineStringSegment"),e.appendChild(b),this.Qe(b,c,d))};
+l.yi=function(b,c,d){var e=d[d.length-1],f=e.srsName,e=e.surface;f&&b.setAttribute("srsName",f);c=c.ce();qp({node:b,srsName:f,surface:e},Js,this.c,c,d,void 0,this)};l.gp=function(b,c,d){var e=d[d.length-1].srsName;e&&b.setAttribute("srsName",e);c=c.ve();qp({node:b,srsName:e},Ks,lp("pointMember"),c,d,void 0,this)};l.xi=function(b,c,d){var e=d[d.length-1],f=e.srsName,e=e.curve;f&&b.setAttribute("srsName",f);c=c.sd();qp({node:b,srsName:f,curve:e},Ls,this.c,c,d,void 0,this)};
+l.Ai=function(b,c,d){var e=Lo(b.namespaceURI,"LinearRing");b.appendChild(e);this.wi(e,c,d)};l.Bi=function(b,c,d){var e=this.b(c,d);e&&(b.appendChild(e),this.Ve(e,c,d))};l.hp=function(b,c,d){var e=Lo(b.namespaceURI,"Point");b.appendChild(e);this.zi(e,c,d)};l.vi=function(b,c,d){var e=this.b(c,d);e&&(b.appendChild(e),this.Qe(e,c,d))};
+l.Te=function(b,c,d){var e=d[d.length-1],f=Tb(e);f.node=b;var g;ga(c)?e.dataProjection?g=Ze(c,e.featureProjection,e.dataProjection):g=c:g=Qr(c,!0,e);qp(f,Ms,this.b,[g],d,void 0,this)};
+l.ri=function(b,c,d){var e=c.Oa();e&&b.setAttribute("fid",e);var e=d[d.length-1],f=e.featureNS,g=c.a;e.xc||(e.xc={},e.xc[f]={});var h=c.R();c=[];var k=[],m;for(m in h){var n=h[m];null!==n&&(c.push(m),k.push(n),m==g||n instanceof $e?m in e.xc[f]||(e.xc[f][m]=O(this.Te,this)):m in e.xc[f]||(e.xc[f][m]=O(Bs)))}m=Tb(e);m.node=b;qp(m,e.xc,lp(void 0,f),k,d,c)};
+var Js={"http://www.opengis.net/gml":{surfaceMember:O(Fs.prototype.Bi),polygonMember:O(Fs.prototype.Bi)}},Ks={"http://www.opengis.net/gml":{pointMember:O(Fs.prototype.hp)}},Ls={"http://www.opengis.net/gml":{lineStringMember:O(Fs.prototype.vi),curveMember:O(Fs.prototype.vi)}},Is={"http://www.opengis.net/gml":{exterior:O(Fs.prototype.Ai),interior:O(Fs.prototype.Ai)}},Ms={"http://www.opengis.net/gml":{Curve:O(Fs.prototype.Qe),MultiCurve:O(Fs.prototype.xi),Point:O(Fs.prototype.zi),MultiPoint:O(Fs.prototype.gp),
+LineString:O(Fs.prototype.Qe),MultiLineString:O(Fs.prototype.xi),LinearRing:O(Fs.prototype.wi),Polygon:O(Fs.prototype.Ve),MultiPolygon:O(Fs.prototype.yi),Surface:O(Fs.prototype.Ve),MultiSurface:O(Fs.prototype.yi),Envelope:O(Fs.prototype.fp)}},Ns={MultiLineString:"lineStringMember",MultiCurve:"curveMember",MultiPolygon:"polygonMember",MultiSurface:"surfaceMember"};Fs.prototype.c=function(b,c){return Lo("http://www.opengis.net/gml",Ns[c[c.length-1].node.nodeName])};
+Fs.prototype.b=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.W(),"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)};
+Fs.prototype.o=function(b,c){c=Pr(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};c&&Wb(e,c);this.Te(d,b,[e]);return d};
+Fs.prototype.f=function(b,c){c=Pr(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};c&&Wb(e,c);var e=[e],f=e[e.length-1],g=f.featureType,h=f.featureNS,k={};k[h]={};k[h][g]=O(this.ri,this);f=Tb(f);f.node=d;qp(f,k,lp(g,h),b,e);return d};function Os(b){b=b?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326");this.a=b.readExtensions}y(Os,ps);var Ps=[null,"http://www.topografix.com/GPX/1/0","http://www.topografix.com/GPX/1/1"];function Qs(b,c,d){b.push(parseFloat(c.getAttribute("lon")),parseFloat(c.getAttribute("lat")));"ele"in d?(b.push(d.ele),delete d.ele):b.push(0);"time"in d?(b.push(d.time),delete d.time):b.push(0);return b}
+function Rs(b,c){var d=c[c.length-1],e=b.getAttribute("href");null!==e&&(d.link=e);pp(Ss,b,c)}function Ts(b,c){c[c.length-1].extensionsNode_=b}function Us(b,c){var d=c[0],e=Q({flatCoordinates:[]},Vs,b,c);if(e){var f=e.flatCoordinates;delete e.flatCoordinates;var g=new T(null);g.ba("XYZM",f);Qr(g,!1,d);d=new pn(g);d.I(e);return d}}
+function Ws(b,c){var d=c[0],e=Q({flatCoordinates:[],ends:[]},Xs,b,c);if(e){var f=e.flatCoordinates;delete e.flatCoordinates;var g=e.ends;delete e.ends;var h=new U(null);h.ba("XYZM",f,g);Qr(h,!1,d);d=new pn(h);d.I(e);return d}}function Ys(b,c){var d=c[0],e=Q({},Zs,b,c);if(e){var f=Qs([],b,e),f=new E(f,"XYZM");Qr(f,!1,d);d=new pn(f);d.I(e);return d}}
+var $s={rte:Us,trk:Ws,wpt:Ys},at=P(Ps,{rte:hp(Us),trk:hp(Ws),wpt:hp(Ys)}),Ss=P(Ps,{text:N(W,"linkText"),type:N(W,"linkType")}),Vs=P(Ps,{name:N(W),cmt:N(W),desc:N(W),src:N(W),link:Rs,number:N(ys),extensions:Ts,type:N(W),rtept:function(b,c){var d=Q({},bt,b,c);d&&Qs(c[c.length-1].flatCoordinates,b,d)}}),bt=P(Ps,{ele:N(ws),time:N(vs)}),Xs=P(Ps,{name:N(W),cmt:N(W),desc:N(W),src:N(W),link:Rs,number:N(ys),type:N(W),extensions:Ts,trkseg:function(b,c){var d=c[c.length-1];pp(ct,b,c);d.ends.push(d.flatCoordinates.length)}}),
+ct=P(Ps,{trkpt:function(b,c){var d=Q({},dt,b,c);d&&Qs(c[c.length-1].flatCoordinates,b,d)}}),dt=P(Ps,{ele:N(ws),time:N(vs)}),Zs=P(Ps,{ele:N(ws),time:N(vs),magvar:N(ws),geoidheight:N(ws),name:N(W),cmt:N(W),desc:N(W),src:N(W),link:Rs,sym:N(W),type:N(W),fix:N(W),sat:N(ys),hdop:N(ws),vdop:N(ws),pdop:N(ws),ageofdgpsdata:N(ws),dgpsid:N(ys),extensions:Ts});
+function et(b,c){c||(c=[]);for(var d=0,e=c.length;d<e;++d){var f=c[d];if(b.a){var g=f.get("extensionsNode_")||null;b.a(f,g)}f.set("extensionsNode_",void 0)}}Os.prototype.Eh=function(b,c){if(!vb(Ps,b.namespaceURI))return null;var d=$s[b.localName];if(!d)return null;d=d(b,[Or(this,b,c)]);if(!d)return null;et(this,[d]);return d};Os.prototype.ic=function(b,c){if(!vb(Ps,b.namespaceURI))return[];if("gpx"==b.localName){var d=Q([],at,b,[Or(this,b,c)]);if(d)return et(this,d),d}return[]};
+function ft(b,c,d){b.setAttribute("href",c);c=d[d.length-1].properties;qp({node:b},gt,np,[c.linkText,c.linkType],d,ht)}function it(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=jt[f];e=op(g,c);qp({node:b,properties:g},kt,np,e,d,c)}
+var ht=["text","type"],gt=P(Ps,{text:O(Bs),type:O(Bs)}),lt=P(Ps,"name cmt desc src link number type rtept".split(" ")),mt=P(Ps,{name:O(Bs),cmt:O(Bs),desc:O(Bs),src:O(Bs),link:O(ft),number:O(Ds),type:O(Bs),rtept:kp(O(it))}),nt=P(Ps,"name cmt desc src link number type trkseg".split(" ")),qt=P(Ps,{name:O(Bs),cmt:O(Bs),desc:O(Bs),src:O(Bs),link:O(ft),number:O(Ds),type:O(Bs),trkseg:kp(O(function(b,c,d){qp({node:b,geometryLayout:c.b,properties:{}},ot,pt,c.Z(),d)}))}),pt=lp("trkpt"),ot=P(Ps,{trkpt:O(it)}),
+jt=P(Ps,"ele time magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid".split(" ")),kt=P(Ps,{ele:O(Cs),time:O(function(b,c){var d=new Date(1E3*c),d=d.getUTCFullYear()+"-"+Pa(d.getUTCMonth()+1)+"-"+Pa(d.getUTCDate())+"T"+Pa(d.getUTCHours())+":"+Pa(d.getUTCMinutes())+":"+Pa(d.getUTCSeconds())+"Z";b.appendChild(Io.createTextNode(d))}),magvar:O(Cs),geoidheight:O(Cs),name:O(Bs),cmt:O(Bs),desc:O(Bs),src:O(Bs),link:O(ft),sym:O(Bs),type:O(Bs),fix:O(Bs),sat:O(Ds),
+hdop:O(Cs),vdop:O(Cs),pdop:O(Cs),ageofdgpsdata:O(Cs),dgpsid:O(Ds)}),rt={Point:"wpt",LineString:"rte",MultiLineString:"trk"};function st(b,c){var d=b.X();if(d&&(d=rt[d.W()]))return Lo(c[c.length-1].node.namespaceURI,d)}
+var tt=P(Ps,{rte:O(function(b,c,d){var e=d[0],f=c.R();b={node:b,properties:f};if(c=c.X())c=Qr(c,!0,e),b.geometryLayout=c.b,f.rtept=c.Z();e=lt[d[d.length-1].node.namespaceURI];f=op(f,e);qp(b,mt,np,f,d,e)}),trk:O(function(b,c,d){var e=d[0],f=c.R();b={node:b,properties:f};if(c=c.X())c=Qr(c,!0,e),f.trkseg=c.sd();e=nt[d[d.length-1].node.namespaceURI];f=op(f,e);qp(b,qt,np,f,d,e)}),wpt:O(function(b,c,d){var e=d[0],f=d[d.length-1];f.properties=c.R();if(c=c.X())c=Qr(c,!0,e),f.geometryLayout=c.b,it(b,c.Z(),
+d)})});Os.prototype.f=function(b,c){c=Pr(this,c);var d=Lo("http://www.topografix.com/GPX/1/1","gpx");qp({node:d},tt,st,b,[c]);return d};function ut(b){b=vt(b);return db(b,function(b){return b.b.substring(b.f,b.a)})}function wt(b,c,d){this.b=b;this.f=c;this.a=d}function vt(b){for(var c=RegExp("\r\n|\r|\n","g"),d=0,e,f=[];e=c.exec(b);)d=new wt(b,d,e.index),f.push(d),d=c.lastIndex;d<b.length&&(d=new wt(b,d,b.length),f.push(d));return f};function xt(){this.defaultDataProjection=null}y(xt,Nr);l=xt.prototype;l.W=function(){return"text"};l.Tb=function(b,c){return this.Dd(ia(b)?b:"",Pr(this,c))};l.Ba=function(b,c){return this.Lf(ia(b)?b:"",Pr(this,c))};l.Tc=function(b,c){return this.Fd(ia(b)?b:"",Pr(this,c))};l.Ia=function(){return this.defaultDataProjection};l.Kd=function(b,c){return this.Re(b,Pr(this,c))};l.Vb=function(b,c){return this.si(b,Pr(this,c))};l.Xc=function(b,c){return this.Ld(b,Pr(this,c))};function yt(b){b=b?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326");this.a=b.altitudeMode?b.altitudeMode:"none"}y(yt,xt);var zt=/^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/,At=/^H.([A-Z]{3}).*?:(.*)/,Bt=/^HFDTE(\d{2})(\d{2})(\d{2})/;
+yt.prototype.Dd=function(b,c){var d=this.a,e=ut(b),f={},g=[],h=2E3,k=0,m=1,n,p;n=0;for(p=e.length;n<p;++n){var q=e[n],r;if("B"==q.charAt(0)){if(r=zt.exec(q)){var q=parseInt(r[1],10),t=parseInt(r[2],10),x=parseInt(r[3],10),z=parseInt(r[4],10)+parseInt(r[5],10)/6E4;"S"==r[6]&&(z=-z);var A=parseInt(r[7],10)+parseInt(r[8],10)/6E4;"W"==r[9]&&(A=-A);g.push(A,z);"none"!=d&&g.push("gps"==d?parseInt(r[11],10):"barometric"==d?parseInt(r[12],10):0);g.push(Date.UTC(h,k,m,q,t,x)/1E3)}}else if("H"==q.charAt(0))if(r=
+Bt.exec(q))m=parseInt(r[1],10),k=parseInt(r[2],10)-1,h=2E3+parseInt(r[3],10);else if(r=At.exec(q))f[r[1]]=r[2].trim(),Bt.exec(q)}if(0===g.length)return null;e=new T(null);e.ba("none"==d?"XYM":"XYZM",g);d=new pn(Qr(e,!1,c));d.I(f);return d};yt.prototype.Lf=function(b,c){var d=this.Dd(b,c);return d?[d]:[]};function Ct(b,c){this.f=this.j=this.c="";this.l=null;this.g=this.a="";this.i=!1;var d;b instanceof Ct?(this.i=ca(c)?c:b.i,Dt(this,b.c),this.j=b.j,this.f=b.f,Et(this,b.l),this.a=b.a,Ft(this,b.b.clone()),this.g=b.g):b&&(d=String(b).match(go))?(this.i=!!c,Dt(this,d[1]||"",!0),this.j=Gt(d[2]||""),this.f=Gt(d[3]||"",!0),Et(this,d[4]),this.a=Gt(d[5]||"",!0),Ft(this,d[6]||"",!0),this.g=Gt(d[7]||"")):(this.i=!!c,this.b=new Ht(null,0,this.i))}
+Ct.prototype.toString=function(){var b=[],c=this.c;c&&b.push(It(c,Jt,!0),":");var d=this.f;if(d||"file"==c)b.push("//"),(c=this.j)&&b.push(It(c,Jt,!0),"@"),b.push(encodeURIComponent(String(d)).replace(/%25([0-9a-fA-F]{2})/g,"%$1")),d=this.l,null!=d&&b.push(":",String(d));if(d=this.a)this.f&&"/"!=d.charAt(0)&&b.push("/"),b.push(It(d,"/"==d.charAt(0)?Kt:Lt,!0));(d=this.b.toString())&&b.push("?",d);(d=this.g)&&b.push("#",It(d,Mt));return b.join("")};Ct.prototype.clone=function(){return new Ct(this)};
+function Dt(b,c,d){b.c=d?Gt(c,!0):c;b.c&&(b.c=b.c.replace(/:$/,""))}function Et(b,c){if(c){c=Number(c);if(isNaN(c)||0>c)throw Error("Bad port number "+c);b.l=c}else b.l=null}function Ft(b,c,d){c instanceof Ht?(b.b=c,Nt(b.b,b.i)):(d||(c=It(c,Ot)),b.b=new Ht(c,0,b.i))}function Pt(b){return b instanceof Ct?b.clone():new Ct(b,void 0)}
+function Qt(b,c){b instanceof Ct||(b=Pt(b));c instanceof Ct||(c=Pt(c));var d=b,e=c,f=d.clone(),g=!!e.c;g?Dt(f,e.c):g=!!e.j;g?f.j=e.j:g=!!e.f;g?f.f=e.f:g=null!=e.l;var h=e.a;if(g)Et(f,e.l);else if(g=!!e.a)if("/"!=h.charAt(0)&&(d.f&&!d.a?h="/"+h:(d=f.a.lastIndexOf("/"),-1!=d&&(h=f.a.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=[],m=0;m<d.length;){var n=d[m++];"."==n?h&&m==d.length&&k.push(""):".."==n?
+((1<k.length||1==k.length&&""!=k[0])&&k.pop(),h&&m==d.length&&k.push("")):(k.push(n),h=!0)}h=k.join("/")}else h=d;g?f.a=h:g=""!==e.b.toString();g?Ft(f,Gt(e.b.toString())):g=!!e.g;g&&(f.g=e.g);return f}function Gt(b,c){return b?c?decodeURI(b.replace(/%25/g,"%2525")):decodeURIComponent(b):""}function It(b,c,d){return ia(b)?(b=encodeURI(b).replace(c,Rt),d&&(b=b.replace(/%25([0-9a-fA-F]{2})/g,"%$1")),b):null}function Rt(b){b=b.charCodeAt(0);return"%"+(b>>4&15).toString(16)+(b&15).toString(16)}
+var Jt=/[#\/\?@]/g,Lt=/[\#\?:]/g,Kt=/[\#\?]/g,Ot=/[\#\?@]/g,Mt=/#/g;function Ht(b,c,d){this.f=this.a=null;this.b=b||null;this.c=!!d}function St(b){b.a||(b.a=new pi,b.f=0,b.b&&ho(b.b,function(c,d){b.add(decodeURIComponent(c.replace(/\+/g," ")),d)}))}l=Ht.prototype;l.qc=function(){St(this);return this.f};l.add=function(b,c){St(this);this.b=null;b=Tt(this,b);var d=this.a.get(b);d||this.a.set(b,d=[]);d.push(c);this.f++;return this};
+l.remove=function(b){St(this);b=Tt(this,b);return ri(this.a.f,b)?(this.b=null,this.f-=this.a.get(b).length,this.a.remove(b)):!1};l.clear=function(){this.a=this.b=null;this.f=0};l.La=function(){St(this);return 0==this.f};function Ut(b,c){St(b);c=Tt(b,c);return ri(b.a.f,c)}l.P=function(){St(this);for(var b=this.a.sc(),c=this.a.P(),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.sc=function(b){St(this);var c=[];if(ia(b))Ut(this,b)&&(c=ib(c,this.a.get(Tt(this,b))));else{b=this.a.sc();for(var d=0;d<b.length;d++)c=ib(c,b[d])}return c};l.set=function(b,c){St(this);this.b=null;b=Tt(this,b);Ut(this,b)&&(this.f-=this.a.get(b).length);this.a.set(b,[c]);this.f++;return this};l.get=function(b,c){var d=b?this.sc(b):[];return 0<d.length?String(d[0]):c};function Vt(b,c,d){b.remove(c);0<d.length&&(b.b=null,b.a.set(Tt(b,c),jb(d)),b.f+=d.length)}
+l.toString=function(){if(this.b)return this.b;if(!this.a)return"";for(var b=[],c=this.a.P(),d=0;d<c.length;d++)for(var e=c[d],f=encodeURIComponent(String(e)),e=this.sc(e),g=0;g<e.length;g++){var h=f;""!==e[g]&&(h+="="+encodeURIComponent(String(e[g])));b.push(h)}return this.b=b.join("&")};l.clone=function(){var b=new Ht;b.b=this.b;this.a&&(b.a=this.a.clone(),b.f=this.f);return b};function Tt(b,c){var d=String(c);b.c&&(d=d.toLowerCase());return d}
+function Nt(b,c){c&&!b.c&&(St(b),b.b=null,b.a.forEach(function(b,c){var f=c.toLowerCase();c!=f&&(this.remove(c),Vt(this,f,b))},b));b.c=c};function Wt(b){b=b||{};this.b=b.font;this.i=b.rotation;this.f=b.scale;this.G=b.text;this.l=b.textAlign;this.o=b.textBaseline;this.a=void 0!==b.fill?b.fill:new Tl({color:"#333"});this.j=void 0!==b.stroke?b.stroke:null;this.c=void 0!==b.offsetX?b.offsetX:0;this.g=void 0!==b.offsetY?b.offsetY:0}l=Wt.prototype;l.Nj=function(){return this.b};l.ak=function(){return this.c};l.bk=function(){return this.g};l.An=function(){return this.a};l.Bn=function(){return this.i};l.Cn=function(){return this.f};l.Dn=function(){return this.j};
+l.Ca=function(){return this.G};l.nk=function(){return this.l};l.pk=function(){return this.o};l.Lo=function(b){this.b=b};l.ci=function(b){this.c=b};l.di=function(b){this.g=b};l.Ko=function(b){this.a=b};l.En=function(b){this.i=b};l.Fn=function(b){this.f=b};l.Ro=function(b){this.j=b};l.fi=function(b){this.G=b};l.gi=function(b){this.l=b};l.So=function(b){this.o=b};function Xt(b){b=b?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326");this.b=b.defaultStyle?b.defaultStyle:Yt;this.c=void 0!==b.extractStyles?b.extractStyles:!0;this.i=void 0!==b.writeStyles?b.writeStyles:!0;this.a={};this.g=void 0!==b.showPointNames?b.showPointNames:!0}y(Xt,ps);
+var Zt=["http://www.google.com/kml/ext/2.2"],$t=[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"],au=[255,255,255,1],bu=new Tl({color:au}),cu=[20,2],du=[64,64],eu=new tk({anchor:cu,anchorOrigin:"bottom-left",anchorXUnits:"pixels",anchorYUnits:"pixels",crossOrigin:"anonymous",rotation:0,scale:.5,size:du,src:"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"}),fu=new Yl({color:au,width:1}),gu=new Wt({font:"bold 16px Helvetica",
+fill:bu,stroke:new Yl({color:[51,51,51,1],width:2}),scale:.8}),Yt=[new $l({fill:bu,image:eu,text:gu,stroke:fu,zIndex:0})],hu={fraction:"fraction",pixels:"pixels"};function iu(b,c){var d=null,e=[0,0],f="start";b.f&&(d=b.f.rd())&&2==d.length&&(e[0]=b.f.j*d[0]/2,e[1]=-b.f.j*d[1]/2,f="left");Pb(b.Ca())?d=new Wt({text:c,offsetX:e[0],offsetY:e[1],textAlign:f}):(d=Tb(b.Ca()),d.fi(c),d.gi(f),d.ci(e[0]),d.di(e[1]));return new $l({text:d})}
+function ju(b,c,d,e,f){return function(){var g=f,h="";g&&this.X()&&(g="Point"===this.X().W());g&&(h=this.R().name,g=g&&h);if(b)return g?(g=iu(b[0],h),b.concat(g)):b;if(c){var k=ku(c,d,e);return g?(g=iu(k[0],h),k.concat(g)):k}return g?(g=iu(d[0],h),d.concat(g)):d}}function ku(b,c,d){return ga(b)?b:ia(b)?(!(b in d)&&"#"+b in d&&(b="#"+b),ku(d[b],c,d)):c}
+function lu(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 mu(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 nu(b){var c=Mo(b,!1);return b.baseURI?Qt(b.baseURI,c.trim()).toString():c.trim()}function ou(b){b=ws(b);if(void 0!==b)return Math.sqrt(b)}function pu(b,c){return Q(null,qu,b,c)}function ru(b,c){var d=Q({A:[],oi:[]},su,b,c);if(d){var e=d.A,d=d.oi,f,g;f=0;for(g=Math.min(e.length,d.length);f<g;++f)e[4*f+3]=d[f];d=new T(null);d.ba("XYZM",e);return d}}function tu(b,c){var d=Q({},uu,b,c),e=Q(null,vu,b,c);if(e){var f=new T(null);f.ba("XYZ",e);f.I(d);return f}}
+function wu(b,c){var d=Q({},uu,b,c),e=Q(null,vu,b,c);if(e){var f=new F(null);f.ba("XYZ",e,[e.length]);f.I(d);return f}}
+function xu(b,c){var d=Q([],yu,b,c);if(!d)return null;if(0===d.length)return new gs(d);var e=!0,f=d[0].W(),g,h,k;h=1;for(k=d.length;h<k;++h)if(g=d[h],g.W()!=f){e=!1;break}if(e){if("Point"==f){g=d[0];e=g.b;f=g.ia();h=1;for(k=d.length;h<k;++h)g=d[h],kb(f,g.ia());g=new Xr(null);g.ba(e,f);zu(g,d);return g}return"LineString"==f?(g=new U(null),Wr(g,d),zu(g,d),g):"Polygon"==f?(g=new V(null),Zr(g,d),zu(g,d),g):"GeometryCollection"==f?new gs(d):null}return new gs(d)}
+function Au(b,c){var d=Q({},uu,b,c),e=Q(null,vu,b,c);if(e){var f=new E(null);f.ba("XYZ",e);f.I(d);return f}}function Bu(b,c){var d=Q({},uu,b,c),e=Q([null],Cu,b,c);if(e&&e[0]){var f=new F(null),g=e[0],h=[g.length],k,m;k=1;for(m=e.length;k<m;++k)kb(g,e[k]),h.push(g.length);f.ba("XYZ",g,h);f.I(d);return f}}
+function Du(b,c){var d=Q({},Eu,b,c);if(!d)return null;var e="fillStyle"in d?d.fillStyle:bu,f=d.fill;void 0===f||f||(e=null);var f="imageStyle"in d?d.imageStyle:eu,g="textStyle"in d?d.textStyle:gu,h="strokeStyle"in d?d.strokeStyle:fu,d=d.outline;void 0===d||d||(h=null);return[new $l({fill:e,image:f,stroke:h,text:g,zIndex:void 0})]}
+function zu(b,c){var d=c.length,e=Array(c.length),f=Array(c.length),g,h,k,m;k=m=!1;for(h=0;h<d;++h)g=c[h],e[h]=g.get("extrude"),f[h]=g.get("altitudeMode"),k=k||void 0!==e[h],m=m||f[h];k&&b.set("extrude",e);m&&b.set("altitudeMode",f)}function Fu(b,c){pp(Gu,b,c)}
+var Hu=P($t,{value:ip(W)}),Gu=P($t,{Data:function(b,c){var d=b.getAttribute("name");if(null!==d){var e=Q(void 0,Hu,b,c);e&&(c[c.length-1][d]=e)}},SchemaData:function(b,c){pp(Iu,b,c)}}),uu=P($t,{extrude:N(ts),altitudeMode:N(W)}),qu=P($t,{coordinates:ip(mu)}),Cu=P($t,{innerBoundaryIs:function(b,c){var d=Q(void 0,Ju,b,c);d&&c[c.length-1].push(d)},outerBoundaryIs:function(b,c){var d=Q(void 0,Ku,b,c);d&&(c[c.length-1][0]=d)}}),su=P($t,{when:function(b,c){var d=c[c.length-1].oi,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),e[3]?parseInt(e[3],10)-1:0,e[5]?parseInt(e[5],10):1,e[7]?parseInt(e[7],10):0,e[8]?parseInt(e[8],10):0,e[9]?parseInt(e[9],10):0);if(e[10]&&"Z"!=e[10]){var g="-"==e[11]?-1:1,f=f+60*g*parseInt(e[12],10);e[13]&&(f+=3600*g*parseInt(e[13],10))}d.push(f)}else d.push(0)}},P(Zt,{coord:function(b,c){var d=c[c.length-1].A,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)}})),vu=P($t,{coordinates:ip(mu)}),Lu=P($t,{href:N(nu)},P(Zt,{x:N(ws),y:N(ws),w:N(ws),h:N(ws)})),Mu=P($t,{Icon:N(function(b,c){var d=Q({},Lu,b,c);return d?d:null}),heading:N(ws),hotSpot:N(function(b){var c=b.getAttribute("xunits"),d=b.getAttribute("yunits");return{x:parseFloat(b.getAttribute("x")),$f:hu[c],y:parseFloat(b.getAttribute("y")),ag:hu[d]}}),scale:N(ou)}),Ju=P($t,{LinearRing:ip(pu)}),Nu=P($t,{color:N(lu),scale:N(ou)}),Ou=P($t,{color:N(lu),
+width:N(ws)}),yu=P($t,{LineString:hp(tu),LinearRing:hp(wu),MultiGeometry:hp(xu),Point:hp(Au),Polygon:hp(Bu)}),Pu=P(Zt,{Track:hp(ru)}),Ru=P($t,{ExtendedData:Fu,Link:function(b,c){pp(Qu,b,c)},address:N(W),description:N(W),name:N(W),open:N(ts),phoneNumber:N(W),visibility:N(ts)}),Qu=P($t,{href:N(nu)}),Ku=P($t,{LinearRing:ip(pu)}),Su=P($t,{Style:N(Du),key:N(W),styleUrl:N(function(b){var c=Mo(b,!1).trim();return b.baseURI?Qt(b.baseURI,c).toString():c})}),Uu=P($t,{ExtendedData:Fu,MultiGeometry:N(xu,"geometry"),
+LineString:N(tu,"geometry"),LinearRing:N(wu,"geometry"),Point:N(Au,"geometry"),Polygon:N(Bu,"geometry"),Style:N(Du),StyleMap:function(b,c){var d=Q(void 0,Tu,b,c);if(d){var e=c[c.length-1];ga(d)?e.Style=d:ia(d)&&(e.styleUrl=d)}},address:N(W),description:N(W),name:N(W),open:N(ts),phoneNumber:N(W),styleUrl:N(nu),visibility:N(ts)},P(Zt,{MultiTrack:N(function(b,c){var d=Q([],Pu,b,c);if(d){var e=new U(null);Wr(e,d);return e}},"geometry"),Track:N(ru,"geometry")})),Vu=P($t,{color:N(lu),fill:N(ts),outline:N(ts)}),
+Iu=P($t,{SimpleData:function(b,c){var d=b.getAttribute("name");if(null!==d){var e=W(b);c[c.length-1][d]=e}}}),Eu=P($t,{IconStyle:function(b,c){var d=Q({},Mu,b,c);if(d){var e=c[c.length-1],f="Icon"in d?d.Icon:{},g;g=(g=f.href)?g:"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png";var h,k,m,n=d.hotSpot;n?(h=[n.x,n.y],k=n.$f,m=n.ag):"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"===g?(h=cu,m=k="pixels"):/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(g)&&(h=[.5,0],m=k="fraction");
+var p,n=f.x,q=f.y;void 0!==n&&void 0!==q&&(p=[n,q]);var r,n=f.w,f=f.h;void 0!==n&&void 0!==f&&(r=[n,f]);var t,f=d.heading;void 0!==f&&(t=Xa(f));d=d.scale;"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"==g&&(r=du,void 0===d&&(d=.5));h=new tk({anchor:h,anchorOrigin:"bottom-left",anchorXUnits:k,anchorYUnits:m,crossOrigin:"anonymous",offset:p,offsetOrigin:"bottom-left",rotation:t,scale:d,size:r,src:g});e.imageStyle=h}},LabelStyle:function(b,c){var d=Q({},Nu,b,c);d&&(c[c.length-1].textStyle=
+new Wt({fill:new Tl({color:"color"in d?d.color:au}),scale:d.scale}))},LineStyle:function(b,c){var d=Q({},Ou,b,c);d&&(c[c.length-1].strokeStyle=new Yl({color:"color"in d?d.color:au,width:"width"in d?d.width:1}))},PolyStyle:function(b,c){var d=Q({},Vu,b,c);if(d){var e=c[c.length-1];e.fillStyle=new Tl({color:"color"in d?d.color:au});var f=d.fill;void 0!==f&&(e.fill=f);d=d.outline;void 0!==d&&(e.outline=d)}}}),Tu=P($t,{Pair:function(b,c){var d=Q({},Su,b,c);if(d){var e=d.key;e&&"normal"==e&&((e=d.styleUrl)&&
+(c[c.length-1]=e),(d=d.Style)&&(c[c.length-1]=d))}}});l=Xt.prototype;l.Hf=function(b,c){Qo(b);var d=P($t,{Document:gp(this.Hf,this),Folder:gp(this.Hf,this),Placemark:hp(this.Pf,this),Style:ua(this.vo,this),StyleMap:ua(this.uo,this)});if(d=Q([],d,b,c,this))return d};
+l.Pf=function(b,c){var d=Q({geometry:null},Uu,b,c);if(d){var e=new pn,f=b.getAttribute("id");null!==f&&e.jc(f);var f=c[0],g=d.geometry;g&&Qr(g,!1,f);e.Ma(g);delete d.geometry;this.c&&e.wf(ju(d.Style,d.styleUrl,this.b,this.a,this.g));delete d.Style;e.I(d);return e}};l.vo=function(b,c){var d=b.getAttribute("id");if(null!==d){var e=Du(b,c);e&&(d=b.baseURI?Qt(b.baseURI,"#"+d).toString():"#"+d,this.a[d]=e)}};
+l.uo=function(b,c){var d=b.getAttribute("id");if(null!==d){var e=Q(void 0,Tu,b,c);e&&(d=b.baseURI?Qt(b.baseURI,"#"+d).toString():"#"+d,this.a[d]=e)}};l.Eh=function(b,c){if(!vb($t,b.namespaceURI))return null;var d=this.Pf(b,[Or(this,b,c)]);return d?d:null};
+l.ic=function(b,c){if(!vb($t,b.namespaceURI))return[];var d;d=Qo(b);if("Document"==d||"Folder"==d)return(d=this.Hf(b,[Or(this,b,c)]))?d:[];if("Placemark"==d)return(d=this.Pf(b,[Or(this,b,c)]))?[d]:[];if("kml"==d){d=[];var e;for(e=b.firstElementChild;e;e=e.nextElementSibling){var f=this.ic(e,c);f&&kb(d,f)}return d}return[]};l.po=function(b){if(To(b))return Wu(this,b);if(Wo(b))return Xu(this,b);if(ia(b))return b=fp(b),Wu(this,b)};
+function Wu(b,c){var d;for(d=c.firstChild;d;d=d.nextSibling)if(1==d.nodeType){var e=Xu(b,d);if(e)return e}}function Xu(b,c){var d;for(d=c.firstElementChild;d;d=d.nextElementSibling)if(vb($t,d.namespaceURI)&&"name"==d.localName)return W(d);for(d=c.firstElementChild;d;d=d.nextElementSibling){var e=Qo(d);if(vb($t,d.namespaceURI)&&("Document"==e||"Folder"==e||"Placemark"==e||"kml"==e)&&(e=Xu(b,d)))return e}}
+l.qo=function(b){var c=[];To(b)?kb(c,Yu(this,b)):Wo(b)?kb(c,Zu(this,b)):ia(b)&&(b=fp(b),kb(c,Yu(this,b)));return c};function Yu(b,c){var d,e=[];for(d=c.firstChild;d;d=d.nextSibling)1==d.nodeType&&kb(e,Zu(b,d));return e}
+function Zu(b,c){var d,e=[];for(d=c.firstElementChild;d;d=d.nextElementSibling)if(vb($t,d.namespaceURI)&&"NetworkLink"==d.localName){var f=Q({},Ru,d,[]);e.push(f)}for(d=c.firstElementChild;d;d=d.nextElementSibling)f=Qo(d),!vb($t,d.namespaceURI)||"Document"!=f&&"Folder"!=f&&"kml"!=f||kb(e,Zu(b,d));return e}function $u(b,c){var d=tg(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}Bs(b,d.join(""))}
+function av(b,c,d){qp({node:b},bv,cv,[c],d)}function dv(b,c,d){var e={node:b};c.Oa()&&b.setAttribute("id",c.Oa());b=c.R();var f=c.ac();if(f&&(f=f.call(c,0))&&0<f.length){var g=f[0];this.i&&(b.Style=f[0]);(f=g.Ca())&&(b.name=f.Ca())}f=ev[d[d.length-1].node.namespaceURI];b=op(b,f);qp(e,fv,np,b,d,f);b=d[0];(c=c.X())&&(c=Qr(c,!0,b));qp(e,fv,gv,[c],d)}function hv(b,c,d){var e=c.ia();b={node:b};b.layout=c.b;b.stride=c.ra();qp(b,iv,jv,[e],d)}
+function kv(b,c,d){c=c.be();var e=c.shift();b={node:b};qp(b,lv,mv,c,d);qp(b,lv,nv,[e],d)}function ov(b,c){Cs(b,c*c)}
+var pv=P($t,["Document","Placemark"]),sv=P($t,{Document:O(function(b,c,d){qp({node:b},qv,rv,c,d,void 0,this)}),Placemark:O(dv)}),qv=P($t,{Placemark:O(dv)}),tv={Point:"Point",LineString:"LineString",LinearRing:"LinearRing",Polygon:"Polygon",MultiPoint:"MultiGeometry",MultiLineString:"MultiGeometry",MultiPolygon:"MultiGeometry"},uv=P($t,["href"],P(Zt,["x","y","w","h"])),vv=P($t,{href:O(Bs)},P(Zt,{x:O(Cs),y:O(Cs),w:O(Cs),h:O(Cs)})),wv=P($t,["scale","heading","Icon","hotSpot"]),yv=P($t,{Icon:O(function(b,
+c,d){b={node:b};var e=uv[d[d.length-1].node.namespaceURI],f=op(c,e);qp(b,vv,np,f,d,e);e=uv[Zt[0]];f=op(c,e);qp(b,vv,xv,f,d,e)}),heading:O(Cs),hotSpot:O(function(b,c){b.setAttribute("x",c.x);b.setAttribute("y",c.y);b.setAttribute("xunits",c.$f);b.setAttribute("yunits",c.ag)}),scale:O(ov)}),zv=P($t,["color","scale"]),Av=P($t,{color:O($u),scale:O(ov)}),Bv=P($t,["color","width"]),Cv=P($t,{color:O($u),width:O(Cs)}),bv=P($t,{LinearRing:O(hv)}),Dv=P($t,{LineString:O(hv),Point:O(hv),Polygon:O(kv)}),ev=P($t,
+"name open visibility address phoneNumber description styleUrl Style".split(" ")),fv=P($t,{MultiGeometry:O(function(b,c,d){b={node:b};var e=c.W(),f,g;"MultiPoint"==e?(f=c.ve(),g=Ev):"MultiLineString"==e?(f=c.sd(),g=Fv):"MultiPolygon"==e&&(f=c.ce(),g=Gv);qp(b,Dv,g,f,d)}),LineString:O(hv),LinearRing:O(hv),Point:O(hv),Polygon:O(kv),Style:O(function(b,c,d){b={node:b};var e={},f=c.g,g=c.b,h=c.f;c=c.Ca();h instanceof tk&&(e.IconStyle=h);c&&(e.LabelStyle=c);g&&(e.LineStyle=g);f&&(e.PolyStyle=f);c=Hv[d[d.length-
+1].node.namespaceURI];e=op(e,c);qp(b,Iv,np,e,d,c)}),address:O(Bs),description:O(Bs),name:O(Bs),open:O(As),phoneNumber:O(Bs),styleUrl:O(Bs),visibility:O(As)}),iv=P($t,{coordinates:O(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]}Bs(b,k)})}),lv=P($t,{outerBoundaryIs:O(av),innerBoundaryIs:O(av)}),Jv=P($t,{color:O($u)}),
+Hv=P($t,["IconStyle","LabelStyle","LineStyle","PolyStyle"]),Iv=P($t,{IconStyle:O(function(b,c,d){b={node:b};var e={},f=c.Cb(),g=c.rd(),h={href:c.a.i};if(f){h.w=f[0];h.h=f[1];var k=c.Xb(),m=c.Da();m&&g&&0!==m[0]&&m[1]!==f[1]&&(h.x=m[0],h.y=g[1]-(m[1]+f[1]));k&&0!==k[0]&&k[1]!==f[1]&&(e.hotSpot={x:k[0],$f:"pixels",y:f[1]-k[1],ag:"pixels"})}e.Icon=h;f=c.j;1!==f&&(e.scale=f);c=c.G;0!==c&&(e.heading=c);c=wv[d[d.length-1].node.namespaceURI];e=op(e,c);qp(b,yv,np,e,d,c)}),LabelStyle:O(function(b,c,d){b={node:b};
+var e={},f=c.a;f&&(e.color=f.a);(c=c.f)&&1!==c&&(e.scale=c);c=zv[d[d.length-1].node.namespaceURI];e=op(e,c);qp(b,Av,np,e,d,c)}),LineStyle:O(function(b,c,d){b={node:b};var e=Bv[d[d.length-1].node.namespaceURI];c=op({color:c.a,width:c.f},e);qp(b,Cv,np,c,d,e)}),PolyStyle:O(function(b,c,d){qp({node:b},Jv,Kv,[c.a],d)})});function xv(b,c,d){return Lo(Zt[0],"gx:"+d)}function rv(b,c){return Lo(c[c.length-1].node.namespaceURI,"Placemark")}
+function gv(b,c){if(b)return Lo(c[c.length-1].node.namespaceURI,tv[b.W()])}var Kv=lp("color"),jv=lp("coordinates"),mv=lp("innerBoundaryIs"),Ev=lp("Point"),Fv=lp("LineString"),cv=lp("LinearRing"),Gv=lp("Polygon"),nv=lp("outerBoundaryIs");
+Xt.prototype.f=function(b,c){c=Pr(this,c);var d=Lo($t[4],"kml");ep(d,"http://www.w3.org/2000/xmlns/","xmlns:gx",Zt[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=pv[d.namespaceURI],f=op(f,g);qp(e,sv,np,f,[c],g,this);
+return d};(function(){var b={},c={ja:b};(function(d){if("object"===typeof b&&"undefined"!==typeof c)c.ja=d();else{var e;"undefined"!==typeof window?e=window:"undefined"!==typeof global?e=global:"undefined"!==typeof self?e=self:e=this;e.Ap=d()}})(function(){return function e(b,c,h){function k(n,q){if(!c[n]){if(!b[n]){var r="function"==typeof require&&require;if(!q&&r)return r(n,!0);if(m)return m(n,!0);r=Error("Cannot find module '"+n+"'");throw r.code="MODULE_NOT_FOUND",r;}r=c[n]={ja:{}};b[n][0].call(r.ja,function(c){var e=
+b[n][1][c];return k(e?e:c)},r,r.ja,e,b,c,h)}return c[n].ja}for(var m="function"==typeof require&&require,n=0;n<h.length;n++)k(h[n]);return k}({1:[function(b,c){function g(b){var c;b&&b.length&&(c=b,b=c.length);b=new Uint8Array(b||0);c&&b.set(c);b.Ph=m.Ph;b.Zf=m.Zf;b.Hh=m.Hh;b.ui=m.ui;b.Of=m.Of;b.ti=m.ti;b.If=m.If;b.pi=m.pi;b.toString=m.toString;b.write=m.write;b.slice=m.slice;b.pg=m.pg;b.dj=!0;return b}function h(b){for(var c=b.length,e=[],f=0,g,h;f<c;f++){g=b.charCodeAt(f);if(55295<g&&57344>g)if(h)if(56320>
+g){e.push(239,191,189);h=g;continue}else g=h-55296<<10|g-56320|65536,h=null;else{56319<g||f+1===c?e.push(239,191,189):h=g;continue}else h&&(e.push(239,191,189),h=null);128>g?e.push(g):2048>g?e.push(g>>6|192,g&63|128):65536>g?e.push(g>>12|224,g>>6&63|128,g&63|128):e.push(g>>18|240,g>>12&63|128,g>>6&63|128,g&63|128)}return e}c.ja=g;var k=b("ieee754"),m,n,p;m={Ph:function(b){return(this[b]|this[b+1]<<8|this[b+2]<<16)+16777216*this[b+3]},Zf:function(b,c){this[c]=b;this[c+1]=b>>>8;this[c+2]=b>>>16;this[c+
+3]=b>>>24},Hh:function(b){return(this[b]|this[b+1]<<8|this[b+2]<<16)+(this[b+3]<<24)},Of:function(b){return k.read(this,b,!0,23,4)},If:function(b){return k.read(this,b,!0,52,8)},ti:function(b,c){return k.write(this,b,c,!0,23,4)},pi:function(b,c){return k.write(this,b,c,!0,52,8)},toString:function(b,c,e){var f=b="";e=Math.min(this.length,e||this.length);for(c=c||0;c<e;c++){var g=this[c];127>=g?(b+=decodeURIComponent(f)+String.fromCharCode(g),f=""):f+="%"+g.toString(16)}return b+=decodeURIComponent(f)},
+write:function(b,c){for(var e=b===n?p:h(b),f=0;f<e.length;f++)this[c+f]=e[f]},slice:function(b,c){return this.subarray(b,c)},pg:function(b,c){c=c||0;for(var e=0;e<this.length;e++)b[c+e]=this[e]}};m.ui=m.Zf;g.byteLength=function(b){n=b;p=h(b);return p.length};g.isBuffer=function(b){return!(!b||!b.dj)}},{ieee754:3}],2:[function(b,c){(function(g){function h(b){this.Fb=k.isBuffer(b)?b:new k(b||0);this.da=0;this.length=this.Fb.length}c.ja=h;var k=g.ip||b("./buffer");h.c=0;h.b=1;h.a=2;h.f=5;var m=Math.pow(2,
+63);h.prototype={Mf:function(b,c,e){for(e=e||this.length;this.da<e;){var f=this.za(),g=this.da;b(f>>3,c,this);this.da===g&&this.Xo(f)}return c},lo:function(){var b=this.Fb.Of(this.da);this.da+=4;return b},ho:function(){var b=this.Fb.If(this.da);this.da+=8;return b},za:function(){var b=this.Fb,c,e,f,g,h;c=b[this.da++];if(128>c)return c;c=c&127;f=b[this.da++];if(128>f)return c|f<<7;f=(f&127)<<7;g=b[this.da++];if(128>g)return c|f|g<<14;g=(g&127)<<14;h=b[this.da++];if(128>h)return c|f|g|h<<21;e=b[this.da++];
+c=(c|f|g|(h&127)<<21)+268435456*(e&127);if(128>e)return c;e=b[this.da++];c+=34359738368*(e&127);if(128>e)return c;e=b[this.da++];c+=4398046511104*(e&127);if(128>e)return c;e=b[this.da++];c+=562949953421312*(e&127);if(128>e)return c;e=b[this.da++];c+=72057594037927936*(e&127);if(128>e)return c;e=b[this.da++];c+=0x7fffffffffffffff*(e&127);if(128>e)return c;throw Error("Expected varint not more than 10 bytes");},wo:function(){var b=this.da,c=this.za();if(c<m)return c;for(var e=this.da-2;255===this.Fb[e];)e--;
+e<b&&(e=b);for(var f=c=0;f<e-b+1;f++)var g=~this.Fb[b+f]&127,c=c+(4>f?g<<7*f:g*Math.pow(2,7*f));return-c-1},Gd:function(){var b=this.za();return 1===b%2?(b+1)/-2:b/2},eo:function(){return Boolean(this.za())},Rf:function(){var b=this.za()+this.da,c=this.Fb.toString("utf8",this.da,b);this.da=b;return c},Xo:function(b){b=b&7;if(b===h.c)for(;127<this.Fb[this.da++];);else if(b===h.a)this.da=this.za()+this.da;else if(b===h.f)this.da+=4;else if(b===h.b)this.da+=8;else throw Error("Unimplemented type: "+
+b);}}}).call(this,"undefined"!==typeof global?global:"undefined"!==typeof self?self:"undefined"!==typeof window?window:{})},{"./buffer":1}],3:[function(b,c,g){g.read=function(b,c,e,f,g){var q;q=8*g-f-1;var r=(1<<q)-1,t=r>>1,x=-7;g=e?g-1:0;var z=e?-1:1,A=b[c+g];g+=z;e=A&(1<<-x)-1;A>>=-x;for(x+=q;0<x;e=256*e+b[c+g],g+=z,x-=8);q=e&(1<<-x)-1;e>>=-x;for(x+=f;0<x;q=256*q+b[c+g],g+=z,x-=8);if(0===e)e=1-t;else{if(e===r)return q?NaN:Infinity*(A?-1:1);q+=Math.pow(2,f);e=e-t}return(A?-1:1)*q*Math.pow(2,e-f)};
+g.write=function(b,c,e,f,g,q){var r,t=8*q-g-1,x=(1<<t)-1,z=x>>1,A=23===g?Math.pow(2,-24)-Math.pow(2,-77):0;q=f?0:q-1;var B=f?1:-1,v=0>c||0===c&&0>1/c?1:0;c=Math.abs(c);isNaN(c)||Infinity===c?(c=isNaN(c)?1:0,f=x):(f=Math.floor(Math.log(c)/Math.LN2),1>c*(r=Math.pow(2,-f))&&(f--,r*=2),c=1<=f+z?c+A/r:c+A*Math.pow(2,1-z),2<=c*r&&(f++,r/=2),f+z>=x?(c=0,f=x):1<=f+z?(c=(c*r-1)*Math.pow(2,g),f+=z):(c=c*Math.pow(2,z-1)*Math.pow(2,g),f=0));for(;8<=g;b[e+q]=c&255,q+=B,c/=256,g-=8);f=f<<g|c;for(t+=g;0<t;b[e+q]=
+f&255,q+=B,f/=256,t-=8);b[e+q-B]|=128*v}},{}]},{},[2])(2)});wp=c.ja})();(function(){var b={},c={ja:b};(function(d){if("object"===typeof b&&"undefined"!==typeof c)c.ja=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(n,q){if(!c[n]){if(!b[n]){var r="function"==typeof require&&require;if(!q&&r)return r(n,!0);if(m)return m(n,!0);r=Error("Cannot find module '"+n+"'");throw r.code="MODULE_NOT_FOUND",r;}r=c[n]={ja:{}};b[n][0].call(r.ja,function(c){var e=
+b[n][1][c];return k(e?e:c)},r,r.ja,e,b,c,h)}return c[n].ja}for(var m="function"==typeof require&&require,n=0;n<h.length;n++)k(h[n]);return k}({1:[function(b,c){c.ja.Vi=b("./lib/vectortile.js");c.ja.vp=b("./lib/vectortilefeature.js");c.ja.wp=b("./lib/vectortilelayer.js")},{"./lib/vectortile.js":2,"./lib/vectortilefeature.js":3,"./lib/vectortilelayer.js":4}],2:[function(b,c){function g(b,c,e){3===b&&(b=new h(e,e.za()+e.da),b.length&&(c[b.name]=b))}var h=b("./vectortilelayer");c.ja=function(b,c){this.layers=
+b.Mf(g,{},c)}},{"./vectortilelayer":4}],3:[function(b,c){function g(b,c,e,f,g){this.properties={};this.extent=e;this.type=0;this.oc=b;this.Ze=-1;this.Qd=f;this.Sd=g;b.Mf(h,this,c)}function h(b,c,e){if(1==b)c.yp=e.za();else if(2==b)for(b=e.za()+e.da;e.da<b;){var f=c.Qd[e.za()],g=c.Sd[e.za()];c.properties[f]=g}else 3==b?c.type=e.za():4==b&&(c.Ze=e.da)}var k=b("point-geometry");c.ja=g;g.types=["Unknown","Point","LineString","Polygon"];g.prototype.Rg=function(){var b=this.oc;b.da=this.Ze;for(var c=b.za()+
+b.da,e=1,f=0,g=0,h=0,x=[],z;b.da<c;)if(f||(f=b.za(),e=f&7,f=f>>3),f--,1===e||2===e)g+=b.Gd(),h+=b.Gd(),1===e&&(z&&x.push(z),z=[]),z.push(new k(g,h));else if(7===e)z&&z.push(z[0].clone());else throw Error("unknown command "+e);z&&x.push(z);return x};g.prototype.bbox=function(){var b=this.oc;b.da=this.Ze;for(var c=b.za()+b.da,e=1,f=0,g=0,h=0,k=Infinity,z=-Infinity,A=Infinity,B=-Infinity;b.da<c;)if(f||(f=b.za(),e=f&7,f=f>>3),f--,1===e||2===e)g+=b.Gd(),h+=b.Gd(),g<k&&(k=g),g>z&&(z=g),h<A&&(A=h),h>B&&
+(B=h);else if(7!==e)throw Error("unknown command "+e);return[k,A,z,B]}},{"point-geometry":5}],4:[function(b,c){function g(b,c){this.version=1;this.name=null;this.extent=4096;this.length=0;this.oc=b;this.Qd=[];this.Sd=[];this.Pd=[];b.Mf(h,this,c);this.length=this.Pd.length}function h(b,c,e){15===b?c.version=e.za():1===b?c.name=e.Rf():5===b?c.extent=e.za():2===b?c.Pd.push(e.da):3===b?c.Qd.push(e.Rf()):4===b&&c.Sd.push(k(e))}function k(b){for(var c=null,e=b.za()+b.da;b.da<e;)c=b.za()>>3,c=1===c?b.Rf():
+2===c?b.lo():3===c?b.ho():4===c?b.wo():5===c?b.za():6===c?b.Gd():7===c?b.eo():null;return c}var m=b("./vectortilefeature.js");c.ja=g;g.prototype.feature=function(b){if(0>b||b>=this.Pd.length)throw Error("feature index out of bounds");this.oc.da=this.Pd[b];b=this.oc.za()+this.oc.da;return new m(this.oc,b,this.extent,this.Qd,this.Sd)}},{"./vectortilefeature.js":3}],5:[function(b,c){function g(b,c){this.x=b;this.y=c}c.ja=g;g.prototype={clone:function(){return new g(this.x,this.y)},add:function(b){return this.clone().Wi(b)},
+rotate:function(b){return this.clone().gj(b)},round:function(){return this.clone().hj()},angle:function(){return Math.atan2(this.y,this.x)},Wi:function(b){this.x+=b.x;this.y+=b.y;return this},gj:function(b){var c=Math.cos(b);b=Math.sin(b);var e=b*this.x+c*this.y;this.x=c*this.x-b*this.y;this.y=e;return this},hj:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this}};g.a=function(b){return b instanceof g?b:Array.isArray(b)?new g(b[0],b[1]):b}},{}]},{},[1])(1)});xp=c.ja})();function Lv(b){this.defaultDataProjection=null;b=b?b:{};this.defaultDataProjection=new Ce({code:"EPSG:3857",units:"tile-pixels"});this.a=b.featureClass?b.featureClass:Sm;this.c=b.geometryName?b.geometryName:"geometry";this.f=b.layerName?b.layerName:"layer";this.b=b.layers?b.layers:null}y(Lv,Nr);Lv.prototype.W=function(){return"arraybuffer"};
+Lv.prototype.Ba=function(b,c){var d=this.b,e=new wp(b),e=new xp.Vi(e),f=[],g=this.a,h,k,m;for(m in e.layers)if(!d||-1!=d.indexOf(m)){h=e.layers[m];for(var n=0,p=h.length;n<p;++n){if(g===Sm){var q=h.feature(n);k=m;var r=q.Rg(),t=[],x=[];Mv(r,x,t);var z=q.type,A=void 0;1===z?A=1===r.length?"Point":"MultiPoint":2===z?A=1===r.length?"LineString":"MultiLineString":3===z&&(A="Polygon");q=q.properties;q[this.f]=k;k=new this.a(A,x,t,q)}else{q=h.feature(n);A=m;x=c;k=new this.a;t=q.properties;t[this.f]=A;A=
+q.type;if(0===A)A=null;else{q=q.Rg();r=[];z=[];Mv(q,z,r);var B=void 0;1===A?B=1===q.length?new E(null):new Xr(null):2===A?1===q.length?B=new T(null):B=new U(null):3===A&&(B=new F(null));B.ba("XY",z,r);A=B}(x=Qr(A,!1,Pr(this,x)))&&(t[this.c]=x);k.I(t);k.yc(this.c)}f.push(k)}}return f};Lv.prototype.Ia=function(){return this.defaultDataProjection};Lv.prototype.g=function(b){this.b=b};
+function Mv(b,c,d){for(var e=0,f,g,h=0,k=b.length;h<k;++h){f=b[h];for(var m=0,n=f.length;m<n;++m)g=f[m],c.push(g.x,g.y);e+=2*m;d.push(e)}};function Nv(){this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326")}y(Nv,ps);function Ov(b,c){c[c.length-1].Jd[b.getAttribute("k")]=b.getAttribute("v")}
+var Pv=[null],Qv=P(Pv,{nd:function(b,c){c[c.length-1].Oc.push(b.getAttribute("ref"))},tag:Ov}),Sv=P(Pv,{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.Ug[f]=g;var h=Q({Jd:{}},Rv,b,c);Pb(h.Jd)||(g=new E(g),Qr(g,!1,d),d=new pn(g),d.jc(f),d.I(h.Jd),e.features.push(d))},way:function(b,c){for(var d=c[0],e=b.getAttribute("id"),f=Q({Oc:[],Jd:{}},Qv,b,c),g=c[c.length-1],h=[],k=0,m=f.Oc.length;k<m;k++)kb(h,g.Ug[f.Oc[k]]);
+f.Oc[0]==f.Oc[f.Oc.length-1]?(k=new F(null),k.ba("XY",h,[h.length])):(k=new T(null),k.ba("XY",h));Qr(k,!1,d);d=new pn(k);d.jc(e);d.I(f.Jd);g.features.push(d)}}),Rv=P(Pv,{tag:Ov});Nv.prototype.ic=function(b,c){var d=Or(this,b,c);return"osm"==b.localName&&(d=Q({Ug:{},features:[]},Sv,b,[d]),d.features)?d.features:[]};function Tv(b){return b.getAttributeNS("http://www.w3.org/1999/xlink","href")};function Uv(){}Uv.prototype.read=function(b){return To(b)?this.f(b):Wo(b)?this.a(b):ia(b)?(b=fp(b),this.f(b)):null};function Vv(){}y(Vv,Uv);Vv.prototype.f=function(b){for(b=b.firstChild;b;b=b.nextSibling)if(1==b.nodeType)return this.a(b);return null};Vv.prototype.a=function(b){return(b=Q({},Wv,b,[]))?b:null};
+var Xv=[null,"http://www.opengis.net/ows/1.1"],Wv=P(Xv,{ServiceIdentification:N(function(b,c){return Q({},Yv,b,c)}),ServiceProvider:N(function(b,c){return Q({},Zv,b,c)}),OperationsMetadata:N(function(b,c){return Q({},$v,b,c)})}),aw=P(Xv,{DeliveryPoint:N(W),City:N(W),AdministrativeArea:N(W),PostalCode:N(W),Country:N(W),ElectronicMailAddress:N(W)}),bw=P(Xv,{Value:jp(function(b){return W(b)})}),cw=P(Xv,{AllowedValues:N(function(b,c){return Q({},bw,b,c)})}),ew=P(Xv,{Phone:N(function(b,c){return Q({},
+dw,b,c)}),Address:N(function(b,c){return Q({},aw,b,c)})}),gw=P(Xv,{HTTP:N(function(b,c){return Q({},fw,b,c)})}),fw=P(Xv,{Get:jp(function(b,c){var d=Tv(b);return d?Q({href:d},hw,b,c):void 0}),Post:void 0}),iw=P(Xv,{DCP:N(function(b,c){return Q({},gw,b,c)})}),$v=P(Xv,{Operation:function(b,c){var d=b.getAttribute("name"),e=Q({},iw,b,c);e&&(c[c.length-1][d]=e)}}),dw=P(Xv,{Voice:N(W),Facsimile:N(W)}),hw=P(Xv,{Constraint:jp(function(b,c){var d=b.getAttribute("name");return d?Q({name:d},cw,b,c):void 0})}),
+jw=P(Xv,{IndividualName:N(W),PositionName:N(W),ContactInfo:N(function(b,c){return Q({},ew,b,c)})}),Yv=P(Xv,{Title:N(W),ServiceTypeVersion:N(W),ServiceType:N(W)}),Zv=P(Xv,{ProviderName:N(W),ProviderSite:N(Tv),ServiceContact:N(function(b,c){return Q({},jw,b,c)})});function kw(b,c,d,e){var f;void 0!==e?f=e:f=[];e=0;var g,h;for(g=0;g<c;)for(h=b[g++],f[e++]=b[g++],f[e++]=h,h=2;h<d;++h)f[e++]=b[g++];f.length=e};function lw(b){b=b?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326");this.a=b.factor?b.factor:1E5;this.f=b.geometryLayout?b.geometryLayout:"XY"}y(lw,xt);function mw(b,c,d){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],m=k-f[e];f[e]=k;b[g]=m}return nw(b,d?d:1E5)}
+function ow(b,c,d){var e,f=Array(c);for(e=0;e<c;++e)f[e]=0;b=pw(b,d?d:1E5);var g;d=0;for(g=b.length;d<g;)for(e=0;e<c;++e,++d)f[e]+=b[d],b[d]=f[e];return b}function nw(b,c){var d=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 pw(b,c){var d=c?c:1E5,e=[],f=0,g=0,h,k;h=0;for(k=b.length;h<k;++h){var m=b.charCodeAt(h)-63,f=f|(m&31)<<g;32>m?(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=lw.prototype;l.Dd=function(b,c){var d=this.Fd(b,c);return new pn(d)};l.Lf=function(b,c){return[this.Dd(b,c)]};l.Fd=function(b,c){var d=cf(this.f),e=ow(b,d,this.a);kw(e,e.length,d,e);d=qf(e,0,e.length,d);return Qr(new T(d,this.f),!1,Pr(this,c))};
+l.Re=function(b,c){var d=b.X();return d?this.Ld(d,c):""};l.si=function(b,c){return this.Re(b[0],c)};l.Ld=function(b,c){b=Qr(b,!0,Pr(this,c));var d=b.ia(),e=b.ra();kw(d,d.length,e,d);return mw(d,e,this.a)};function qw(b){b=b?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee(b.defaultDataProjection?b.defaultDataProjection:"EPSG:4326")}y(qw,Rr);function rw(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 sw(b,c,d,e,f){b=b.geometries;var g=[],h,k;h=0;for(k=b.length;h<k;++h)g[h]=tw(b[h],c,d,e,f);return g}
+function tw(b,c,d,e,f){var g=b.type,h=uw[g];c="Point"===g||"MultiPoint"===g?h(b,d,e):h(b,c);d=new pn;d.Ma(Qr(c,!1,f));void 0!==b.id&&d.jc(b.id);b.properties&&d.I(b.properties);return d}
+qw.prototype.Kf=function(b,c){if("Topology"==b.type){var d,e=null,f=null;b.transform&&(d=b.transform,e=d.scale,f=d.translate);var g=b.arcs;if(d){d=e;var h=f,k,m;k=0;for(m=g.length;k<m;++k)for(var n=g[k],p=d,q=h,r=0,t=0,x=void 0,z=void 0,A=void 0,z=0,A=n.length;z<A;++z)x=n[z],r+=x[0],t+=x[1],x[0]=r,x[1]=t,vw(x,p,q)}d=[];h=Lb(b.objects);k=0;for(m=h.length;k<m;++k)"GeometryCollection"===h[k].type?(n=h[k],d.push.apply(d,sw(n,g,e,f,c))):(n=h[k],d.push(tw(n,g,e,f,c)));return d}return[]};
+function vw(b,c,d){b[0]=b[0]*c[0]+d[0];b[1]=b[1]*c[1]+d[1]}qw.prototype.Ia=function(){return this.defaultDataProjection};
+var uw={Point:function(b,c,d){b=b.coordinates;c&&d&&vw(b,c,d);return new E(b)},LineString:function(b,c){var d=rw(b.arcs,c);return new T(d)},Polygon:function(b,c){var d=[],e,f;e=0;for(f=b.arcs.length;e<f;++e)d[e]=rw(b.arcs[e],c);return new F(d)},MultiPoint:function(b,c,d){b=b.coordinates;var e,f;if(c&&d)for(e=0,f=b.length;e<f;++e)vw(b[e],c,d);return new Xr(b)},MultiLineString:function(b,c){var d=[],e,f;e=0;for(f=b.arcs.length;e<f;++e)d[e]=rw(b.arcs[e],c);return new U(d)},MultiPolygon:function(b,c){var d=
+[],e,f,g,h,k,m;k=0;for(m=b.arcs.length;k<m;++k){e=b.arcs[k];f=[];g=0;for(h=e.length;g<h;++g)f[g]=rw(e[g],c);d[k]=f}return new V(d)}};function ww(b){b=b?b:{};this.g=b.featureType;this.b=b.featureNS;this.a=b.gmlFormat?b.gmlFormat:new Fs;this.c=b.schemaLocation?b.schemaLocation:"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";this.defaultDataProjection=null}y(ww,ps);ww.prototype.ic=function(b,c){var d={featureType:this.g,featureNS:this.b};Wb(d,Or(this,b,c?c:{}));d=[d];this.a.a["http://www.opengis.net/gml"].featureMember=hp(ss.prototype.Ed);(d=Q([],this.a.a,b,d,this.a))||(d=[]);return d};
+ww.prototype.j=function(b){if(To(b))return xw(b);if(Wo(b))return Q({},yw,b,[]);if(ia(b))return b=fp(b),xw(b)};ww.prototype.i=function(b){if(To(b))return zw(this,b);if(Wo(b))return Aw(this,b);if(ia(b))return b=fp(b),zw(this,b)};function zw(b,c){for(var d=c.firstChild;d;d=d.nextSibling)if(1==d.nodeType)return Aw(b,d)}var Bw={"http://www.opengis.net/gml":{boundedBy:N(ss.prototype.Je,"bounds")}};
+function Aw(b,c){var d={},e=zs(c.getAttribute("numberOfFeatures"));d.numberOfFeatures=e;return Q(d,Bw,c,[],b.a)}
+var Cw={"http://www.opengis.net/wfs":{totalInserted:N(ys),totalUpdated:N(ys),totalDeleted:N(ys)}},Dw={"http://www.opengis.net/ogc":{FeatureId:hp(function(b){return b.getAttribute("fid")})}},Ew={"http://www.opengis.net/wfs":{Feature:function(b,c){pp(Dw,b,c)}}},yw={"http://www.opengis.net/wfs":{TransactionSummary:N(function(b,c){return Q({},Cw,b,c)},"transactionSummary"),InsertResults:N(function(b,c){return Q([],Ew,b,c)},"insertIds")}};
+function xw(b){for(b=b.firstChild;b;b=b.nextSibling)if(1==b.nodeType)return Q({},yw,b,[])}var Fw={"http://www.opengis.net/wfs":{PropertyName:O(Bs)}};function Gw(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 Hw={"http://www.opengis.net/wfs":{Insert:O(function(b,c,d){var e=d[d.length-1],e=Lo(e.featureNS,e.featureType);b.appendChild(e);Fs.prototype.ri(e,c,d)}),Update:O(function(b,c,d){var e=d[d.length-1],f=e.featureType,g=e.featurePrefix,g=g?g:"feature",h=e.featureNS;b.setAttribute("typeName",g+":"+f);ep(b,"http://www.w3.org/2000/xmlns/","xmlns:"+g,h);if(f=c.Oa()){for(var g=c.P(),h=[],k=0,m=g.length;k<m;k++){var n=c.get(g[k]);void 0!==n&&h.push({name:g[k],value:n})}qp({node:b,srsName:e.srsName},Hw,
+lp("Property"),h,d);Gw(b,f)}}),Delete:O(function(b,c,d){var e=d[d.length-1];d=e.featureType;var f=e.featurePrefix,f=f?f:"feature",e=e.featureNS;b.setAttribute("typeName",f+":"+d);ep(b,"http://www.w3.org/2000/xmlns/","xmlns:"+f,e);(c=c.Oa())&&Gw(b,c)}),Property:O(function(b,c,d){var e=Lo("http://www.opengis.net/wfs","Name");b.appendChild(e);Bs(e,c.name);void 0!==c.value&&null!==c.value&&(e=Lo("http://www.opengis.net/wfs","Value"),b.appendChild(e),c.value instanceof $e?Fs.prototype.Te(e,c.value,d):
+Bs(e,c.value))}),Native:O(function(b,c){c.ep&&b.setAttribute("vendorId",c.ep);void 0!==c.Io&&b.setAttribute("safeToIgnore",c.Io);void 0!==c.value&&Bs(b,c.value)})}},Iw={"http://www.opengis.net/wfs":{Query:O(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",(f?f+":":"")+c);k&&b.setAttribute("srsName",k);g&&ep(b,"http://www.w3.org/2000/xmlns/","xmlns:"+f,g);c=Tb(e);c.node=b;qp(c,Fw,lp("PropertyName"),h,d);if(e=e.bbox)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"),Bs(g,c),f.appendChild(g),Fs.prototype.Te(f,e,d),b.appendChild(h)})}};
+ww.prototype.l=function(b){var c=Lo("http://www.opengis.net/wfs","GetFeature");c.setAttribute("service","WFS");c.setAttribute("version","1.1.0");b&&(b.handle&&c.setAttribute("handle",b.handle),b.outputFormat&&c.setAttribute("outputFormat",b.outputFormat),void 0!==b.maxFeatures&&c.setAttribute("maxFeatures",b.maxFeatures),b.resultType&&c.setAttribute("resultType",b.resultType),void 0!==b.startIndex&&c.setAttribute("startIndex",b.startIndex),void 0!==b.count&&c.setAttribute("count",b.count));ep(c,"http://www.w3.org/2001/XMLSchema-instance",
+"xsi:schemaLocation",this.c);var d=b.featureTypes;b=[{node:c,srsName:b.srsName,featureNS:b.featureNS?b.featureNS:this.b,featurePrefix:b.featurePrefix,geometryName:b.geometryName,bbox:b.bbox,propertyNames:b.propertyNames?b.propertyNames:[]}];var e=Tb(b[b.length-1]);e.node=c;qp(e,Iw,lp("Query"),d,b);return c};
+ww.prototype.B=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;e&&(h=e.gmlOptions?e.gmlOptions:{},e.handle&&g.setAttribute("handle",e.handle));ep(g,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.c);b&&(k={node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},Wb(k,h),qp(k,Hw,lp("Insert"),b,f));c&&(k={node:g,featureNS:e.featureNS,featureType:e.featureType,
+featurePrefix:e.featurePrefix},Wb(k,h),qp(k,Hw,lp("Update"),c,f));d&&qp({node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},Hw,lp("Delete"),d,f);e.nativeElements&&qp({node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},Hw,lp("Native"),e.nativeElements,f);return g};ww.prototype.Qf=function(b){for(b=b.firstChild;b;b=b.nextSibling)if(1==b.nodeType)return this.Me(b);return null};
+ww.prototype.Me=function(b){if(b.firstElementChild&&b.firstElementChild.firstElementChild)for(b=b.firstElementChild.firstElementChild,b=b.firstElementChild;b;b=b.nextElementSibling)if(0!==b.childNodes.length&&(1!==b.childNodes.length||3!==b.firstChild.nodeType)){var c=[{}];this.a.Je(b,c);return Ee(c.pop().srsName)}return null};function Jw(b){b=b?b:{};this.defaultDataProjection=null;this.a=void 0!==b.splitCollection?b.splitCollection:!1}y(Jw,xt);function Kw(b){b=b.Z();return 0===b.length?"":b[0]+" "+b[1]}function Lw(b){b=b.Z();for(var c=[],d=0,e=b.length;d<e;++d)c.push(b[d][0]+" "+b[d][1]);return c.join(",")}function Mw(b){var c=[];b=b.be();for(var d=0,e=b.length;d<e;++d)c.push("("+Lw(b[d])+")");return c.join(",")}function Nw(b){var c=b.W();b=(0,Ow[c])(b);c=c.toUpperCase();return 0===b.length?c+" EMPTY":c+"("+b+")"}
+var Ow={Point:Kw,LineString:Lw,Polygon:Mw,MultiPoint:function(b){var c=[];b=b.ve();for(var d=0,e=b.length;d<e;++d)c.push("("+Kw(b[d])+")");return c.join(",")},MultiLineString:function(b){var c=[];b=b.sd();for(var d=0,e=b.length;d<e;++d)c.push("("+Lw(b[d])+")");return c.join(",")},MultiPolygon:function(b){var c=[];b=b.ce();for(var d=0,e=b.length;d<e;++d)c.push("("+Mw(b[d])+")");return c.join(",")},GeometryCollection:function(b){var c=[];b=b.Ag();for(var d=0,e=b.length;d<e;++d)c.push(Nw(b[d]));return c.join(",")}};
+l=Jw.prototype;l.Dd=function(b,c){var d=this.Fd(b,c);if(d){var e=new pn;e.Ma(d);return e}return null};l.Lf=function(b,c){var d=[],e=this.Fd(b,c);this.a&&"GeometryCollection"==e.W()?d=e.c:d=[e];for(var f=[],g=0,h=d.length;g<h;++g)e=new pn,e.Ma(d[g]),f.push(e);return f};l.Fd=function(b,c){var d;d=new Pw(new Qw(b));d.a=Rw(d.f);return(d=Sw(d))?Qr(d,!1,c):null};l.Re=function(b,c){var d=b.X();return d?this.Ld(d,c):""};
+l.si=function(b,c){if(1==b.length)return this.Re(b[0],c);for(var d=[],e=0,f=b.length;e<f;++e)d.push(b[e].X());d=new gs(d);return this.Ld(d,c)};l.Ld=function(b,c){return Nw(Qr(b,!0,c))};function Qw(b){this.f=b;this.a=-1}function Tw(b,c){return"0"<=b&&"9">=b||"."==b&&!(void 0!==c&&c)}
+function Rw(b){var c=b.f.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(Tw(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.f.charAt(++b.a)}while(Tw(e,f)||!g&&("e"==e||"E"==e)||g&&("-"==e||"+"==e));b=parseFloat(b.f.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.f.charAt(++b.a);while("a"<=e&&"z">=e||"A"<=e&&"Z">=e);b=b.f.substring(c,b.a--).toUpperCase();
+d.value=b}else{if(" "==c||"\t"==c||"\r"==c||"\n"==c)return Rw(b);if(""===c)d.type=6;else throw Error("Unexpected character: "+c);}return d}function Pw(b){this.f=b}l=Pw.prototype;l.match=function(b){if(b=this.a.type==b)this.a=Rw(this.f);return b};
+function Sw(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(Sw(b));while(b.match(5));if(b.match(3)){b=c;break a}}else if(Uw(b)){b=[];break a}throw Error(Vw(b));}return new gs(b)}var e=Ww[d],c=Xw[d];if(!e||!c)throw Error("Invalid geometry type: "+d);b=e.call(b);return new c(b)}throw Error(Vw(b));}l.Ff=function(){if(this.match(2)){var b=Yw(this);if(this.match(3))return b}else if(Uw(this))return null;throw Error(Vw(this));};
+l.Ef=function(){if(this.match(2)){var b=Zw(this);if(this.match(3))return b}else if(Uw(this))return[];throw Error(Vw(this));};l.Gf=function(){if(this.match(2)){var b=$w(this);if(this.match(3))return b}else if(Uw(this))return[];throw Error(Vw(this));};l.Sn=function(){if(this.match(2)){var b;if(2==this.a.type)for(b=[this.Ff()];this.match(5);)b.push(this.Ff());else b=Zw(this);if(this.match(3))return b}else if(Uw(this))return[];throw Error(Vw(this));};
+l.Rn=function(){if(this.match(2)){var b=$w(this);if(this.match(3))return b}else if(Uw(this))return[];throw Error(Vw(this));};l.Tn=function(){if(this.match(2)){for(var b=[this.Gf()];this.match(5);)b.push(this.Gf());if(this.match(3))return b}else if(Uw(this))return[];throw Error(Vw(this));};function Yw(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(Vw(b));}function Zw(b){for(var c=[Yw(b)];b.match(5);)c.push(Yw(b));return c}
+function $w(b){for(var c=[b.Ef()];b.match(5);)c.push(b.Ef());return c}function Uw(b){var c=1==b.a.type&&"EMPTY"==b.a.value;c&&(b.a=Rw(b.f));return c}function Vw(b){return"Unexpected `"+b.a.value+"` at position "+b.a.position+" in `"+b.f.f+"`"}var Xw={POINT:E,LINESTRING:T,POLYGON:F,MULTIPOINT:Xr,MULTILINESTRING:U,MULTIPOLYGON:V},Ww={POINT:Pw.prototype.Ff,LINESTRING:Pw.prototype.Ef,POLYGON:Pw.prototype.Gf,MULTIPOINT:Pw.prototype.Sn,MULTILINESTRING:Pw.prototype.Rn,MULTIPOLYGON:Pw.prototype.Tn};function ax(){this.version=void 0}y(ax,Uv);ax.prototype.f=function(b){for(b=b.firstChild;b;b=b.nextSibling)if(1==b.nodeType)return this.a(b);return null};ax.prototype.a=function(b){this.version=b.getAttribute("version").trim();return(b=Q({version:this.version},bx,b,[]))?b:null};function cx(b,c){return Q({},dx,b,c)}function ex(b,c){return Q({},fx,b,c)}function gx(b,c){var d=cx(b,c);if(d){var e=[zs(b.getAttribute("width")),zs(b.getAttribute("height"))];d.size=e;return d}}
+function hx(b,c){return Q([],ix,b,c)}
+var jx=[null,"http://www.opengis.net/wms"],bx=P(jx,{Service:N(function(b,c){return Q({},kx,b,c)}),Capability:N(function(b,c){return Q({},lx,b,c)})}),lx=P(jx,{Request:N(function(b,c){return Q({},mx,b,c)}),Exception:N(function(b,c){return Q([],nx,b,c)}),Layer:N(function(b,c){return Q({},ox,b,c)})}),kx=P(jx,{Name:N(W),Title:N(W),Abstract:N(W),KeywordList:N(hx),OnlineResource:N(Tv),ContactInformation:N(function(b,c){return Q({},px,b,c)}),Fees:N(W),AccessConstraints:N(W),LayerLimit:N(ys),MaxWidth:N(ys),
+MaxHeight:N(ys)}),px=P(jx,{ContactPersonPrimary:N(function(b,c){return Q({},qx,b,c)}),ContactPosition:N(W),ContactAddress:N(function(b,c){return Q({},rx,b,c)}),ContactVoiceTelephone:N(W),ContactFacsimileTelephone:N(W),ContactElectronicMailAddress:N(W)}),qx=P(jx,{ContactPerson:N(W),ContactOrganization:N(W)}),rx=P(jx,{AddressType:N(W),Address:N(W),City:N(W),StateOrProvince:N(W),PostCode:N(W),Country:N(W)}),nx=P(jx,{Format:hp(W)}),ox=P(jx,{Name:N(W),Title:N(W),Abstract:N(W),KeywordList:N(hx),CRS:jp(W),
+EX_GeographicBoundingBox:N(function(b,c){var d=Q({},sx,b,c);if(d){var e=d.westBoundLongitude,f=d.southBoundLatitude,g=d.eastBoundLongitude,d=d.northBoundLatitude;return void 0===e||void 0===f||void 0===g||void 0===d?void 0:[e,f,g,d]}}),BoundingBox:jp(function(b){var c=[xs(b.getAttribute("minx")),xs(b.getAttribute("miny")),xs(b.getAttribute("maxx")),xs(b.getAttribute("maxy"))],d=[xs(b.getAttribute("resx")),xs(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:us(b.getAttribute("multipleValues")),nearestValue:us(b.getAttribute("nearestValue")),current:us(b.getAttribute("current")),values:W(b)}}),Attribution:N(function(b,c){return Q({},tx,b,c)}),AuthorityURL:jp(function(b,c){var d=cx(b,c);if(d)return d.name=b.getAttribute("name"),d}),Identifier:jp(W),MetadataURL:jp(function(b,c){var d=cx(b,c);if(d)return d.type=b.getAttribute("type"),
+d}),DataURL:jp(cx),FeatureListURL:jp(cx),Style:jp(function(b,c){return Q({},ux,b,c)}),MinScaleDenominator:N(ws),MaxScaleDenominator:N(ws),Layer:jp(function(b,c){var d=c[c.length-1],e=Q({},ox,b,c);if(e){var f=us(b.getAttribute("queryable"));void 0===f&&(f=d.queryable);e.queryable=void 0!==f?f:!1;f=zs(b.getAttribute("cascaded"));void 0===f&&(f=d.cascaded);e.cascaded=f;f=us(b.getAttribute("opaque"));void 0===f&&(f=d.opaque);e.opaque=void 0!==f?f:!1;f=us(b.getAttribute("noSubsets"));void 0===f&&(f=d.noSubsets);
+e.noSubsets=void 0!==f?f:!1;(f=xs(b.getAttribute("fixedWidth")))||(f=d.fixedWidth);e.fixedWidth=f;(f=xs(b.getAttribute("fixedHeight")))||(f=d.fixedHeight);e.fixedHeight=f;["Style","CRS","AuthorityURL"].forEach(function(b){if(b in d){var c=Sb(e,b),c=c.concat(d[b]);e[b]=c}});"EX_GeographicBoundingBox BoundingBox Dimension Attribution MinScaleDenominator MaxScaleDenominator".split(" ").forEach(function(b){b in e||(e[b]=d[b])});return e}})}),tx=P(jx,{Title:N(W),OnlineResource:N(Tv),LogoURL:N(gx)}),sx=
+P(jx,{westBoundLongitude:N(ws),eastBoundLongitude:N(ws),southBoundLatitude:N(ws),northBoundLatitude:N(ws)}),mx=P(jx,{GetCapabilities:N(ex),GetMap:N(ex),GetFeatureInfo:N(ex)}),fx=P(jx,{Format:jp(W),DCPType:jp(function(b,c){return Q({},vx,b,c)})}),vx=P(jx,{HTTP:N(function(b,c){return Q({},wx,b,c)})}),wx=P(jx,{Get:N(cx),Post:N(cx)}),ux=P(jx,{Name:N(W),Title:N(W),Abstract:N(W),LegendURL:jp(gx),StyleSheetURL:N(cx),StyleURL:N(cx)}),dx=P(jx,{Format:N(W),OnlineResource:N(Tv)}),ix=P(jx,{Keyword:hp(W)});function xx(){this.b="http://mapserver.gis.umn.edu/mapserver";this.a=new Es;this.defaultDataProjection=null}y(xx,ps);
+xx.prototype.ic=function(b,c){var d={featureType:this.featureType,featureNS:this.featureNS};c&&Wb(d,Or(this,b,c));var e=[d];b.namespaceURI=this.b;var f=Qo(b),d=[];if(0!==b.childNodes.length){if("msGMLOutput"==f)for(var g=0,h=b.childNodes.length;g<h;g++){var k=b.childNodes[g];if(1===k.nodeType){var m=e[0],n=k.localName.replace("_layer","")+"_feature";m.featureType=n;m.featureNS=this.b;var p={};p[n]=hp(this.a.Jf,this.a);m=P([m.featureNS,null],p);k.namespaceURI=this.b;(k=Q([],m,k,e,this.a))&&kb(d,k)}}"FeatureCollection"==
+f&&(e=Q([],this.a.a,b,[{}],this.a))&&(d=e)}return d};function yx(){this.b=new Vv}y(yx,Uv);yx.prototype.f=function(b){for(b=b.firstChild;b;b=b.nextSibling)if(1==b.nodeType)return this.a(b);return null};yx.prototype.a=function(b){this.version=b.getAttribute("version").trim();var c=this.b.a(b);if(!c)return null;c.version=this.version;return(c=Q(c,zx,b,[]))?c:null};function Ax(b){var c=W(b).split(" ");if(c&&2==c.length)return b=+c[0],c=+c[1],isNaN(b)||isNaN(c)?void 0:[b,c]}
+var Bx=[null,"http://www.opengis.net/wmts/1.0"],Cx=[null,"http://www.opengis.net/ows/1.1"],zx=P(Bx,{Contents:N(function(b,c){return Q({},Dx,b,c)})}),Dx=P(Bx,{Layer:jp(function(b,c){return Q({},Ex,b,c)}),TileMatrixSet:jp(function(b,c){return Q({},Fx,b,c)})}),Ex=P(Bx,{Style:jp(function(b,c){var d=Q({},Gx,b,c);if(d){var e="true"===b.getAttribute("isDefault");d.isDefault=e;return d}}),Format:jp(W),TileMatrixSetLink:jp(function(b,c){return Q({},Hx,b,c)}),Dimension:jp(function(b,c){return Q({},Ix,b,c)}),
+ResourceURL:jp(function(b){var c=b.getAttribute("format"),d=b.getAttribute("template");b=b.getAttribute("resourceType");var e={};c&&(e.format=c);d&&(e.template=d);b&&(e.resourceType=b);return e})},P(Cx,{Title:N(W),Abstract:N(W),WGS84BoundingBox:N(function(b,c){var d=Q([],Jx,b,c);return 2!=d.length?void 0:Ld(d)}),Identifier:N(W)})),Gx=P(Bx,{LegendURL:jp(function(b){var c={};c.format=b.getAttribute("format");c.href=Tv(b);return c})},P(Cx,{Title:N(W),Identifier:N(W)})),Hx=P(Bx,{TileMatrixSet:N(W)}),
+Ix=P(Bx,{Default:N(W),Value:jp(W)},P(Cx,{Identifier:N(W)})),Jx=P(Cx,{LowerCorner:hp(Ax),UpperCorner:hp(Ax)}),Fx=P(Bx,{WellKnownScaleSet:N(W),TileMatrix:jp(function(b,c){return Q({},Kx,b,c)})},P(Cx,{SupportedCRS:N(W),Identifier:N(W)})),Kx=P(Bx,{TopLeftCorner:N(Ax),ScaleDenominator:N(ws),TileWidth:N(ys),TileHeight:N(ys),MatrixWidth:N(ys),MatrixHeight:N(ys)},P(Cx,{Identifier:N(W)}));var Lx=new ze(6378137);function Mx(b){gd.call(this);b=b||{};this.a=null;this.c=Xe;this.b=void 0;D(this,id("projection"),this.Dl,!1,this);D(this,id("tracking"),this.El,!1,this);void 0!==b.projection&&this.Yg(Ee(b.projection));void 0!==b.trackingOptions&&this.hi(b.trackingOptions);this.re(void 0!==b.tracking?b.tracking:!1)}y(Mx,gd);l=Mx.prototype;l.Y=function(){this.re(!1);Mx.ca.Y.call(this)};l.Dl=function(){var b=this.Wg();b&&(this.c=Ie(Ee("EPSG:4326"),b),this.a&&this.set("position",this.c(this.a)))};
+l.El=function(){if(Zi){var b=this.Xg();b&&void 0===this.b?this.b=ba.navigator.geolocation.watchPosition(ua(this.$n,this),ua(this.ao,this),this.Ig()):b||void 0===this.b||(ba.navigator.geolocation.clearWatch(this.b),this.b=void 0)}};
+l.$n=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:Xa(b.heading));this.a?(this.a[0]=b.longitude,this.a[1]=b.latitude):this.a=[b.longitude,b.latitude];var c=this.c(this.a);this.set("position",c);this.set("speed",null===b.speed?void 0:b.speed);b=Jf(Lx,this.a,b.accuracy);b.pc(this.c);this.set("accuracyGeometry",b);
+this.u()};l.ao=function(b){b.type="error";this.re(!1);this.s(b)};l.Dj=function(){return this.get("accuracy")};l.Ej=function(){return this.get("accuracyGeometry")||null};l.Gj=function(){return this.get("altitude")};l.Hj=function(){return this.get("altitudeAccuracy")};l.Bl=function(){return this.get("heading")};l.Cl=function(){return this.get("position")};l.Wg=function(){return this.get("projection")};l.lk=function(){return this.get("speed")};l.Xg=function(){return this.get("tracking")};l.Ig=function(){return this.get("trackingOptions")};
+l.Yg=function(b){this.set("projection",b)};l.re=function(b){this.set("tracking",b)};l.hi=function(b){this.set("trackingOptions",b)};function Nx(b,c,d){bf.call(this);this.Uf(b,c?c:0,d)}y(Nx,bf);l=Nx.prototype;l.clone=function(){var b=new Nx(null),c=this.A.slice();df(b,this.b,c);b.u();return b};l.nb=function(b,c,d,e){var f=this.A;b-=f[0];var g=c-f[1];c=b*b+g*g;if(c<e){if(0===c)for(e=0;e<this.a;++e)d[e]=f[e];else for(e=this.zf()/Math.sqrt(c),d[0]=f[0]+e*b,d[1]=f[1]+e*g,e=2;e<this.a;++e)d[e]=f[e];d.length=this.a;return c}return e};l.uc=function(b,c){var d=this.A,e=b-d[0],d=c-d[1];return e*e+d*d<=Ox(this)};
+l.wd=function(){return this.A.slice(0,this.a)};l.Wd=function(b){var c=this.A,d=c[this.a]-c[0];return Pd(c[0]-d,c[1]-d,c[0]+d,c[1]+d,b)};l.zf=function(){return Math.sqrt(Ox(this))};function Ox(b){var c=b.A[b.a]-b.A[0];b=b.A[b.a+1]-b.A[1];return c*c+b*b}l.W=function(){return"Circle"};l.Ea=function(b){var c=this.J();return oe(b,c)?(c=this.wd(),b[0]<=c[0]&&b[2]>=c[0]||b[1]<=c[1]&&b[3]>=c[1]?!0:ce(b,this.og,this)):!1};
+l.Yl=function(b){var c=this.a,d=this.A[c]-this.A[0],e=b.slice();e[c]=e[0]+d;for(d=1;d<c;++d)e[c+d]=b[d];df(this,this.b,e);this.u()};l.Uf=function(b,c,d){if(b){ef(this,d,b,0);this.A||(this.A=[]);d=this.A;b=nf(d,b);d[b++]=d[0]+c;var e;c=1;for(e=this.a;c<e;++c)d[b++]=d[c];d.length=b}else df(this,"XY",null);this.u()};l.Zl=function(b){this.A[this.a]=this.A[0]+b;this.u()};function Px(b,c,d){for(var e=[],f=b(0),g=b(1),h=c(f),k=c(g),m=[g,f],n=[k,h],p=[1,0],q={},r=1E5,t,x,z,A,B;0<--r&&0<p.length;)z=p.pop(),f=m.pop(),h=n.pop(),g=z.toString(),g in q||(e.push(h[0],h[1]),q[g]=!0),A=p.pop(),g=m.pop(),k=n.pop(),B=(z+A)/2,t=b(B),x=c(t),Va(x[0],x[1],h[0],h[1],k[0],k[1])<d?(e.push(k[0],k[1]),g=A.toString(),q[g]=!0):(p.push(A,B,B,z),n.push(k,x,x,h),m.push(g,t,t,f));return e}function Qx(b,c,d,e,f){var g=Ee("EPSG:4326");return Px(function(e){return[b,c+(d-c)*e]},We(g,e),f)}
+function Rx(b,c,d,e,f){var g=Ee("EPSG:4326");return Px(function(e){return[c+(d-c)*e,b]},We(g,e),f)};function Sx(b){b=b||{};this.g=this.l=null;this.b=this.i=Infinity;this.c=this.j=-Infinity;this.C=this.v=Infinity;this.O=this.D=-Infinity;this.U=void 0!==b.targetSize?b.targetSize:100;this.na=void 0!==b.maxLines?b.maxLines:100;this.a=[];this.f=[];this.va=void 0!==b.strokeStyle?b.strokeStyle:Tx;this.B=this.o=void 0;this.G=null;this.setMap(void 0!==b.map?b.map:null)}var Tx=new Yl({color:"rgba(0,0,0,0.2)"}),Ux=[90,45,30,20,10,5,2,1,.5,.2,.1,.05,.01,.005,.002,.001];
+function Vx(b,c,d,e,f,g,h){var k=h;c=Qx(c,d,e,b.g,f);k=void 0!==b.a[k]?b.a[k]:new T(null);k.ba("XY",c);oe(k.J(),g)&&(b.a[h++]=k);return h}function Wx(b,c,d,e,f){var g=f;c=Rx(c,b.c,b.b,b.g,d);g=void 0!==b.f[g]?b.f[g]:new T(null);g.ba("XY",c);oe(g.J(),e)&&(b.f[f++]=g);return f}l=Sx.prototype;l.Fl=function(){return this.l};l.Zj=function(){return this.a};l.fk=function(){return this.f};
+l.Mg=function(b){var c=b.vectorContext,d=b.frameState,e=d.extent;b=d.viewState;var f=b.center,g=b.projection,h=b.resolution;b=d.pixelRatio;b=h*h/(4*b*b);if(!this.g||!Ve(this.g,g)){var k=Ee("EPSG:4326"),m=g.J(),n=g.j,p=Ze(n,k,g),q=n[2],r=n[1],t=n[0],x=p[3],z=p[2],A=p[1],p=p[0];this.i=n[3];this.b=q;this.j=r;this.c=t;this.v=x;this.C=z;this.D=A;this.O=p;this.o=We(k,g);this.B=We(g,k);this.G=this.B(le(m));this.g=g}k=0;g.b&&(g=g.J(),k=je(g),d=d.focus[0],d<g[0]||d>g[2])&&(k*=Math.ceil((g[0]-d)/k),e=[e[0]+
+k,e[1],e[2]+k,e[3]]);d=this.G[0];g=this.G[1];k=-1;n=Math.pow(this.U*h,2);q=[];r=[];h=0;for(m=Ux.length;h<m;++h){t=Ux[h]/2;q[0]=d-t;q[1]=g-t;r[0]=d+t;r[1]=g+t;this.o(q,q);this.o(r,r);t=Math.pow(r[0]-q[0],2)+Math.pow(r[1]-q[1],2);if(t<=n)break;k=Ux[h]}h=k;if(-1==h)this.a.length=this.f.length=0;else{d=this.B(f);f=d[0];d=d[1];g=this.na;k=[Math.max(e[0],this.O),Math.max(e[1],this.D),Math.min(e[2],this.C),Math.min(e[3],this.v)];k=Ze(k,this.g,"EPSG:4326");n=k[3];r=k[1];f=Math.floor(f/h)*h;q=Sa(f,this.c,
+this.b);m=Vx(this,q,r,n,b,e,0);for(k=0;q!=this.c&&k++<g;)q=Math.max(q-h,this.c),m=Vx(this,q,r,n,b,e,m);q=Sa(f,this.c,this.b);for(k=0;q!=this.b&&k++<g;)q=Math.min(q+h,this.b),m=Vx(this,q,r,n,b,e,m);this.a.length=m;d=Math.floor(d/h)*h;f=Sa(d,this.j,this.i);m=Wx(this,f,b,e,0);for(k=0;f!=this.j&&k++<g;)f=Math.max(f-h,this.j),m=Wx(this,f,b,e,m);f=Sa(d,this.j,this.i);for(k=0;f!=this.i&&k++<g;)f=Math.min(f+h,this.i),m=Wx(this,f,b,e,m);this.f.length=m}c.bb(null,this.va);b=0;for(f=this.a.length;b<f;++b)h=
+this.a[b],c.Wb(h,null);b=0;for(f=this.f.length;b<f;++b)h=this.f[b],c.Wb(h,null)};l.setMap=function(b){this.l&&(this.l.K("postcompose",this.Mg,this),this.l.render());b&&(b.H("postcompose",this.Mg,this),b.render());this.l=b};function Xx(b,c,d,e,f,g,h){ek.call(this,b,c,d,0,e);this.l=f;this.f=new Image;null!==g&&(this.f.crossOrigin=g);this.g={};this.c=null;this.state=0;this.j=h}y(Xx,ek);Xx.prototype.a=function(b){if(void 0!==b){var c;b=w(b);if(b in this.g)return this.g[b];Pb(this.g)?c=this.f:c=this.f.cloneNode(!1);return this.g[b]=c}return this.f};Xx.prototype.o=function(){this.state=3;this.c.forEach(Wc);this.c=null;fk(this)};
+Xx.prototype.G=function(){void 0===this.resolution&&(this.resolution=ke(this.extent)/this.f.height);this.state=2;this.c.forEach(Wc);this.c=null;fk(this)};Xx.prototype.load=function(){0==this.state&&(this.state=1,fk(this),this.c=[Uc(this.f,"error",this.o,!1,this),Uc(this.f,"load",this.G,!1,this)],this.j(this,this.l))};function Yx(b,c,d,e,f){uh.call(this,b,c);this.l=d;this.b=new Image;null!==e&&(this.b.crossOrigin=e);this.c={};this.j=null;this.o=f}y(Yx,uh);l=Yx.prototype;l.Y=function(){1==this.state&&Zx(this);this.f&&sc(this.f);Yx.ca.Y.call(this)};l.Ta=function(b){if(void 0!==b){var c=w(b);if(c in this.c)return this.c[c];b=Pb(this.c)?this.b:this.b.cloneNode(!1);return this.c[c]=b}return this.b};l.$a=function(){return this.l};l.Gl=function(){this.state=3;Zx(this);vh(this)};
+l.Hl=function(){this.state=this.b.naturalWidth&&this.b.naturalHeight?2:4;Zx(this);vh(this)};l.load=function(){0==this.state&&(this.state=1,vh(this),this.j=[Uc(this.b,"error",this.Gl,!1,this),Uc(this.b,"load",this.Hl,!1,this)],this.o(this,this.l))};function Zx(b){b.j.forEach(Wc);b.j=null};function $x(b,c){$c.call(this);this.a=new wr(this);var d=b;c&&(d=Cg(b));this.a.Ra(d,"dragenter",this.Jn);d!=b&&this.a.Ra(d,"dragover",this.Kn);this.a.Ra(b,"dragover",this.Ln);this.a.Ra(b,"drop",this.Mn)}y($x,$c);l=$x.prototype;l.ld=!1;l.Y=function(){$x.ca.Y.call(this);this.a.Fc()};l.Jn=function(b){var c=b.a.dataTransfer;(this.ld=!(!c||!(c.types&&(0<=ab(c.types,"Files")||0<=ab(c.types,"public.file-url"))||c.files&&0<c.files.length)))&&b.preventDefault()};
+l.Kn=function(b){this.ld&&(b.preventDefault(),b.a.dataTransfer.dropEffect="none")};l.Ln=function(b){if(this.ld){b.preventDefault();b.b();b=b.a.dataTransfer;try{b.effectAllowed="all"}catch(c){}b.dropEffect="copy"}};l.Mn=function(b){this.ld&&(b.preventDefault(),b.b(),b=new xc(b.a),b.type="drop",this.s(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 ay(b,c){this.g=[];this.v=b;this.B=c||null;this.c=this.a=!1;this.b=void 0;this.o=this.C=this.j=!1;this.i=0;this.f=null;this.l=0}ay.prototype.cancel=function(b){if(this.a)this.b instanceof ay&&this.b.cancel();else{if(this.f){var c=this.f;delete this.f;b?c.cancel(b):(c.l--,0>=c.l&&c.cancel())}this.v?this.v.call(this.B,this):this.o=!0;this.a||(b=new by,cy(this),dy(this,!1,b))}};ay.prototype.G=function(b,c){this.j=!1;dy(this,b,c)};function dy(b,c,d){b.a=!0;b.b=d;b.c=!c;ey(b)}
+function cy(b){if(b.a){if(!b.o)throw new fy;b.o=!1}}ay.prototype.ad=function(b){cy(this);dy(this,!0,b)};function gy(b,c,d,e){b.g.push([c,d,e]);b.a&&ey(b)}ay.prototype.then=function(b,c,d){var e,f,g=new En(function(b,c){e=b;f=c});gy(this,e,function(b){b instanceof by?g.cancel():f(b)});return g.then(b,c,d)};rn(ay);function hy(b){return eb(b.g,function(b){return ka(b[1])})}
+function ey(b){if(b.i&&b.a&&hy(b)){var c=b.i,d=iy[c];d&&(ba.clearTimeout(d.xa),delete iy[c]);b.i=0}b.f&&(b.f.l--,delete b.f);for(var c=b.b,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.c?h:g)try{var k=g.call(f||b.B,c);ca(k)&&(b.c=b.c&&(k==c||k instanceof Error),b.b=c=k);if(sn(c)||"function"===typeof ba.Promise&&c instanceof ba.Promise)e=!0,b.j=!0}catch(m){c=m,b.c=!0,hy(b)||(d=!0)}}b.b=c;e&&(k=ua(b.G,b,!0),e=ua(b.G,b,!1),c instanceof ay?(gy(c,k,e),c.C=!0):c.then(k,e));d&&
+(c=new jy(c),iy[c.xa]=c,b.i=c.xa)}function fy(){Aa.call(this)}y(fy,Aa);fy.prototype.message="Deferred has already fired";fy.prototype.name="AlreadyCalledError";function by(){Aa.call(this)}y(by,Aa);by.prototype.message="Deferred was canceled";by.prototype.name="CanceledError";function jy(b){this.xa=ba.setTimeout(ua(this.f,this),0);this.a=b}jy.prototype.f=function(){delete iy[this.xa];throw this.a;};var iy={};function ky(b,c){ca(b.name)?(this.name=b.name,this.code=ly[b.name]):(this.code=b.code,this.name=my(b.code));Aa.call(this,Da("%s %s",this.name,c))}y(ky,Aa);function my(b){var c=Ob(ly,function(c){return b==c});if(!ca(c))throw Error("Invalid code: "+b);return c}var ly={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 ny(b,c){tc.call(this,b.type,c)}y(ny,tc);function oy(){$c.call(this);this.tb=new FileReader;this.tb.onloadstart=ua(this.a,this);this.tb.onprogress=ua(this.a,this);this.tb.onload=ua(this.a,this);this.tb.onabort=ua(this.a,this);this.tb.onerror=ua(this.a,this);this.tb.onloadend=ua(this.a,this)}y(oy,$c);oy.prototype.getError=function(){return this.tb.error&&new ky(this.tb.error,"reading file")};oy.prototype.a=function(b){this.s(new ny(b,this))};oy.prototype.Y=function(){oy.ca.Y.call(this);delete this.tb};
+function py(b){var c=new ay;b.Ra("loadend",va(function(b,c){var f=c.tb.result,g=c.getError();null==f||g?(cy(b),dy(b,!1,g)):b.ad(f);c.Fc()},c,b));return c};function qy(b){b=b?b:{};Lk.call(this,{handleEvent:te});this.j=b.formatConstructors?b.formatConstructors:[];this.v=b.projection?Ee(b.projection):null;this.c=null;this.a=void 0}y(qy,Lk);qy.prototype.Y=function(){this.a&&Wc(this.a);qy.ca.Y.call(this)};qy.prototype.l=function(b){b=b.a.dataTransfer.files;var c,d,e;c=0;for(d=b.length;c<d;++c){e=b[c];var f;f=e;var g=new oy,h=py(g);g.tb.readAsText(f,"");f=h;e=va(this.o,e);gy(f,e,null,this)}};
+qy.prototype.o=function(b,c){var d=this.B,e=this.v;e||(e=d.aa().g);var d=this.j,f=[],g,h;g=0;for(h=d.length;g<h;++g){var k=new d[g],m;try{m=k.Ba(c)}catch(t){m=null}if(m){var k=k.Ia(c),k=We(k,e),n,p;n=0;for(p=m.length;n<p;++n){var q=m[n],r=q.X();r&&r.pc(k);f.push(q)}}}this.s(new ry(sy,this,b,f,e))};qy.prototype.setMap=function(b){this.a&&(Wc(this.a),this.a=void 0);this.c&&(sc(this.c),this.c=null);qy.ca.setMap.call(this,b);b&&(this.c=new $x(b.a),this.a=D(this.c,"drop",this.l,!1,this))};var sy="addfeatures";
+function ry(b,c,d,e,f){tc.call(this,b,c);this.features=e;this.file=d;this.projection=f}y(ry,tc);function ty(b,c){this.x=b;this.y=c}y(ty,yg);ty.prototype.clone=function(){return new ty(this.x,this.y)};ty.prototype.scale=yg.prototype.scale;ty.prototype.add=function(b){this.x+=b.x;this.y+=b.y;return this};ty.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 uy(b){b=b?b:{};Yk.call(this,{handleDownEvent:vy,handleDragEvent:wy,handleUpEvent:xy});this.o=b.condition?b.condition:Vk;this.a=this.c=void 0;this.l=0;this.v=void 0!==b.duration?b.duration:400}y(uy,Yk);
+function wy(b){if(Xk(b)){var c=b.map,d=c.Sa();b=b.pixel;b=new ty(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.aa();c.render();if(void 0!==this.c){var f=d-this.c;Mk(c,e,e.Fa()-f)}this.c=d;void 0!==this.a&&(d=this.a*(e.$()/b),Ok(c,e,d));void 0!==this.a&&(this.l=this.a/b);this.a=b}}
+function xy(b){if(!Xk(b))return!0;b=b.map;var c=b.aa();Sf(c,-1);var d=this.l-1,e=c.Fa(),e=c.constrainRotation(e,0);Mk(b,c,e,void 0,void 0);var e=c.$(),f=this.v,e=c.constrainResolution(e,0,d);Ok(b,c,e,void 0,f);this.l=0;return!1}function vy(b){return Xk(b)&&this.o(b)?(Sf(b.map.aa(),1),this.a=this.c=void 0,!0):!1};function yy(b,c){tc.call(this,b);this.feature=c}y(yy,tc);
+function zy(b){Yk.call(this,{handleDownEvent:Ay,handleEvent:By,handleUpEvent:Cy});this.ga=null;this.T=!1;this.nc=b.source?b.source:null;this.Db=b.features?b.features:null;this.jj=b.snapTolerance?b.snapTolerance:12;this.V=b.type;this.c=Dy(this.V);this.hb=b.minPoints?b.minPoints:this.c===Ey?3:2;this.wa=b.maxPoints?b.maxPoints:Infinity;var c=b.geometryFunction;if(!c)if("Circle"===this.V)c=function(b,c){var d=c?c:new Nx([NaN,NaN]);d.Uf(b[0],Math.sqrt(vd(b[0],b[1])));return d};else{var d,c=this.c;c===
+Fy?d=E:c===Gy?d=T:c===Ey&&(d=F);c=function(b,c){var g=c;g?g.la(b):g=new d(b);return g}}this.D=c;this.U=this.v=this.a=this.O=this.l=this.o=null;this.sj=b.clickTolerance?b.clickTolerance*b.clickTolerance:36;this.oa=new H({source:new R({useSpatialIndex:!1,wrapX:b.wrapX?b.wrapX:!1}),style:b.style?b.style:Hy()});this.Eb=b.geometryName;this.Ci=b.condition?b.condition:Uk;this.pa=b.freehandCondition?b.freehandCondition:Vk;D(this,id("active"),this.ni,!1,this)}y(zy,Yk);
+function Hy(){var b=em();return function(c){return b[c.X().W()]}}l=zy.prototype;l.setMap=function(b){zy.ca.setMap.call(this,b);this.ni()};function By(b){var c=!this.T;this.T&&b.type===Wj?(Iy(this,b),c=!1):b.type===Vj?c=Jy(this,b):b.type===Pj&&(c=!1);return Zk.call(this,b)&&c}function Ay(b){if(this.Ci(b))return this.ga=b.pixel,!0;if(this.c!==Gy&&this.c!==Ey||!this.pa(b))return!1;this.ga=b.pixel;this.T=!0;this.o||Ky(this,b);return!0}
+function Cy(b){this.T=!1;var c=this.ga,d=b.pixel,e=c[0]-d[0],c=c[1]-d[1],d=!0;e*e+c*c<=this.sj&&(Jy(this,b),this.o?this.c===Ly?this.od():My(this,b)?this.od():Iy(this,b):(Ky(this,b),this.c===Fy&&this.od()),d=!1);return d}
+function Jy(b,c){if(b.o){var d=c.coordinate,e=b.l.X(),f;b.c===Fy?f=b.a:b.c===Ey?(f=b.a[0],f=f[f.length-1],My(b,c)&&(d=b.o.slice())):(f=b.a,f=f[f.length-1]);f[0]=d[0];f[1]=d[1];b.D(b.a,e);b.O&&b.O.X().la(d);e instanceof F&&b.c!==Ey?(b.v||(b.v=new pn(new T(null))),e=e.Dg(0),d=b.v.X(),d.ba(e.b,e.ia())):b.U&&(d=b.v.X(),d.la(b.U));Ny(b)}else d=c.coordinate.slice(),b.O?b.O.X().la(d):(b.O=new pn(new E(d)),Ny(b));return!0}
+function My(b,c){var d=!1;if(b.l){var e=!1,f=[b.o];b.c===Gy?e=b.a.length>b.hb:b.c===Ey&&(e=b.a[0].length>b.hb,f=[b.a[0][0],b.a[0][b.a[0].length-2]]);if(e)for(var e=c.map,g=0,h=f.length;g<h;g++){var k=f[g],m=e.Pa(k),n=c.pixel,d=n[0]-m[0],m=n[1]-m[1],n=b.T&&b.pa(c)?1:b.jj;if(d=Math.sqrt(d*d+m*m)<=n){b.o=k;break}}}return d}
+function Ky(b,c){var d=c.coordinate;b.o=d;b.c===Fy?b.a=d.slice():b.c===Ey?(b.a=[[d.slice(),d.slice()]],b.U=b.a[0]):(b.a=[d.slice(),d.slice()],b.c===Ly&&(b.U=b.a));b.U&&(b.v=new pn(new T(b.U)));d=b.D(b.a);b.l=new pn;b.Eb&&b.l.yc(b.Eb);b.l.Ma(d);Ny(b);b.s(new yy("drawstart",b.l))}
+function Iy(b,c){var d=c.coordinate,e=b.l.X(),f,g;if(b.c===Gy)b.o=d.slice(),g=b.a,g.push(d.slice()),f=g.length>b.wa,b.D(g,e);else if(b.c===Ey){g=b.a[0];g.push(d.slice());if(f=g.length>b.wa)b.o=g[0];b.D(b.a,e)}Ny(b);f&&b.od()}l.Ao=function(){var b=this.l.X(),c,d;this.c===Gy?(c=this.a,c.splice(-2,1),this.D(c,b)):this.c===Ey&&(c=this.a[0],c.splice(-2,1),d=this.v.X(),d.la(c),this.D(this.a,b));0===c.length&&(this.o=null);Ny(this)};
+l.od=function(){var b=Oy(this),c=this.a,d=b.X();this.c===Gy?(c.pop(),this.D(c,d)):this.c===Ey&&(c[0].pop(),c[0].push(c[0][0]),this.D(c,d));"MultiPoint"===this.V?b.Ma(new Xr([c])):"MultiLineString"===this.V?b.Ma(new U([c])):"MultiPolygon"===this.V&&b.Ma(new V([c]));this.s(new yy("drawend",b));this.Db&&this.Db.push(b);this.nc&&this.nc.Bd(b)};function Oy(b){b.o=null;var c=b.l;c&&(b.l=null,b.O=null,b.v=null,b.oa.ea().clear(!0));return c}
+l.fm=function(b){var c=b.X();this.l=b;this.a=c.Z();b=this.a[this.a.length-1];this.o=b.slice();this.a.push(b.slice());Ny(this);this.s(new yy("drawstart",this.l))};l.Ac=se;function Ny(b){var c=[];b.l&&c.push(b.l);b.v&&c.push(b.v);b.O&&c.push(b.O);b=b.oa.ea();b.clear(!0);b.Ec(c)}l.ni=function(){var b=this.B,c=this.b();b&&c||Oy(this);this.oa.setMap(c?b:null)};
+function Dy(b){var c;"Point"===b||"MultiPoint"===b?c=Fy:"LineString"===b||"MultiLineString"===b?c=Gy:"Polygon"===b||"MultiPolygon"===b?c=Ey:"Circle"===b&&(c=Ly);return c}var Fy="Point",Gy="LineString",Ey="Polygon",Ly="Circle";function Py(b,c,d){tc.call(this,b);this.features=c;this.mapBrowserPointerEvent=d}y(Py,tc);
+function Qy(b){Yk.call(this,{handleDownEvent:Ry,handleDragEvent:Sy,handleEvent:Ty,handleUpEvent:Uy});this.wa=b.deleteCondition?b.deleteCondition:ye(Uk,Tk);this.pa=this.c=null;this.ga=[0,0];this.D=this.T=!1;this.a=new zp;this.O=void 0!==b.pixelTolerance?b.pixelTolerance:10;this.o=this.oa=!1;this.l=null;this.U=new H({source:new R({useSpatialIndex:!1,wrapX:!!b.wrapX}),style:b.style?b.style:Vy(),updateWhileAnimating:!0,updateWhileInteracting:!0});this.V={Point:this.mm,LineString:this.eh,LinearRing:this.eh,
+Polygon:this.nm,MultiPoint:this.km,MultiLineString:this.jm,MultiPolygon:this.lm,GeometryCollection:this.im};this.v=b.features;this.v.forEach(this.Af,this);D(this.v,"add",this.gm,!1,this);D(this.v,"remove",this.hm,!1,this)}y(Qy,Yk);l=Qy.prototype;l.Af=function(b){var c=b.X();c.W()in this.V&&this.V[c.W()].call(this,b,c);(c=this.B)&&Wy(this,this.ga,c);D(b,"change",this.dh,!1,this)};function Xy(b,c){b.D||(b.D=!0,b.s(new Py("modifystart",b.v,c)))}
+function Yy(b,c){Zy(b,c);b.c&&0===b.v.$b()&&(b.U.ea().Rc(b.c),b.c=null);Vc(c,"change",b.dh,!1,b)}function Zy(b,c){var d=b.a,e=[];d.forEach(function(b){c===b.feature&&e.push(b)});for(var f=e.length-1;0<=f;--f)d.remove(e[f])}l.setMap=function(b){this.U.setMap(b);Qy.ca.setMap.call(this,b)};l.gm=function(b){this.Af(b.element)};l.dh=function(b){this.o||(b=b.target,Yy(this,b),this.Af(b))};l.hm=function(b){Yy(this,b.element)};
+l.mm=function(b,c){var d=c.Z(),d={feature:b,geometry:c,ka:[d,d]};this.a.ya(c.J(),d)};l.km=function(b,c){var d=c.Z(),e,f,g;f=0;for(g=d.length;f<g;++f)e=d[f],e={feature:b,geometry:c,depth:[f],index:f,ka:[e,e]},this.a.ya(c.J(),e)};l.eh=function(b,c){var d=c.Z(),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,ka:g},this.a.ya(Ld(g),h)};
+l.jm=function(b,c){var d=c.Z(),e,f,g,h,k,m,n;h=0;for(k=d.length;h<k;++h)for(e=d[h],f=0,g=e.length-1;f<g;++f)m=e.slice(f,f+2),n={feature:b,geometry:c,depth:[h],index:f,ka:m},this.a.ya(Ld(m),n)};l.nm=function(b,c){var d=c.Z(),e,f,g,h,k,m,n;h=0;for(k=d.length;h<k;++h)for(e=d[h],f=0,g=e.length-1;f<g;++f)m=e.slice(f,f+2),n={feature:b,geometry:c,depth:[h],index:f,ka:m},this.a.ya(Ld(m),n)};
+l.lm=function(b,c){var d=c.Z(),e,f,g,h,k,m,n,p,q,r;m=0;for(n=d.length;m<n;++m)for(p=d[m],h=0,k=p.length;h<k;++h)for(e=p[h],f=0,g=e.length-1;f<g;++f)q=e.slice(f,f+2),r={feature:b,geometry:c,depth:[h,m],index:f,ka:q},this.a.ya(Ld(q),r)};l.im=function(b,c){var d,e=c.c;for(d=0;d<e.length;++d)this.V[e[d].W()].call(this,b,e[d])};function $y(b,c){var d=b.c;d?d.X().la(c):(d=new pn(new E(c)),b.c=d,b.U.ea().Bd(d))}function az(b,c){return b.index-c.index}
+function Ry(b){Wy(this,b.pixel,b.map);this.l=[];this.D=!1;var c=this.c;if(c){var d=[],c=c.X().Z(),e=Ld([c]),e=Cp(this.a,e),f={};e.sort(az);for(var g=0,h=e.length;g<h;++g){var k=e[g],m=k.ka,n=w(k.feature),p=k.depth;p&&(n+="-"+p.join("-"));f[n]||(f[n]=Array(2));if(td(m[0],c)&&!f[n][0])this.l.push([k,0]),f[n][0]=k;else if(td(m[1],c)&&!f[n][1]){if("LineString"!==k.geometry.W()&&"MultiLineString"!==k.geometry.W()||!f[n][0]||0!==f[n][0].index)this.l.push([k,1]),f[n][1]=k}else w(m)in this.pa&&!f[n][0]&&
+!f[n][1]&&d.push([k,c])}d.length&&Xy(this,b);for(g=d.length-1;0<=g;--g)this.el.apply(this,d[g])}return!!this.c}
+function Sy(b){this.T=!1;Xy(this,b);b=b.coordinate;for(var c=0,d=this.l.length;c<d;++c){for(var e=this.l[c],f=e[0],g=f.depth,h=f.geometry,k=h.Z(),m=f.ka,e=e[1];b.length<h.ra();)b.push(0);switch(h.W()){case "Point":k=b;m[0]=m[1]=b;break;case "MultiPoint":k[f.index]=b;m[0]=m[1]=b;break;case "LineString":k[f.index+e]=b;m[e]=b;break;case "MultiLineString":k[g[0]][f.index+e]=b;m[e]=b;break;case "Polygon":k[g[0]][f.index+e]=b;m[e]=b;break;case "MultiPolygon":k[g[1]][g[0]][f.index+e]=b,m[e]=b}f=h;this.o=
+!0;f.la(k);this.o=!1}$y(this,b)}function Uy(b){for(var c,d=this.l.length-1;0<=d;--d)c=this.l[d][0],Ap(this.a,Ld(c.ka),c);this.D&&(this.s(new Py("modifyend",this.v,b)),this.D=!1);return!1}
+function Ty(b){if(!(b instanceof Lj))return!0;var c;b.map.aa().b.slice()[1]||b.type!=Vj||this.C||(this.ga=b.pixel,Wy(this,b.pixel,b.map));if(this.c&&this.wa(b))if(b.type==Qj&&this.T)c=!0;else{this.c.X();Xy(this,b);c=this.l;var d={},e,f,g,h,k,m,n,p,q;for(k=c.length-1;0<=k;--k)if(g=c[k],p=g[0],h=p.geometry,f=h.Z(),q=w(p.feature),p.depth&&(q+="-"+p.depth.join("-")),n=e=m=void 0,0===g[1]?(e=p,m=p.index):1==g[1]&&(n=p,m=p.index+1),q in d||(d[q]=[n,e,m]),g=d[q],void 0!==n&&(g[0]=n),void 0!==e&&(g[1]=e),
+void 0!==g[0]&&void 0!==g[1]){e=f;q=!1;n=m-1;switch(h.W()){case "MultiLineString":f[p.depth[0]].splice(m,1);q=!0;break;case "LineString":f.splice(m,1);q=!0;break;case "MultiPolygon":e=e[p.depth[1]];case "Polygon":e=e[p.depth[0]],4<e.length&&(m==e.length-1&&(m=0),e.splice(m,1),q=!0,0===m&&(e.pop(),e.push(e[0]),n=e.length-1))}q&&(this.a.remove(g[0]),this.a.remove(g[1]),e=h,this.o=!0,e.la(f),this.o=!1,f={depth:p.depth,feature:p.feature,geometry:p.geometry,index:n,ka:[g[0].ka[0],g[1].ka[1]]},this.a.ya(Ld(f.ka),
+f),bz(this,h,m,p.depth,-1),this.c&&(this.U.ea().Rc(this.c),this.c=null))}c=!0;this.s(new Py("modifyend",this.v,b));this.D=!1}b.type==Qj&&(this.T=!1);return Zk.call(this,b)&&!c}
+function Wy(b,c,d){function e(b,c){return wd(f,b.ka)-wd(f,c.ka)}var f=d.Ga(c),g=d.Ga([c[0]-b.O,c[1]+b.O]),h=d.Ga([c[0]+b.O,c[1]-b.O]),g=Ld([g,h]),g=Cp(b.a,g);if(0<g.length){g.sort(e);var h=g[0].ka,k=qd(f,h),m=d.Pa(k);if(Math.sqrt(vd(c,m))<=b.O){c=d.Pa(h[0]);d=d.Pa(h[1]);c=vd(m,c);d=vd(m,d);b.oa=Math.sqrt(Math.min(c,d))<=b.O;b.oa&&(k=c>d?h[1]:h[0]);$y(b,k);d={};d[w(h)]=!0;c=1;for(m=g.length;c<m;++c)if(k=g[c].ka,td(h[0],k[0])&&td(h[1],k[1])||td(h[0],k[1])&&td(h[1],k[0]))d[w(k)]=!0;else break;b.pa=d;
+return}}b.c&&(b.U.ea().Rc(b.c),b.c=null)}
+l.el=function(b,c){for(var d=b.ka,e=b.feature,f=b.geometry,g=b.depth,h=b.index,k;c.length<f.ra();)c.push(0);switch(f.W()){case "MultiLineString":k=f.Z();k[g[0]].splice(h+1,0,c);break;case "Polygon":k=f.Z();k[g[0]].splice(h+1,0,c);break;case "MultiPolygon":k=f.Z();k[g[1]][g[0]].splice(h+1,0,c);break;case "LineString":k=f.Z();k.splice(h+1,0,c);break;default:return}this.o=!0;f.la(k);this.o=!1;k=this.a;k.remove(b);bz(this,f,h,g,1);var m={ka:[d[0],c],feature:e,geometry:f,depth:g,index:h};k.ya(Ld(m.ka),
+m);this.l.push([m,1]);d={ka:[c,d[1]],feature:e,geometry:f,depth:g,index:h+1};k.ya(Ld(d.ka),d);this.l.push([d,0]);this.T=!0};function bz(b,c,d,e,f){Ep(b.a,c.J(),function(b){b.geometry===c&&(void 0===e||void 0===b.depth||rb(b.depth,e))&&b.index>d&&(b.index+=f)})}function Vy(){var b=em();return function(){return b.Point}};function cz(b,c,d,e){tc.call(this,b);this.selected=c;this.deselected=d;this.mapBrowserEvent=e}y(cz,tc);
+function dz(b){Lk.call(this,{handleEvent:ez});b=b?b:{};this.C=b.condition?b.condition:Tk;this.o=b.addCondition?b.addCondition:se;this.D=b.removeCondition?b.removeCondition:se;this.O=b.toggleCondition?b.toggleCondition:Vk;this.v=b.multi?b.multi:!1;this.j=b.filter?b.filter:te;var c;if(b.layers)if(ka(b.layers))c=b.layers;else{var d=b.layers;c=function(b){return vb(d,b)}}else c=te;this.l=c;this.a={};this.c=new H({source:new R({useSpatialIndex:!1,features:b.features,wrapX:b.wrapX}),style:b.style?b.style:
+fz(),updateWhileAnimating:!0,updateWhileInteracting:!0});b=this.c.ea().c;D(b,"add",this.om,!1,this);D(b,"remove",this.rm,!1,this)}y(dz,Lk);l=dz.prototype;l.pm=function(){return this.c.ea().c};l.qm=function(b){b=w(b);return this.a[b]};
+function ez(b){if(!this.C(b))return!0;var c=this.o(b),d=this.D(b),e=this.O(b),f=!c&&!d&&!e,g=b.map,h=this.c.ea().c,k=[],m=[],n=!1;if(f)g.pd(b.pixel,function(b,c){if(this.j(b,c)){m.push(b);var d=w(b);this.a[d]=c;return!this.v}},this,this.l),0<m.length&&1==h.$b()&&h.item(0)==m[0]||(n=!0,0!==h.$b()&&(k=Array.prototype.concat(h.a),h.clear()),h.uf(m),0===m.length?Qb(this.a):0<k.length&&k.forEach(function(b){b=w(b);delete this.a[b]},this));else{g.pd(b.pixel,function(b,f){if(!vb(h.a,b)){if((c||e)&&this.j(b,
+f)){m.push(b);var g=w(b);this.a[g]=f}}else if(d||e)k.push(b),g=w(b),delete this.a[g]},this,this.l);for(f=k.length-1;0<=f;--f)h.remove(k[f]);h.uf(m);if(0<m.length||0<k.length)n=!0}n&&this.s(new cz("select",m,k,b));return Sk(b)}l.setMap=function(b){var c=this.B,d=this.c.ea().c;c&&d.forEach(c.li,c);dz.ca.setMap.call(this,b);this.c.setMap(b);b&&d.forEach(b.ii,b)};function fz(){var b=em();kb(b.Polygon,b.LineString);kb(b.GeometryCollection,b.LineString);return function(c){return b[c.X().W()]}}
+l.om=function(b){b=b.element;var c=this.B;c&&c.ii(b)};l.rm=function(b){b=b.element;var c=this.B;c&&c.li(b)};function gz(b){Yk.call(this,{handleEvent:hz,handleDownEvent:te,handleUpEvent:iz});b=b?b:{};this.o=b.source?b.source:null;this.l=b.features?b.features:null;this.ga=[];this.D={};this.O={};this.T={};this.v={};this.U=null;this.c=void 0!==b.pixelTolerance?b.pixelTolerance:10;this.oa=ua(jz,this);this.a=new zp;this.V={Point:this.xm,LineString:this.hh,LinearRing:this.hh,Polygon:this.ym,MultiPoint:this.vm,MultiLineString:this.um,MultiPolygon:this.wm,GeometryCollection:this.tm}}y(gz,Yk);l=gz.prototype;
+l.xd=function(b,c){var d=void 0!==c?c:!0,e=b.X(),f=this.V[e.W()];if(f){var g=w(b);this.T[g]=e.J(Md());f.call(this,b,e);d&&(this.O[g]=e.H("change",ua(this.Ck,this,b),this),this.D[g]=b.H(id(b.a),this.sm,this))}};l.Aj=function(b){this.xd(b)};l.Bj=function(b){this.yd(b)};l.fh=function(b){var c;b instanceof Jp?c=b.feature:b instanceof ng&&(c=b.element);this.xd(c)};l.gh=function(b){var c;b instanceof Jp?c=b.feature:b instanceof ng&&(c=b.element);this.yd(c)};
+l.sm=function(b){b=b.g;this.yd(b,!0);this.xd(b,!0)};l.Ck=function(b){if(this.C){var c=w(b);c in this.v||(this.v[c]=b)}else this.mi(b)};l.yd=function(b,c){var d=void 0!==c?c:!0,e=w(b),f=this.T[e];if(f){var g=this.a,h=[];Ep(g,f,function(c){b===c.feature&&h.push(c)});for(f=h.length-1;0<=f;--f)g.remove(h[f]);d&&(Wc(this.O[e]),delete this.O[e],Wc(this.D[e]),delete this.D[e])}};
+l.setMap=function(b){var c=this.B,d=this.ga,e;this.l?e=this.l:this.o&&(e=this.o.ze());c&&(d.forEach(ed),d.length=0,e.forEach(this.Bj,this));gz.ca.setMap.call(this,b);b&&(this.l?(d.push(this.l.H("add",this.fh,this)),d.push(this.l.H("remove",this.gh,this))):this.o&&(d.push(this.o.H("addfeature",this.fh,this)),d.push(this.o.H("removefeature",this.gh,this))),e.forEach(this.Aj,this))};l.Ac=se;l.mi=function(b){this.yd(b,!1);this.xd(b,!1)};
+l.tm=function(b,c){var d,e=c.c;for(d=0;d<e.length;++d)this.V[e[d].W()].call(this,b,e[d])};l.hh=function(b,c){var d=c.Z(),e,f,g,h;e=0;for(f=d.length-1;e<f;++e)g=d.slice(e,e+2),h={feature:b,ka:g},this.a.ya(Ld(g),h)};l.um=function(b,c){var d=c.Z(),e,f,g,h,k,m,n;h=0;for(k=d.length;h<k;++h)for(e=d[h],f=0,g=e.length-1;f<g;++f)m=e.slice(f,f+2),n={feature:b,ka:m},this.a.ya(Ld(m),n)};l.vm=function(b,c){var d=c.Z(),e,f,g;f=0;for(g=d.length;f<g;++f)e=d[f],e={feature:b,ka:[e,e]},this.a.ya(c.J(),e)};
+l.wm=function(b,c){var d=c.Z(),e,f,g,h,k,m,n,p,q,r;m=0;for(n=d.length;m<n;++m)for(p=d[m],h=0,k=p.length;h<k;++h)for(e=p[h],f=0,g=e.length-1;f<g;++f)q=e.slice(f,f+2),r={feature:b,ka:q},this.a.ya(Ld(q),r)};l.xm=function(b,c){var d=c.Z(),d={feature:b,ka:[d,d]};this.a.ya(c.J(),d)};l.ym=function(b,c){var d=c.Z(),e,f,g,h,k,m,n;h=0;for(k=d.length;h<k;++h)for(e=d[h],f=0,g=e.length-1;f<g;++f)m=e.slice(f,f+2),n={feature:b,ka:m},this.a.ya(Ld(m),n)};
+function hz(b){var c,d,e=b.pixel,f=b.coordinate;c=b.map;var g=c.Ga([e[0]-this.c,e[1]+this.c]);d=c.Ga([e[0]+this.c,e[1]-this.c]);var g=Ld([g,d]),h=Cp(this.a,g),k=!1,g=!1,m=null;d=null;0<h.length&&(this.U=f,h.sort(this.oa),h=h[0].ka,m=qd(f,h),d=c.Pa(m),Math.sqrt(vd(e,d))<=this.c&&(g=!0,e=c.Pa(h[0]),f=c.Pa(h[1]),e=vd(d,e),f=vd(d,f),k=Math.sqrt(Math.min(e,f))<=this.c))&&(m=e>f?h[1]:h[0],d=c.Pa(m),d=[Math.round(d[0]),Math.round(d[1])]);c=m;g&&(b.coordinate=c.slice(0,2),b.pixel=d);return Zk.call(this,b)}
+function iz(){var b=Lb(this.v);b.length&&(b.forEach(this.mi,this),this.v={});return!1}function jz(b,c){return wd(this.U,b.ka)-wd(this.U,c.ka)};function kz(b,c,d){tc.call(this,b);this.features=c;this.coordinate=d}y(kz,tc);function lz(b){Yk.call(this,{handleDownEvent:mz,handleDragEvent:nz,handleMoveEvent:oz,handleUpEvent:pz});this.o=void 0;this.a=null;this.c=void 0!==b.features?b.features:null;this.l=null}y(lz,Yk);function mz(b){this.l=qz(this,b.pixel,b.map);return!this.a&&this.l?(this.a=b.coordinate,oz.call(this,b),this.s(new kz("translatestart",this.c,b.coordinate)),!0):!1}
+function pz(b){return this.a?(this.a=null,oz.call(this,b),this.s(new kz("translateend",this.c,b.coordinate)),!0):!1}function nz(b){if(this.a){b=b.coordinate;var c=b[0]-this.a[0],d=b[1]-this.a[1];if(this.c)this.c.forEach(function(b){var e=b.X();e.Pc(c,d);b.Ma(e)});else if(this.l){var e=this.l.X();e.Pc(c,d);this.l.Ma(e)}this.a=b;this.s(new kz("translating",this.c,b))}}
+function oz(b){var c=b.map.Mc();if(b=b.map.pd(b.pixel,function(b){return b})){var d=!1;this.c&&vb(this.c.a,b)&&(d=!0);this.o=c.style.cursor;c.style.cursor=this.a?"-webkit-grabbing":d?"-webkit-grab":"pointer";c.style.cursor=this.a?d?"grab":"pointer":"grabbing"}else c.style.cursor=void 0!==this.o?this.o:"",this.o=void 0}function qz(b,c,d){var e=null;c=d.pd(c,function(b){return b});b.c&&vb(b.c.a,c)&&(e=c);return e};function X(b){b=b?b:{};var c=Tb(b);delete c.gradient;delete c.radius;delete c.blur;delete c.shadow;delete c.weight;H.call(this,c);this.g=null;this.V=void 0!==b.shadow?b.shadow:250;this.T=void 0;this.U=null;D(this,id("gradient"),this.Dk,!1,this);this.Zh(b.gradient?b.gradient:rz);this.Vh(void 0!==b.blur?b.blur:15);this.kh(void 0!==b.radius?b.radius:8);D(this,[id("blur"),id("radius")],this.Ng,!1,this);this.Ng();var d=b.weight?b.weight:"weight",e;ia(d)?e=function(b){return b.get(d)}:e=d;this.c(ua(function(b){b=
+e(b);b=void 0!==b?Sa(b,0,1):1;var c=255*b|0,d=this.U[c];d||(d=[new $l({image:new tk({opacity:b,src:this.T})})],this.U[c]=d);return d},this));this.set("renderOrder",null);D(this,"render",this.Vk,!1,this)}y(X,H);var rz=["#00f","#0ff","#0f0","#ff0","#f00"];l=X.prototype;l.ug=function(){return this.get("blur")};l.Bg=function(){return this.get("gradient")};l.jh=function(){return this.get("radius")};
+l.Dk=function(){for(var b=this.Bg(),c=Ni(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.g=c.getImageData(0,0,1,256).data};l.Ng=function(){var b=this.jh(),c=this.ug(),d=b+c+1,e=2*d,e=Ni(e,e);e.shadowOffsetX=e.shadowOffsetY=this.V;e.shadowBlur=c;e.shadowColor="#000";e.beginPath();c=d-this.V;e.arc(c,c,b,0,2*Math.PI,!0);e.fill();this.T=e.canvas.toDataURL();this.U=Array(256);this.u()};
+l.Vk=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.g[g],d[e+1]=this.g[g+1],d[e+2]=this.g[g+2];b.putImageData(c,0,0)};l.Vh=function(b){this.set("blur",b)};l.Zh=function(b){this.set("gradient",b)};l.kh=function(b){this.set("radius",b)};function sz(b,c,d,e,f,g,h,k,m,n){uh.call(this,f,0);this.D=void 0!==n?n:!1;this.C=h;this.j=null;this.i={};this.l=c;this.G=e;this.B=g?g:f;this.b=[];this.c=null;this.o=0;g=e.Aa(this.B);n=this.G.J();f=this.l.J();g=n?ne(g,n):g;if(0===he(g))this.state=4;else if((n=b.J())&&(f?f=ne(f,n):f=n),e=e.$(this.B[0]),e=Zm(b,d,le(g),e),!isFinite(e)||isNaN(e)||0>=e)this.state=4;else if(this.v=new bn(b,d,g,f,e*(void 0!==m?m:.5)),0===this.v.c.length)this.state=4;else if(this.o=Ih(c,e),d=dn(this.v),f&&(b.b?(d[1]=Sa(d[1],
+f[1],f[3]),d[3]=Sa(d[3],f[1],f[3])):d=ne(d,f)),he(d))if(b=Dh(c,d,this.o),100>kg(b)*jg(b)){for(c=b.a;c<=b.c;c++)for(d=b.f;d<=b.b;d++)(m=k(this.o,c,d,h))&&this.b.push(m);0===this.b.length&&(this.state=4)}else this.state=3;else this.state=4}y(sz,uh);sz.prototype.Y=function(){1==this.state&&(this.c.forEach(Wc),this.c=null);sz.ca.Y.call(this)};sz.prototype.Ta=function(b){if(void 0!==b){var c=w(b);if(c in this.i)return this.i[c];b=Pb(this.i)?this.j:this.j.cloneNode(!1);return this.i[c]=b}return this.j};
+function tz(b){var c=[];b.b.forEach(function(b){b&&2==b.state&&c.push({extent:this.l.Aa(b.a),image:b.Ta()})},b);b.b.length=0;var d=b.B[0],e=b.G.Ka(d),f=ja(e)?e:e[0],e=ja(e)?e:e[1],d=b.G.$(d),g=b.l.$(b.o),h=b.G.Aa(b.B);b.j=an(f,e,b.C,g,b.l.J(),d,h,b.v,c,b.D);b.state=2;vh(b)}
+sz.prototype.load=function(){if(0==this.state){this.state=1;vh(this);var b=0;this.c=[];this.b.forEach(function(c){var d=c.state;if(0==d||1==d){b++;var e;e=c.Ra("change",function(){var d=c.state;if(2==d||3==d||4==d)Wc(e),b--,0===b&&(this.c.forEach(Wc),this.c=null,tz(this))},!1,this);this.c.push(e)}},this);this.b.forEach(function(b){0==b.state&&b.load()});0===b&&tz(this)}};function uz(b,c){var d=c||{},e=d.document||document,f=document.createElement("SCRIPT"),g={Uh:f,Bc:void 0},h=new ay(vz,g),k=null,m=null!=d.timeout?d.timeout:5E3;0<m&&(k=window.setTimeout(function(){wz(f,!0);var c=new xz(yz,"Timeout reached for loading script "+b);cy(h);dy(h,!1,c)},m),g.Bc=k);f.onload=f.onreadystatechange=function(){f.readyState&&"loaded"!=f.readyState&&"complete"!=f.readyState||(wz(f,d.vj||!1,k),h.ad(null))};f.onerror=function(){wz(f,!0,k);var c=new xz(zz,"Error while loading script "+
+b);cy(h);dy(h,!1,c)};g=d.attributes||{};Wb(g,{type:"text/javascript",charset:"UTF-8",src:b});Eg(f,g);Az(e).appendChild(f);return h}function Az(b){var c=b.getElementsByTagName("HEAD");return c&&0!=c.length?c[0]:b.documentElement}function vz(){if(this&&this.Uh){var b=this.Uh;b&&"SCRIPT"==b.tagName&&wz(b,!0,this.Bc)}}function wz(b,c,d){null!=d&&ba.clearTimeout(d);b.onload=da;b.onerror=da;b.onreadystatechange=da;c&&window.setTimeout(function(){Mg(b)},0)}var zz=0,yz=1;
+function xz(b,c){var d="Jsloader error (code #"+b+")";c&&(d+=": "+c);Aa.call(this,d);this.code=b}y(xz,Aa);function Bz(b,c){this.f=new Ct(b);this.a=c?c:"callback";this.Bc=5E3}var Cz=0;function Dz(b,c,d,e){c=c||null;var f="_"+(Cz++).toString(36)+wa().toString(36);ba._callbacks_||(ba._callbacks_={});var g=b.f.clone();if(c)for(var h in c)if(!c.hasOwnProperty||c.hasOwnProperty(h)){var k=g,m=h,n=c[h];ga(n)||(n=[String(n)]);Vt(k.b,m,n)}d&&(ba._callbacks_[f]=Ez(f,d),d=b.a,h="_callbacks_."+f,ga(h)||(h=[String(h)]),Vt(g.b,d,h));b=uz(g.toString(),{timeout:b.Bc,vj:!0});gy(b,null,Fz(f,c,e),void 0)}
+Bz.prototype.cancel=function(b){b&&(b.wj&&b.wj.cancel(),b.xa&&Gz(b.xa,!1))};function Fz(b,c,d){return function(){Gz(b,!1);d&&d(c)}}function Ez(b,c){return function(d){Gz(b,!0);c.apply(void 0,arguments)}}function Gz(b,c){ba._callbacks_[b]&&(c?delete ba._callbacks_[b]:ba._callbacks_[b]=da)};function Y(b){Wp.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,opaque:b.opaque,projection:b.projection,state:void 0!==b.state?b.state:void 0,tileGrid:b.tileGrid,tileLoadFunction:b.tileLoadFunction?b.tileLoadFunction:Hz,tilePixelRatio:b.tilePixelRatio,tileUrlFunction:b.tileUrlFunction,url:b.url,urls:b.urls,wrapX:b.wrapX});this.crossOrigin=void 0!==b.crossOrigin?b.crossOrigin:null;this.tileClass=void 0!==b.tileClass?b.tileClass:Yx;this.j={};this.o={};this.wa=b.reprojectionErrorThreshold;
+this.T=!1}y(Y,Wp);l=Y.prototype;l.qh=function(){return sh(this.a)?!0:Jb(this.j,function(b){return sh(b)})};l.rh=function(b,c){var d=this.ud(b);th(this.a,this.a==d?c:{});Ib(this.j,function(b){th(b,b==d?c:{})})};l.ib=function(b){var c=this.b;return!this.tileGrid||c&&!Ve(c,b)?(c=w(b).toString(),c in this.o||(this.o[c]=Jh(b)),this.o[c]):this.tileGrid};l.ud=function(b){var c=this.b;if(!c||Ve(c,b))return this.a;b=w(b).toString();b in this.j||(this.j[b]=new rh);return this.j[b]};
+function Iz(b,c,d,e,f,g,h){c=[c,d,e];f=(d=Ph(b,c,g))?b.tileUrlFunction(d,f,g):void 0;f=new b.tileClass(c,void 0!==f?0:4,void 0!==f?f:"",b.crossOrigin,b.tileLoadFunction);f.key=h;D(f,"change",b.sh,!1,b);return f}
+l.Pb=function(b,c,d,e,f){if(this.b&&f&&!Ve(this.b,f)){e=this.ud(f);c=[b,c,d];b=this.Ab.apply(this,c);if(qh(e,b))return e.get(b);var g=this.b;d=this.ib(g);var h=this.ib(f),k=Ph(this,c,f);f=new sz(g,d,f,h,c,k,this.v,ua(function(b,c,d,e){return Jz(this,b,c,d,e,g)},this),this.wa,this.T);e.set(b,f);return f}return Jz(this,b,c,d,e,f)};
+function Jz(b,c,d,e,f,g){var h=null,k=b.Ab(c,d,e),m=b.Cg();if(qh(b.a,k)){if(h=b.a.get(k),h.key!=m){var n=h;h.f&&h.f.key==m?(h=h.f,2==n.state&&(h.f=n)):(h=Iz(b,c,d,e,f,g,m),2==n.state?h.f=n:n.f&&2==n.f.state&&(h.f=n.f,n.f=null));h.f&&(h.f.f=null);b.a.replace(k,h)}}else h=Iz(b,c,d,e,f,g,m),b.a.set(k,h);return h}l.vb=function(b){this.T!=b&&(this.T=b,Ib(this.j,function(b){b.clear()}),this.u())};l.wb=function(b,c){var d=Ee(b);d&&(d=w(d).toString(),d in this.o||(this.o[d]=c))};
+function Hz(b,c){b.Ta().src=c};function Kz(b){Y.call(this,{crossOrigin:"anonymous",opaque:!0,projection:Ee("EPSG:3857"),reprojectionErrorThreshold:b.reprojectionErrorThreshold,state:"loading",tileLoadFunction:b.tileLoadFunction,wrapX:void 0!==b.wrapX?b.wrapX:!0});this.l=void 0!==b.culture?b.culture:"en-us";this.g=void 0!==b.maxZoom?b.maxZoom:-1;var c=new Ct("https://dev.virtualearth.net/REST/v1/Imagery/Metadata/"+b.imagerySet);Dz(new Bz(c,"jsonp"),{include:"ImageryProviders",uriScheme:"https",key:b.key},ua(this.C,this))}y(Kz,Y);
+var Lz=new mg({html:'<a class="ol-attribution-bing-tos" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'});
+Kz.prototype.C=function(b){if(200!=b.statusCode||"OK"!=b.statusDescription||"ValidCredentials"!=b.authenticationResultCode||1!=b.resourceSets.length||1!=b.resourceSets[0].resources.length)yh(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.g?d.zoomMax:this.g;b=Kh(this.b);var f=Mh({extent:b,minZoom:d.zoomMin,maxZoom:e,tileSize:d.imageWidth==d.imageHeight?d.imageWidth:[d.imageWidth,d.imageHeight]});this.tileGrid=
+f;var g=this.l;this.tileUrlFunction=Tp(d.imageUrlSubdomains.map(function(b){var c=[0,0,0],e=d.imageUrl.replace("{subdomain}",b).replace("{culture}",g);return function(b){if(b)return ag(b[0],b[1],-b[2]-1,c),e.replace("{quadkey}",dg(c))}}));if(d.imageryProviders){var h=Ie(Ee("EPSG:4326"),this.b);b=d.imageryProviders.map(function(b){var c=b.attribution,d={};b.coverageAreas.forEach(function(b){var c=b.zoomMin,g=Math.min(b.zoomMax,e);b=b.bbox;b=qe([b[1],b[0],b[3],b[2]],h);var k,m;for(k=c;k<=g;++k)m=k.toString(),
+c=Dh(f,b,k),m in d?d[m].push(c):d[m]=[c]});return new mg({html:c,tileRanges:d})});b.push(Lz);this.ma(b)}this.U=c;yh(this,"ready")}};function Mz(b){R.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,projection:b.projection,wrapX:b.wrapX});this.D=void 0;this.fa=void 0!==b.distance?b.distance:20;this.C=[];this.v=b.source;this.v.H("change",Mz.prototype.oa,this)}y(Mz,R);Mz.prototype.ga=function(){return this.v};Mz.prototype.Nc=function(b,c,d){this.v.Nc(b,c,d);c!==this.D&&(this.clear(),this.D=c,Nz(this),this.Ec(this.C))};Mz.prototype.oa=function(){this.clear();Nz(this);this.Ec(this.C);this.u()};
+function Nz(b){if(void 0!==b.D){b.C.length=0;for(var c=Md(),d=b.fa*b.D,e=b.v.ze(),f={},g=0,h=e.length;g<h;g++){var k=e[g];w(k).toString()in f||(k=k.X().Z(),Xd(k,c),Qd(c,d,c),k=b.v.mf(c),k=k.filter(function(b){b=w(b).toString();return b in f?!1:f[b]=!0}),b.C.push(Oz(k)))}}}function Oz(b){for(var c=b.length,d=[0,0],e=0;e<c;e++){var f=b[e].X().Z();pd(d,f)}c=1/c;d[0]*=c;d[1]*=c;d=new pn(new E(d));d.set("features",b);return d};function Pz(b){gn.call(this,{projection:b.projection,resolutions:b.resolutions});this.ga=void 0!==b.crossOrigin?b.crossOrigin:null;this.o=void 0!==b.displayDpi?b.displayDpi:96;this.j=void 0!==b.params?b.params:{};this.V=b.url;this.c=void 0!==b.imageLoadFunction?b.imageLoadFunction:nn;this.pa=void 0!==b.hidpi?b.hidpi:!0;this.fa=void 0!==b.metersPerUnit?b.metersPerUnit:1;this.v=void 0!==b.ratio?b.ratio:1;this.wa=void 0!==b.useOverlay?b.useOverlay:!1;this.g=null;this.T=0}y(Pz,gn);l=Pz.prototype;
+l.Hm=function(){return this.j};
+l.qd=function(b,c,d){c=hn(this,c);d=this.pa?d:1;var e=this.g;if(e&&this.T==this.f&&e.$()==c&&e.b==d&&Vd(e.J(),b))return e;1!=this.v&&(b=b.slice(),pe(b,this.v));var f=[je(b)/c*d,ke(b)/c*d];if(void 0!==this.V){var e=this.V,g=le(b),h=this.fa,k=je(b),m=ke(b),n=f[0],p=f[1],q=.0254/this.o,f={OPERATION:this.wa?"GETDYNAMICMAPOVERLAYIMAGE":"GETMAPIMAGE",VERSION:"2.0.0",LOCALE:"en",CLIENTAGENT:"ol.source.ImageMapGuide source",CLIP:"1",SETDISPLAYDPI:this.o,SETDISPLAYWIDTH:Math.round(f[0]),SETDISPLAYHEIGHT:Math.round(f[1]),
+SETVIEWSCALE:p*k>n*m?k*h/(n*q):m*h/(p*q),SETVIEWCENTERX:g[0],SETVIEWCENTERY:g[1]};Wb(f,this.j);e=io(ko([e],f));e=new Xx(b,c,d,this.i,e,this.ga,this.c);D(e,"change",this.l,!1,this)}else e=null;this.g=e;this.T=this.f;return e};l.Gm=function(){return this.c};l.Jm=function(b){Wb(this.j,b);this.u()};l.Im=function(b){this.g=null;this.c=b;this.u()};function Qz(b){var c=void 0!==b.attributions?b.attributions:null,d=b.imageExtent,e=void 0!==b.crossOrigin?b.crossOrigin:null,f=void 0!==b.imageLoadFunction?b.imageLoadFunction:nn;gn.call(this,{attributions:c,logo:b.logo,projection:Ee(b.projection)});this.c=new Xx(d,void 0,1,c,b.url,e,f);this.g=b.imageSize?b.imageSize:null;D(this.c,"change",this.l,!1,this)}y(Qz,gn);Qz.prototype.qd=function(b){return oe(b,this.c.J())?this.c:null};
+Qz.prototype.l=function(b){if(2==this.c.state){var c=this.c.J(),d=this.c.a(),e,f;this.g?(e=this.g[0],f=this.g[1]):(e=d.width,f=d.height);c=Math.ceil(je(c)/(ke(c)/f));if(c!=e){var g=document.createElement("canvas");g.width=c;g.height=f;g.getContext("2d").drawImage(d,0,0,e,f,0,0,g.width,g.height);this.c.f=g}}Qz.ca.l.call(this,b)};function Rz(b){b=b||{};gn.call(this,{attributions:b.attributions,logo:b.logo,projection:b.projection,resolutions:b.resolutions});this.pa=void 0!==b.crossOrigin?b.crossOrigin:null;this.j=b.url;this.T=void 0!==b.imageLoadFunction?b.imageLoadFunction:nn;this.g=b.params;this.v=!0;Sz(this);this.ga=b.serverType;this.wa=void 0!==b.hidpi?b.hidpi:!0;this.c=null;this.V=[0,0];this.fa=0;this.o=void 0!==b.ratio?b.ratio:1.5}y(Rz,gn);var Tz=[101,101];l=Rz.prototype;
+l.Pm=function(b,c,d,e){if(void 0!==this.j){var f=me(b,c,0,Tz),g={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:this.g.LAYERS};Wb(g,this.g,e);e=Math.floor((f[3]-b[1])/c);g[this.v?"I":"X"]=Math.floor((b[0]-f[0])/c);g[this.v?"J":"Y"]=e;return Uz(this,f,Tz,1,Ee(d),g)}};l.Rm=function(){return this.g};
+l.qd=function(b,c,d,e){if(void 0===this.j)return null;c=hn(this,c);1==d||this.wa&&void 0!==this.ga||(d=1);b=b.slice();var f=(b[0]+b[2])/2,g=(b[1]+b[3])/2,h=c/d,k=je(b)/h,h=ke(b)/h,m=this.c;if(m&&this.fa==this.f&&m.$()==c&&m.b==d&&Vd(m.J(),b))return m;if(1!=this.o){var m=this.o*je(b)/2,n=this.o*ke(b)/2;b[0]=f-m;b[1]=g-n;b[2]=f+m;b[3]=g+n}f={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};Wb(f,this.g);this.V[0]=Math.ceil(k*this.o);this.V[1]=Math.ceil(h*this.o);e=Uz(this,
+b,this.V,d,e,f);this.c=new Xx(b,c,d,this.i,e,this.pa,this.T);this.fa=this.f;D(this.c,"change",this.l,!1,this);return this.c};l.Qm=function(){return this.T};
+function Uz(b,c,d,e,f,g){g[b.v?"CRS":"SRS"]=f.a;"STYLES"in b.g||(g.STYLES=new String(""));if(1!=e)switch(b.ga){case "geoserver":e=90*e+.5|0;g.FORMAT_OPTIONS="FORMAT_OPTIONS"in g?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.g;var h;b.v&&"ne"==d.substr(0,2)?h=[c[1],c[0],c[3],c[2]]:h=c;g.BBOX=h.join(",");return io(ko([b.j],g))}l.Sm=function(){return this.j};
+l.Tm=function(b){this.c=null;this.T=b;this.u()};l.Um=function(b){b!=this.j&&(this.j=b,this.c=null,this.u())};l.Vm=function(b){Wb(this.g,b);Sz(this);this.c=null;this.u()};function Sz(b){b.v=0<=Qa(Rb(b.g,"VERSION","1.3.0"),"1.3")};function Vz(b){var c=void 0!==b.projection?b.projection:"EPSG:3857",d=void 0!==b.tileGrid?b.tileGrid:Mh({extent:Kh(c),maxZoom:b.maxZoom,tileSize:b.tileSize});Y.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,projection:c,reprojectionErrorThreshold:b.reprojectionErrorThreshold,tileGrid:d,tileLoadFunction:b.tileLoadFunction,tilePixelRatio:b.tilePixelRatio,tileUrlFunction:b.tileUrlFunction,url:b.url,urls:b.urls,wrapX:void 0!==b.wrapX?b.wrapX:!0})}y(Vz,Y);function Wz(b){b=b||{};var c;void 0!==b.attributions?c=b.attributions:c=[Xz];Vz.call(this,{attributions:c,crossOrigin:void 0!==b.crossOrigin?b.crossOrigin:"anonymous",opaque:!0,maxZoom:void 0!==b.maxZoom?b.maxZoom:19,reprojectionErrorThreshold:b.reprojectionErrorThreshold,tileLoadFunction:b.tileLoadFunction,url:void 0!==b.url?b.url:"https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png",wrapX:b.wrapX})}y(Wz,Vz);var Xz=new mg({html:'&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.'});function Yz(b){b=b||{};var c=Zz[b.layer];this.g=b.layer;Vz.call(this,{attributions:c.attributions,crossOrigin:"anonymous",logo:"https://developer.mapquest.com/content/osm/mq_logo.png",maxZoom:c.maxZoom,reprojectionErrorThreshold:b.reprojectionErrorThreshold,opaque:!0,tileLoadFunction:b.tileLoadFunction,url:void 0!==b.url?b.url:"https://otile{1-4}-s.mqcdn.com/tiles/1.0.0/"+this.g+"/{z}/{x}/{y}.jpg"})}y(Yz,Vz);
+var $z=new mg({html:'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a>'}),Zz={osm:{maxZoom:19,attributions:[$z,Xz]},sat:{maxZoom:18,attributions:[$z,new mg({html:"Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency"})]},hyb:{maxZoom:18,attributions:[$z,Xz]}};Yz.prototype.l=function(){return this.g};(function(){var b={},c={ja:b};(function(d){if("object"===typeof b&&"undefined"!==typeof c)c.ja=d();else{var e;"undefined"!==typeof window?e=window:"undefined"!==typeof global?e=global:"undefined"!==typeof self?e=self:e=this;e.Bp=d()}})(function(){return function e(b,c,h){function k(n,q){if(!c[n]){if(!b[n]){var r="function"==typeof require&&require;if(!q&&r)return r(n,!0);if(m)return m(n,!0);r=Error("Cannot find module '"+n+"'");throw r.code="MODULE_NOT_FOUND",r;}r=c[n]={ja:{}};b[n][0].call(r.ja,function(c){var e=
+b[n][1][c];return k(e?e:c)},r,r.ja,e,b,c,h)}return c[n].ja}for(var m="function"==typeof require&&require,n=0;n<h.length;n++)k(h[n]);return k}({1:[function(b,c,g){b=b("./processor");g.Qi=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,m=e[0].byteLength,B;if(c.imageOps){m=Array(k);for(B=0;B<k;++B)m[B]=new ImageData(new Uint8ClampedArray(e[B]),g,h);g=b(m,f).data}else{g=new Uint8ClampedArray(m);h=Array(k);c=Array(k);for(B=
+0;B<k;++B)h[B]=new Uint8ClampedArray(e[B]),c[B]=[0,0,0,0];for(e=0;e<m;e+=4){for(B=0;B<k;++B){var v=h[B];c[B][0]=v[e];c[B][1]=v[e+1];c[B][2]=v[e+2];c[B][3]=v[e+3]}B=b(c,f);g[e]=B[0];g[e+1]=B[1];g[e+2]=B[2];g[e+3]=B[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),qe:b.qe}})},0)}}}function m(b){this.$e=!!b.cl;var c;0===b.threads?c=0:this.$e?c=1:c=b.threads||1;var e=[];if(c)for(var f=0;f<c;++f)e[f]=h(b,this.ig.bind(this,f));else e[0]=k(b,this.ig.bind(this,
+0));this.Td=e;this.Zc=[];this.ej=b.co||Infinity;this.Rd=0;this.Dc={};this.af=null}m.prototype.bo=function(b,c,e){this.bj({tc:b,qe:c,ad:e});this.fg()};m.prototype.bj=function(b){for(this.Zc.push(b);this.Zc.length>this.ej;)this.Zc.shift().ad(null,null)};m.prototype.fg=function(){if(0===this.Rd&&0<this.Zc.length){var b=this.af=this.Zc.shift(),c=b.tc[0].width,e=b.tc[0].height,f=b.tc.map(function(b){return b.data.buffer}),g=this.Td.length;this.Rd=g;if(1===g)this.Td[0].postMessage({buffers:f,meta:b.qe,
+imageOps:this.$e,width:c,height:e},f);else for(var h=4*Math.ceil(b.tc[0].data.length/4/g),k=0;k<g;++k){for(var m=k*h,B=[],v=0,L=f.length;v<L;++v)B.push(f[k].slice(m,m+h));this.Td[k].postMessage({buffers:B,meta:b.qe,imageOps:this.$e,width:c,height:e},B)}}};m.prototype.ig=function(b,c){this.xp||(this.Dc[b]=c.data,--this.Rd,0===this.Rd&&this.fj())};m.prototype.fj=function(){var b=this.af,c=this.Td.length,e,f;if(1===c)e=new Uint8ClampedArray(this.Dc[0].buffer),f=this.Dc[0].meta;else{var g=b.tc[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.Dc[h].buffer),k);f[h]=this.Dc[h].meta}}this.af=null;this.Dc={};b.ad(null,new ImageData(e,b.tc[0].width,b.tc[0].height),f);this.fg()};c.ja=m},{}]},{},[1])(1)});yp=c.ja})();function aA(b){this.T=null;this.wa=void 0!==b.operationType?b.operationType:"pixel";this.hb=void 0!==b.threads?b.threads:1;this.c=bA(b.sources);for(var c=0,d=this.c.length;c<d;++c)D(this.c[c],"change",this.u,!1,this);this.g=Ni();this.ga=new Hk(function(){return 1},ua(this.u,this));for(var c=cA(this.c),d={},e=0,f=c.length;e<f;++e)d[w(c[e].layer)]=c[e];this.j=this.o=null;this.fa={animate:!1,attributions:{},coordinateToPixelMatrix:Bd(),extent:null,focus:null,index:0,layerStates:d,layerStatesArray:c,
+logos:{},pixelRatio:1,pixelToCoordinateMatrix:Bd(),postRenderFunctions:[],size:[0,0],skippedFeatureUids:{},tileQueue:this.ga,time:Date.now(),usedTiles:{},viewState:{rotation:0},viewHints:[],wantedTiles:{}};gn.call(this,{});void 0!==b.operation&&this.v(b.operation,b.lib)}y(aA,gn);aA.prototype.v=function(b,c){this.T=new yp.Qi({operation:b,cl:"image"===this.wa,co:1,lib:c,threads:this.hb});this.u()};function dA(b,c,d){var e=b.o;return!e||b.f!==e.Ho||d!==e.resolution||!ae(c,e.extent)}
+aA.prototype.C=function(b,c,d,e){d=!0;for(var f,g=0,h=this.c.length;g<h;++g)if(f=this.c[g].a.ea(),"ready"!==f.B){d=!1;break}if(!d)return null;if(!dA(this,b,c))return this.j;d=this.g.canvas;f=Math.round(je(b)/c);g=Math.round(ke(b)/c);if(f!==d.width||g!==d.height)d.width=f,d.height=g;f=Tb(this.fa);f.viewState=Tb(f.viewState);var g=le(b),h=Math.round(je(b)/c),k=Math.round(ke(b)/c);f.extent=b;f.focus=le(b);f.size[0]=h;f.size[1]=k;h=f.viewState;h.center=g;h.projection=e;h.resolution=c;this.j=e=new Xm(b,
+c,1,this.i,d,this.V.bind(this,f));this.o={extent:b,resolution:c,Ho:this.f};return e};
+aA.prototype.V=function(b,c){for(var d=this.c.length,e=Array(d),f=0;f<d;++f){var g;var h=this.c[f],k=b;h.Ad(k,b.layerStatesArray[f]);if(g=h.zd()){var h=h.nf(),m=Math.round(h[12]),n=Math.round(h[13]),p=k.size[0],k=k.size[1];if(g instanceof Image){if(eA){var q=eA.canvas;q.width!==p||q.height!==k?eA=Ni(p,k):eA.clearRect(0,0,p,k)}else eA=Ni(p,k);eA.drawImage(g,m,n,Math.round(g.width*h[0]),Math.round(g.height*h[5]));g=eA.getImageData(0,0,p,k)}else g=g.getContext("2d").getImageData(-m,-n,p,k)}else g=null;
+if(g)e[f]=g;else return}d={};this.s(new fA(gA,b,d));this.T.bo(e,d,this.pa.bind(this,b,c));Ik(b.tileQueue,16,16)};aA.prototype.pa=function(b,c,d,e,f){d?c(d):e&&(this.s(new fA(hA,b,f)),dA(this,b.extent,b.viewState.resolution/b.pixelRatio)||this.g.putImageData(e,0,0),c(null))};var eA=null;function cA(b){return b.map(function(b){return Zj(b.a)})}
+function bA(b){for(var c=b.length,d=Array(c),e=0;e<c;++e){var f=e,g=b[e],h=null;g instanceof Nh?(g=new G({source:g}),h=new Pp(g)):g instanceof gn&&(g=new Pl({source:g}),h=new Op(g));d[f]=h}return d}function fA(b,c,d){tc.call(this,b);this.extent=c.extent;this.resolution=c.viewState.resolution/c.pixelRatio;this.data=d}y(fA,tc);var gA="beforeoperations",hA="afteroperations";var iA={terrain:{ob:"jpg",opaque:!0},"terrain-background":{ob:"jpg",opaque:!0},"terrain-labels":{ob:"png",opaque:!1},"terrain-lines":{ob:"png",opaque:!1},"toner-background":{ob:"png",opaque:!0},toner:{ob:"png",opaque:!0},"toner-hybrid":{ob:"png",opaque:!1},"toner-labels":{ob:"png",opaque:!1},"toner-lines":{ob:"png",opaque:!1},"toner-lite":{ob:"png",opaque:!0},watercolor:{ob:"jpg",opaque:!0}},jA={terrain:{minZoom:4,maxZoom:18},toner:{minZoom:0,maxZoom:20},watercolor:{minZoom:3,maxZoom:16}};
+function kA(b){var c=b.layer.indexOf("-"),c=-1==c?b.layer:b.layer.slice(0,c),d=iA[b.layer];Vz.call(this,{attributions:lA,crossOrigin:"anonymous",maxZoom:jA[c].maxZoom,opaque:d.opaque,reprojectionErrorThreshold:b.reprojectionErrorThreshold,tileLoadFunction:b.tileLoadFunction,url:void 0!==b.url?b.url:"https://stamen-tiles-{a-d}.a.ssl.fastly.net/"+b.layer+"/{z}/{x}/{y}."+d.ob})}y(kA,Vz);
+var lA=[new mg({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>.'}),Xz];function mA(b){b=b||{};var c=void 0!==b.params?b.params:{};Y.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,projection:b.projection,reprojectionErrorThreshold:b.reprojectionErrorThreshold,tileGrid:b.tileGrid,tileLoadFunction:b.tileLoadFunction,tileUrlFunction:ua(this.D,this),url:b.url,urls:b.urls,wrapX:void 0!==b.wrapX?b.wrapX:!0});this.g=c;this.l=Md()}y(mA,Y);mA.prototype.C=function(){return this.g};
+mA.prototype.Qb=function(b,c,d){b=mA.ca.Qb.call(this,b,c,d);return 1==c?b:ld(b,c,this.c)};
+mA.prototype.D=function(b,c,d){var e=this.tileGrid;e||(e=this.ib(d));if(!(e.a.length<=b[0])){var f=e.Aa(b,this.l),g=md(e.Ka(b[0]),this.c);1!=c&&(g=ld(g,c,this.c));e={F:"image",FORMAT:"PNG32",TRANSPARENT:!0};Wb(e,this.g);var h=this.urls;h?(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(e.DPI?e.DPI*c:90*c),b=1==h.length?h[0]:h[nd((b[1]<<b[0])+b[2],h.length)],Ca(b,"/")||(b+="/"),Ca(b,"MapServer/")?b+="export":Ca(b,"ImageServer/")&&(b+="exportImage"),
+b=io(ko([b],e))):b=void 0;return b}};mA.prototype.V=function(b){Wb(this.g,b);this.u()};function nA(b,c,d){uh.call(this,b,2);this.i=c;this.c=d;this.b={}}y(nA,uh);nA.prototype.Ta=function(b){b=void 0!==b?w(b):-1;if(b in this.b)return this.b[b];var c=this.i,d=Ni(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 oA(b){Nh.call(this,{opaque:!1,projection:b.projection,tileGrid:b.tileGrid,wrapX:void 0!==b.wrapX?b.wrapX:!0})}y(oA,Nh);oA.prototype.Pb=function(b,c,d){var e=this.Ab(b,c,d);if(qh(this.a,e))return this.a.get(e);var f=md(this.tileGrid.Ka(b));b=[b,c,d];c=(c=Ph(this,b))?eg(Ph(this,c)):"";f=new nA(b,f,c);this.a.set(e,f);return f};function pA(b){Y.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,projection:Ee("EPSG:3857"),reprojectionErrorThreshold:b.reprojectionErrorThreshold,state:"loading",tileLoadFunction:b.tileLoadFunction,wrapX:void 0!==b.wrapX?b.wrapX:!0});Dz(new Bz(b.url),void 0,ua(this.l,this),ua(this.g,this))}y(pA,Y);
+pA.prototype.l=function(b){var c=Ee("EPSG:4326"),d=this.b,e;void 0!==b.bounds&&(e=qe(b.bounds,Ie(c,d)));var f=b.minzoom||0,g=b.maxzoom||22;this.tileGrid=d=Mh({extent:Kh(d),maxZoom:g,minZoom:f});this.tileUrlFunction=Sp(b.tiles,d);if(void 0!==b.attribution&&!this.i){c=void 0!==e?e:c.J();e={};for(var h;f<=g;++f)h=f.toString(),e[h]=[Dh(d,c,f)];this.ma([new mg({html:b.attribution,tileRanges:e})])}yh(this,"ready")};pA.prototype.g=function(){yh(this,"error")};function qA(b){Nh.call(this,{projection:Ee("EPSG:3857"),state:"loading"});this.l=void 0!==b.preemptive?b.preemptive:!0;this.g=Up;this.j=void 0;Dz(new Bz(b.url),void 0,ua(this.Xm,this))}y(qA,Nh);l=qA.prototype;l.mk=function(){return this.j};l.zj=function(b,c,d,e,f){this.tileGrid?(c=this.tileGrid.fe(b,c),rA(this.Pb(c[0],c[1],c[2],1,this.b),b,d,e,f)):!0===f?li(function(){d.call(e,null)}):d.call(e,null)};
+l.Xm=function(b){var c=Ee("EPSG:4326"),d=this.b,e;void 0!==b.bounds&&(e=qe(b.bounds,Ie(c,d)));var f=b.minzoom||0,g=b.maxzoom||22;this.tileGrid=d=Mh({extent:Kh(d),maxZoom:g,minZoom:f});this.j=b.template;var h=b.grids;if(h){this.g=Sp(h,d);if(void 0!==b.attribution){c=void 0!==e?e:c.J();for(e={};f<=g;++f)h=f.toString(),e[h]=[Dh(d,c,f)];this.ma([new mg({html:b.attribution,tileRanges:e})])}yh(this,"ready")}else yh(this,"error")};
+l.Pb=function(b,c,d,e,f){var g=this.Ab(b,c,d);if(qh(this.a,g))return this.a.get(g);b=[b,c,d];c=Ph(this,b,f);e=this.g(c,e,f);e=new sA(b,void 0!==e?0:4,void 0!==e?e:"",this.tileGrid.Aa(b),this.l);this.a.set(g,e);return e};l.Yf=function(b,c,d){b=this.Ab(b,c,d);qh(this.a,b)&&this.a.get(b)};function sA(b,c,d,e,f){uh.call(this,b,c);this.l=d;this.b=e;this.o=f;this.j=this.i=this.c=null}y(sA,uh);l=sA.prototype;l.Ta=function(){return null};
+function tA(b,c){if(!b.c||!b.i||!b.j)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-=32;return d in b.i?b.j[b.i[d]]:null}function rA(b,c,d,e,f){0==b.state&&!0===f?(Uc(b,"change",function(){d.call(e,tA(this,c))},!1,b),uA(b)):!0===f?li(function(){d.call(e,tA(this,c))},b):d.call(e,tA(b,c))}l.$a=function(){return this.l};l.Bk=function(){this.state=3;vh(this)};
+l.Wm=function(b){this.c=b.grid;this.i=b.keys;this.j=b.data;this.state=4;vh(this)};function uA(b){0==b.state&&(b.state=1,Dz(new Bz(b.l),void 0,ua(b.Wm,b),ua(b.Bk,b)))}l.load=function(){this.o&&uA(this)};function vA(b){b=b||{};var c=void 0!==b.params?b.params:{};Y.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,opaque:!Rb(c,"TRANSPARENT",!0),projection:b.projection,reprojectionErrorThreshold:b.reprojectionErrorThreshold,tileGrid:b.tileGrid,tileLoadFunction:b.tileLoadFunction,tileUrlFunction:ua(this.$m,this),url:b.url,urls:b.urls,wrapX:void 0!==b.wrapX?b.wrapX:!0});this.C=void 0!==b.gutter?b.gutter:0;this.g=c;this.l=!0;this.D=b.serverType;this.fa=void 0!==b.hidpi?b.hidpi:
+!0;this.V="";wA(this);this.ga=Md();xA(this)}y(vA,Y);l=vA.prototype;
+l.Ym=function(b,c,d,e){d=Ee(d);var f=this.tileGrid;f||(f=this.ib(d));c=f.fe(b,c);if(!(f.a.length<=c[0])){var g=f.$(c[0]),h=f.Aa(c,this.ga),f=md(f.Ka(c[0]),this.c),k=this.C;0!==k&&(f=kd(f,k,this.c),h=Qd(h,g*k,h));k={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:this.g.LAYERS};Wb(k,this.g,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 yA(this,c,f,h,1,d,k)}};l.ae=function(){return this.C};
+l.Ab=function(b,c,d){return this.V+vA.ca.Ab.call(this,b,c,d)};l.Zm=function(){return this.g};
+function yA(b,c,d,e,f,g,h){var k=b.urls;if(k){h.WIDTH=d[0];h.HEIGHT=d[1];h[b.l?"CRS":"SRS"]=g.a;"STYLES"in b.g||(h.STYLES=new String(""));if(1!=f)switch(b.D){case "geoserver":d=90*f+.5|0;h.FORMAT_OPTIONS="FORMAT_OPTIONS"in h?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.g;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 io(ko([1==k.length?k[0]:k[nd((c[1]<<
+c[0])+c[2],k.length)]],h))}}l.Qb=function(b,c,d){b=vA.ca.Qb.call(this,b,c,d);return 1!=c&&this.fa&&void 0!==this.D?ld(b,c,this.c):b};function wA(b){var c=0,d=[];if(b.urls){var e,f;e=0;for(f=b.urls.length;e<f;++e)d[c++]=b.urls[e]}for(var g in b.g)d[c++]=g+"-"+b.g[g];b.V=d.join("#")}
+l.$m=function(b,c,d){var e=this.tileGrid;e||(e=this.ib(d));if(!(e.a.length<=b[0])){1==c||this.fa&&void 0!==this.D||(c=1);var f=e.$(b[0]),g=e.Aa(b,this.ga),e=md(e.Ka(b[0]),this.c),h=this.C;0!==h&&(e=kd(e,h,this.c),g=Qd(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};Wb(f,this.g);return yA(this,b,e,g,c,d,f)}};l.an=function(b){Wb(this.g,b);wA(this);xA(this);this.u()};function xA(b){b.l=0<=Qa(Rb(b.g,"VERSION","1.3.0"),"1.3")};function zA(b){this.j=b.matrixIds;zh.call(this,{extent:b.extent,origin:b.origin,origins:b.origins,resolutions:b.resolutions,tileSize:b.tileSize,tileSizes:b.tileSizes,sizes:b.sizes})}y(zA,zh);zA.prototype.o=function(){return this.j};
+function AA(b,c){var d=[],e=[],f=[],g=[],h=[],k;k=Ee(b.SupportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"));var m=k.Kc(),n="ne"==k.g.substr(0,2);b.TileMatrix.sort(function(b,c){return c.ScaleDenominator-b.ScaleDenominator});b.TileMatrix.forEach(function(b){e.push(b.Identifier);var c=2.8E-4*b.ScaleDenominator/m,k=b.TileWidth,t=b.TileHeight;n?f.push([b.TopLeftCorner[1],b.TopLeftCorner[0]]):f.push(b.TopLeftCorner);d.push(c);g.push(k==t?k:[k,t]);h.push([b.MatrixWidth,-b.MatrixHeight])});
+return new zA({extent:c,origins:f,resolutions:d,matrixIds:e,tileSizes:g,sizes:h})};function Z(b){function c(b){b="KVP"==e?io(ko([b],g)):b.replace(/\{(\w+?)\}/g,function(b,c){return c.toLowerCase()in g?g[c.toLowerCase()]:b});return function(c){if(c){var d={TileMatrix:f.j[c[0]],TileCol:c[1],TileRow:-c[2]-1};Wb(d,h);c=b;return c="KVP"==e?io(ko([c],d)):c.replace(/\{(\w+?)\}/g,function(b,c){return d[c]})}}}this.ga=void 0!==b.version?b.version:"1.0.0";this.D=void 0!==b.format?b.format:"image/jpeg";this.g=void 0!==b.dimensions?b.dimensions:{};this.l="";BA(this);this.V=b.layer;this.C=b.matrixSet;
+this.fa=b.style;var d=b.urls;void 0===d&&void 0!==b.url&&(d=Vp(b.url));var e=this.oa=void 0!==b.requestEncoding?b.requestEncoding:"KVP",f=b.tileGrid,g={layer:this.V,style:this.fa,tilematrixset:this.C};"KVP"==e&&Wb(g,{Service:"WMTS",Request:"GetTile",Version:this.ga,Format:this.D});var h=this.g,k=d&&0<d.length?Tp(d.map(c)):Up;Y.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,projection:b.projection,reprojectionErrorThreshold:b.reprojectionErrorThreshold,tileClass:b.tileClass,
+tileGrid:f,tileLoadFunction:b.tileLoadFunction,tilePixelRatio:b.tilePixelRatio,tileUrlFunction:k,urls:d,wrapX:void 0!==b.wrapX?b.wrapX:!1})}y(Z,Y);l=Z.prototype;l.Lj=function(){return this.g};l.bn=function(){return this.D};l.Cg=function(){return this.l};l.cn=function(){return this.V};l.Yj=function(){return this.C};l.kk=function(){return this.oa};l.dn=function(){return this.fa};l.rk=function(){return this.ga};function BA(b){var c=0,d=[],e;for(e in b.g)d[c++]=e+"-"+b.g[e];b.l=d.join("/")}
+l.cp=function(b){Wb(this.g,b);BA(this);this.u()};function CA(b){b=b||{};var c=b.size,d=c[0],e=c[1],f=[],g=256;switch(void 0!==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 zh({extent:c,origin:ge(c),resolutions:g}),k=b.url;
+Y.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,reprojectionErrorThreshold:b.reprojectionErrorThreshold,tileClass:DA,tileGrid:c,tileUrlFunction:function(b){if(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"}}})}y(CA,Y);function DA(b,c,d,e,f){Yx.call(this,b,c,d,e,f);this.i={}}y(DA,Yx);
+DA.prototype.Ta=function(b){var c=void 0!==b?w(b).toString():"";if(c in this.i)return this.i[c];b=DA.ca.Ta.call(this,b);if(2==this.state){if(256==b.width&&256==b.height)return this.i[c]=b;var d=Ni(256,256);d.drawImage(b,0,0);return this.i[c]=d.canvas}return b};function EA(b){b=b||{};this.f=void 0!==b.initialSize?b.initialSize:256;this.b=void 0!==b.maxSize?b.maxSize:void 0!==xa?xa:2048;this.a=void 0!==b.space?b.space:1;this.g=[new FA(this.f,this.a)];this.c=this.f;this.i=[new FA(this.c,this.a)]}EA.prototype.add=function(b,c,d,e,f,g){if(c+this.a>this.b||d+this.a>this.b)return null;e=GA(this,!1,b,c,d,e,g);if(!e)return null;b=GA(this,!0,b,c,d,void 0!==f?f:ue,g);return{offsetX:e.offsetX,offsetY:e.offsetY,image:e.image,Og:b.image}};
+function GA(b,c,d,e,f,g,h){var k=c?b.i:b.g,m,n,p;n=0;for(p=k.length;n<p;++n){m=k[n];if(m=m.add(d,e,f,g,h))return m;m||n!==p-1||(c?(m=Math.min(2*b.c,b.b),b.c=m):(m=Math.min(2*b.f,b.b),b.f=m),m=new FA(m,b.a),k.push(m),++p)}}function FA(b,c){this.a=c;this.f=[{x:0,y:0,width:b,height:b}];this.c={};this.b=document.createElement("CANVAS");this.b.width=b;this.b.height=b;this.g=this.b.getContext("2d")}FA.prototype.get=function(b){return Rb(this.c,b,null)};
+FA.prototype.add=function(b,c,d,e,f){var g,h,k;h=0;for(k=this.f.length;h<k;++h)if(g=this.f[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.c[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},HA(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},HA(this,b,e,f)),k;return null};function HA(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.f.splice.apply(b.f,c)};function IA(b){this.v=this.c=this.g=null;this.o=void 0!==b.fill?b.fill:null;this.va=[0,0];this.a=b.points;this.b=void 0!==b.radius?b.radius:b.radius1;this.i=void 0!==b.radius2?b.radius2:this.b;this.l=void 0!==b.angle?b.angle:0;this.f=void 0!==b.stroke?b.stroke:null;this.na=this.U=this.O=null;var c=b.atlasManager,d="",e="",f=0,g=null,h,k=0;this.f&&(h=vg(this.f.a),k=this.f.f,void 0===k&&(k=1),g=this.f.b,Wi||(g=null),e=this.f.g,void 0===e&&(e="round"),d=this.f.c,void 0===d&&(d="round"),f=this.f.i,void 0===
+f&&(f=10));var m=2*(this.b+k)+1,d={strokeStyle:h,Id:k,size:m,lineCap:d,lineDash:g,lineJoin:e,miterLimit:f};if(void 0===c){this.c=document.createElement("CANVAS");this.c.height=m;this.c.width=m;var c=m=this.c.width,n=this.c.getContext("2d");this.xh(d,n,0,0);this.o?this.v=this.c:(n=this.v=document.createElement("CANVAS"),n.height=d.size,n.width=d.size,n=n.getContext("2d"),this.wh(d,n,0,0))}else m=Math.round(m),(e=!this.o)&&(n=ua(this.wh,this,d)),f=this.Jb(),n=c.add(f,m,m,ua(this.xh,this,d),n),this.c=
+n.image,this.va=[n.offsetX,n.offsetY],c=n.image.width,this.v=e?n.Og:this.c;this.O=[m/2,m/2];this.U=[m,m];this.na=[c,c];sk.call(this,{opacity:1,rotateWithView:!1,rotation:void 0!==b.rotation?b.rotation:0,scale:1,snapToPixel:void 0!==b.snapToPixel?b.snapToPixel:!0})}y(IA,sk);l=IA.prototype;l.Xb=function(){return this.O};l.jn=function(){return this.l};l.kn=function(){return this.o};l.Ae=function(){return this.v};l.gc=function(){return this.c};l.rd=function(){return this.na};l.Cd=function(){return 2};
+l.Da=function(){return this.va};l.ln=function(){return this.a};l.mn=function(){return this.b};l.jk=function(){return this.i};l.Cb=function(){return this.U};l.nn=function(){return this.f};l.tf=za;l.load=za;l.Xf=za;
+l.xh=function(b,c,d,e){var f;c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();this.i!==this.b&&(this.a*=2);for(d=0;d<=this.a;d++)e=2*d*Math.PI/this.a-Math.PI/2+this.l,f=0===d%2?this.b:this.i,c.lineTo(b.size/2+f*Math.cos(e),b.size/2+f*Math.sin(e));this.o&&(c.fillStyle=vg(this.o.a),c.fill());this.f&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Id,b.lineDash&&c.setLineDash(b.lineDash),c.lineCap=b.lineCap,c.lineJoin=b.lineJoin,c.miterLimit=b.miterLimit,c.stroke());c.closePath()};
+l.wh=function(b,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();this.i!==this.b&&(this.a*=2);var f;for(d=0;d<=this.a;d++)f=2*d*Math.PI/this.a-Math.PI/2+this.l,e=0===d%2?this.b:this.i,c.lineTo(b.size/2+e*Math.cos(f),b.size/2+e*Math.sin(f));c.fillStyle=Ql;c.fill();this.f&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Id,b.lineDash&&c.setLineDash(b.lineDash),c.stroke());c.closePath()};
+l.Jb=function(){var b=this.f?this.f.Jb():"-",c=this.o?this.o.Jb():"-";this.g&&b==this.g[1]&&c==this.g[2]&&this.b==this.g[3]&&this.i==this.g[4]&&this.l==this.g[5]&&this.a==this.g[6]||(this.g=["r"+b+c+(void 0!==this.b?this.b.toString():"-")+(void 0!==this.i?this.i.toString():"-")+(void 0!==this.l?this.l.toString():"-")+(void 0!==this.a?this.a.toString():"-"),b,c,this.b,this.i,this.l,this.a]);return this.g[0]};u("ol.animation.bounce",function(b){var c=b.resolution,d=b.start?b.start:Date.now(),e=void 0!==b.duration?b.duration:1E3,f=b.easing?b.easing:Xf;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),m=c-h.viewState.resolution;h.animate=!0;h.viewState.resolution+=k*m;h.viewHints[0]+=1;return!0}return!1}},OPENLAYERS);u("ol.animation.pan",Yf,OPENLAYERS);u("ol.animation.rotate",Zf,OPENLAYERS);u("ol.animation.zoom",$f,OPENLAYERS);
+u("ol.Attribution",mg,OPENLAYERS);mg.prototype.getHTML=mg.prototype.b;ng.prototype.element=ng.prototype.element;u("ol.Collection",og,OPENLAYERS);og.prototype.clear=og.prototype.clear;og.prototype.extend=og.prototype.uf;og.prototype.forEach=og.prototype.forEach;og.prototype.getArray=og.prototype.wl;og.prototype.item=og.prototype.item;og.prototype.getLength=og.prototype.$b;og.prototype.insertAt=og.prototype.le;og.prototype.pop=og.prototype.pop;og.prototype.push=og.prototype.push;
+og.prototype.remove=og.prototype.remove;og.prototype.removeAt=og.prototype.Sf;og.prototype.setAt=og.prototype.Jo;u("ol.coordinate.add",pd,OPENLAYERS);u("ol.coordinate.createStringXY",function(b){return function(c){return xd(c,b)}},OPENLAYERS);u("ol.coordinate.format",sd,OPENLAYERS);u("ol.coordinate.rotate",ud,OPENLAYERS);u("ol.coordinate.toStringHDMS",function(b){return b?rd(b[1],"NS")+" "+rd(b[0],"EW"):""},OPENLAYERS);u("ol.coordinate.toStringXY",xd,OPENLAYERS);u("ol.DeviceOrientation",Mr,OPENLAYERS);
+Mr.prototype.getAlpha=Mr.prototype.Fj;Mr.prototype.getBeta=Mr.prototype.Ij;Mr.prototype.getGamma=Mr.prototype.Oj;Mr.prototype.getHeading=Mr.prototype.xl;Mr.prototype.getTracking=Mr.prototype.Vg;Mr.prototype.setTracking=Mr.prototype.vf;u("ol.easing.easeIn",Tf,OPENLAYERS);u("ol.easing.easeOut",Uf,OPENLAYERS);u("ol.easing.inAndOut",Vf,OPENLAYERS);u("ol.easing.linear",Wf,OPENLAYERS);u("ol.easing.upAndDown",Xf,OPENLAYERS);u("ol.extent.boundingExtent",Ld,OPENLAYERS);u("ol.extent.buffer",Qd,OPENLAYERS);
+u("ol.extent.containsCoordinate",Td,OPENLAYERS);u("ol.extent.containsExtent",Vd,OPENLAYERS);u("ol.extent.containsXY",Ud,OPENLAYERS);u("ol.extent.createEmpty",Md,OPENLAYERS);u("ol.extent.equals",ae,OPENLAYERS);u("ol.extent.extend",be,OPENLAYERS);u("ol.extent.getBottomLeft",de,OPENLAYERS);u("ol.extent.getBottomRight",ee,OPENLAYERS);u("ol.extent.getCenter",le,OPENLAYERS);u("ol.extent.getHeight",ke,OPENLAYERS);u("ol.extent.getIntersection",ne,OPENLAYERS);
+u("ol.extent.getSize",function(b){return[b[2]-b[0],b[3]-b[1]]},OPENLAYERS);u("ol.extent.getTopLeft",ge,OPENLAYERS);u("ol.extent.getTopRight",fe,OPENLAYERS);u("ol.extent.getWidth",je,OPENLAYERS);u("ol.extent.intersects",oe,OPENLAYERS);u("ol.extent.isEmpty",ie,OPENLAYERS);u("ol.extent.applyTransform",qe,OPENLAYERS);u("ol.Feature",pn,OPENLAYERS);pn.prototype.clone=pn.prototype.clone;pn.prototype.getGeometry=pn.prototype.X;pn.prototype.getId=pn.prototype.Oa;pn.prototype.getGeometryName=pn.prototype.Qj;
+pn.prototype.getStyle=pn.prototype.zl;pn.prototype.getStyleFunction=pn.prototype.ac;pn.prototype.setGeometry=pn.prototype.Ma;pn.prototype.setStyle=pn.prototype.wf;pn.prototype.setId=pn.prototype.jc;pn.prototype.setGeometryName=pn.prototype.yc;u("ol.featureloader.tile",sp,OPENLAYERS);u("ol.featureloader.xhr",tp,OPENLAYERS);u("ol.Geolocation",Mx,OPENLAYERS);Mx.prototype.getAccuracy=Mx.prototype.Dj;Mx.prototype.getAccuracyGeometry=Mx.prototype.Ej;Mx.prototype.getAltitude=Mx.prototype.Gj;
+Mx.prototype.getAltitudeAccuracy=Mx.prototype.Hj;Mx.prototype.getHeading=Mx.prototype.Bl;Mx.prototype.getPosition=Mx.prototype.Cl;Mx.prototype.getProjection=Mx.prototype.Wg;Mx.prototype.getSpeed=Mx.prototype.lk;Mx.prototype.getTracking=Mx.prototype.Xg;Mx.prototype.getTrackingOptions=Mx.prototype.Ig;Mx.prototype.setProjection=Mx.prototype.Yg;Mx.prototype.setTracking=Mx.prototype.re;Mx.prototype.setTrackingOptions=Mx.prototype.hi;u("ol.Graticule",Sx,OPENLAYERS);Sx.prototype.getMap=Sx.prototype.Fl;
+Sx.prototype.getMeridians=Sx.prototype.Zj;Sx.prototype.getParallels=Sx.prototype.fk;Sx.prototype.setMap=Sx.prototype.setMap;u("ol.has.DEVICE_PIXEL_RATIO",Vi,OPENLAYERS);u("ol.has.CANVAS",Xi,OPENLAYERS);u("ol.has.DEVICE_ORIENTATION",Yi,OPENLAYERS);u("ol.has.GEOLOCATION",Zi,OPENLAYERS);u("ol.has.TOUCH",$i,OPENLAYERS);u("ol.has.WEBGL",Ui,OPENLAYERS);Xx.prototype.getImage=Xx.prototype.a;Yx.prototype.getImage=Yx.prototype.Ta;u("ol.Kinetic",Jk,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=Ih(b,d),f=Dh(b,c,e),g=[],e=[e,0,0];for(e[1]=f.a;e[1]<=f.c;++e[1])for(e[2]=f.f;e[2]<=f.b;++e[2])g.push(b.Aa(e));return g}},OPENLAYERS);u("ol.Map",S,OPENLAYERS);S.prototype.addControl=S.prototype.kj;S.prototype.addInteraction=S.prototype.lj;S.prototype.addLayer=S.prototype.kg;S.prototype.addOverlay=S.prototype.lg;S.prototype.beforeRender=S.prototype.Na;
+S.prototype.forEachFeatureAtPixel=S.prototype.pd;S.prototype.forEachLayerAtPixel=S.prototype.Jl;S.prototype.hasFeatureAtPixel=S.prototype.bl;S.prototype.getEventCoordinate=S.prototype.Mj;S.prototype.getEventPixel=S.prototype.$d;S.prototype.getTarget=S.prototype.xf;S.prototype.getTargetElement=S.prototype.Mc;S.prototype.getCoordinateFromPixel=S.prototype.Ga;S.prototype.getControls=S.prototype.Kj;S.prototype.getOverlays=S.prototype.dk;S.prototype.getOverlayById=S.prototype.ck;
+S.prototype.getInteractions=S.prototype.Rj;S.prototype.getLayerGroup=S.prototype.rc;S.prototype.getLayers=S.prototype.Zg;S.prototype.getPixelFromCoordinate=S.prototype.Pa;S.prototype.getSize=S.prototype.Sa;S.prototype.getView=S.prototype.aa;S.prototype.getViewport=S.prototype.sk;S.prototype.renderSync=S.prototype.Fo;S.prototype.render=S.prototype.render;S.prototype.removeControl=S.prototype.yo;S.prototype.removeInteraction=S.prototype.zo;S.prototype.removeLayer=S.prototype.Bo;
+S.prototype.removeOverlay=S.prototype.Co;S.prototype.setLayerGroup=S.prototype.$h;S.prototype.setSize=S.prototype.Vf;S.prototype.setTarget=S.prototype.Kl;S.prototype.setView=S.prototype.To;S.prototype.updateSize=S.prototype.Vc;Kj.prototype.originalEvent=Kj.prototype.originalEvent;Kj.prototype.pixel=Kj.prototype.pixel;Kj.prototype.coordinate=Kj.prototype.coordinate;Kj.prototype.dragging=Kj.prototype.dragging;Kj.prototype.preventDefault=Kj.prototype.preventDefault;Kj.prototype.stopPropagation=Kj.prototype.b;
+nh.prototype.map=nh.prototype.map;nh.prototype.frameState=nh.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.P;gd.prototype.getProperties=gd.prototype.R;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.u;dd.prototype.dispatchEvent=dd.prototype.s;dd.prototype.getRevision=dd.prototype.L;dd.prototype.on=dd.prototype.H;dd.prototype.once=dd.prototype.M;dd.prototype.un=dd.prototype.K;dd.prototype.unByKey=dd.prototype.N;u("ol.inherits",y,OPENLAYERS);u("ol.Overlay",jr,OPENLAYERS);jr.prototype.getElement=jr.prototype.se;jr.prototype.getId=jr.prototype.Oa;jr.prototype.getMap=jr.prototype.te;jr.prototype.getOffset=jr.prototype.Gg;jr.prototype.getPosition=jr.prototype.$g;
+jr.prototype.getPositioning=jr.prototype.Hg;jr.prototype.setElement=jr.prototype.Xh;jr.prototype.setMap=jr.prototype.setMap;jr.prototype.setOffset=jr.prototype.bi;jr.prototype.setPosition=jr.prototype.yf;jr.prototype.setPositioning=jr.prototype.ei;u("ol.render.toContext",function(b,c){var d=b.canvas,e=c?c:{},f=e.pixelRatio||Vi;if(e=e.size)d.width=e[0]*f,d.height=e[1]*f,d.style.width=e[0]+"px",d.style.height=e[1]+"px";d=[0,0,d.width,d.height];e=gk(Bd(),0,0,f,f,0,0,0);return new gm(b,f,d,e,0)},OPENLAYERS);
+u("ol.size.toSize",md,OPENLAYERS);uh.prototype.getTileCoord=uh.prototype.g;Co.prototype.getFormat=Co.prototype.Ll;Co.prototype.setLoader=Co.prototype.ai;u("ol.View",Nf,OPENLAYERS);Nf.prototype.constrainCenter=Nf.prototype.Xd;Nf.prototype.constrainResolution=Nf.prototype.constrainResolution;Nf.prototype.constrainRotation=Nf.prototype.constrainRotation;Nf.prototype.getCenter=Nf.prototype.Ua;Nf.prototype.calculateExtent=Nf.prototype.$c;Nf.prototype.getProjection=Nf.prototype.Ml;
+Nf.prototype.getResolution=Nf.prototype.$;Nf.prototype.getRotation=Nf.prototype.Fa;Nf.prototype.getZoom=Nf.prototype.uk;Nf.prototype.fit=Nf.prototype.kf;Nf.prototype.centerOn=Nf.prototype.uj;Nf.prototype.rotate=Nf.prototype.rotate;Nf.prototype.setCenter=Nf.prototype.kb;Nf.prototype.setResolution=Nf.prototype.Ub;Nf.prototype.setRotation=Nf.prototype.ue;Nf.prototype.setZoom=Nf.prototype.Wo;u("ol.xml.getAllTextContent",Mo,OPENLAYERS);u("ol.xml.parse",fp,OPENLAYERS);rq.prototype.getGL=rq.prototype.Gn;
+rq.prototype.useProgram=rq.prototype.He;u("ol.tilegrid.TileGrid",zh,OPENLAYERS);zh.prototype.getMaxZoom=zh.prototype.Eg;zh.prototype.getMinZoom=zh.prototype.Fg;zh.prototype.getOrigin=zh.prototype.Da;zh.prototype.getResolution=zh.prototype.$;zh.prototype.getResolutions=zh.prototype.zh;zh.prototype.getTileCoordExtent=zh.prototype.Aa;zh.prototype.getTileCoordForCoordAndResolution=zh.prototype.fe;zh.prototype.getTileCoordForCoordAndZ=zh.prototype.ge;zh.prototype.getTileSize=zh.prototype.Ka;
+u("ol.tilegrid.createXYZ",Mh,OPENLAYERS);u("ol.tilegrid.WMTS",zA,OPENLAYERS);zA.prototype.getMatrixIds=zA.prototype.o;u("ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet",AA,OPENLAYERS);u("ol.style.AtlasManager",EA,OPENLAYERS);u("ol.style.Circle",Zl,OPENLAYERS);Zl.prototype.getFill=Zl.prototype.en;Zl.prototype.getImage=Zl.prototype.gc;Zl.prototype.getRadius=Zl.prototype.fn;Zl.prototype.getStroke=Zl.prototype.gn;u("ol.style.Fill",Tl,OPENLAYERS);Tl.prototype.getColor=Tl.prototype.b;
+Tl.prototype.setColor=Tl.prototype.c;u("ol.style.Icon",tk,OPENLAYERS);tk.prototype.getAnchor=tk.prototype.Xb;tk.prototype.getImage=tk.prototype.gc;tk.prototype.getOrigin=tk.prototype.Da;tk.prototype.getSrc=tk.prototype.hn;tk.prototype.getSize=tk.prototype.Cb;tk.prototype.load=tk.prototype.load;u("ol.style.Image",sk,OPENLAYERS);sk.prototype.getOpacity=sk.prototype.Be;sk.prototype.getRotateWithView=sk.prototype.de;sk.prototype.getRotation=sk.prototype.Ce;sk.prototype.getScale=sk.prototype.De;
+sk.prototype.getSnapToPixel=sk.prototype.ee;sk.prototype.setOpacity=sk.prototype.Ee;sk.prototype.setRotation=sk.prototype.Fe;sk.prototype.setScale=sk.prototype.Ge;u("ol.style.RegularShape",IA,OPENLAYERS);IA.prototype.getAnchor=IA.prototype.Xb;IA.prototype.getAngle=IA.prototype.jn;IA.prototype.getFill=IA.prototype.kn;IA.prototype.getImage=IA.prototype.gc;IA.prototype.getOrigin=IA.prototype.Da;IA.prototype.getPoints=IA.prototype.ln;IA.prototype.getRadius=IA.prototype.mn;IA.prototype.getRadius2=IA.prototype.jk;
+IA.prototype.getSize=IA.prototype.Cb;IA.prototype.getStroke=IA.prototype.nn;u("ol.style.Stroke",Yl,OPENLAYERS);Yl.prototype.getColor=Yl.prototype.pn;Yl.prototype.getLineCap=Yl.prototype.Uj;Yl.prototype.getLineDash=Yl.prototype.qn;Yl.prototype.getLineJoin=Yl.prototype.Vj;Yl.prototype.getMiterLimit=Yl.prototype.$j;Yl.prototype.getWidth=Yl.prototype.rn;Yl.prototype.setColor=Yl.prototype.sn;Yl.prototype.setLineCap=Yl.prototype.Oo;Yl.prototype.setLineDash=Yl.prototype.tn;Yl.prototype.setLineJoin=Yl.prototype.Po;
+Yl.prototype.setMiterLimit=Yl.prototype.Qo;Yl.prototype.setWidth=Yl.prototype.Uo;u("ol.style.Style",$l,OPENLAYERS);$l.prototype.getGeometry=$l.prototype.X;$l.prototype.getGeometryFunction=$l.prototype.Pj;$l.prototype.getFill=$l.prototype.vn;$l.prototype.getImage=$l.prototype.wn;$l.prototype.getStroke=$l.prototype.xn;$l.prototype.getText=$l.prototype.Ca;$l.prototype.getZIndex=$l.prototype.yn;$l.prototype.setGeometry=$l.prototype.yh;$l.prototype.setZIndex=$l.prototype.zn;u("ol.style.Text",Wt,OPENLAYERS);
+Wt.prototype.getFont=Wt.prototype.Nj;Wt.prototype.getOffsetX=Wt.prototype.ak;Wt.prototype.getOffsetY=Wt.prototype.bk;Wt.prototype.getFill=Wt.prototype.An;Wt.prototype.getRotation=Wt.prototype.Bn;Wt.prototype.getScale=Wt.prototype.Cn;Wt.prototype.getStroke=Wt.prototype.Dn;Wt.prototype.getText=Wt.prototype.Ca;Wt.prototype.getTextAlign=Wt.prototype.nk;Wt.prototype.getTextBaseline=Wt.prototype.pk;Wt.prototype.setFont=Wt.prototype.Lo;Wt.prototype.setOffsetX=Wt.prototype.ci;Wt.prototype.setOffsetY=Wt.prototype.di;
+Wt.prototype.setFill=Wt.prototype.Ko;Wt.prototype.setRotation=Wt.prototype.En;Wt.prototype.setScale=Wt.prototype.Fn;Wt.prototype.setStroke=Wt.prototype.Ro;Wt.prototype.setText=Wt.prototype.fi;Wt.prototype.setTextAlign=Wt.prototype.gi;Wt.prototype.setTextBaseline=Wt.prototype.So;u("ol.Sphere",ze,OPENLAYERS);ze.prototype.geodesicArea=ze.prototype.f;ze.prototype.haversineDistance=ze.prototype.a;u("ol.source.BingMaps",Kz,OPENLAYERS);u("ol.source.BingMaps.TOS_ATTRIBUTION",Lz,OPENLAYERS);
+u("ol.source.Cluster",Mz,OPENLAYERS);Mz.prototype.getSource=Mz.prototype.ga;u("ol.source.ImageCanvas",on,OPENLAYERS);u("ol.source.ImageMapGuide",Pz,OPENLAYERS);Pz.prototype.getParams=Pz.prototype.Hm;Pz.prototype.getImageLoadFunction=Pz.prototype.Gm;Pz.prototype.updateParams=Pz.prototype.Jm;Pz.prototype.setImageLoadFunction=Pz.prototype.Im;u("ol.source.Image",gn,OPENLAYERS);jn.prototype.image=jn.prototype.image;u("ol.source.ImageStatic",Qz,OPENLAYERS);u("ol.source.ImageVector",Mp,OPENLAYERS);
+Mp.prototype.getSource=Mp.prototype.Km;Mp.prototype.getStyle=Mp.prototype.Lm;Mp.prototype.getStyleFunction=Mp.prototype.Mm;Mp.prototype.setStyle=Mp.prototype.ph;u("ol.source.ImageWMS",Rz,OPENLAYERS);Rz.prototype.getGetFeatureInfoUrl=Rz.prototype.Pm;Rz.prototype.getParams=Rz.prototype.Rm;Rz.prototype.getImageLoadFunction=Rz.prototype.Qm;Rz.prototype.getUrl=Rz.prototype.Sm;Rz.prototype.setImageLoadFunction=Rz.prototype.Tm;Rz.prototype.setUrl=Rz.prototype.Um;Rz.prototype.updateParams=Rz.prototype.Vm;
+u("ol.source.MapQuest",Yz,OPENLAYERS);Yz.prototype.getLayer=Yz.prototype.l;u("ol.source.OSM",Wz,OPENLAYERS);u("ol.source.OSM.ATTRIBUTION",Xz,OPENLAYERS);u("ol.source.Raster",aA,OPENLAYERS);aA.prototype.setOperation=aA.prototype.v;fA.prototype.extent=fA.prototype.extent;fA.prototype.resolution=fA.prototype.resolution;fA.prototype.data=fA.prototype.data;u("ol.source.Source",wh,OPENLAYERS);wh.prototype.getAttributions=wh.prototype.sa;wh.prototype.getLogo=wh.prototype.qa;wh.prototype.getProjection=wh.prototype.ta;
+wh.prototype.getState=wh.prototype.ua;wh.prototype.setAttributions=wh.prototype.ma;u("ol.source.Stamen",kA,OPENLAYERS);u("ol.source.TileArcGISRest",mA,OPENLAYERS);mA.prototype.getParams=mA.prototype.C;mA.prototype.updateParams=mA.prototype.V;u("ol.source.TileDebug",oA,OPENLAYERS);u("ol.source.TileImage",Y,OPENLAYERS);Y.prototype.setRenderReprojectionEdges=Y.prototype.vb;Y.prototype.setTileGridForProjection=Y.prototype.wb;u("ol.source.TileJSON",pA,OPENLAYERS);u("ol.source.Tile",Nh,OPENLAYERS);
+Nh.prototype.getTileGrid=Nh.prototype.Ha;Qh.prototype.tile=Qh.prototype.tile;u("ol.source.TileUTFGrid",qA,OPENLAYERS);qA.prototype.getTemplate=qA.prototype.mk;qA.prototype.forDataAtCoordinateAndResolution=qA.prototype.zj;u("ol.source.TileWMS",vA,OPENLAYERS);vA.prototype.getGetFeatureInfoUrl=vA.prototype.Ym;vA.prototype.getParams=vA.prototype.Zm;vA.prototype.updateParams=vA.prototype.an;Wp.prototype.getTileLoadFunction=Wp.prototype.Xa;Wp.prototype.getTileUrlFunction=Wp.prototype.Ya;
+Wp.prototype.getUrls=Wp.prototype.Za;Wp.prototype.setTileLoadFunction=Wp.prototype.eb;Wp.prototype.setTileUrlFunction=Wp.prototype.Ja;Wp.prototype.setUrl=Wp.prototype.Va;Wp.prototype.setUrls=Wp.prototype.Wa;u("ol.source.Vector",R,OPENLAYERS);R.prototype.addFeature=R.prototype.Bd;R.prototype.addFeatures=R.prototype.Ec;R.prototype.clear=R.prototype.clear;R.prototype.forEachFeature=R.prototype.sg;R.prototype.forEachFeatureInExtent=R.prototype.pb;R.prototype.forEachFeatureIntersectingExtent=R.prototype.tg;
+R.prototype.getFeaturesCollection=R.prototype.zg;R.prototype.getFeatures=R.prototype.ze;R.prototype.getFeaturesAtCoordinate=R.prototype.yg;R.prototype.getFeaturesInExtent=R.prototype.mf;R.prototype.getClosestFeatureToCoordinate=R.prototype.vg;R.prototype.getExtent=R.prototype.J;R.prototype.getFeatureById=R.prototype.xg;R.prototype.removeFeature=R.prototype.Rc;Jp.prototype.feature=Jp.prototype.feature;u("ol.source.VectorTile",Xp,OPENLAYERS);u("ol.source.WMTS",Z,OPENLAYERS);
+Z.prototype.getDimensions=Z.prototype.Lj;Z.prototype.getFormat=Z.prototype.bn;Z.prototype.getLayer=Z.prototype.cn;Z.prototype.getMatrixSet=Z.prototype.Yj;Z.prototype.getRequestEncoding=Z.prototype.kk;Z.prototype.getStyle=Z.prototype.dn;Z.prototype.getVersion=Z.prototype.rk;Z.prototype.updateDimensions=Z.prototype.cp;
+u("ol.source.WMTS.optionsFromCapabilities",function(b,c){var d=fb(b.Contents.Layer,function(b){return b.Identifier==c.layer}),e=b.Contents.TileMatrixSet,f,g;f=1<d.TileMatrixSetLink.length?"projection"in c?gb(d.TileMatrixSetLink,function(b){return fb(e,function(c){return c.Identifier==b.TileMatrixSet}).SupportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3")==c.projection}):gb(d.TileMatrixSetLink,function(b){return b.TileMatrixSet==c.matrixSet}):0;0>f&&(f=0);g=d.TileMatrixSetLink[f].TileMatrixSet;
+var h=d.Format[0];"format"in c&&(h=c.format);f=gb(d.Style,function(b){return"style"in c?b.Title==c.style:b.isDefault});0>f&&(f=0);f=d.Style[f].Identifier;var k={};"Dimension"in d&&d.Dimension.forEach(function(b){var c=b.Identifier,d=b.Default;void 0===d&&(d=b.Value[0]);k[c]=d});var m=fb(b.Contents.TileMatrixSet,function(b){return b.Identifier==g}),n;n="projection"in c?Ee(c.projection):Ee(m.SupportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"));var p=d.WGS84BoundingBox,q,r;void 0!==p&&
+(r=Ee("EPSG:4326").J(),r=p[0]==r[0]&&p[2]==r[2],q=Ze(p,"EPSG:4326",n),(p=n.J())&&(Vd(p,q)||(q=void 0)));var m=AA(m,q),t=[];q=c.requestEncoding;q=void 0!==q?q:"";if(b.hasOwnProperty("OperationsMetadata")&&b.OperationsMetadata.hasOwnProperty("GetTile")&&0!==q.indexOf("REST"))for(var d=b.OperationsMetadata.GetTile.DCP.HTTP.Get,p=0,x=d.length;p<x;++p){var z=fb(d[p].Constraint,function(b){return"GetEncoding"==b.name}).AllowedValues.Value;0<z.length&&vb(z,"KVP")&&(q="KVP",t.push(d[p].href))}else q="REST",
+d.ResourceURL.forEach(function(b){"tile"==b.resourceType&&(h=b.format,t.push(b.template))});return{urls:t,layer:c.layer,matrixSet:g,format:h,projection:n,requestEncoding:q,tileGrid:m,style:f,dimensions:k,wrapX:r}},OPENLAYERS);u("ol.source.XYZ",Vz,OPENLAYERS);u("ol.source.Zoomify",CA,OPENLAYERS);bk.prototype.vectorContext=bk.prototype.vectorContext;bk.prototype.frameState=bk.prototype.frameState;bk.prototype.context=bk.prototype.context;bk.prototype.glContext=bk.prototype.glContext;
+Sm.prototype.get=Sm.prototype.get;Sm.prototype.getExtent=Sm.prototype.J;Sm.prototype.getGeometry=Sm.prototype.X;Sm.prototype.getProperties=Sm.prototype.Cm;Sm.prototype.getType=Sm.prototype.W;u("ol.render.VectorContext",ak,OPENLAYERS);Oq.prototype.drawAsync=Oq.prototype.md;Oq.prototype.drawCircleGeometry=Oq.prototype.Gc;Oq.prototype.drawFeature=Oq.prototype.jf;Oq.prototype.drawGeometryCollectionGeometry=Oq.prototype.Yd;Oq.prototype.drawPointGeometry=Oq.prototype.Hb;
+Oq.prototype.drawLineStringGeometry=Oq.prototype.Wb;Oq.prototype.drawMultiLineStringGeometry=Oq.prototype.Hc;Oq.prototype.drawMultiPointGeometry=Oq.prototype.Gb;Oq.prototype.drawMultiPolygonGeometry=Oq.prototype.Ic;Oq.prototype.drawPolygonGeometry=Oq.prototype.Jc;Oq.prototype.drawText=Oq.prototype.Ib;Oq.prototype.setFillStrokeStyle=Oq.prototype.bb;Oq.prototype.setImageStyle=Oq.prototype.ub;Oq.prototype.setTextStyle=Oq.prototype.cb;gm.prototype.drawAsync=gm.prototype.md;
+gm.prototype.drawCircleGeometry=gm.prototype.Gc;gm.prototype.drawFeature=gm.prototype.jf;gm.prototype.drawPointGeometry=gm.prototype.Hb;gm.prototype.drawMultiPointGeometry=gm.prototype.Gb;gm.prototype.drawLineStringGeometry=gm.prototype.Wb;gm.prototype.drawMultiLineStringGeometry=gm.prototype.Hc;gm.prototype.drawPolygonGeometry=gm.prototype.Jc;gm.prototype.drawMultiPolygonGeometry=gm.prototype.Ic;gm.prototype.setFillStrokeStyle=gm.prototype.bb;gm.prototype.setImageStyle=gm.prototype.ub;
+gm.prototype.setTextStyle=gm.prototype.cb;u("ol.proj.common.add",Ol,OPENLAYERS);u("ol.proj.METERS_PER_UNIT",Be,OPENLAYERS);u("ol.proj.Projection",Ce,OPENLAYERS);Ce.prototype.getCode=Ce.prototype.Jj;Ce.prototype.getExtent=Ce.prototype.J;Ce.prototype.getUnits=Ce.prototype.Am;Ce.prototype.getMetersPerUnit=Ce.prototype.Kc;Ce.prototype.getWorldExtent=Ce.prototype.tk;Ce.prototype.isGlobal=Ce.prototype.gl;Ce.prototype.setGlobal=Ce.prototype.No;Ce.prototype.setExtent=Ce.prototype.Bm;
+Ce.prototype.setWorldExtent=Ce.prototype.Vo;Ce.prototype.setGetPointResolution=Ce.prototype.Mo;Ce.prototype.getPointResolution=Ce.prototype.getPointResolution;u("ol.proj.addEquivalentProjections",Fe,OPENLAYERS);u("ol.proj.addProjection",Se,OPENLAYERS);u("ol.proj.addCoordinateTransforms",Ge,OPENLAYERS);u("ol.proj.fromLonLat",function(b,c){return Ye(b,"EPSG:4326",void 0!==c?c:"EPSG:3857")},OPENLAYERS);u("ol.proj.toLonLat",function(b,c){return Ye(b,void 0!==c?c:"EPSG:3857","EPSG:4326")},OPENLAYERS);
+u("ol.proj.get",Ee,OPENLAYERS);u("ol.proj.getTransform",We,OPENLAYERS);u("ol.proj.transform",Ye,OPENLAYERS);u("ol.proj.transformExtent",Ze,OPENLAYERS);u("ol.layer.Heatmap",X,OPENLAYERS);X.prototype.getBlur=X.prototype.ug;X.prototype.getGradient=X.prototype.Bg;X.prototype.getRadius=X.prototype.jh;X.prototype.setBlur=X.prototype.Vh;X.prototype.setGradient=X.prototype.Zh;X.prototype.setRadius=X.prototype.kh;u("ol.layer.Image",Pl,OPENLAYERS);Pl.prototype.getSource=Pl.prototype.ea;
+u("ol.layer.Layer",ck,OPENLAYERS);ck.prototype.getSource=ck.prototype.ea;ck.prototype.setMap=ck.prototype.setMap;ck.prototype.setSource=ck.prototype.zc;u("ol.layer.Base",Yj,OPENLAYERS);Yj.prototype.getExtent=Yj.prototype.J;Yj.prototype.getMaxResolution=Yj.prototype.Nb;Yj.prototype.getMinResolution=Yj.prototype.Ob;Yj.prototype.getOpacity=Yj.prototype.Rb;Yj.prototype.getVisible=Yj.prototype.rb;Yj.prototype.getZIndex=Yj.prototype.Sb;Yj.prototype.setExtent=Yj.prototype.cc;
+Yj.prototype.setMaxResolution=Yj.prototype.kc;Yj.prototype.setMinResolution=Yj.prototype.lc;Yj.prototype.setOpacity=Yj.prototype.dc;Yj.prototype.setVisible=Yj.prototype.ec;Yj.prototype.setZIndex=Yj.prototype.fc;u("ol.layer.Group",Hl,OPENLAYERS);Hl.prototype.getLayers=Hl.prototype.Qc;Hl.prototype.setLayers=Hl.prototype.ih;u("ol.layer.Tile",G,OPENLAYERS);G.prototype.getPreload=G.prototype.a;G.prototype.getSource=G.prototype.ea;G.prototype.setPreload=G.prototype.c;
+G.prototype.getUseInterimTilesOnError=G.prototype.b;G.prototype.setUseInterimTilesOnError=G.prototype.g;u("ol.layer.Vector",H,OPENLAYERS);H.prototype.getSource=H.prototype.ea;H.prototype.getStyle=H.prototype.C;H.prototype.getStyleFunction=H.prototype.D;H.prototype.setStyle=H.prototype.c;u("ol.layer.VectorTile",I,OPENLAYERS);I.prototype.getPreload=I.prototype.g;I.prototype.getSource=I.prototype.ea;I.prototype.getUseInterimTilesOnError=I.prototype.U;I.prototype.setPreload=I.prototype.T;
+I.prototype.setUseInterimTilesOnError=I.prototype.V;u("ol.interaction.DoubleClickZoom",Pk,OPENLAYERS);u("ol.interaction.DoubleClickZoom.handleEvent",Qk,OPENLAYERS);u("ol.interaction.DragAndDrop",qy,OPENLAYERS);u("ol.interaction.DragAndDrop.handleEvent",te,OPENLAYERS);ry.prototype.features=ry.prototype.features;ry.prototype.file=ry.prototype.file;ry.prototype.projection=ry.prototype.projection;ll.prototype.coordinate=ll.prototype.coordinate;u("ol.interaction.DragBox",ml,OPENLAYERS);
+ml.prototype.getGeometry=ml.prototype.X;u("ol.interaction.DragPan",al,OPENLAYERS);u("ol.interaction.DragRotateAndZoom",uy,OPENLAYERS);u("ol.interaction.DragRotate",el,OPENLAYERS);u("ol.interaction.DragZoom",rl,OPENLAYERS);yy.prototype.feature=yy.prototype.feature;u("ol.interaction.Draw",zy,OPENLAYERS);u("ol.interaction.Draw.handleEvent",By,OPENLAYERS);zy.prototype.removeLastPoint=zy.prototype.Ao;zy.prototype.finishDrawing=zy.prototype.od;zy.prototype.extend=zy.prototype.fm;
+u("ol.interaction.Draw.createRegularPolygon",function(b,c){return function(d,e){var f=d[0],g=d[1],h=Math.sqrt(vd(f,g)),k=e?e:Lf(new Nx(f),b);Mf(k,f,h,c?c:Math.atan((g[1]-f[1])/(g[0]-f[0])));return k}},OPENLAYERS);u("ol.interaction.Interaction",Lk,OPENLAYERS);Lk.prototype.getActive=Lk.prototype.b;Lk.prototype.getMap=Lk.prototype.i;Lk.prototype.setActive=Lk.prototype.g;u("ol.interaction.defaults",Gl,OPENLAYERS);u("ol.interaction.KeyboardPan",sl,OPENLAYERS);
+u("ol.interaction.KeyboardPan.handleEvent",tl,OPENLAYERS);u("ol.interaction.KeyboardZoom",ul,OPENLAYERS);u("ol.interaction.KeyboardZoom.handleEvent",vl,OPENLAYERS);Py.prototype.features=Py.prototype.features;Py.prototype.mapBrowserPointerEvent=Py.prototype.mapBrowserPointerEvent;u("ol.interaction.Modify",Qy,OPENLAYERS);u("ol.interaction.Modify.handleEvent",Ty,OPENLAYERS);u("ol.interaction.MouseWheelZoom",wl,OPENLAYERS);u("ol.interaction.MouseWheelZoom.handleEvent",xl,OPENLAYERS);
+wl.prototype.setMouseAnchor=wl.prototype.D;u("ol.interaction.PinchRotate",yl,OPENLAYERS);u("ol.interaction.PinchZoom",Cl,OPENLAYERS);u("ol.interaction.Pointer",Yk,OPENLAYERS);u("ol.interaction.Pointer.handleEvent",Zk,OPENLAYERS);cz.prototype.selected=cz.prototype.selected;cz.prototype.deselected=cz.prototype.deselected;cz.prototype.mapBrowserEvent=cz.prototype.mapBrowserEvent;u("ol.interaction.Select",dz,OPENLAYERS);dz.prototype.getFeatures=dz.prototype.pm;dz.prototype.getLayer=dz.prototype.qm;
+u("ol.interaction.Select.handleEvent",ez,OPENLAYERS);dz.prototype.setMap=dz.prototype.setMap;u("ol.interaction.Snap",gz,OPENLAYERS);gz.prototype.addFeature=gz.prototype.xd;gz.prototype.removeFeature=gz.prototype.yd;kz.prototype.features=kz.prototype.features;kz.prototype.coordinate=kz.prototype.coordinate;u("ol.interaction.Translate",lz,OPENLAYERS);u("ol.geom.Circle",Nx,OPENLAYERS);Nx.prototype.clone=Nx.prototype.clone;Nx.prototype.getCenter=Nx.prototype.wd;Nx.prototype.getRadius=Nx.prototype.zf;
+Nx.prototype.getType=Nx.prototype.W;Nx.prototype.intersectsExtent=Nx.prototype.Ea;Nx.prototype.setCenter=Nx.prototype.Yl;Nx.prototype.setCenterAndRadius=Nx.prototype.Uf;Nx.prototype.setRadius=Nx.prototype.Zl;Nx.prototype.transform=Nx.prototype.lb;u("ol.geom.Geometry",$e,OPENLAYERS);$e.prototype.getClosestPoint=$e.prototype.qb;$e.prototype.getExtent=$e.prototype.J;$e.prototype.simplify=$e.prototype.xb;$e.prototype.transform=$e.prototype.lb;u("ol.geom.GeometryCollection",gs,OPENLAYERS);
+gs.prototype.clone=gs.prototype.clone;gs.prototype.getGeometries=gs.prototype.Ag;gs.prototype.getType=gs.prototype.W;gs.prototype.intersectsExtent=gs.prototype.Ea;gs.prototype.setGeometries=gs.prototype.Yh;gs.prototype.applyTransform=gs.prototype.pc;gs.prototype.translate=gs.prototype.Pc;u("ol.geom.LinearRing",vf,OPENLAYERS);vf.prototype.clone=vf.prototype.clone;vf.prototype.getArea=vf.prototype.bm;vf.prototype.getCoordinates=vf.prototype.Z;vf.prototype.getType=vf.prototype.W;
+vf.prototype.setCoordinates=vf.prototype.la;u("ol.geom.LineString",T,OPENLAYERS);T.prototype.appendCoordinate=T.prototype.mj;T.prototype.clone=T.prototype.clone;T.prototype.forEachSegment=T.prototype.Cj;T.prototype.getCoordinateAtM=T.prototype.$l;T.prototype.getCoordinates=T.prototype.Z;T.prototype.getLength=T.prototype.am;T.prototype.getType=T.prototype.W;T.prototype.intersectsExtent=T.prototype.Ea;T.prototype.setCoordinates=T.prototype.la;u("ol.geom.MultiLineString",U,OPENLAYERS);
+U.prototype.appendLineString=U.prototype.nj;U.prototype.clone=U.prototype.clone;U.prototype.getCoordinateAtM=U.prototype.cm;U.prototype.getCoordinates=U.prototype.Z;U.prototype.getLineString=U.prototype.Wj;U.prototype.getLineStrings=U.prototype.sd;U.prototype.getType=U.prototype.W;U.prototype.intersectsExtent=U.prototype.Ea;U.prototype.setCoordinates=U.prototype.la;u("ol.geom.MultiPoint",Xr,OPENLAYERS);Xr.prototype.appendPoint=Xr.prototype.pj;Xr.prototype.clone=Xr.prototype.clone;
+Xr.prototype.getCoordinates=Xr.prototype.Z;Xr.prototype.getPoint=Xr.prototype.gk;Xr.prototype.getPoints=Xr.prototype.ve;Xr.prototype.getType=Xr.prototype.W;Xr.prototype.intersectsExtent=Xr.prototype.Ea;Xr.prototype.setCoordinates=Xr.prototype.la;u("ol.geom.MultiPolygon",V,OPENLAYERS);V.prototype.appendPolygon=V.prototype.qj;V.prototype.clone=V.prototype.clone;V.prototype.getArea=V.prototype.dm;V.prototype.getCoordinates=V.prototype.Z;V.prototype.getInteriorPoints=V.prototype.Tj;
+V.prototype.getPolygon=V.prototype.ik;V.prototype.getPolygons=V.prototype.ce;V.prototype.getType=V.prototype.W;V.prototype.intersectsExtent=V.prototype.Ea;V.prototype.setCoordinates=V.prototype.la;u("ol.geom.Point",E,OPENLAYERS);E.prototype.clone=E.prototype.clone;E.prototype.getCoordinates=E.prototype.Z;E.prototype.getType=E.prototype.W;E.prototype.intersectsExtent=E.prototype.Ea;E.prototype.setCoordinates=E.prototype.la;u("ol.geom.Polygon",F,OPENLAYERS);F.prototype.appendLinearRing=F.prototype.oj;
+F.prototype.clone=F.prototype.clone;F.prototype.getArea=F.prototype.em;F.prototype.getCoordinates=F.prototype.Z;F.prototype.getInteriorPoint=F.prototype.Sj;F.prototype.getLinearRingCount=F.prototype.Xj;F.prototype.getLinearRing=F.prototype.Dg;F.prototype.getLinearRings=F.prototype.be;F.prototype.getType=F.prototype.W;F.prototype.intersectsExtent=F.prototype.Ea;F.prototype.setCoordinates=F.prototype.la;u("ol.geom.Polygon.circular",Jf,OPENLAYERS);u("ol.geom.Polygon.fromExtent",Kf,OPENLAYERS);
+u("ol.geom.Polygon.fromCircle",Lf,OPENLAYERS);u("ol.geom.SimpleGeometry",bf,OPENLAYERS);bf.prototype.getFirstCoordinate=bf.prototype.Kb;bf.prototype.getLastCoordinate=bf.prototype.Lb;bf.prototype.getLayout=bf.prototype.Mb;bf.prototype.applyTransform=bf.prototype.pc;bf.prototype.translate=bf.prototype.Pc;u("ol.format.EsriJSON",$r,OPENLAYERS);$r.prototype.readFeature=$r.prototype.Tb;$r.prototype.readFeatures=$r.prototype.Ba;$r.prototype.readGeometry=$r.prototype.Tc;$r.prototype.readProjection=$r.prototype.Ia;
+$r.prototype.writeGeometry=$r.prototype.Xc;$r.prototype.writeGeometryObject=$r.prototype.Ue;$r.prototype.writeFeature=$r.prototype.Kd;$r.prototype.writeFeatureObject=$r.prototype.Wc;$r.prototype.writeFeatures=$r.prototype.Vb;$r.prototype.writeFeaturesObject=$r.prototype.Se;u("ol.format.Feature",Nr,OPENLAYERS);u("ol.format.GeoJSON",ks,OPENLAYERS);ks.prototype.readFeature=ks.prototype.Tb;ks.prototype.readFeatures=ks.prototype.Ba;ks.prototype.readGeometry=ks.prototype.Tc;
+ks.prototype.readProjection=ks.prototype.Ia;ks.prototype.writeFeature=ks.prototype.Kd;ks.prototype.writeFeatureObject=ks.prototype.Wc;ks.prototype.writeFeatures=ks.prototype.Vb;ks.prototype.writeFeaturesObject=ks.prototype.Se;ks.prototype.writeGeometry=ks.prototype.Xc;ks.prototype.writeGeometryObject=ks.prototype.Ue;u("ol.format.GPX",Os,OPENLAYERS);Os.prototype.readFeature=Os.prototype.Tb;Os.prototype.readFeatures=Os.prototype.Ba;Os.prototype.readProjection=Os.prototype.Ia;
+Os.prototype.writeFeatures=Os.prototype.Vb;Os.prototype.writeFeaturesNode=Os.prototype.f;u("ol.format.IGC",yt,OPENLAYERS);yt.prototype.readFeature=yt.prototype.Tb;yt.prototype.readFeatures=yt.prototype.Ba;yt.prototype.readProjection=yt.prototype.Ia;u("ol.format.KML",Xt,OPENLAYERS);Xt.prototype.readFeature=Xt.prototype.Tb;Xt.prototype.readFeatures=Xt.prototype.Ba;Xt.prototype.readName=Xt.prototype.po;Xt.prototype.readNetworkLinks=Xt.prototype.qo;Xt.prototype.readProjection=Xt.prototype.Ia;
+Xt.prototype.writeFeatures=Xt.prototype.Vb;Xt.prototype.writeFeaturesNode=Xt.prototype.f;u("ol.format.MVT",Lv,OPENLAYERS);Lv.prototype.setLayers=Lv.prototype.g;u("ol.format.OSMXML",Nv,OPENLAYERS);Nv.prototype.readFeatures=Nv.prototype.Ba;Nv.prototype.readProjection=Nv.prototype.Ia;u("ol.format.Polyline",lw,OPENLAYERS);u("ol.format.Polyline.encodeDeltas",mw,OPENLAYERS);u("ol.format.Polyline.decodeDeltas",ow,OPENLAYERS);u("ol.format.Polyline.encodeFloats",nw,OPENLAYERS);
+u("ol.format.Polyline.decodeFloats",pw,OPENLAYERS);lw.prototype.readFeature=lw.prototype.Tb;lw.prototype.readFeatures=lw.prototype.Ba;lw.prototype.readGeometry=lw.prototype.Tc;lw.prototype.readProjection=lw.prototype.Ia;lw.prototype.writeGeometry=lw.prototype.Xc;u("ol.format.TopoJSON",qw,OPENLAYERS);qw.prototype.readFeatures=qw.prototype.Ba;qw.prototype.readProjection=qw.prototype.Ia;u("ol.format.WFS",ww,OPENLAYERS);ww.prototype.readFeatures=ww.prototype.Ba;ww.prototype.readTransactionResponse=ww.prototype.j;
+ww.prototype.readFeatureCollectionMetadata=ww.prototype.i;ww.prototype.writeGetFeature=ww.prototype.l;ww.prototype.writeTransaction=ww.prototype.B;ww.prototype.readProjection=ww.prototype.Ia;u("ol.format.WKT",Jw,OPENLAYERS);Jw.prototype.readFeature=Jw.prototype.Tb;Jw.prototype.readFeatures=Jw.prototype.Ba;Jw.prototype.readGeometry=Jw.prototype.Tc;Jw.prototype.writeFeature=Jw.prototype.Kd;Jw.prototype.writeFeatures=Jw.prototype.Vb;Jw.prototype.writeGeometry=Jw.prototype.Xc;
+u("ol.format.WMSCapabilities",ax,OPENLAYERS);ax.prototype.read=ax.prototype.read;u("ol.format.WMSGetFeatureInfo",xx,OPENLAYERS);xx.prototype.readFeatures=xx.prototype.Ba;u("ol.format.WMTSCapabilities",yx,OPENLAYERS);yx.prototype.read=yx.prototype.read;u("ol.format.GML2",Es,OPENLAYERS);u("ol.format.GML3",Fs,OPENLAYERS);Fs.prototype.writeGeometryNode=Fs.prototype.o;Fs.prototype.writeFeatures=Fs.prototype.Vb;Fs.prototype.writeFeaturesNode=Fs.prototype.f;u("ol.format.GML",Fs,OPENLAYERS);
+Fs.prototype.writeFeatures=Fs.prototype.Vb;Fs.prototype.writeFeaturesNode=Fs.prototype.f;ss.prototype.readFeatures=ss.prototype.Ba;u("ol.events.condition.altKeyOnly",function(b){b=b.a;return b.f&&!b.l&&!b.c},OPENLAYERS);u("ol.events.condition.altShiftKeysOnly",Rk,OPENLAYERS);u("ol.events.condition.always",te,OPENLAYERS);u("ol.events.condition.click",function(b){return b.type==Oj},OPENLAYERS);u("ol.events.condition.never",se,OPENLAYERS);u("ol.events.condition.pointerMove",Sk,OPENLAYERS);
+u("ol.events.condition.singleClick",Tk,OPENLAYERS);u("ol.events.condition.doubleClick",function(b){return b.type==Pj},OPENLAYERS);u("ol.events.condition.noModifierKeys",Uk,OPENLAYERS);u("ol.events.condition.platformModifierKeyOnly",function(b){b=b.a;return!b.f&&b.l&&!b.c},OPENLAYERS);u("ol.events.condition.shiftKeyOnly",Vk,OPENLAYERS);u("ol.events.condition.targetNotEditable",Wk,OPENLAYERS);u("ol.events.condition.mouseOnly",Xk,OPENLAYERS);u("ol.control.Attribution",Rh,OPENLAYERS);
+u("ol.control.Attribution.render",Sh,OPENLAYERS);Rh.prototype.getCollapsible=Rh.prototype.Ol;Rh.prototype.setCollapsible=Rh.prototype.Rl;Rh.prototype.setCollapsed=Rh.prototype.Ql;Rh.prototype.getCollapsed=Rh.prototype.Nl;u("ol.control.Control",oh,OPENLAYERS);oh.prototype.getMap=oh.prototype.g;oh.prototype.setMap=oh.prototype.setMap;oh.prototype.setTarget=oh.prototype.c;u("ol.control.defaults",Xh,OPENLAYERS);u("ol.control.FullScreen",bi,OPENLAYERS);u("ol.control.MousePosition",di,OPENLAYERS);
+u("ol.control.MousePosition.render",ei,OPENLAYERS);di.prototype.getCoordinateFormat=di.prototype.wg;di.prototype.getProjection=di.prototype.ah;di.prototype.setCoordinateFormat=di.prototype.Wh;di.prototype.setProjection=di.prototype.bh;u("ol.control.OverviewMap",nr,OPENLAYERS);u("ol.control.OverviewMap.render",or,OPENLAYERS);nr.prototype.getCollapsible=nr.prototype.Ul;nr.prototype.setCollapsible=nr.prototype.Xl;nr.prototype.setCollapsed=nr.prototype.Wl;nr.prototype.getCollapsed=nr.prototype.Tl;
+nr.prototype.getOverviewMap=nr.prototype.ek;u("ol.control.Rotate",Uh,OPENLAYERS);u("ol.control.Rotate.render",Vh,OPENLAYERS);u("ol.control.ScaleLine",sr,OPENLAYERS);sr.prototype.getUnits=sr.prototype.D;u("ol.control.ScaleLine.render",tr,OPENLAYERS);sr.prototype.setUnits=sr.prototype.T;u("ol.control.Zoom",Wh,OPENLAYERS);u("ol.control.ZoomSlider",Gr,OPENLAYERS);u("ol.control.ZoomSlider.render",Ir,OPENLAYERS);u("ol.control.ZoomToExtent",Lr,OPENLAYERS);u("ol.color.asArray",tg,OPENLAYERS);
+u("ol.color.asString",vg,OPENLAYERS);gd.prototype.changed=gd.prototype.u;gd.prototype.dispatchEvent=gd.prototype.s;gd.prototype.getRevision=gd.prototype.L;gd.prototype.on=gd.prototype.H;gd.prototype.once=gd.prototype.M;gd.prototype.un=gd.prototype.K;gd.prototype.unByKey=gd.prototype.N;og.prototype.get=og.prototype.get;og.prototype.getKeys=og.prototype.P;og.prototype.getProperties=og.prototype.R;og.prototype.set=og.prototype.set;og.prototype.setProperties=og.prototype.I;og.prototype.unset=og.prototype.S;
+og.prototype.changed=og.prototype.u;og.prototype.dispatchEvent=og.prototype.s;og.prototype.getRevision=og.prototype.L;og.prototype.on=og.prototype.H;og.prototype.once=og.prototype.M;og.prototype.un=og.prototype.K;og.prototype.unByKey=og.prototype.N;Mr.prototype.get=Mr.prototype.get;Mr.prototype.getKeys=Mr.prototype.P;Mr.prototype.getProperties=Mr.prototype.R;Mr.prototype.set=Mr.prototype.set;Mr.prototype.setProperties=Mr.prototype.I;Mr.prototype.unset=Mr.prototype.S;Mr.prototype.changed=Mr.prototype.u;
+Mr.prototype.dispatchEvent=Mr.prototype.s;Mr.prototype.getRevision=Mr.prototype.L;Mr.prototype.on=Mr.prototype.H;Mr.prototype.once=Mr.prototype.M;Mr.prototype.un=Mr.prototype.K;Mr.prototype.unByKey=Mr.prototype.N;pn.prototype.get=pn.prototype.get;pn.prototype.getKeys=pn.prototype.P;pn.prototype.getProperties=pn.prototype.R;pn.prototype.set=pn.prototype.set;pn.prototype.setProperties=pn.prototype.I;pn.prototype.unset=pn.prototype.S;pn.prototype.changed=pn.prototype.u;pn.prototype.dispatchEvent=pn.prototype.s;
+pn.prototype.getRevision=pn.prototype.L;pn.prototype.on=pn.prototype.H;pn.prototype.once=pn.prototype.M;pn.prototype.un=pn.prototype.K;pn.prototype.unByKey=pn.prototype.N;Mx.prototype.get=Mx.prototype.get;Mx.prototype.getKeys=Mx.prototype.P;Mx.prototype.getProperties=Mx.prototype.R;Mx.prototype.set=Mx.prototype.set;Mx.prototype.setProperties=Mx.prototype.I;Mx.prototype.unset=Mx.prototype.S;Mx.prototype.changed=Mx.prototype.u;Mx.prototype.dispatchEvent=Mx.prototype.s;Mx.prototype.getRevision=Mx.prototype.L;
+Mx.prototype.on=Mx.prototype.H;Mx.prototype.once=Mx.prototype.M;Mx.prototype.un=Mx.prototype.K;Mx.prototype.unByKey=Mx.prototype.N;Yx.prototype.getTileCoord=Yx.prototype.g;S.prototype.get=S.prototype.get;S.prototype.getKeys=S.prototype.P;S.prototype.getProperties=S.prototype.R;S.prototype.set=S.prototype.set;S.prototype.setProperties=S.prototype.I;S.prototype.unset=S.prototype.S;S.prototype.changed=S.prototype.u;S.prototype.dispatchEvent=S.prototype.s;S.prototype.getRevision=S.prototype.L;
+S.prototype.on=S.prototype.H;S.prototype.once=S.prototype.M;S.prototype.un=S.prototype.K;S.prototype.unByKey=S.prototype.N;Kj.prototype.map=Kj.prototype.map;Kj.prototype.frameState=Kj.prototype.frameState;Lj.prototype.originalEvent=Lj.prototype.originalEvent;Lj.prototype.pixel=Lj.prototype.pixel;Lj.prototype.coordinate=Lj.prototype.coordinate;Lj.prototype.dragging=Lj.prototype.dragging;Lj.prototype.preventDefault=Lj.prototype.preventDefault;Lj.prototype.stopPropagation=Lj.prototype.b;
+Lj.prototype.map=Lj.prototype.map;Lj.prototype.frameState=Lj.prototype.frameState;jr.prototype.get=jr.prototype.get;jr.prototype.getKeys=jr.prototype.P;jr.prototype.getProperties=jr.prototype.R;jr.prototype.set=jr.prototype.set;jr.prototype.setProperties=jr.prototype.I;jr.prototype.unset=jr.prototype.S;jr.prototype.changed=jr.prototype.u;jr.prototype.dispatchEvent=jr.prototype.s;jr.prototype.getRevision=jr.prototype.L;jr.prototype.on=jr.prototype.H;jr.prototype.once=jr.prototype.M;
+jr.prototype.un=jr.prototype.K;jr.prototype.unByKey=jr.prototype.N;Co.prototype.getTileCoord=Co.prototype.g;Nf.prototype.get=Nf.prototype.get;Nf.prototype.getKeys=Nf.prototype.P;Nf.prototype.getProperties=Nf.prototype.R;Nf.prototype.set=Nf.prototype.set;Nf.prototype.setProperties=Nf.prototype.I;Nf.prototype.unset=Nf.prototype.S;Nf.prototype.changed=Nf.prototype.u;Nf.prototype.dispatchEvent=Nf.prototype.s;Nf.prototype.getRevision=Nf.prototype.L;Nf.prototype.on=Nf.prototype.H;Nf.prototype.once=Nf.prototype.M;
+Nf.prototype.un=Nf.prototype.K;Nf.prototype.unByKey=Nf.prototype.N;zA.prototype.getMaxZoom=zA.prototype.Eg;zA.prototype.getMinZoom=zA.prototype.Fg;zA.prototype.getOrigin=zA.prototype.Da;zA.prototype.getResolution=zA.prototype.$;zA.prototype.getResolutions=zA.prototype.zh;zA.prototype.getTileCoordExtent=zA.prototype.Aa;zA.prototype.getTileCoordForCoordAndResolution=zA.prototype.fe;zA.prototype.getTileCoordForCoordAndZ=zA.prototype.ge;zA.prototype.getTileSize=zA.prototype.Ka;
+Zl.prototype.getOpacity=Zl.prototype.Be;Zl.prototype.getRotateWithView=Zl.prototype.de;Zl.prototype.getRotation=Zl.prototype.Ce;Zl.prototype.getScale=Zl.prototype.De;Zl.prototype.getSnapToPixel=Zl.prototype.ee;Zl.prototype.setOpacity=Zl.prototype.Ee;Zl.prototype.setRotation=Zl.prototype.Fe;Zl.prototype.setScale=Zl.prototype.Ge;tk.prototype.getOpacity=tk.prototype.Be;tk.prototype.getRotateWithView=tk.prototype.de;tk.prototype.getRotation=tk.prototype.Ce;tk.prototype.getScale=tk.prototype.De;
+tk.prototype.getSnapToPixel=tk.prototype.ee;tk.prototype.setOpacity=tk.prototype.Ee;tk.prototype.setRotation=tk.prototype.Fe;tk.prototype.setScale=tk.prototype.Ge;IA.prototype.getOpacity=IA.prototype.Be;IA.prototype.getRotateWithView=IA.prototype.de;IA.prototype.getRotation=IA.prototype.Ce;IA.prototype.getScale=IA.prototype.De;IA.prototype.getSnapToPixel=IA.prototype.ee;IA.prototype.setOpacity=IA.prototype.Ee;IA.prototype.setRotation=IA.prototype.Fe;IA.prototype.setScale=IA.prototype.Ge;
+wh.prototype.get=wh.prototype.get;wh.prototype.getKeys=wh.prototype.P;wh.prototype.getProperties=wh.prototype.R;wh.prototype.set=wh.prototype.set;wh.prototype.setProperties=wh.prototype.I;wh.prototype.unset=wh.prototype.S;wh.prototype.changed=wh.prototype.u;wh.prototype.dispatchEvent=wh.prototype.s;wh.prototype.getRevision=wh.prototype.L;wh.prototype.on=wh.prototype.H;wh.prototype.once=wh.prototype.M;wh.prototype.un=wh.prototype.K;wh.prototype.unByKey=wh.prototype.N;Nh.prototype.getAttributions=Nh.prototype.sa;
+Nh.prototype.getLogo=Nh.prototype.qa;Nh.prototype.getProjection=Nh.prototype.ta;Nh.prototype.getState=Nh.prototype.ua;Nh.prototype.setAttributions=Nh.prototype.ma;Nh.prototype.get=Nh.prototype.get;Nh.prototype.getKeys=Nh.prototype.P;Nh.prototype.getProperties=Nh.prototype.R;Nh.prototype.set=Nh.prototype.set;Nh.prototype.setProperties=Nh.prototype.I;Nh.prototype.unset=Nh.prototype.S;Nh.prototype.changed=Nh.prototype.u;Nh.prototype.dispatchEvent=Nh.prototype.s;Nh.prototype.getRevision=Nh.prototype.L;
+Nh.prototype.on=Nh.prototype.H;Nh.prototype.once=Nh.prototype.M;Nh.prototype.un=Nh.prototype.K;Nh.prototype.unByKey=Nh.prototype.N;Wp.prototype.getTileGrid=Wp.prototype.Ha;Wp.prototype.getAttributions=Wp.prototype.sa;Wp.prototype.getLogo=Wp.prototype.qa;Wp.prototype.getProjection=Wp.prototype.ta;Wp.prototype.getState=Wp.prototype.ua;Wp.prototype.setAttributions=Wp.prototype.ma;Wp.prototype.get=Wp.prototype.get;Wp.prototype.getKeys=Wp.prototype.P;Wp.prototype.getProperties=Wp.prototype.R;
+Wp.prototype.set=Wp.prototype.set;Wp.prototype.setProperties=Wp.prototype.I;Wp.prototype.unset=Wp.prototype.S;Wp.prototype.changed=Wp.prototype.u;Wp.prototype.dispatchEvent=Wp.prototype.s;Wp.prototype.getRevision=Wp.prototype.L;Wp.prototype.on=Wp.prototype.H;Wp.prototype.once=Wp.prototype.M;Wp.prototype.un=Wp.prototype.K;Wp.prototype.unByKey=Wp.prototype.N;Y.prototype.getTileLoadFunction=Y.prototype.Xa;Y.prototype.getTileUrlFunction=Y.prototype.Ya;Y.prototype.getUrls=Y.prototype.Za;
+Y.prototype.setTileLoadFunction=Y.prototype.eb;Y.prototype.setTileUrlFunction=Y.prototype.Ja;Y.prototype.setUrl=Y.prototype.Va;Y.prototype.setUrls=Y.prototype.Wa;Y.prototype.getTileGrid=Y.prototype.Ha;Y.prototype.getAttributions=Y.prototype.sa;Y.prototype.getLogo=Y.prototype.qa;Y.prototype.getProjection=Y.prototype.ta;Y.prototype.getState=Y.prototype.ua;Y.prototype.setAttributions=Y.prototype.ma;Y.prototype.get=Y.prototype.get;Y.prototype.getKeys=Y.prototype.P;Y.prototype.getProperties=Y.prototype.R;
+Y.prototype.set=Y.prototype.set;Y.prototype.setProperties=Y.prototype.I;Y.prototype.unset=Y.prototype.S;Y.prototype.changed=Y.prototype.u;Y.prototype.dispatchEvent=Y.prototype.s;Y.prototype.getRevision=Y.prototype.L;Y.prototype.on=Y.prototype.H;Y.prototype.once=Y.prototype.M;Y.prototype.un=Y.prototype.K;Y.prototype.unByKey=Y.prototype.N;Kz.prototype.setRenderReprojectionEdges=Kz.prototype.vb;Kz.prototype.setTileGridForProjection=Kz.prototype.wb;Kz.prototype.getTileLoadFunction=Kz.prototype.Xa;
+Kz.prototype.getTileUrlFunction=Kz.prototype.Ya;Kz.prototype.getUrls=Kz.prototype.Za;Kz.prototype.setTileLoadFunction=Kz.prototype.eb;Kz.prototype.setTileUrlFunction=Kz.prototype.Ja;Kz.prototype.setUrl=Kz.prototype.Va;Kz.prototype.setUrls=Kz.prototype.Wa;Kz.prototype.getTileGrid=Kz.prototype.Ha;Kz.prototype.getAttributions=Kz.prototype.sa;Kz.prototype.getLogo=Kz.prototype.qa;Kz.prototype.getProjection=Kz.prototype.ta;Kz.prototype.getState=Kz.prototype.ua;Kz.prototype.setAttributions=Kz.prototype.ma;
+Kz.prototype.get=Kz.prototype.get;Kz.prototype.getKeys=Kz.prototype.P;Kz.prototype.getProperties=Kz.prototype.R;Kz.prototype.set=Kz.prototype.set;Kz.prototype.setProperties=Kz.prototype.I;Kz.prototype.unset=Kz.prototype.S;Kz.prototype.changed=Kz.prototype.u;Kz.prototype.dispatchEvent=Kz.prototype.s;Kz.prototype.getRevision=Kz.prototype.L;Kz.prototype.on=Kz.prototype.H;Kz.prototype.once=Kz.prototype.M;Kz.prototype.un=Kz.prototype.K;Kz.prototype.unByKey=Kz.prototype.N;R.prototype.getAttributions=R.prototype.sa;
+R.prototype.getLogo=R.prototype.qa;R.prototype.getProjection=R.prototype.ta;R.prototype.getState=R.prototype.ua;R.prototype.setAttributions=R.prototype.ma;R.prototype.get=R.prototype.get;R.prototype.getKeys=R.prototype.P;R.prototype.getProperties=R.prototype.R;R.prototype.set=R.prototype.set;R.prototype.setProperties=R.prototype.I;R.prototype.unset=R.prototype.S;R.prototype.changed=R.prototype.u;R.prototype.dispatchEvent=R.prototype.s;R.prototype.getRevision=R.prototype.L;R.prototype.on=R.prototype.H;
+R.prototype.once=R.prototype.M;R.prototype.un=R.prototype.K;R.prototype.unByKey=R.prototype.N;Mz.prototype.addFeature=Mz.prototype.Bd;Mz.prototype.addFeatures=Mz.prototype.Ec;Mz.prototype.clear=Mz.prototype.clear;Mz.prototype.forEachFeature=Mz.prototype.sg;Mz.prototype.forEachFeatureInExtent=Mz.prototype.pb;Mz.prototype.forEachFeatureIntersectingExtent=Mz.prototype.tg;Mz.prototype.getFeaturesCollection=Mz.prototype.zg;Mz.prototype.getFeatures=Mz.prototype.ze;Mz.prototype.getFeaturesAtCoordinate=Mz.prototype.yg;
+Mz.prototype.getFeaturesInExtent=Mz.prototype.mf;Mz.prototype.getClosestFeatureToCoordinate=Mz.prototype.vg;Mz.prototype.getExtent=Mz.prototype.J;Mz.prototype.getFeatureById=Mz.prototype.xg;Mz.prototype.removeFeature=Mz.prototype.Rc;Mz.prototype.getAttributions=Mz.prototype.sa;Mz.prototype.getLogo=Mz.prototype.qa;Mz.prototype.getProjection=Mz.prototype.ta;Mz.prototype.getState=Mz.prototype.ua;Mz.prototype.setAttributions=Mz.prototype.ma;Mz.prototype.get=Mz.prototype.get;Mz.prototype.getKeys=Mz.prototype.P;
+Mz.prototype.getProperties=Mz.prototype.R;Mz.prototype.set=Mz.prototype.set;Mz.prototype.setProperties=Mz.prototype.I;Mz.prototype.unset=Mz.prototype.S;Mz.prototype.changed=Mz.prototype.u;Mz.prototype.dispatchEvent=Mz.prototype.s;Mz.prototype.getRevision=Mz.prototype.L;Mz.prototype.on=Mz.prototype.H;Mz.prototype.once=Mz.prototype.M;Mz.prototype.un=Mz.prototype.K;Mz.prototype.unByKey=Mz.prototype.N;gn.prototype.getAttributions=gn.prototype.sa;gn.prototype.getLogo=gn.prototype.qa;
+gn.prototype.getProjection=gn.prototype.ta;gn.prototype.getState=gn.prototype.ua;gn.prototype.setAttributions=gn.prototype.ma;gn.prototype.get=gn.prototype.get;gn.prototype.getKeys=gn.prototype.P;gn.prototype.getProperties=gn.prototype.R;gn.prototype.set=gn.prototype.set;gn.prototype.setProperties=gn.prototype.I;gn.prototype.unset=gn.prototype.S;gn.prototype.changed=gn.prototype.u;gn.prototype.dispatchEvent=gn.prototype.s;gn.prototype.getRevision=gn.prototype.L;gn.prototype.on=gn.prototype.H;
+gn.prototype.once=gn.prototype.M;gn.prototype.un=gn.prototype.K;gn.prototype.unByKey=gn.prototype.N;on.prototype.getAttributions=on.prototype.sa;on.prototype.getLogo=on.prototype.qa;on.prototype.getProjection=on.prototype.ta;on.prototype.getState=on.prototype.ua;on.prototype.setAttributions=on.prototype.ma;on.prototype.get=on.prototype.get;on.prototype.getKeys=on.prototype.P;on.prototype.getProperties=on.prototype.R;on.prototype.set=on.prototype.set;on.prototype.setProperties=on.prototype.I;
+on.prototype.unset=on.prototype.S;on.prototype.changed=on.prototype.u;on.prototype.dispatchEvent=on.prototype.s;on.prototype.getRevision=on.prototype.L;on.prototype.on=on.prototype.H;on.prototype.once=on.prototype.M;on.prototype.un=on.prototype.K;on.prototype.unByKey=on.prototype.N;Pz.prototype.getAttributions=Pz.prototype.sa;Pz.prototype.getLogo=Pz.prototype.qa;Pz.prototype.getProjection=Pz.prototype.ta;Pz.prototype.getState=Pz.prototype.ua;Pz.prototype.setAttributions=Pz.prototype.ma;
+Pz.prototype.get=Pz.prototype.get;Pz.prototype.getKeys=Pz.prototype.P;Pz.prototype.getProperties=Pz.prototype.R;Pz.prototype.set=Pz.prototype.set;Pz.prototype.setProperties=Pz.prototype.I;Pz.prototype.unset=Pz.prototype.S;Pz.prototype.changed=Pz.prototype.u;Pz.prototype.dispatchEvent=Pz.prototype.s;Pz.prototype.getRevision=Pz.prototype.L;Pz.prototype.on=Pz.prototype.H;Pz.prototype.once=Pz.prototype.M;Pz.prototype.un=Pz.prototype.K;Pz.prototype.unByKey=Pz.prototype.N;Qz.prototype.getAttributions=Qz.prototype.sa;
+Qz.prototype.getLogo=Qz.prototype.qa;Qz.prototype.getProjection=Qz.prototype.ta;Qz.prototype.getState=Qz.prototype.ua;Qz.prototype.setAttributions=Qz.prototype.ma;Qz.prototype.get=Qz.prototype.get;Qz.prototype.getKeys=Qz.prototype.P;Qz.prototype.getProperties=Qz.prototype.R;Qz.prototype.set=Qz.prototype.set;Qz.prototype.setProperties=Qz.prototype.I;Qz.prototype.unset=Qz.prototype.S;Qz.prototype.changed=Qz.prototype.u;Qz.prototype.dispatchEvent=Qz.prototype.s;Qz.prototype.getRevision=Qz.prototype.L;
+Qz.prototype.on=Qz.prototype.H;Qz.prototype.once=Qz.prototype.M;Qz.prototype.un=Qz.prototype.K;Qz.prototype.unByKey=Qz.prototype.N;Mp.prototype.getAttributions=Mp.prototype.sa;Mp.prototype.getLogo=Mp.prototype.qa;Mp.prototype.getProjection=Mp.prototype.ta;Mp.prototype.getState=Mp.prototype.ua;Mp.prototype.setAttributions=Mp.prototype.ma;Mp.prototype.get=Mp.prototype.get;Mp.prototype.getKeys=Mp.prototype.P;Mp.prototype.getProperties=Mp.prototype.R;Mp.prototype.set=Mp.prototype.set;
+Mp.prototype.setProperties=Mp.prototype.I;Mp.prototype.unset=Mp.prototype.S;Mp.prototype.changed=Mp.prototype.u;Mp.prototype.dispatchEvent=Mp.prototype.s;Mp.prototype.getRevision=Mp.prototype.L;Mp.prototype.on=Mp.prototype.H;Mp.prototype.once=Mp.prototype.M;Mp.prototype.un=Mp.prototype.K;Mp.prototype.unByKey=Mp.prototype.N;Rz.prototype.getAttributions=Rz.prototype.sa;Rz.prototype.getLogo=Rz.prototype.qa;Rz.prototype.getProjection=Rz.prototype.ta;Rz.prototype.getState=Rz.prototype.ua;
+Rz.prototype.setAttributions=Rz.prototype.ma;Rz.prototype.get=Rz.prototype.get;Rz.prototype.getKeys=Rz.prototype.P;Rz.prototype.getProperties=Rz.prototype.R;Rz.prototype.set=Rz.prototype.set;Rz.prototype.setProperties=Rz.prototype.I;Rz.prototype.unset=Rz.prototype.S;Rz.prototype.changed=Rz.prototype.u;Rz.prototype.dispatchEvent=Rz.prototype.s;Rz.prototype.getRevision=Rz.prototype.L;Rz.prototype.on=Rz.prototype.H;Rz.prototype.once=Rz.prototype.M;Rz.prototype.un=Rz.prototype.K;
+Rz.prototype.unByKey=Rz.prototype.N;Vz.prototype.setRenderReprojectionEdges=Vz.prototype.vb;Vz.prototype.setTileGridForProjection=Vz.prototype.wb;Vz.prototype.getTileLoadFunction=Vz.prototype.Xa;Vz.prototype.getTileUrlFunction=Vz.prototype.Ya;Vz.prototype.getUrls=Vz.prototype.Za;Vz.prototype.setTileLoadFunction=Vz.prototype.eb;Vz.prototype.setTileUrlFunction=Vz.prototype.Ja;Vz.prototype.setUrl=Vz.prototype.Va;Vz.prototype.setUrls=Vz.prototype.Wa;Vz.prototype.getTileGrid=Vz.prototype.Ha;
+Vz.prototype.getAttributions=Vz.prototype.sa;Vz.prototype.getLogo=Vz.prototype.qa;Vz.prototype.getProjection=Vz.prototype.ta;Vz.prototype.getState=Vz.prototype.ua;Vz.prototype.setAttributions=Vz.prototype.ma;Vz.prototype.get=Vz.prototype.get;Vz.prototype.getKeys=Vz.prototype.P;Vz.prototype.getProperties=Vz.prototype.R;Vz.prototype.set=Vz.prototype.set;Vz.prototype.setProperties=Vz.prototype.I;Vz.prototype.unset=Vz.prototype.S;Vz.prototype.changed=Vz.prototype.u;Vz.prototype.dispatchEvent=Vz.prototype.s;
+Vz.prototype.getRevision=Vz.prototype.L;Vz.prototype.on=Vz.prototype.H;Vz.prototype.once=Vz.prototype.M;Vz.prototype.un=Vz.prototype.K;Vz.prototype.unByKey=Vz.prototype.N;Yz.prototype.setRenderReprojectionEdges=Yz.prototype.vb;Yz.prototype.setTileGridForProjection=Yz.prototype.wb;Yz.prototype.getTileLoadFunction=Yz.prototype.Xa;Yz.prototype.getTileUrlFunction=Yz.prototype.Ya;Yz.prototype.getUrls=Yz.prototype.Za;Yz.prototype.setTileLoadFunction=Yz.prototype.eb;Yz.prototype.setTileUrlFunction=Yz.prototype.Ja;
+Yz.prototype.setUrl=Yz.prototype.Va;Yz.prototype.setUrls=Yz.prototype.Wa;Yz.prototype.getTileGrid=Yz.prototype.Ha;Yz.prototype.getAttributions=Yz.prototype.sa;Yz.prototype.getLogo=Yz.prototype.qa;Yz.prototype.getProjection=Yz.prototype.ta;Yz.prototype.getState=Yz.prototype.ua;Yz.prototype.setAttributions=Yz.prototype.ma;Yz.prototype.get=Yz.prototype.get;Yz.prototype.getKeys=Yz.prototype.P;Yz.prototype.getProperties=Yz.prototype.R;Yz.prototype.set=Yz.prototype.set;Yz.prototype.setProperties=Yz.prototype.I;
+Yz.prototype.unset=Yz.prototype.S;Yz.prototype.changed=Yz.prototype.u;Yz.prototype.dispatchEvent=Yz.prototype.s;Yz.prototype.getRevision=Yz.prototype.L;Yz.prototype.on=Yz.prototype.H;Yz.prototype.once=Yz.prototype.M;Yz.prototype.un=Yz.prototype.K;Yz.prototype.unByKey=Yz.prototype.N;Wz.prototype.setRenderReprojectionEdges=Wz.prototype.vb;Wz.prototype.setTileGridForProjection=Wz.prototype.wb;Wz.prototype.getTileLoadFunction=Wz.prototype.Xa;Wz.prototype.getTileUrlFunction=Wz.prototype.Ya;
+Wz.prototype.getUrls=Wz.prototype.Za;Wz.prototype.setTileLoadFunction=Wz.prototype.eb;Wz.prototype.setTileUrlFunction=Wz.prototype.Ja;Wz.prototype.setUrl=Wz.prototype.Va;Wz.prototype.setUrls=Wz.prototype.Wa;Wz.prototype.getTileGrid=Wz.prototype.Ha;Wz.prototype.getAttributions=Wz.prototype.sa;Wz.prototype.getLogo=Wz.prototype.qa;Wz.prototype.getProjection=Wz.prototype.ta;Wz.prototype.getState=Wz.prototype.ua;Wz.prototype.setAttributions=Wz.prototype.ma;Wz.prototype.get=Wz.prototype.get;
+Wz.prototype.getKeys=Wz.prototype.P;Wz.prototype.getProperties=Wz.prototype.R;Wz.prototype.set=Wz.prototype.set;Wz.prototype.setProperties=Wz.prototype.I;Wz.prototype.unset=Wz.prototype.S;Wz.prototype.changed=Wz.prototype.u;Wz.prototype.dispatchEvent=Wz.prototype.s;Wz.prototype.getRevision=Wz.prototype.L;Wz.prototype.on=Wz.prototype.H;Wz.prototype.once=Wz.prototype.M;Wz.prototype.un=Wz.prototype.K;Wz.prototype.unByKey=Wz.prototype.N;aA.prototype.getAttributions=aA.prototype.sa;
+aA.prototype.getLogo=aA.prototype.qa;aA.prototype.getProjection=aA.prototype.ta;aA.prototype.getState=aA.prototype.ua;aA.prototype.setAttributions=aA.prototype.ma;aA.prototype.get=aA.prototype.get;aA.prototype.getKeys=aA.prototype.P;aA.prototype.getProperties=aA.prototype.R;aA.prototype.set=aA.prototype.set;aA.prototype.setProperties=aA.prototype.I;aA.prototype.unset=aA.prototype.S;aA.prototype.changed=aA.prototype.u;aA.prototype.dispatchEvent=aA.prototype.s;aA.prototype.getRevision=aA.prototype.L;
+aA.prototype.on=aA.prototype.H;aA.prototype.once=aA.prototype.M;aA.prototype.un=aA.prototype.K;aA.prototype.unByKey=aA.prototype.N;kA.prototype.setRenderReprojectionEdges=kA.prototype.vb;kA.prototype.setTileGridForProjection=kA.prototype.wb;kA.prototype.getTileLoadFunction=kA.prototype.Xa;kA.prototype.getTileUrlFunction=kA.prototype.Ya;kA.prototype.getUrls=kA.prototype.Za;kA.prototype.setTileLoadFunction=kA.prototype.eb;kA.prototype.setTileUrlFunction=kA.prototype.Ja;kA.prototype.setUrl=kA.prototype.Va;
+kA.prototype.setUrls=kA.prototype.Wa;kA.prototype.getTileGrid=kA.prototype.Ha;kA.prototype.getAttributions=kA.prototype.sa;kA.prototype.getLogo=kA.prototype.qa;kA.prototype.getProjection=kA.prototype.ta;kA.prototype.getState=kA.prototype.ua;kA.prototype.setAttributions=kA.prototype.ma;kA.prototype.get=kA.prototype.get;kA.prototype.getKeys=kA.prototype.P;kA.prototype.getProperties=kA.prototype.R;kA.prototype.set=kA.prototype.set;kA.prototype.setProperties=kA.prototype.I;kA.prototype.unset=kA.prototype.S;
+kA.prototype.changed=kA.prototype.u;kA.prototype.dispatchEvent=kA.prototype.s;kA.prototype.getRevision=kA.prototype.L;kA.prototype.on=kA.prototype.H;kA.prototype.once=kA.prototype.M;kA.prototype.un=kA.prototype.K;kA.prototype.unByKey=kA.prototype.N;mA.prototype.setRenderReprojectionEdges=mA.prototype.vb;mA.prototype.setTileGridForProjection=mA.prototype.wb;mA.prototype.getTileLoadFunction=mA.prototype.Xa;mA.prototype.getTileUrlFunction=mA.prototype.Ya;mA.prototype.getUrls=mA.prototype.Za;
+mA.prototype.setTileLoadFunction=mA.prototype.eb;mA.prototype.setTileUrlFunction=mA.prototype.Ja;mA.prototype.setUrl=mA.prototype.Va;mA.prototype.setUrls=mA.prototype.Wa;mA.prototype.getTileGrid=mA.prototype.Ha;mA.prototype.getAttributions=mA.prototype.sa;mA.prototype.getLogo=mA.prototype.qa;mA.prototype.getProjection=mA.prototype.ta;mA.prototype.getState=mA.prototype.ua;mA.prototype.setAttributions=mA.prototype.ma;mA.prototype.get=mA.prototype.get;mA.prototype.getKeys=mA.prototype.P;
+mA.prototype.getProperties=mA.prototype.R;mA.prototype.set=mA.prototype.set;mA.prototype.setProperties=mA.prototype.I;mA.prototype.unset=mA.prototype.S;mA.prototype.changed=mA.prototype.u;mA.prototype.dispatchEvent=mA.prototype.s;mA.prototype.getRevision=mA.prototype.L;mA.prototype.on=mA.prototype.H;mA.prototype.once=mA.prototype.M;mA.prototype.un=mA.prototype.K;mA.prototype.unByKey=mA.prototype.N;oA.prototype.getTileGrid=oA.prototype.Ha;oA.prototype.getAttributions=oA.prototype.sa;
+oA.prototype.getLogo=oA.prototype.qa;oA.prototype.getProjection=oA.prototype.ta;oA.prototype.getState=oA.prototype.ua;oA.prototype.setAttributions=oA.prototype.ma;oA.prototype.get=oA.prototype.get;oA.prototype.getKeys=oA.prototype.P;oA.prototype.getProperties=oA.prototype.R;oA.prototype.set=oA.prototype.set;oA.prototype.setProperties=oA.prototype.I;oA.prototype.unset=oA.prototype.S;oA.prototype.changed=oA.prototype.u;oA.prototype.dispatchEvent=oA.prototype.s;oA.prototype.getRevision=oA.prototype.L;
+oA.prototype.on=oA.prototype.H;oA.prototype.once=oA.prototype.M;oA.prototype.un=oA.prototype.K;oA.prototype.unByKey=oA.prototype.N;pA.prototype.setRenderReprojectionEdges=pA.prototype.vb;pA.prototype.setTileGridForProjection=pA.prototype.wb;pA.prototype.getTileLoadFunction=pA.prototype.Xa;pA.prototype.getTileUrlFunction=pA.prototype.Ya;pA.prototype.getUrls=pA.prototype.Za;pA.prototype.setTileLoadFunction=pA.prototype.eb;pA.prototype.setTileUrlFunction=pA.prototype.Ja;pA.prototype.setUrl=pA.prototype.Va;
+pA.prototype.setUrls=pA.prototype.Wa;pA.prototype.getTileGrid=pA.prototype.Ha;pA.prototype.getAttributions=pA.prototype.sa;pA.prototype.getLogo=pA.prototype.qa;pA.prototype.getProjection=pA.prototype.ta;pA.prototype.getState=pA.prototype.ua;pA.prototype.setAttributions=pA.prototype.ma;pA.prototype.get=pA.prototype.get;pA.prototype.getKeys=pA.prototype.P;pA.prototype.getProperties=pA.prototype.R;pA.prototype.set=pA.prototype.set;pA.prototype.setProperties=pA.prototype.I;pA.prototype.unset=pA.prototype.S;
+pA.prototype.changed=pA.prototype.u;pA.prototype.dispatchEvent=pA.prototype.s;pA.prototype.getRevision=pA.prototype.L;pA.prototype.on=pA.prototype.H;pA.prototype.once=pA.prototype.M;pA.prototype.un=pA.prototype.K;pA.prototype.unByKey=pA.prototype.N;qA.prototype.getTileGrid=qA.prototype.Ha;qA.prototype.getAttributions=qA.prototype.sa;qA.prototype.getLogo=qA.prototype.qa;qA.prototype.getProjection=qA.prototype.ta;qA.prototype.getState=qA.prototype.ua;qA.prototype.setAttributions=qA.prototype.ma;
+qA.prototype.get=qA.prototype.get;qA.prototype.getKeys=qA.prototype.P;qA.prototype.getProperties=qA.prototype.R;qA.prototype.set=qA.prototype.set;qA.prototype.setProperties=qA.prototype.I;qA.prototype.unset=qA.prototype.S;qA.prototype.changed=qA.prototype.u;qA.prototype.dispatchEvent=qA.prototype.s;qA.prototype.getRevision=qA.prototype.L;qA.prototype.on=qA.prototype.H;qA.prototype.once=qA.prototype.M;qA.prototype.un=qA.prototype.K;qA.prototype.unByKey=qA.prototype.N;
+vA.prototype.setRenderReprojectionEdges=vA.prototype.vb;vA.prototype.setTileGridForProjection=vA.prototype.wb;vA.prototype.getTileLoadFunction=vA.prototype.Xa;vA.prototype.getTileUrlFunction=vA.prototype.Ya;vA.prototype.getUrls=vA.prototype.Za;vA.prototype.setTileLoadFunction=vA.prototype.eb;vA.prototype.setTileUrlFunction=vA.prototype.Ja;vA.prototype.setUrl=vA.prototype.Va;vA.prototype.setUrls=vA.prototype.Wa;vA.prototype.getTileGrid=vA.prototype.Ha;vA.prototype.getAttributions=vA.prototype.sa;
+vA.prototype.getLogo=vA.prototype.qa;vA.prototype.getProjection=vA.prototype.ta;vA.prototype.getState=vA.prototype.ua;vA.prototype.setAttributions=vA.prototype.ma;vA.prototype.get=vA.prototype.get;vA.prototype.getKeys=vA.prototype.P;vA.prototype.getProperties=vA.prototype.R;vA.prototype.set=vA.prototype.set;vA.prototype.setProperties=vA.prototype.I;vA.prototype.unset=vA.prototype.S;vA.prototype.changed=vA.prototype.u;vA.prototype.dispatchEvent=vA.prototype.s;vA.prototype.getRevision=vA.prototype.L;
+vA.prototype.on=vA.prototype.H;vA.prototype.once=vA.prototype.M;vA.prototype.un=vA.prototype.K;vA.prototype.unByKey=vA.prototype.N;Xp.prototype.getTileLoadFunction=Xp.prototype.Xa;Xp.prototype.getTileUrlFunction=Xp.prototype.Ya;Xp.prototype.getUrls=Xp.prototype.Za;Xp.prototype.setTileLoadFunction=Xp.prototype.eb;Xp.prototype.setTileUrlFunction=Xp.prototype.Ja;Xp.prototype.setUrl=Xp.prototype.Va;Xp.prototype.setUrls=Xp.prototype.Wa;Xp.prototype.getTileGrid=Xp.prototype.Ha;
+Xp.prototype.getAttributions=Xp.prototype.sa;Xp.prototype.getLogo=Xp.prototype.qa;Xp.prototype.getProjection=Xp.prototype.ta;Xp.prototype.getState=Xp.prototype.ua;Xp.prototype.setAttributions=Xp.prototype.ma;Xp.prototype.get=Xp.prototype.get;Xp.prototype.getKeys=Xp.prototype.P;Xp.prototype.getProperties=Xp.prototype.R;Xp.prototype.set=Xp.prototype.set;Xp.prototype.setProperties=Xp.prototype.I;Xp.prototype.unset=Xp.prototype.S;Xp.prototype.changed=Xp.prototype.u;Xp.prototype.dispatchEvent=Xp.prototype.s;
+Xp.prototype.getRevision=Xp.prototype.L;Xp.prototype.on=Xp.prototype.H;Xp.prototype.once=Xp.prototype.M;Xp.prototype.un=Xp.prototype.K;Xp.prototype.unByKey=Xp.prototype.N;Z.prototype.setRenderReprojectionEdges=Z.prototype.vb;Z.prototype.setTileGridForProjection=Z.prototype.wb;Z.prototype.getTileLoadFunction=Z.prototype.Xa;Z.prototype.getTileUrlFunction=Z.prototype.Ya;Z.prototype.getUrls=Z.prototype.Za;Z.prototype.setTileLoadFunction=Z.prototype.eb;Z.prototype.setTileUrlFunction=Z.prototype.Ja;
+Z.prototype.setUrl=Z.prototype.Va;Z.prototype.setUrls=Z.prototype.Wa;Z.prototype.getTileGrid=Z.prototype.Ha;Z.prototype.getAttributions=Z.prototype.sa;Z.prototype.getLogo=Z.prototype.qa;Z.prototype.getProjection=Z.prototype.ta;Z.prototype.getState=Z.prototype.ua;Z.prototype.setAttributions=Z.prototype.ma;Z.prototype.get=Z.prototype.get;Z.prototype.getKeys=Z.prototype.P;Z.prototype.getProperties=Z.prototype.R;Z.prototype.set=Z.prototype.set;Z.prototype.setProperties=Z.prototype.I;
+Z.prototype.unset=Z.prototype.S;Z.prototype.changed=Z.prototype.u;Z.prototype.dispatchEvent=Z.prototype.s;Z.prototype.getRevision=Z.prototype.L;Z.prototype.on=Z.prototype.H;Z.prototype.once=Z.prototype.M;Z.prototype.un=Z.prototype.K;Z.prototype.unByKey=Z.prototype.N;CA.prototype.setRenderReprojectionEdges=CA.prototype.vb;CA.prototype.setTileGridForProjection=CA.prototype.wb;CA.prototype.getTileLoadFunction=CA.prototype.Xa;CA.prototype.getTileUrlFunction=CA.prototype.Ya;CA.prototype.getUrls=CA.prototype.Za;
+CA.prototype.setTileLoadFunction=CA.prototype.eb;CA.prototype.setTileUrlFunction=CA.prototype.Ja;CA.prototype.setUrl=CA.prototype.Va;CA.prototype.setUrls=CA.prototype.Wa;CA.prototype.getTileGrid=CA.prototype.Ha;CA.prototype.getAttributions=CA.prototype.sa;CA.prototype.getLogo=CA.prototype.qa;CA.prototype.getProjection=CA.prototype.ta;CA.prototype.getState=CA.prototype.ua;CA.prototype.setAttributions=CA.prototype.ma;CA.prototype.get=CA.prototype.get;CA.prototype.getKeys=CA.prototype.P;
+CA.prototype.getProperties=CA.prototype.R;CA.prototype.set=CA.prototype.set;CA.prototype.setProperties=CA.prototype.I;CA.prototype.unset=CA.prototype.S;CA.prototype.changed=CA.prototype.u;CA.prototype.dispatchEvent=CA.prototype.s;CA.prototype.getRevision=CA.prototype.L;CA.prototype.on=CA.prototype.H;CA.prototype.once=CA.prototype.M;CA.prototype.un=CA.prototype.K;CA.prototype.unByKey=CA.prototype.N;sz.prototype.getTileCoord=sz.prototype.g;jk.prototype.changed=jk.prototype.u;
+jk.prototype.dispatchEvent=jk.prototype.s;jk.prototype.getRevision=jk.prototype.L;jk.prototype.on=jk.prototype.H;jk.prototype.once=jk.prototype.M;jk.prototype.un=jk.prototype.K;jk.prototype.unByKey=jk.prototype.N;Tq.prototype.changed=Tq.prototype.u;Tq.prototype.dispatchEvent=Tq.prototype.s;Tq.prototype.getRevision=Tq.prototype.L;Tq.prototype.on=Tq.prototype.H;Tq.prototype.once=Tq.prototype.M;Tq.prototype.un=Tq.prototype.K;Tq.prototype.unByKey=Tq.prototype.N;Wq.prototype.changed=Wq.prototype.u;
+Wq.prototype.dispatchEvent=Wq.prototype.s;Wq.prototype.getRevision=Wq.prototype.L;Wq.prototype.on=Wq.prototype.H;Wq.prototype.once=Wq.prototype.M;Wq.prototype.un=Wq.prototype.K;Wq.prototype.unByKey=Wq.prototype.N;br.prototype.changed=br.prototype.u;br.prototype.dispatchEvent=br.prototype.s;br.prototype.getRevision=br.prototype.L;br.prototype.on=br.prototype.H;br.prototype.once=br.prototype.M;br.prototype.un=br.prototype.K;br.prototype.unByKey=br.prototype.N;dr.prototype.changed=dr.prototype.u;
+dr.prototype.dispatchEvent=dr.prototype.s;dr.prototype.getRevision=dr.prototype.L;dr.prototype.on=dr.prototype.H;dr.prototype.once=dr.prototype.M;dr.prototype.un=dr.prototype.K;dr.prototype.unByKey=dr.prototype.N;cq.prototype.changed=cq.prototype.u;cq.prototype.dispatchEvent=cq.prototype.s;cq.prototype.getRevision=cq.prototype.L;cq.prototype.on=cq.prototype.H;cq.prototype.once=cq.prototype.M;cq.prototype.un=cq.prototype.K;cq.prototype.unByKey=cq.prototype.N;dq.prototype.changed=dq.prototype.u;
+dq.prototype.dispatchEvent=dq.prototype.s;dq.prototype.getRevision=dq.prototype.L;dq.prototype.on=dq.prototype.H;dq.prototype.once=dq.prototype.M;dq.prototype.un=dq.prototype.K;dq.prototype.unByKey=dq.prototype.N;eq.prototype.changed=eq.prototype.u;eq.prototype.dispatchEvent=eq.prototype.s;eq.prototype.getRevision=eq.prototype.L;eq.prototype.on=eq.prototype.H;eq.prototype.once=eq.prototype.M;eq.prototype.un=eq.prototype.K;eq.prototype.unByKey=eq.prototype.N;gq.prototype.changed=gq.prototype.u;
+gq.prototype.dispatchEvent=gq.prototype.s;gq.prototype.getRevision=gq.prototype.L;gq.prototype.on=gq.prototype.H;gq.prototype.once=gq.prototype.M;gq.prototype.un=gq.prototype.K;gq.prototype.unByKey=gq.prototype.N;um.prototype.changed=um.prototype.u;um.prototype.dispatchEvent=um.prototype.s;um.prototype.getRevision=um.prototype.L;um.prototype.on=um.prototype.H;um.prototype.once=um.prototype.M;um.prototype.un=um.prototype.K;um.prototype.unByKey=um.prototype.N;Op.prototype.changed=Op.prototype.u;
+Op.prototype.dispatchEvent=Op.prototype.s;Op.prototype.getRevision=Op.prototype.L;Op.prototype.on=Op.prototype.H;Op.prototype.once=Op.prototype.M;Op.prototype.un=Op.prototype.K;Op.prototype.unByKey=Op.prototype.N;Pp.prototype.changed=Pp.prototype.u;Pp.prototype.dispatchEvent=Pp.prototype.s;Pp.prototype.getRevision=Pp.prototype.L;Pp.prototype.on=Pp.prototype.H;Pp.prototype.once=Pp.prototype.M;Pp.prototype.un=Pp.prototype.K;Pp.prototype.unByKey=Pp.prototype.N;Qp.prototype.changed=Qp.prototype.u;
+Qp.prototype.dispatchEvent=Qp.prototype.s;Qp.prototype.getRevision=Qp.prototype.L;Qp.prototype.on=Qp.prototype.H;Qp.prototype.once=Qp.prototype.M;Qp.prototype.un=Qp.prototype.K;Qp.prototype.unByKey=Qp.prototype.N;Zp.prototype.changed=Zp.prototype.u;Zp.prototype.dispatchEvent=Zp.prototype.s;Zp.prototype.getRevision=Zp.prototype.L;Zp.prototype.on=Zp.prototype.H;Zp.prototype.once=Zp.prototype.M;Zp.prototype.un=Zp.prototype.K;Zp.prototype.unByKey=Zp.prototype.N;Yj.prototype.get=Yj.prototype.get;
+Yj.prototype.getKeys=Yj.prototype.P;Yj.prototype.getProperties=Yj.prototype.R;Yj.prototype.set=Yj.prototype.set;Yj.prototype.setProperties=Yj.prototype.I;Yj.prototype.unset=Yj.prototype.S;Yj.prototype.changed=Yj.prototype.u;Yj.prototype.dispatchEvent=Yj.prototype.s;Yj.prototype.getRevision=Yj.prototype.L;Yj.prototype.on=Yj.prototype.H;Yj.prototype.once=Yj.prototype.M;Yj.prototype.un=Yj.prototype.K;Yj.prototype.unByKey=Yj.prototype.N;ck.prototype.getExtent=ck.prototype.J;
+ck.prototype.getMaxResolution=ck.prototype.Nb;ck.prototype.getMinResolution=ck.prototype.Ob;ck.prototype.getOpacity=ck.prototype.Rb;ck.prototype.getVisible=ck.prototype.rb;ck.prototype.getZIndex=ck.prototype.Sb;ck.prototype.setExtent=ck.prototype.cc;ck.prototype.setMaxResolution=ck.prototype.kc;ck.prototype.setMinResolution=ck.prototype.lc;ck.prototype.setOpacity=ck.prototype.dc;ck.prototype.setVisible=ck.prototype.ec;ck.prototype.setZIndex=ck.prototype.fc;ck.prototype.get=ck.prototype.get;
+ck.prototype.getKeys=ck.prototype.P;ck.prototype.getProperties=ck.prototype.R;ck.prototype.set=ck.prototype.set;ck.prototype.setProperties=ck.prototype.I;ck.prototype.unset=ck.prototype.S;ck.prototype.changed=ck.prototype.u;ck.prototype.dispatchEvent=ck.prototype.s;ck.prototype.getRevision=ck.prototype.L;ck.prototype.on=ck.prototype.H;ck.prototype.once=ck.prototype.M;ck.prototype.un=ck.prototype.K;ck.prototype.unByKey=ck.prototype.N;H.prototype.setMap=H.prototype.setMap;H.prototype.setSource=H.prototype.zc;
+H.prototype.getExtent=H.prototype.J;H.prototype.getMaxResolution=H.prototype.Nb;H.prototype.getMinResolution=H.prototype.Ob;H.prototype.getOpacity=H.prototype.Rb;H.prototype.getVisible=H.prototype.rb;H.prototype.getZIndex=H.prototype.Sb;H.prototype.setExtent=H.prototype.cc;H.prototype.setMaxResolution=H.prototype.kc;H.prototype.setMinResolution=H.prototype.lc;H.prototype.setOpacity=H.prototype.dc;H.prototype.setVisible=H.prototype.ec;H.prototype.setZIndex=H.prototype.fc;H.prototype.get=H.prototype.get;
+H.prototype.getKeys=H.prototype.P;H.prototype.getProperties=H.prototype.R;H.prototype.set=H.prototype.set;H.prototype.setProperties=H.prototype.I;H.prototype.unset=H.prototype.S;H.prototype.changed=H.prototype.u;H.prototype.dispatchEvent=H.prototype.s;H.prototype.getRevision=H.prototype.L;H.prototype.on=H.prototype.H;H.prototype.once=H.prototype.M;H.prototype.un=H.prototype.K;H.prototype.unByKey=H.prototype.N;X.prototype.getSource=X.prototype.ea;X.prototype.getStyle=X.prototype.C;
+X.prototype.getStyleFunction=X.prototype.D;X.prototype.setStyle=X.prototype.c;X.prototype.setMap=X.prototype.setMap;X.prototype.setSource=X.prototype.zc;X.prototype.getExtent=X.prototype.J;X.prototype.getMaxResolution=X.prototype.Nb;X.prototype.getMinResolution=X.prototype.Ob;X.prototype.getOpacity=X.prototype.Rb;X.prototype.getVisible=X.prototype.rb;X.prototype.getZIndex=X.prototype.Sb;X.prototype.setExtent=X.prototype.cc;X.prototype.setMaxResolution=X.prototype.kc;X.prototype.setMinResolution=X.prototype.lc;
+X.prototype.setOpacity=X.prototype.dc;X.prototype.setVisible=X.prototype.ec;X.prototype.setZIndex=X.prototype.fc;X.prototype.get=X.prototype.get;X.prototype.getKeys=X.prototype.P;X.prototype.getProperties=X.prototype.R;X.prototype.set=X.prototype.set;X.prototype.setProperties=X.prototype.I;X.prototype.unset=X.prototype.S;X.prototype.changed=X.prototype.u;X.prototype.dispatchEvent=X.prototype.s;X.prototype.getRevision=X.prototype.L;X.prototype.on=X.prototype.H;X.prototype.once=X.prototype.M;
+X.prototype.un=X.prototype.K;X.prototype.unByKey=X.prototype.N;Pl.prototype.setMap=Pl.prototype.setMap;Pl.prototype.setSource=Pl.prototype.zc;Pl.prototype.getExtent=Pl.prototype.J;Pl.prototype.getMaxResolution=Pl.prototype.Nb;Pl.prototype.getMinResolution=Pl.prototype.Ob;Pl.prototype.getOpacity=Pl.prototype.Rb;Pl.prototype.getVisible=Pl.prototype.rb;Pl.prototype.getZIndex=Pl.prototype.Sb;Pl.prototype.setExtent=Pl.prototype.cc;Pl.prototype.setMaxResolution=Pl.prototype.kc;
+Pl.prototype.setMinResolution=Pl.prototype.lc;Pl.prototype.setOpacity=Pl.prototype.dc;Pl.prototype.setVisible=Pl.prototype.ec;Pl.prototype.setZIndex=Pl.prototype.fc;Pl.prototype.get=Pl.prototype.get;Pl.prototype.getKeys=Pl.prototype.P;Pl.prototype.getProperties=Pl.prototype.R;Pl.prototype.set=Pl.prototype.set;Pl.prototype.setProperties=Pl.prototype.I;Pl.prototype.unset=Pl.prototype.S;Pl.prototype.changed=Pl.prototype.u;Pl.prototype.dispatchEvent=Pl.prototype.s;Pl.prototype.getRevision=Pl.prototype.L;
+Pl.prototype.on=Pl.prototype.H;Pl.prototype.once=Pl.prototype.M;Pl.prototype.un=Pl.prototype.K;Pl.prototype.unByKey=Pl.prototype.N;Hl.prototype.getExtent=Hl.prototype.J;Hl.prototype.getMaxResolution=Hl.prototype.Nb;Hl.prototype.getMinResolution=Hl.prototype.Ob;Hl.prototype.getOpacity=Hl.prototype.Rb;Hl.prototype.getVisible=Hl.prototype.rb;Hl.prototype.getZIndex=Hl.prototype.Sb;Hl.prototype.setExtent=Hl.prototype.cc;Hl.prototype.setMaxResolution=Hl.prototype.kc;Hl.prototype.setMinResolution=Hl.prototype.lc;
+Hl.prototype.setOpacity=Hl.prototype.dc;Hl.prototype.setVisible=Hl.prototype.ec;Hl.prototype.setZIndex=Hl.prototype.fc;Hl.prototype.get=Hl.prototype.get;Hl.prototype.getKeys=Hl.prototype.P;Hl.prototype.getProperties=Hl.prototype.R;Hl.prototype.set=Hl.prototype.set;Hl.prototype.setProperties=Hl.prototype.I;Hl.prototype.unset=Hl.prototype.S;Hl.prototype.changed=Hl.prototype.u;Hl.prototype.dispatchEvent=Hl.prototype.s;Hl.prototype.getRevision=Hl.prototype.L;Hl.prototype.on=Hl.prototype.H;
+Hl.prototype.once=Hl.prototype.M;Hl.prototype.un=Hl.prototype.K;Hl.prototype.unByKey=Hl.prototype.N;G.prototype.setMap=G.prototype.setMap;G.prototype.setSource=G.prototype.zc;G.prototype.getExtent=G.prototype.J;G.prototype.getMaxResolution=G.prototype.Nb;G.prototype.getMinResolution=G.prototype.Ob;G.prototype.getOpacity=G.prototype.Rb;G.prototype.getVisible=G.prototype.rb;G.prototype.getZIndex=G.prototype.Sb;G.prototype.setExtent=G.prototype.cc;G.prototype.setMaxResolution=G.prototype.kc;
+G.prototype.setMinResolution=G.prototype.lc;G.prototype.setOpacity=G.prototype.dc;G.prototype.setVisible=G.prototype.ec;G.prototype.setZIndex=G.prototype.fc;G.prototype.get=G.prototype.get;G.prototype.getKeys=G.prototype.P;G.prototype.getProperties=G.prototype.R;G.prototype.set=G.prototype.set;G.prototype.setProperties=G.prototype.I;G.prototype.unset=G.prototype.S;G.prototype.changed=G.prototype.u;G.prototype.dispatchEvent=G.prototype.s;G.prototype.getRevision=G.prototype.L;G.prototype.on=G.prototype.H;
+G.prototype.once=G.prototype.M;G.prototype.un=G.prototype.K;G.prototype.unByKey=G.prototype.N;I.prototype.getStyle=I.prototype.C;I.prototype.getStyleFunction=I.prototype.D;I.prototype.setStyle=I.prototype.c;I.prototype.setMap=I.prototype.setMap;I.prototype.setSource=I.prototype.zc;I.prototype.getExtent=I.prototype.J;I.prototype.getMaxResolution=I.prototype.Nb;I.prototype.getMinResolution=I.prototype.Ob;I.prototype.getOpacity=I.prototype.Rb;I.prototype.getVisible=I.prototype.rb;
+I.prototype.getZIndex=I.prototype.Sb;I.prototype.setExtent=I.prototype.cc;I.prototype.setMaxResolution=I.prototype.kc;I.prototype.setMinResolution=I.prototype.lc;I.prototype.setOpacity=I.prototype.dc;I.prototype.setVisible=I.prototype.ec;I.prototype.setZIndex=I.prototype.fc;I.prototype.get=I.prototype.get;I.prototype.getKeys=I.prototype.P;I.prototype.getProperties=I.prototype.R;I.prototype.set=I.prototype.set;I.prototype.setProperties=I.prototype.I;I.prototype.unset=I.prototype.S;
+I.prototype.changed=I.prototype.u;I.prototype.dispatchEvent=I.prototype.s;I.prototype.getRevision=I.prototype.L;I.prototype.on=I.prototype.H;I.prototype.once=I.prototype.M;I.prototype.un=I.prototype.K;I.prototype.unByKey=I.prototype.N;Lk.prototype.get=Lk.prototype.get;Lk.prototype.getKeys=Lk.prototype.P;Lk.prototype.getProperties=Lk.prototype.R;Lk.prototype.set=Lk.prototype.set;Lk.prototype.setProperties=Lk.prototype.I;Lk.prototype.unset=Lk.prototype.S;Lk.prototype.changed=Lk.prototype.u;
+Lk.prototype.dispatchEvent=Lk.prototype.s;Lk.prototype.getRevision=Lk.prototype.L;Lk.prototype.on=Lk.prototype.H;Lk.prototype.once=Lk.prototype.M;Lk.prototype.un=Lk.prototype.K;Lk.prototype.unByKey=Lk.prototype.N;Pk.prototype.getActive=Pk.prototype.b;Pk.prototype.getMap=Pk.prototype.i;Pk.prototype.setActive=Pk.prototype.g;Pk.prototype.get=Pk.prototype.get;Pk.prototype.getKeys=Pk.prototype.P;Pk.prototype.getProperties=Pk.prototype.R;Pk.prototype.set=Pk.prototype.set;Pk.prototype.setProperties=Pk.prototype.I;
+Pk.prototype.unset=Pk.prototype.S;Pk.prototype.changed=Pk.prototype.u;Pk.prototype.dispatchEvent=Pk.prototype.s;Pk.prototype.getRevision=Pk.prototype.L;Pk.prototype.on=Pk.prototype.H;Pk.prototype.once=Pk.prototype.M;Pk.prototype.un=Pk.prototype.K;Pk.prototype.unByKey=Pk.prototype.N;qy.prototype.getActive=qy.prototype.b;qy.prototype.getMap=qy.prototype.i;qy.prototype.setActive=qy.prototype.g;qy.prototype.get=qy.prototype.get;qy.prototype.getKeys=qy.prototype.P;qy.prototype.getProperties=qy.prototype.R;
+qy.prototype.set=qy.prototype.set;qy.prototype.setProperties=qy.prototype.I;qy.prototype.unset=qy.prototype.S;qy.prototype.changed=qy.prototype.u;qy.prototype.dispatchEvent=qy.prototype.s;qy.prototype.getRevision=qy.prototype.L;qy.prototype.on=qy.prototype.H;qy.prototype.once=qy.prototype.M;qy.prototype.un=qy.prototype.K;qy.prototype.unByKey=qy.prototype.N;Yk.prototype.getActive=Yk.prototype.b;Yk.prototype.getMap=Yk.prototype.i;Yk.prototype.setActive=Yk.prototype.g;Yk.prototype.get=Yk.prototype.get;
+Yk.prototype.getKeys=Yk.prototype.P;Yk.prototype.getProperties=Yk.prototype.R;Yk.prototype.set=Yk.prototype.set;Yk.prototype.setProperties=Yk.prototype.I;Yk.prototype.unset=Yk.prototype.S;Yk.prototype.changed=Yk.prototype.u;Yk.prototype.dispatchEvent=Yk.prototype.s;Yk.prototype.getRevision=Yk.prototype.L;Yk.prototype.on=Yk.prototype.H;Yk.prototype.once=Yk.prototype.M;Yk.prototype.un=Yk.prototype.K;Yk.prototype.unByKey=Yk.prototype.N;ml.prototype.getActive=ml.prototype.b;ml.prototype.getMap=ml.prototype.i;
+ml.prototype.setActive=ml.prototype.g;ml.prototype.get=ml.prototype.get;ml.prototype.getKeys=ml.prototype.P;ml.prototype.getProperties=ml.prototype.R;ml.prototype.set=ml.prototype.set;ml.prototype.setProperties=ml.prototype.I;ml.prototype.unset=ml.prototype.S;ml.prototype.changed=ml.prototype.u;ml.prototype.dispatchEvent=ml.prototype.s;ml.prototype.getRevision=ml.prototype.L;ml.prototype.on=ml.prototype.H;ml.prototype.once=ml.prototype.M;ml.prototype.un=ml.prototype.K;ml.prototype.unByKey=ml.prototype.N;
+al.prototype.getActive=al.prototype.b;al.prototype.getMap=al.prototype.i;al.prototype.setActive=al.prototype.g;al.prototype.get=al.prototype.get;al.prototype.getKeys=al.prototype.P;al.prototype.getProperties=al.prototype.R;al.prototype.set=al.prototype.set;al.prototype.setProperties=al.prototype.I;al.prototype.unset=al.prototype.S;al.prototype.changed=al.prototype.u;al.prototype.dispatchEvent=al.prototype.s;al.prototype.getRevision=al.prototype.L;al.prototype.on=al.prototype.H;al.prototype.once=al.prototype.M;
+al.prototype.un=al.prototype.K;al.prototype.unByKey=al.prototype.N;uy.prototype.getActive=uy.prototype.b;uy.prototype.getMap=uy.prototype.i;uy.prototype.setActive=uy.prototype.g;uy.prototype.get=uy.prototype.get;uy.prototype.getKeys=uy.prototype.P;uy.prototype.getProperties=uy.prototype.R;uy.prototype.set=uy.prototype.set;uy.prototype.setProperties=uy.prototype.I;uy.prototype.unset=uy.prototype.S;uy.prototype.changed=uy.prototype.u;uy.prototype.dispatchEvent=uy.prototype.s;
+uy.prototype.getRevision=uy.prototype.L;uy.prototype.on=uy.prototype.H;uy.prototype.once=uy.prototype.M;uy.prototype.un=uy.prototype.K;uy.prototype.unByKey=uy.prototype.N;el.prototype.getActive=el.prototype.b;el.prototype.getMap=el.prototype.i;el.prototype.setActive=el.prototype.g;el.prototype.get=el.prototype.get;el.prototype.getKeys=el.prototype.P;el.prototype.getProperties=el.prototype.R;el.prototype.set=el.prototype.set;el.prototype.setProperties=el.prototype.I;el.prototype.unset=el.prototype.S;
+el.prototype.changed=el.prototype.u;el.prototype.dispatchEvent=el.prototype.s;el.prototype.getRevision=el.prototype.L;el.prototype.on=el.prototype.H;el.prototype.once=el.prototype.M;el.prototype.un=el.prototype.K;el.prototype.unByKey=el.prototype.N;rl.prototype.getGeometry=rl.prototype.X;rl.prototype.getActive=rl.prototype.b;rl.prototype.getMap=rl.prototype.i;rl.prototype.setActive=rl.prototype.g;rl.prototype.get=rl.prototype.get;rl.prototype.getKeys=rl.prototype.P;rl.prototype.getProperties=rl.prototype.R;
+rl.prototype.set=rl.prototype.set;rl.prototype.setProperties=rl.prototype.I;rl.prototype.unset=rl.prototype.S;rl.prototype.changed=rl.prototype.u;rl.prototype.dispatchEvent=rl.prototype.s;rl.prototype.getRevision=rl.prototype.L;rl.prototype.on=rl.prototype.H;rl.prototype.once=rl.prototype.M;rl.prototype.un=rl.prototype.K;rl.prototype.unByKey=rl.prototype.N;zy.prototype.getActive=zy.prototype.b;zy.prototype.getMap=zy.prototype.i;zy.prototype.setActive=zy.prototype.g;zy.prototype.get=zy.prototype.get;
+zy.prototype.getKeys=zy.prototype.P;zy.prototype.getProperties=zy.prototype.R;zy.prototype.set=zy.prototype.set;zy.prototype.setProperties=zy.prototype.I;zy.prototype.unset=zy.prototype.S;zy.prototype.changed=zy.prototype.u;zy.prototype.dispatchEvent=zy.prototype.s;zy.prototype.getRevision=zy.prototype.L;zy.prototype.on=zy.prototype.H;zy.prototype.once=zy.prototype.M;zy.prototype.un=zy.prototype.K;zy.prototype.unByKey=zy.prototype.N;sl.prototype.getActive=sl.prototype.b;sl.prototype.getMap=sl.prototype.i;
+sl.prototype.setActive=sl.prototype.g;sl.prototype.get=sl.prototype.get;sl.prototype.getKeys=sl.prototype.P;sl.prototype.getProperties=sl.prototype.R;sl.prototype.set=sl.prototype.set;sl.prototype.setProperties=sl.prototype.I;sl.prototype.unset=sl.prototype.S;sl.prototype.changed=sl.prototype.u;sl.prototype.dispatchEvent=sl.prototype.s;sl.prototype.getRevision=sl.prototype.L;sl.prototype.on=sl.prototype.H;sl.prototype.once=sl.prototype.M;sl.prototype.un=sl.prototype.K;sl.prototype.unByKey=sl.prototype.N;
+ul.prototype.getActive=ul.prototype.b;ul.prototype.getMap=ul.prototype.i;ul.prototype.setActive=ul.prototype.g;ul.prototype.get=ul.prototype.get;ul.prototype.getKeys=ul.prototype.P;ul.prototype.getProperties=ul.prototype.R;ul.prototype.set=ul.prototype.set;ul.prototype.setProperties=ul.prototype.I;ul.prototype.unset=ul.prototype.S;ul.prototype.changed=ul.prototype.u;ul.prototype.dispatchEvent=ul.prototype.s;ul.prototype.getRevision=ul.prototype.L;ul.prototype.on=ul.prototype.H;ul.prototype.once=ul.prototype.M;
+ul.prototype.un=ul.prototype.K;ul.prototype.unByKey=ul.prototype.N;Qy.prototype.getActive=Qy.prototype.b;Qy.prototype.getMap=Qy.prototype.i;Qy.prototype.setActive=Qy.prototype.g;Qy.prototype.get=Qy.prototype.get;Qy.prototype.getKeys=Qy.prototype.P;Qy.prototype.getProperties=Qy.prototype.R;Qy.prototype.set=Qy.prototype.set;Qy.prototype.setProperties=Qy.prototype.I;Qy.prototype.unset=Qy.prototype.S;Qy.prototype.changed=Qy.prototype.u;Qy.prototype.dispatchEvent=Qy.prototype.s;
+Qy.prototype.getRevision=Qy.prototype.L;Qy.prototype.on=Qy.prototype.H;Qy.prototype.once=Qy.prototype.M;Qy.prototype.un=Qy.prototype.K;Qy.prototype.unByKey=Qy.prototype.N;wl.prototype.getActive=wl.prototype.b;wl.prototype.getMap=wl.prototype.i;wl.prototype.setActive=wl.prototype.g;wl.prototype.get=wl.prototype.get;wl.prototype.getKeys=wl.prototype.P;wl.prototype.getProperties=wl.prototype.R;wl.prototype.set=wl.prototype.set;wl.prototype.setProperties=wl.prototype.I;wl.prototype.unset=wl.prototype.S;
+wl.prototype.changed=wl.prototype.u;wl.prototype.dispatchEvent=wl.prototype.s;wl.prototype.getRevision=wl.prototype.L;wl.prototype.on=wl.prototype.H;wl.prototype.once=wl.prototype.M;wl.prototype.un=wl.prototype.K;wl.prototype.unByKey=wl.prototype.N;yl.prototype.getActive=yl.prototype.b;yl.prototype.getMap=yl.prototype.i;yl.prototype.setActive=yl.prototype.g;yl.prototype.get=yl.prototype.get;yl.prototype.getKeys=yl.prototype.P;yl.prototype.getProperties=yl.prototype.R;yl.prototype.set=yl.prototype.set;
+yl.prototype.setProperties=yl.prototype.I;yl.prototype.unset=yl.prototype.S;yl.prototype.changed=yl.prototype.u;yl.prototype.dispatchEvent=yl.prototype.s;yl.prototype.getRevision=yl.prototype.L;yl.prototype.on=yl.prototype.H;yl.prototype.once=yl.prototype.M;yl.prototype.un=yl.prototype.K;yl.prototype.unByKey=yl.prototype.N;Cl.prototype.getActive=Cl.prototype.b;Cl.prototype.getMap=Cl.prototype.i;Cl.prototype.setActive=Cl.prototype.g;Cl.prototype.get=Cl.prototype.get;Cl.prototype.getKeys=Cl.prototype.P;
+Cl.prototype.getProperties=Cl.prototype.R;Cl.prototype.set=Cl.prototype.set;Cl.prototype.setProperties=Cl.prototype.I;Cl.prototype.unset=Cl.prototype.S;Cl.prototype.changed=Cl.prototype.u;Cl.prototype.dispatchEvent=Cl.prototype.s;Cl.prototype.getRevision=Cl.prototype.L;Cl.prototype.on=Cl.prototype.H;Cl.prototype.once=Cl.prototype.M;Cl.prototype.un=Cl.prototype.K;Cl.prototype.unByKey=Cl.prototype.N;dz.prototype.getActive=dz.prototype.b;dz.prototype.getMap=dz.prototype.i;dz.prototype.setActive=dz.prototype.g;
+dz.prototype.get=dz.prototype.get;dz.prototype.getKeys=dz.prototype.P;dz.prototype.getProperties=dz.prototype.R;dz.prototype.set=dz.prototype.set;dz.prototype.setProperties=dz.prototype.I;dz.prototype.unset=dz.prototype.S;dz.prototype.changed=dz.prototype.u;dz.prototype.dispatchEvent=dz.prototype.s;dz.prototype.getRevision=dz.prototype.L;dz.prototype.on=dz.prototype.H;dz.prototype.once=dz.prototype.M;dz.prototype.un=dz.prototype.K;dz.prototype.unByKey=dz.prototype.N;gz.prototype.getActive=gz.prototype.b;
+gz.prototype.getMap=gz.prototype.i;gz.prototype.setActive=gz.prototype.g;gz.prototype.get=gz.prototype.get;gz.prototype.getKeys=gz.prototype.P;gz.prototype.getProperties=gz.prototype.R;gz.prototype.set=gz.prototype.set;gz.prototype.setProperties=gz.prototype.I;gz.prototype.unset=gz.prototype.S;gz.prototype.changed=gz.prototype.u;gz.prototype.dispatchEvent=gz.prototype.s;gz.prototype.getRevision=gz.prototype.L;gz.prototype.on=gz.prototype.H;gz.prototype.once=gz.prototype.M;gz.prototype.un=gz.prototype.K;
+gz.prototype.unByKey=gz.prototype.N;lz.prototype.getActive=lz.prototype.b;lz.prototype.getMap=lz.prototype.i;lz.prototype.setActive=lz.prototype.g;lz.prototype.get=lz.prototype.get;lz.prototype.getKeys=lz.prototype.P;lz.prototype.getProperties=lz.prototype.R;lz.prototype.set=lz.prototype.set;lz.prototype.setProperties=lz.prototype.I;lz.prototype.unset=lz.prototype.S;lz.prototype.changed=lz.prototype.u;lz.prototype.dispatchEvent=lz.prototype.s;lz.prototype.getRevision=lz.prototype.L;
+lz.prototype.on=lz.prototype.H;lz.prototype.once=lz.prototype.M;lz.prototype.un=lz.prototype.K;lz.prototype.unByKey=lz.prototype.N;$e.prototype.get=$e.prototype.get;$e.prototype.getKeys=$e.prototype.P;$e.prototype.getProperties=$e.prototype.R;$e.prototype.set=$e.prototype.set;$e.prototype.setProperties=$e.prototype.I;$e.prototype.unset=$e.prototype.S;$e.prototype.changed=$e.prototype.u;$e.prototype.dispatchEvent=$e.prototype.s;$e.prototype.getRevision=$e.prototype.L;$e.prototype.on=$e.prototype.H;
+$e.prototype.once=$e.prototype.M;$e.prototype.un=$e.prototype.K;$e.prototype.unByKey=$e.prototype.N;bf.prototype.getClosestPoint=bf.prototype.qb;bf.prototype.getExtent=bf.prototype.J;bf.prototype.simplify=bf.prototype.xb;bf.prototype.transform=bf.prototype.lb;bf.prototype.get=bf.prototype.get;bf.prototype.getKeys=bf.prototype.P;bf.prototype.getProperties=bf.prototype.R;bf.prototype.set=bf.prototype.set;bf.prototype.setProperties=bf.prototype.I;bf.prototype.unset=bf.prototype.S;
+bf.prototype.changed=bf.prototype.u;bf.prototype.dispatchEvent=bf.prototype.s;bf.prototype.getRevision=bf.prototype.L;bf.prototype.on=bf.prototype.H;bf.prototype.once=bf.prototype.M;bf.prototype.un=bf.prototype.K;bf.prototype.unByKey=bf.prototype.N;Nx.prototype.getFirstCoordinate=Nx.prototype.Kb;Nx.prototype.getLastCoordinate=Nx.prototype.Lb;Nx.prototype.getLayout=Nx.prototype.Mb;Nx.prototype.getClosestPoint=Nx.prototype.qb;Nx.prototype.getExtent=Nx.prototype.J;Nx.prototype.simplify=Nx.prototype.xb;
+Nx.prototype.get=Nx.prototype.get;Nx.prototype.getKeys=Nx.prototype.P;Nx.prototype.getProperties=Nx.prototype.R;Nx.prototype.set=Nx.prototype.set;Nx.prototype.setProperties=Nx.prototype.I;Nx.prototype.unset=Nx.prototype.S;Nx.prototype.changed=Nx.prototype.u;Nx.prototype.dispatchEvent=Nx.prototype.s;Nx.prototype.getRevision=Nx.prototype.L;Nx.prototype.on=Nx.prototype.H;Nx.prototype.once=Nx.prototype.M;Nx.prototype.un=Nx.prototype.K;Nx.prototype.unByKey=Nx.prototype.N;gs.prototype.getClosestPoint=gs.prototype.qb;
+gs.prototype.getExtent=gs.prototype.J;gs.prototype.simplify=gs.prototype.xb;gs.prototype.transform=gs.prototype.lb;gs.prototype.get=gs.prototype.get;gs.prototype.getKeys=gs.prototype.P;gs.prototype.getProperties=gs.prototype.R;gs.prototype.set=gs.prototype.set;gs.prototype.setProperties=gs.prototype.I;gs.prototype.unset=gs.prototype.S;gs.prototype.changed=gs.prototype.u;gs.prototype.dispatchEvent=gs.prototype.s;gs.prototype.getRevision=gs.prototype.L;gs.prototype.on=gs.prototype.H;
+gs.prototype.once=gs.prototype.M;gs.prototype.un=gs.prototype.K;gs.prototype.unByKey=gs.prototype.N;vf.prototype.getFirstCoordinate=vf.prototype.Kb;vf.prototype.getLastCoordinate=vf.prototype.Lb;vf.prototype.getLayout=vf.prototype.Mb;vf.prototype.getClosestPoint=vf.prototype.qb;vf.prototype.getExtent=vf.prototype.J;vf.prototype.simplify=vf.prototype.xb;vf.prototype.transform=vf.prototype.lb;vf.prototype.get=vf.prototype.get;vf.prototype.getKeys=vf.prototype.P;vf.prototype.getProperties=vf.prototype.R;
+vf.prototype.set=vf.prototype.set;vf.prototype.setProperties=vf.prototype.I;vf.prototype.unset=vf.prototype.S;vf.prototype.changed=vf.prototype.u;vf.prototype.dispatchEvent=vf.prototype.s;vf.prototype.getRevision=vf.prototype.L;vf.prototype.on=vf.prototype.H;vf.prototype.once=vf.prototype.M;vf.prototype.un=vf.prototype.K;vf.prototype.unByKey=vf.prototype.N;T.prototype.getFirstCoordinate=T.prototype.Kb;T.prototype.getLastCoordinate=T.prototype.Lb;T.prototype.getLayout=T.prototype.Mb;
+T.prototype.getClosestPoint=T.prototype.qb;T.prototype.getExtent=T.prototype.J;T.prototype.simplify=T.prototype.xb;T.prototype.transform=T.prototype.lb;T.prototype.get=T.prototype.get;T.prototype.getKeys=T.prototype.P;T.prototype.getProperties=T.prototype.R;T.prototype.set=T.prototype.set;T.prototype.setProperties=T.prototype.I;T.prototype.unset=T.prototype.S;T.prototype.changed=T.prototype.u;T.prototype.dispatchEvent=T.prototype.s;T.prototype.getRevision=T.prototype.L;T.prototype.on=T.prototype.H;
+T.prototype.once=T.prototype.M;T.prototype.un=T.prototype.K;T.prototype.unByKey=T.prototype.N;U.prototype.getFirstCoordinate=U.prototype.Kb;U.prototype.getLastCoordinate=U.prototype.Lb;U.prototype.getLayout=U.prototype.Mb;U.prototype.getClosestPoint=U.prototype.qb;U.prototype.getExtent=U.prototype.J;U.prototype.simplify=U.prototype.xb;U.prototype.transform=U.prototype.lb;U.prototype.get=U.prototype.get;U.prototype.getKeys=U.prototype.P;U.prototype.getProperties=U.prototype.R;U.prototype.set=U.prototype.set;
+U.prototype.setProperties=U.prototype.I;U.prototype.unset=U.prototype.S;U.prototype.changed=U.prototype.u;U.prototype.dispatchEvent=U.prototype.s;U.prototype.getRevision=U.prototype.L;U.prototype.on=U.prototype.H;U.prototype.once=U.prototype.M;U.prototype.un=U.prototype.K;U.prototype.unByKey=U.prototype.N;Xr.prototype.getFirstCoordinate=Xr.prototype.Kb;Xr.prototype.getLastCoordinate=Xr.prototype.Lb;Xr.prototype.getLayout=Xr.prototype.Mb;Xr.prototype.getClosestPoint=Xr.prototype.qb;
+Xr.prototype.getExtent=Xr.prototype.J;Xr.prototype.simplify=Xr.prototype.xb;Xr.prototype.transform=Xr.prototype.lb;Xr.prototype.get=Xr.prototype.get;Xr.prototype.getKeys=Xr.prototype.P;Xr.prototype.getProperties=Xr.prototype.R;Xr.prototype.set=Xr.prototype.set;Xr.prototype.setProperties=Xr.prototype.I;Xr.prototype.unset=Xr.prototype.S;Xr.prototype.changed=Xr.prototype.u;Xr.prototype.dispatchEvent=Xr.prototype.s;Xr.prototype.getRevision=Xr.prototype.L;Xr.prototype.on=Xr.prototype.H;
+Xr.prototype.once=Xr.prototype.M;Xr.prototype.un=Xr.prototype.K;Xr.prototype.unByKey=Xr.prototype.N;V.prototype.getFirstCoordinate=V.prototype.Kb;V.prototype.getLastCoordinate=V.prototype.Lb;V.prototype.getLayout=V.prototype.Mb;V.prototype.getClosestPoint=V.prototype.qb;V.prototype.getExtent=V.prototype.J;V.prototype.simplify=V.prototype.xb;V.prototype.transform=V.prototype.lb;V.prototype.get=V.prototype.get;V.prototype.getKeys=V.prototype.P;V.prototype.getProperties=V.prototype.R;
+V.prototype.set=V.prototype.set;V.prototype.setProperties=V.prototype.I;V.prototype.unset=V.prototype.S;V.prototype.changed=V.prototype.u;V.prototype.dispatchEvent=V.prototype.s;V.prototype.getRevision=V.prototype.L;V.prototype.on=V.prototype.H;V.prototype.once=V.prototype.M;V.prototype.un=V.prototype.K;V.prototype.unByKey=V.prototype.N;E.prototype.getFirstCoordinate=E.prototype.Kb;E.prototype.getLastCoordinate=E.prototype.Lb;E.prototype.getLayout=E.prototype.Mb;E.prototype.getClosestPoint=E.prototype.qb;
+E.prototype.getExtent=E.prototype.J;E.prototype.simplify=E.prototype.xb;E.prototype.transform=E.prototype.lb;E.prototype.get=E.prototype.get;E.prototype.getKeys=E.prototype.P;E.prototype.getProperties=E.prototype.R;E.prototype.set=E.prototype.set;E.prototype.setProperties=E.prototype.I;E.prototype.unset=E.prototype.S;E.prototype.changed=E.prototype.u;E.prototype.dispatchEvent=E.prototype.s;E.prototype.getRevision=E.prototype.L;E.prototype.on=E.prototype.H;E.prototype.once=E.prototype.M;
+E.prototype.un=E.prototype.K;E.prototype.unByKey=E.prototype.N;F.prototype.getFirstCoordinate=F.prototype.Kb;F.prototype.getLastCoordinate=F.prototype.Lb;F.prototype.getLayout=F.prototype.Mb;F.prototype.getClosestPoint=F.prototype.qb;F.prototype.getExtent=F.prototype.J;F.prototype.simplify=F.prototype.xb;F.prototype.transform=F.prototype.lb;F.prototype.get=F.prototype.get;F.prototype.getKeys=F.prototype.P;F.prototype.getProperties=F.prototype.R;F.prototype.set=F.prototype.set;
+F.prototype.setProperties=F.prototype.I;F.prototype.unset=F.prototype.S;F.prototype.changed=F.prototype.u;F.prototype.dispatchEvent=F.prototype.s;F.prototype.getRevision=F.prototype.L;F.prototype.on=F.prototype.H;F.prototype.once=F.prototype.M;F.prototype.un=F.prototype.K;F.prototype.unByKey=F.prototype.N;Es.prototype.readFeatures=Es.prototype.Ba;Fs.prototype.readFeatures=Fs.prototype.Ba;Fs.prototype.readFeatures=Fs.prototype.Ba;oh.prototype.get=oh.prototype.get;oh.prototype.getKeys=oh.prototype.P;
+oh.prototype.getProperties=oh.prototype.R;oh.prototype.set=oh.prototype.set;oh.prototype.setProperties=oh.prototype.I;oh.prototype.unset=oh.prototype.S;oh.prototype.changed=oh.prototype.u;oh.prototype.dispatchEvent=oh.prototype.s;oh.prototype.getRevision=oh.prototype.L;oh.prototype.on=oh.prototype.H;oh.prototype.once=oh.prototype.M;oh.prototype.un=oh.prototype.K;oh.prototype.unByKey=oh.prototype.N;Rh.prototype.getMap=Rh.prototype.g;Rh.prototype.setMap=Rh.prototype.setMap;Rh.prototype.setTarget=Rh.prototype.c;
+Rh.prototype.get=Rh.prototype.get;Rh.prototype.getKeys=Rh.prototype.P;Rh.prototype.getProperties=Rh.prototype.R;Rh.prototype.set=Rh.prototype.set;Rh.prototype.setProperties=Rh.prototype.I;Rh.prototype.unset=Rh.prototype.S;Rh.prototype.changed=Rh.prototype.u;Rh.prototype.dispatchEvent=Rh.prototype.s;Rh.prototype.getRevision=Rh.prototype.L;Rh.prototype.on=Rh.prototype.H;Rh.prototype.once=Rh.prototype.M;Rh.prototype.un=Rh.prototype.K;Rh.prototype.unByKey=Rh.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.P;bi.prototype.getProperties=bi.prototype.R;bi.prototype.set=bi.prototype.set;bi.prototype.setProperties=bi.prototype.I;bi.prototype.unset=bi.prototype.S;bi.prototype.changed=bi.prototype.u;bi.prototype.dispatchEvent=bi.prototype.s;bi.prototype.getRevision=bi.prototype.L;bi.prototype.on=bi.prototype.H;bi.prototype.once=bi.prototype.M;bi.prototype.un=bi.prototype.K;
+bi.prototype.unByKey=bi.prototype.N;di.prototype.getMap=di.prototype.g;di.prototype.setMap=di.prototype.setMap;di.prototype.setTarget=di.prototype.c;di.prototype.get=di.prototype.get;di.prototype.getKeys=di.prototype.P;di.prototype.getProperties=di.prototype.R;di.prototype.set=di.prototype.set;di.prototype.setProperties=di.prototype.I;di.prototype.unset=di.prototype.S;di.prototype.changed=di.prototype.u;di.prototype.dispatchEvent=di.prototype.s;di.prototype.getRevision=di.prototype.L;
+di.prototype.on=di.prototype.H;di.prototype.once=di.prototype.M;di.prototype.un=di.prototype.K;di.prototype.unByKey=di.prototype.N;nr.prototype.getMap=nr.prototype.g;nr.prototype.setMap=nr.prototype.setMap;nr.prototype.setTarget=nr.prototype.c;nr.prototype.get=nr.prototype.get;nr.prototype.getKeys=nr.prototype.P;nr.prototype.getProperties=nr.prototype.R;nr.prototype.set=nr.prototype.set;nr.prototype.setProperties=nr.prototype.I;nr.prototype.unset=nr.prototype.S;nr.prototype.changed=nr.prototype.u;
+nr.prototype.dispatchEvent=nr.prototype.s;nr.prototype.getRevision=nr.prototype.L;nr.prototype.on=nr.prototype.H;nr.prototype.once=nr.prototype.M;nr.prototype.un=nr.prototype.K;nr.prototype.unByKey=nr.prototype.N;Uh.prototype.getMap=Uh.prototype.g;Uh.prototype.setMap=Uh.prototype.setMap;Uh.prototype.setTarget=Uh.prototype.c;Uh.prototype.get=Uh.prototype.get;Uh.prototype.getKeys=Uh.prototype.P;Uh.prototype.getProperties=Uh.prototype.R;Uh.prototype.set=Uh.prototype.set;Uh.prototype.setProperties=Uh.prototype.I;
+Uh.prototype.unset=Uh.prototype.S;Uh.prototype.changed=Uh.prototype.u;Uh.prototype.dispatchEvent=Uh.prototype.s;Uh.prototype.getRevision=Uh.prototype.L;Uh.prototype.on=Uh.prototype.H;Uh.prototype.once=Uh.prototype.M;Uh.prototype.un=Uh.prototype.K;Uh.prototype.unByKey=Uh.prototype.N;sr.prototype.getMap=sr.prototype.g;sr.prototype.setMap=sr.prototype.setMap;sr.prototype.setTarget=sr.prototype.c;sr.prototype.get=sr.prototype.get;sr.prototype.getKeys=sr.prototype.P;sr.prototype.getProperties=sr.prototype.R;
+sr.prototype.set=sr.prototype.set;sr.prototype.setProperties=sr.prototype.I;sr.prototype.unset=sr.prototype.S;sr.prototype.changed=sr.prototype.u;sr.prototype.dispatchEvent=sr.prototype.s;sr.prototype.getRevision=sr.prototype.L;sr.prototype.on=sr.prototype.H;sr.prototype.once=sr.prototype.M;sr.prototype.un=sr.prototype.K;sr.prototype.unByKey=sr.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.P;Wh.prototype.getProperties=Wh.prototype.R;Wh.prototype.set=Wh.prototype.set;Wh.prototype.setProperties=Wh.prototype.I;Wh.prototype.unset=Wh.prototype.S;Wh.prototype.changed=Wh.prototype.u;Wh.prototype.dispatchEvent=Wh.prototype.s;Wh.prototype.getRevision=Wh.prototype.L;Wh.prototype.on=Wh.prototype.H;Wh.prototype.once=Wh.prototype.M;Wh.prototype.un=Wh.prototype.K;Wh.prototype.unByKey=Wh.prototype.N;Gr.prototype.getMap=Gr.prototype.g;Gr.prototype.setMap=Gr.prototype.setMap;
+Gr.prototype.setTarget=Gr.prototype.c;Gr.prototype.get=Gr.prototype.get;Gr.prototype.getKeys=Gr.prototype.P;Gr.prototype.getProperties=Gr.prototype.R;Gr.prototype.set=Gr.prototype.set;Gr.prototype.setProperties=Gr.prototype.I;Gr.prototype.unset=Gr.prototype.S;Gr.prototype.changed=Gr.prototype.u;Gr.prototype.dispatchEvent=Gr.prototype.s;Gr.prototype.getRevision=Gr.prototype.L;Gr.prototype.on=Gr.prototype.H;Gr.prototype.once=Gr.prototype.M;Gr.prototype.un=Gr.prototype.K;Gr.prototype.unByKey=Gr.prototype.N;
+Lr.prototype.getMap=Lr.prototype.g;Lr.prototype.setMap=Lr.prototype.setMap;Lr.prototype.setTarget=Lr.prototype.c;Lr.prototype.get=Lr.prototype.get;Lr.prototype.getKeys=Lr.prototype.P;Lr.prototype.getProperties=Lr.prototype.R;Lr.prototype.set=Lr.prototype.set;Lr.prototype.setProperties=Lr.prototype.I;Lr.prototype.unset=Lr.prototype.S;Lr.prototype.changed=Lr.prototype.u;Lr.prototype.dispatchEvent=Lr.prototype.s;Lr.prototype.getRevision=Lr.prototype.L;Lr.prototype.on=Lr.prototype.H;
+Lr.prototype.once=Lr.prototype.M;Lr.prototype.un=Lr.prototype.K;Lr.prototype.unByKey=Lr.prototype.N;
+ return OPENLAYERS.ol;
+}));
+