From 229d0d7c24540bc6649dfaf96c2f8c76c3bb36cd Mon Sep 17 00:00:00 2001
From: Tor-Einar Skog <tor-einar.skog@nibio.no>
Date: Fri, 23 Jun 2017 13:12:20 -0700
Subject: [PATCH] OpenLayers upgrade and subsequent code updates

---
 VIPSWeb/static/css/3rdparty/ol.css            |      2 +-
 VIPSWeb/static/js/3rdparty/ol-debug.js        | 155713 ++++++---------
 VIPSWeb/static/js/3rdparty/ol.js              |   1953 +-
 VIPSWeb/static/js/forecastmap.js              |     72 +-
 .../static/applefruitmoth/js/map.js           |      2 +-
 .../templates/applefruitmoth/index.html       |      2 +-
 .../templates/observations/detail.html        |      2 +-
 roughage/templates/roughage/nutrition.html    |      2 +-
 8 files changed, 60907 insertions(+), 96841 deletions(-)
 mode change 100755 => 100644 VIPSWeb/static/css/3rdparty/ol.css
 mode change 100755 => 100644 VIPSWeb/static/js/3rdparty/ol-debug.js
 mode change 100755 => 100644 VIPSWeb/static/js/3rdparty/ol.js

diff --git a/VIPSWeb/static/css/3rdparty/ol.css b/VIPSWeb/static/css/3rdparty/ol.css
old mode 100755
new mode 100644
index 1f80aeb9..17197261
--- a/VIPSWeb/static/css/3rdparty/ol.css
+++ b/VIPSWeb/static/css/3rdparty/ol.css
@@ -1 +1 @@
-.ol-mouse-position{top:8px;right:8px;position:absolute}.ol-scale-line{background:#95b9e6;background:rgba(0,60,136,.3);border-radius:4px;bottom:8px;left:8px;padding:2px;position:absolute}.ol-scale-line-inner{border:1px solid #eee;border-top:none;color:#eee;font-size:10px;text-align:center;margin:1px;will-change:contents,width}.ol-overlay-container{will-change:left,right,top,bottom}.ol-unsupported{display:none}.ol-viewport .ol-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.ol-control{position:absolute;background-color:#eee;background-color:rgba(255,255,255,.4);border-radius:4px;padding:2px}.ol-control:hover{background-color:rgba(255,255,255,.6)}.ol-zoom{top:.5em;left:.5em}.ol-rotate{top:.5em;right:.5em;transition:opacity .25s linear,visibility 0s linear}.ol-rotate.ol-hidden{opacity:0;visibility:hidden;transition:opacity .25s linear,visibility 0s linear .25s}.ol-zoom-extent{top:4.643em;left:.5em}.ol-full-screen{right:.5em;top:.5em}@media print{.ol-control{display:none}}.ol-control button{display:block;margin:1px;padding:0;color:#fff;font-size:1.14em;font-weight:700;text-decoration:none;text-align:center;height:1.375em;width:1.375em;line-height:.4em;background-color:#7b98bc;background-color:rgba(0,60,136,.5);border:none;border-radius:2px}.ol-control button::-moz-focus-inner{border:none;padding:0}.ol-zoom-extent button{line-height:1.4em}.ol-compass{display:block;font-weight:400;font-size:1.2em;will-change:transform}.ol-touch .ol-control button{font-size:1.5em}.ol-touch .ol-zoom-extent{top:5.5em}.ol-control button:focus,.ol-control button:hover{text-decoration:none;background-color:#4c6079;background-color:rgba(0,60,136,.7)}.ol-zoom .ol-zoom-in{border-radius:2px 2px 0 0}.ol-zoom .ol-zoom-out{border-radius:0 0 2px 2px}.ol-attribution{text-align:right;bottom:.5em;right:.5em;max-width:calc(100% - 1.3em)}.ol-attribution ul{margin:0;padding:0 .5em;font-size:.7rem;line-height:1.375em;color:#000;text-shadow:0 0 2px #fff}.ol-attribution li{display:inline;list-style:none;line-height:inherit}.ol-attribution li:not(:last-child):after{content:" "}.ol-attribution img{max-height:2em;max-width:inherit}.ol-attribution button,.ol-attribution ul{display:inline-block}.ol-attribution.ol-collapsed ul{display:none}.ol-attribution.ol-logo-only ul{display:block}.ol-attribution:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-attribution.ol-uncollapsible{bottom:0;right:0;border-radius:4px 0 0;height:1.1em;line-height:1em}.ol-attribution.ol-logo-only{background:0 0;bottom:.4em;height:1.1em;line-height:1em}.ol-attribution.ol-uncollapsible img{margin-top:-.2em;max-height:1.6em}.ol-attribution.ol-logo-only button,.ol-attribution.ol-uncollapsible button{display:none}.ol-zoomslider{top:4.5em;left:.5em;width:24px;height:200px}.ol-zoomslider-thumb{position:absolute;background:#7b98bc;background:rgba(0,60,136,.5);border-radius:2px;cursor:pointer;height:10px;width:22px;margin:3px}.ol-touch .ol-zoomslider{top:5.5em;width:2.052em}.ol-touch .ol-zoomslider-thumb{width:1.8em}.ol-overviewmap{left:.5em;bottom:.5em}.ol-overviewmap.ol-uncollapsible{bottom:0;left:0;border-radius:0 4px 0 0}.ol-overviewmap .ol-overviewmap-map,.ol-overviewmap button{display:inline-block}.ol-overviewmap .ol-overviewmap-map{border:1px solid #7b98bc;height:150px;margin:2px;width:150px}.ol-overviewmap:not(.ol-collapsed) button{bottom:1px;left:2px;position:absolute}.ol-overviewmap.ol-collapsed .ol-overviewmap-map,.ol-overviewmap.ol-uncollapsible button{display:none}.ol-overviewmap:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-overviewmap-box{border:2px dotted rgba(0,60,136,.7)}
\ No newline at end of file
+.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;padding:2px;position:absolute}.ol-scale-line-inner{border:1px solid #eee;border-top:none;color:#eee;font-size:10px;text-align:center;margin:1px;will-change:contents,width}.ol-overlay-container{will-change:left,right,top,bottom}.ol-unsupported{display:none}.ol-unselectable,.ol-viewport{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.ol-selectable{-webkit-touch-callout:default;-webkit-user-select:auto;-moz-user-select:auto;-ms-user-select:auto;user-select:auto}.ol-grabbing{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.ol-grab{cursor:move;cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.ol-control{position:absolute;background-color:rgba(255,255,255,.4);border-radius:4px;padding:2px}.ol-control:hover{background-color:rgba(255,255,255,.6)}.ol-zoom{top:.5em;left:.5em}.ol-rotate{top:.5em;right:.5em;transition:opacity .25s linear,visibility 0s linear}.ol-rotate.ol-hidden{opacity:0;visibility:hidden;transition:opacity .25s linear,visibility 0s linear .25s}.ol-zoom-extent{top:4.643em;left:.5em}.ol-full-screen{right:.5em;top:.5em}@media print{.ol-control{display:none}}.ol-control button{display:block;margin:1px;padding:0;color:#fff;font-size:1.14em;font-weight:700;text-decoration:none;text-align:center;height:1.375em;width:1.375em;line-height:.4em;background-color: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)}.ol-overviewmap .ol-overviewmap-box:hover{cursor:move}
\ No newline at end of file
diff --git a/VIPSWeb/static/js/3rdparty/ol-debug.js b/VIPSWeb/static/js/3rdparty/ol-debug.js
old mode 100755
new mode 100644
index 33323018..e7c7f9ea
--- a/VIPSWeb/static/js/3rdparty/ol-debug.js
+++ b/VIPSWeb/static/js/3rdparty/ol-debug.js
@@ -1,8 +1,7 @@
-// OpenLayers 3. See http://openlayers.org/
-// License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md
-// Version: v3.10.1
-
-(function (root, factory) {
+// OpenLayers. See https://openlayers.org/
+// License: https://raw.githubusercontent.com/openlayers/openlayers/master/LICENSE.md
+// Version: v4.2.0
+;(function (root, factory) {
   if (typeof exports === "object") {
     module.exports = factory();
   } else if (typeof define === "function" && define.amd) {
@@ -35,6 +34,9 @@ this.CLOSURE_NO_DEPS = true;
  * global <code>CLOSURE_NO_DEPS</code> is set to true.  This allows projects to
  * include their own deps file(s) from different locations.
  *
+ * Avoid including base.js more than once. This is strictly discouraged and not
+ * supported. goog.require(...) won't work properly in that case.
+ *
  * @author arv@google.com (Erik Arvidsson)
  *
  * @provideGoog
@@ -151,7 +153,7 @@ goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
     if (!parts.length && goog.isDef(opt_object)) {
       // last part and we have an object; use it
       cur[part] = opt_object;
-    } else if (cur[part]) {
+    } else if (cur[part] && cur[part] !== Object.prototype[part]) {
       cur = cur[part];
     } else {
       cur = cur[part] = {};
@@ -177,7 +179,8 @@ goog.define = function(name, defaultValue) {
         Object.prototype.hasOwnProperty.call(
             goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) {
       value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name];
-    } else if (goog.global.CLOSURE_DEFINES &&
+    } else if (
+        goog.global.CLOSURE_DEFINES &&
         Object.prototype.hasOwnProperty.call(
             goog.global.CLOSURE_DEFINES, name)) {
       value = goog.global.CLOSURE_DEFINES[name];
@@ -239,7 +242,7 @@ goog.define('goog.TRUSTED_SITE', true);
  *
  * 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
+ * @see https://goo.gl/PudQ4y
  *
  */
 goog.define('goog.STRICT_MODE_COMPATIBLE', false);
@@ -282,6 +285,9 @@ goog.define('goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING', false);
  *     "goog.package.part".
  */
 goog.provide = function(name) {
+  if (goog.isInModuleLoader_()) {
+    throw Error('goog.provide can not be used within a goog.module.');
+  }
   if (!COMPILED) {
     // Ensure that the same namespace isn't provided twice.
     // A goog.module/goog.provide maps a goog.require to a specific file
@@ -357,15 +363,21 @@ goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/;
  *
  * @param {string} name Namespace provided by this file in the form
  *     "goog.package.part", is expected but not required.
+ * @return {void}
  */
 goog.module = function(name) {
-  if (!goog.isString(name) ||
-      !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.');
+    throw Error(
+        'Module ' + name + ' has been loaded incorrectly. Note, ' +
+        'modules cannot be loaded as normal scripts. They require some kind of ' +
+        'pre-processing step. You\'re likely trying to load a module via a ' +
+        'script tag or as a part of a concatenated bundle without rewriting the ' +
+        'module. For more info see: ' +
+        'https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide.');
   }
   if (goog.moduleLoaderState_.moduleName) {
     throw Error('goog.module may only be called once per module.');
@@ -406,20 +418,19 @@ goog.module.get = function(name) {
  */
 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;
+    if (name in goog.loadedModules_) {
+      return goog.loadedModules_[name];
+    } else if (!goog.implicitNamespaces_[name]) {
+      var ns = goog.getObjectByName(name);
+      return ns != null ? ns : null;
     }
   }
+  return null;
 };
 
 
 /**
- * @private {?{moduleName: (string|undefined)}}
+ * @private {?{moduleName: (string|undefined), declareLegacyNamespace:boolean}}
  */
 goog.moduleLoaderState_ = null;
 
@@ -441,11 +452,13 @@ goog.isInModuleLoader_ = function() {
  */
 goog.module.declareLegacyNamespace = function() {
   if (!COMPILED && !goog.isInModuleLoader_()) {
-    throw new Error('goog.module.declareLegacyNamespace must be called from ' +
+    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 ' +
+    throw Error(
+        'goog.module must be called prior to ' +
         'goog.module.declareLegacyNamespace.');
   }
   goog.moduleLoaderState_.declareLegacyNamespace = true;
@@ -466,8 +479,9 @@ goog.module.declareLegacyNamespace = function() {
 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 : '.'));
+    throw Error(
+        'Importing test-only code into non-debug environment' +
+        (opt_message ? ': ' + opt_message : '.'));
   }
 };
 
@@ -484,6 +498,9 @@ goog.setTestOnly = function(opt_message) {
  * into the JavaScript binary. If it is required elsewhere, it will be type
  * checked as normal.
  *
+ * Before using goog.forwardDeclare, please read the documentation at
+ * https://github.com/google/closure-compiler/wiki/Bad-Type-Annotation to
+ * understand the options and tradeoffs when working with forward declarations.
  *
  * @param {string} name The namespace to forward declare in the form of
  *     "goog.package.part".
@@ -497,11 +514,11 @@ goog.forwardDeclare = function(name) {};
  * and thus block property disambiguation.
  */
 goog.forwardDeclare('Document');
+goog.forwardDeclare('HTMLScriptElement');
 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.
@@ -512,7 +529,7 @@ if (!COMPILED) {
   goog.isProvided_ = function(name) {
     return (name in goog.loadedModules_) ||
         (!goog.implicitNamespaces_[name] &&
-            goog.isDefAndNotNull(goog.getObjectByName(name)));
+         goog.isDefAndNotNull(goog.getObjectByName(name)));
   };
 
   /**
@@ -546,7 +563,7 @@ if (!COMPILED) {
 goog.getObjectByName = function(name, opt_obj) {
   var parts = name.split('.');
   var cur = opt_obj || goog.global;
-  for (var part; part = parts.shift(); ) {
+  for (var part; part = parts.shift();) {
     if (goog.isDefAndNotNull(cur[part])) {
       cur = cur[part];
     } else {
@@ -580,17 +597,22 @@ goog.globalize = function(obj, opt_global) {
  *     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.
+ * @param {boolean|!Object<string>=} opt_loadFlags Parameters indicating
+ *     how the file must be loaded.  The boolean 'true' is equivalent
+ *     to {'module': 'goog'} for backwards-compatibility.  Valid properties
+ *     and values include {'module': 'goog'} and {'lang': 'es6'}.
  */
-goog.addDependency = function(relPath, provides, requires, opt_isModule) {
+goog.addDependency = function(relPath, provides, requires, opt_loadFlags) {
   if (goog.DEPENDENCIES_ENABLED) {
     var provide, require;
     var path = relPath.replace(/\\/g, '/');
     var deps = goog.dependencies_;
+    if (!opt_loadFlags || typeof opt_loadFlags === 'boolean') {
+      opt_loadFlags = opt_loadFlags ? {'module': 'goog'} : {};
+    }
     for (var i = 0; provide = provides[i]; i++) {
       deps.nameToPath[provide] = path;
-      deps.pathIsModule[path] = !!opt_isModule;
+      deps.loadFlags[path] = opt_loadFlags;
     }
     for (var j = 0; require = requires[j]; j++) {
       if (!(path in deps.requires)) {
@@ -612,9 +634,10 @@ goog.addDependency = function(relPath, provides, requires, opt_isModule) {
 // 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
+//
+// User-defined namespaces may need their own deps file. For a reference on
+// creating a deps file, see:
+// Externally: 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
@@ -667,23 +690,20 @@ goog.require = function(name) {
     if (goog.isProvided_(name)) {
       if (goog.isInModuleLoader_()) {
         return goog.module.getInternal_(name);
-      } else {
-        return null;
       }
-    }
-
-    if (goog.ENABLE_DEBUG_LOADER) {
+    } else if (goog.ENABLE_DEBUG_LOADER) {
       var path = goog.getPathFromDeps_(name);
       if (path) {
         goog.writeScripts_(path);
-        return null;
+      } else {
+        var errorMessage = 'goog.require could not find: ' + name;
+        goog.logToConsole_(errorMessage);
+
+        throw Error(errorMessage);
       }
     }
 
-    var errorMessage = 'goog.require could not find: ' + name;
-    goog.logToConsole_(errorMessage);
-
-    throw Error(errorMessage);
+    return null;
   }
 };
 
@@ -754,6 +774,10 @@ goog.abstractMethod = function() {
  *     method to.
  */
 goog.addSingletonGetter = function(ctor) {
+  // instance_ is immediately set to prevent issues with sealed constructors
+  // such as are encountered when a constructor is returned as the export object
+  // of a goog.module in unoptimized code.
+  ctor.instance_ = undefined;
   ctor.getInstance = function() {
     if (ctor.instance_) {
       return ctor.instance_;
@@ -809,14 +833,32 @@ goog.loadedModules_ = {};
 goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER;
 
 
-if (goog.DEPENDENCIES_ENABLED) {
+/**
+ * @define {string} How to decide whether to transpile.  Valid values
+ * are 'always', 'never', and 'detect'.  The default ('detect') is to
+ * use feature detection to determine which language levels need
+ * transpilation.
+ */
+// NOTE(user): we could expand this to accept a language level to bypass
+// detection: e.g. goog.TRANSPILE == 'es5' would transpile ES6 files but
+// would leave ES3 and ES5 files alone.
+goog.define('goog.TRANSPILE', 'detect');
+
 
+/**
+ * @define {string} Path to the transpiler.  Executing the script at this
+ * path (relative to base.js) should define a function $jscomp.transpile.
+ */
+goog.define('goog.TRANSPILER', 'transpile.js');
+
+
+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>,
+   *   loadFlags: !Object<string, !Object<string, string>>,
    *   nameToPath: !Object<string, string>,
    *   requires: !Object<string, !Object<string, boolean>>,
    *   visited: !Object<string, boolean>,
@@ -825,18 +867,18 @@ if (goog.DEPENDENCIES_ENABLED) {
    * }}
    */
   goog.dependencies_ = {
-    pathIsModule: {}, // 1 to 1
+    loadFlags: {},  // 1 to 1
 
-    nameToPath: {}, // 1 to 1
+    nameToPath: {},  // 1 to 1
 
-    requires: {}, // 1 to many
+    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.
+    written: {},  // Used to keep track of script files we have written.
 
-    deferred: {} // Used to track deferred module evaluations in old IEs
+    deferred: {}  // Used to track deferred module evaluations in old IEs
   };
 
 
@@ -848,8 +890,7 @@ if (goog.DEPENDENCIES_ENABLED) {
   goog.inHtmlDocument_ = function() {
     /** @type {Document} */
     var doc = goog.global.document;
-    return typeof doc != 'undefined' &&
-           'write' in doc;  // XULDocument misses write.
+    return doc != null && 'write' in doc;  // XULDocument misses write.
   };
 
 
@@ -890,32 +931,49 @@ if (goog.DEPENDENCIES_ENABLED) {
    * @private
    */
   goog.importScript_ = function(src, opt_sourceText) {
-    var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
-        goog.writeScriptTag_;
+    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);
+  /**
+   * Whether the browser is IE9 or earlier, which needs special handling
+   * for deferred modules.
+   * @const @private {boolean}
+   */
+  goog.IS_OLD_IE_ =
+      !!(!goog.global.atob && goog.global.document && goog.global.document.all);
+
+
+  /**
+   * Whether IE9 or earlier is waiting on a dependency.  This ensures that
+   * deferred modules that have no non-deferred dependencies actually get
+   * loaded, since if we defer them and then never pull in a non-deferred
+   * script, then `goog.loadQueuedModules_` will never be called.  Instead,
+   * if not waiting on anything we simply don't defer in the first place.
+   * @private {boolean}
+   */
+  goog.oldIeWaiting_ = false;
 
 
   /**
-   * Given a URL initiate retrieval and execution of the module.
+   * Given a URL initiate retrieval and execution of a script that needs
+   * pre-processing.
    * @param {string} src Script source URL.
+   * @param {boolean} isModule Whether this is a goog.module.
+   * @param {boolean} needsTranspile Whether this source needs transpilation.
    * @private
    */
-  goog.importModule_ = function(src) {
+  goog.importProcessedScript_ = function(src, isModule, needsTranspile) {
     // 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 + '");';
+    var bootstrap = 'goog.retrieveAndExec_("' + src + '", ' + isModule + ', ' +
+        needsTranspile + ');';
 
-    if (goog.importScript_('', bootstrap)) {
-      goog.dependencies_.written[src] = true;
-    }
+    goog.importScript_('', bootstrap);
   };
 
 
@@ -935,9 +993,8 @@ if (goog.DEPENDENCIES_ENABLED) {
     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.
+          '"use strict";' + scriptText +
+          '\n' +  // terminate any trailing single line comment.
           ';return exports' +
           '});' +
           '\n//# sourceURL=' + srcUrl + '\n';
@@ -986,6 +1043,7 @@ if (goog.DEPENDENCIES_ENABLED) {
         goog.maybeProcessDeferredPath_(path);
       }
     }
+    goog.oldIeWaiting_ = false;
   };
 
 
@@ -996,8 +1054,7 @@ if (goog.DEPENDENCIES_ENABLED) {
    * @private
    */
   goog.maybeProcessDeferredDep_ = function(name) {
-    if (goog.isDeferredModule_(name) &&
-        goog.allDepsAreAvailable_(name)) {
+    if (goog.isDeferredModule_(name) && goog.allDepsAreAvailable_(name)) {
       var path = goog.getPathFromDeps_(name);
       goog.maybeProcessDeferredPath_(goog.basePath + path);
     }
@@ -1011,7 +1068,10 @@ if (goog.DEPENDENCIES_ENABLED) {
    */
   goog.isDeferredModule_ = function(name) {
     var path = goog.getPathFromDeps_(name);
-    if (path && goog.dependencies_.pathIsModule[path]) {
+    var loadFlags = path && goog.dependencies_.loadFlags[path] || {};
+    var languageLevel = loadFlags['lang'] || 'es3';
+    if (path && (loadFlags['module'] == 'goog' ||
+                 goog.needsTranspile_(languageLevel))) {
       var abspath = goog.basePath + path;
       return (abspath) in goog.dependencies_.deferred;
     }
@@ -1053,57 +1113,29 @@ if (goog.DEPENDENCIES_ENABLED) {
 
 
   /**
-   * @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}
+   * Load a goog.module from the provided URL.  This is not a general purpose
+   * code loader and does not support late loading code, that is it should only
+   * be used during page load. This method exists to support unit tests and
+   * "debug" loaders that would otherwise have inserted script tags. Under the
+   * hood this needs to use a synchronous XHR and is not recommeneded for
+   * production code.
+   *
+   * The module's goog.requires must have already been satisified; an exception
+   * will be thrown if this is not the case. This assumption is that no
+   * "deps.js" file exists, so there is no way to discover and locate the
+   * module-to-be-loaded's dependencies and no attempt is made to do so.
+   *
+   * There should only be one attempt to load a module.  If
+   * "goog.loadModuleFromUrl" is called for an already loaded module, an
+   * exception will be throw.
+   *
+   * @param {string} url The URL from which to attempt to load the goog.module.
    */
-  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;
+  goog.loadModuleFromUrl = function(url) {
+    // Because this executes synchronously, we don't need to do any additional
+    // bookkeeping. When "goog.loadModule" the namespace will be marked as
+    // having been provided which is sufficient.
+    goog.retrieveAndExec_(url, true, false);
   };
 
 
@@ -1118,7 +1150,8 @@ if (goog.DEPENDENCIES_ENABLED) {
    */
   goog.writeScriptSrcNode_ = function(src) {
     goog.global.document.write(
-        '<script type="text/javascript" src="' + src + '"></' + 'script>');
+        '<script type="text/javascript" src="' + src + '"></' +
+        'script>');
   };
 
 
@@ -1143,7 +1176,8 @@ if (goog.DEPENDENCIES_ENABLED) {
   goog.appendScriptSrcNode_ = function(src) {
     /** @type {Document} */
     var doc = goog.global.document;
-    var scriptEl = doc.createElement('script');
+    var scriptEl =
+        /** @type {HTMLScriptElement} */ (doc.createElement('script'));
     scriptEl.type = 'text/javascript';
     scriptEl.src = src;
     scriptEl.defer = false;
@@ -1163,7 +1197,7 @@ if (goog.DEPENDENCIES_ENABLED) {
    */
   goog.writeScriptTag_ = function(src, opt_sourceText) {
     if (goog.inHtmlDocument_()) {
-      /** @type {Document} */
+      /** @type {!HTMLDocument} */
       var doc = goog.global.document;
 
       // If the user tries to require a new symbol after document load,
@@ -1184,27 +1218,27 @@ if (goog.DEPENDENCIES_ENABLED) {
         }
       }
 
-      var isOldIE = goog.IS_OLD_IE_;
-
       if (opt_sourceText === undefined) {
-        if (!isOldIE) {
+        if (!goog.IS_OLD_IE_) {
           if (goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING) {
             goog.appendScriptSrcNode_(src);
           } else {
             goog.writeScriptSrcNode_(src);
           }
         } else {
-          var state = " onreadystatechange='goog.onScriptLoad_(this, " +
-              ++goog.lastNonModuleScriptIndex_ + ")' ";
+          goog.oldIeWaiting_ = true;
+          var state = ' onreadystatechange=\'goog.onScriptLoad_(this, ' +
+              ++goog.lastNonModuleScriptIndex_ + ')\' ';
           doc.write(
-              '<script type="text/javascript" src="' +
-                  src + '"' + state + '></' + 'script>');
+              '<script type="text/javascript" src="' + src + '"' + state +
+              '></' +
+              'script>');
         }
       } else {
         doc.write(
             '<script type="text/javascript">' +
-            opt_sourceText +
-            '</' + 'script>');
+            goog.protectScriptTag_(opt_sourceText) + '</' +
+            'script>');
       }
       return true;
     } else {
@@ -1212,6 +1246,42 @@ if (goog.DEPENDENCIES_ENABLED) {
     }
   };
 
+  /**
+   * Rewrites closing script tags in input to avoid ending an enclosing script
+   * tag.
+   *
+   * @param {string} str
+   * @return {string}
+   * @private
+   */
+  goog.protectScriptTag_ = function(str) {
+    return str.replace(/<\/(SCRIPT)/ig, '\\x3c/$1');
+  };
+
+  /**
+   * Determines whether the given language needs to be transpiled.
+   * @param {string} lang
+   * @return {boolean}
+   * @private
+   */
+  goog.needsTranspile_ = function(lang) {
+    if (goog.TRANSPILE == 'always') {
+      return true;
+    } else if (goog.TRANSPILE == 'never') {
+      return false;
+    } else if (!goog.requiresTranspilation_) {
+      goog.requiresTranspilation_ = goog.createRequiresTranspilation_();
+    }
+    if (lang in goog.requiresTranspilation_) {
+      return goog.requiresTranspilation_[lang];
+    } else {
+      throw new Error('Unknown language mode: ' + lang);
+    }
+  };
+
+  /** @private {?Object<string, boolean>} */
+  goog.requiresTranspilation_ = null;
+
 
   /** @private {number} */
   goog.lastNonModuleScriptIndex_ = 0;
@@ -1298,10 +1368,15 @@ if (goog.DEPENDENCIES_ENABLED) {
     for (var i = 0; i < scripts.length; i++) {
       var path = scripts[i];
       if (path) {
-        if (!deps.pathIsModule[path]) {
-          goog.importScript_(goog.basePath + path);
+        var loadFlags = deps.loadFlags[path] || {};
+        var languageLevel = loadFlags['lang'] || 'es3';
+        var needsTranspile = goog.needsTranspile_(languageLevel);
+        if (loadFlags['module'] == 'goog' || needsTranspile) {
+          goog.importProcessedScript_(
+              goog.basePath + path, loadFlags['module'] == 'goog',
+              needsTranspile);
         } else {
-          goog.importModule_(goog.basePath + path);
+          goog.importScript_(goog.basePath + path);
         }
       } else {
         goog.moduleLoaderState_ = moduleState;
@@ -1338,6 +1413,111 @@ if (goog.DEPENDENCIES_ENABLED) {
 }
 
 
+/**
+ * @package {?boolean}
+ * Visible for testing.
+ */
+goog.hasBadLetScoping = null;
+
+
+/**
+ * @return {boolean}
+ * @package Visible for testing.
+ */
+goog.useSafari10Workaround = function() {
+  if (goog.hasBadLetScoping == null) {
+    var hasBadLetScoping;
+    try {
+      hasBadLetScoping = !eval(
+          '"use strict";' +
+          'let x = 1; function f() { return typeof x; };' +
+          'f() == "number";');
+    } catch (e) {
+      // Assume that ES6 syntax isn't supported.
+      hasBadLetScoping = false;
+    }
+    goog.hasBadLetScoping = hasBadLetScoping;
+  }
+  return goog.hasBadLetScoping;
+};
+
+
+/**
+ * @param {string} moduleDef
+ * @return {string}
+ * @package Visible for testing.
+ */
+goog.workaroundSafari10EvalBug = function(moduleDef) {
+  return '(function(){' + moduleDef +
+      '\n' +  // Terminate any trailing single line comment.
+      ';' +   // Terminate any trailing expression.
+      '})();\n';
+};
+
+
+/**
+ * @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,
+      declareLegacyNamespace: false
+    };
+    var exports;
+    if (goog.isFunction(moduleDef)) {
+      exports = moduleDef.call(undefined, {});
+    } else if (goog.isString(moduleDef)) {
+      if (goog.useSafari10Workaround()) {
+        moduleDef = goog.workaroundSafari10EvalBug(moduleDef);
+      }
+
+      exports = goog.loadModuleFromSource_.call(undefined, 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 && typeof exports == 'object' &&
+        exports != null) {
+      Object.seal(exports);
+    }
+
+    goog.loadedModules_[moduleName] = exports;
+  } finally {
+    goog.moduleLoaderState_ = previousState;
+  }
+};
+
+
+/**
+ * @private @const
+ */
+goog.loadModuleFromSource_ = /** @type {function(string):?} */ (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;
+});
+
+
 /**
  * Normalize a file path by removing redundant ".." and extraneous "." file
  * path components.
@@ -1351,8 +1531,9 @@ goog.normalizePath_ = function(path) {
   while (i < components.length) {
     if (components[i] == '.') {
       components.splice(i, 1);
-    } else if (i && components[i] == '..' &&
-        components[i - 1] && components[i - 1] != '..') {
+    } else if (
+        i && components[i] == '..' && components[i - 1] &&
+        components[i - 1] != '..') {
       components.splice(--i, 2);
     } else {
       i++;
@@ -1362,31 +1543,51 @@ goog.normalizePath_ = function(path) {
 };
 
 
+/**
+ * Provides a hook for loading a file when using Closure's goog.require() API
+ * with goog.modules.  In particular this hook is provided to support Node.js.
+ *
+ * @type {(function(string):string)|undefined}
+ */
+goog.global.CLOSURE_LOAD_FILE_SYNC;
+
+
 /**
  * Loads file by synchronous XHR. Should not be used in production environments.
  * @param {string} src Source URL.
- * @return {string} File contents.
+ * @return {?string} File contents, or null if load failed.
  * @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;
+    try {
+      /** @type {XMLHttpRequest} */
+      var xhr = new goog.global['XMLHttpRequest']();
+      xhr.open('get', src, false);
+      xhr.send();
+      // NOTE: Successful http: requests have a status of 200, but successful
+      // file: requests may have a status of zero.  Any other status, or a
+      // thrown exception (particularly in case of file: requests) indicates
+      // some sort of error, which we treat as a missing or unavailable file.
+      return xhr.status == 0 || xhr.status == 200 ? xhr.responseText : null;
+    } catch (err) {
+      // No need to rethrow or log, since errors should show up on their own.
+      return null;
+    }
   }
 };
 
 
 /**
- * Retrieve and execute a module.
+ * Retrieve and execute a script that needs some sort of wrapping.
  * @param {string} src Script source URL.
+ * @param {boolean} isModule Whether to load as a module.
+ * @param {boolean} needsTranspile Whether to transpile down to ES3.
  * @private
  */
-goog.retrieveAndExecModule_ = function(src) {
+goog.retrieveAndExec_ = function(src, isModule, needsTranspile) {
   if (!COMPILED) {
     // The full but non-canonicalized URL for later use.
     var originalPath = src;
@@ -1394,27 +1595,91 @@ goog.retrieveAndExecModule_ = function(src) {
     // 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 importScript =
+        goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_;
 
     var scriptText = goog.loadFileSync_(src);
+    if (scriptText == null) {
+      throw new Error('Load of "' + src + '" failed');
+    }
 
-    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);
-      }
+    if (needsTranspile) {
+      scriptText = goog.transpile_.call(goog.global, scriptText, src);
+    }
+
+    if (isModule) {
+      scriptText = goog.wrapModule_(src, scriptText);
+    } else {
+      scriptText += '\n//# sourceURL=' + src;
+    }
+    var isOldIE = goog.IS_OLD_IE_;
+    if (isOldIE && goog.oldIeWaiting_) {
+      goog.dependencies_.deferred[originalPath] = scriptText;
+      goog.queuedModules_.push(originalPath);
     } else {
-      throw new Error('load of ' + src + 'failed');
+      importScript(src, scriptText);
     }
   }
 };
 
 
+/**
+ * Lazily retrieves the transpiler and applies it to the source.
+ * @param {string} code JS code.
+ * @param {string} path Path to the code.
+ * @return {string} The transpiled code.
+ * @private
+ */
+goog.transpile_ = function(code, path) {
+  var jscomp = goog.global['$jscomp'];
+  if (!jscomp) {
+    goog.global['$jscomp'] = jscomp = {};
+  }
+  var transpile = jscomp.transpile;
+  if (!transpile) {
+    var transpilerPath = goog.basePath + goog.TRANSPILER;
+    var transpilerCode = goog.loadFileSync_(transpilerPath);
+    if (transpilerCode) {
+      // This must be executed synchronously, since by the time we know we
+      // need it, we're about to load and write the ES6 code synchronously,
+      // so a normal script-tag load will be too slow.
+      eval(transpilerCode + '\n//# sourceURL=' + transpilerPath);
+      // Even though the transpiler is optional, if $gwtExport is found, it's
+      // a sign the transpiler was loaded and the $jscomp.transpile *should*
+      // be there.
+      if (goog.global['$gwtExport'] && goog.global['$gwtExport']['$jscomp'] &&
+          !goog.global['$gwtExport']['$jscomp']['transpile']) {
+        throw new Error(
+            'The transpiler did not properly export the "transpile" ' +
+            'method. $gwtExport: ' + JSON.stringify(goog.global['$gwtExport']));
+      }
+      // transpile.js only exports a single $jscomp function, transpile. We
+      // grab just that and add it to the existing definition of $jscomp which
+      // contains the polyfills.
+      goog.global['$jscomp'].transpile =
+          goog.global['$gwtExport']['$jscomp']['transpile'];
+      jscomp = goog.global['$jscomp'];
+      transpile = jscomp.transpile;
+    }
+  }
+  if (!transpile) {
+    // The transpiler is an optional component.  If it's not available then
+    // replace it with a pass-through function that simply logs.
+    var suffix = ' requires transpilation but no transpiler was found.';
+    transpile = jscomp.transpile = function(code, path) {
+      // TODO(user): figure out some way to get this error to show up
+      // in test results, noting that the failure may occur in many
+      // different ways, including in loadModule() before the test
+      // runner even comes up.
+      goog.logToConsole_(path + suffix);
+      return code;
+    };
+  }
+  // Note: any transpilation errors/warnings will be logged to the console.
+  return transpile(code, path);
+};
+
+
 //==============================================================================
 // Language Enhancements
 //==============================================================================
@@ -1423,7 +1688,7 @@ goog.retrieveAndExecModule_ = function(src) {
 /**
  * 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.
+ * @param {?} value The value to get the type of.
  * @return {string} The name of the type.
  */
 goog.typeOf = function(value) {
@@ -1433,7 +1698,7 @@ goog.typeOf = function(value) {
       // Check these first, so we can avoid calling Object.prototype.toString if
       // possible.
       //
-      // IE improperly marshals tyepof across execution contexts, but a
+      // IE improperly marshals typeof across execution contexts, but a
       // cross-context object will still return false for "instanceof Object".
       if (value instanceof Array) {
         return 'array';
@@ -1445,7 +1710,7 @@ goog.typeOf = function(value) {
       //   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));
+          /** @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.
@@ -1476,11 +1741,11 @@ goog.typeOf = function(value) {
            // 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')
+               typeof value.splice != 'undefined' &&
+               typeof value.propertyIsEnumerable != 'undefined' &&
+               !value.propertyIsEnumerable('splice')
 
-          )) {
+               )) {
         return 'array';
       }
       // HACK: There is still an array case that fails.
@@ -1498,9 +1763,9 @@ goog.typeOf = function(value) {
       // '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'))) {
+           typeof value.call != 'undefined' &&
+               typeof value.propertyIsEnumerable != 'undefined' &&
+               !value.propertyIsEnumerable('call'))) {
         return 'function';
       }
 
@@ -1676,10 +1941,10 @@ goog.removeUid = function(obj) {
 
   // 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) {
+  if (obj !== null && 'removeAttribute' in obj) {
     obj.removeAttribute(goog.UID_PROPERTY_);
   }
-  /** @preserveTry */
+
   try {
     delete obj[goog.UID_PROPERTY_];
   } catch (ex) {
@@ -1756,17 +2021,15 @@ goog.cloneObject = function(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 {?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 bind() was
+ * @return {!Function} A partially-applied form of the function goog.bind() was
  *     invoked as a method of.
+ * @template T
  * @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));
@@ -1775,13 +2038,14 @@ goog.bindNative_ = function(fn, selfObj, var_args) {
 
 /**
  * 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 {?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 bind() was
+ * @return {!Function} A partially-applied form of the function goog.bind() was
  *     invoked as a method of.
+ * @template T
  * @private
  */
 goog.bindJs_ = function(fn, selfObj, var_args) {
@@ -1898,10 +2162,11 @@ goog.mixin = function(target, source) {
  *     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();
-});
+             // Unary plus operator converts its operand to a number which in
+             // the case of
+             // a date is done by calling getTime().
+             return +new Date();
+           });
 
 
 /**
@@ -1935,7 +2200,8 @@ goog.globalEval = function(script) {
     } else {
       /** @type {Document} */
       var doc = goog.global.document;
-      var scriptElt = doc.createElement('SCRIPT');
+      var scriptElt =
+          /** @type {!HTMLScriptElement} */ (doc.createElement('SCRIPT'));
       scriptElt.type = 'text/javascript';
       scriptElt.defer = false;
       // Note(user): can't use .innerHTML since "t('<test>')" will fail and
@@ -1979,6 +2245,17 @@ goog.cssNameMapping_;
 goog.cssNameMappingStyle_;
 
 
+
+/**
+ * A hook for modifying the default behavior goog.getCssName. The function
+ * if present, will recieve the standard output of the goog.getCssName as
+ * its input.
+ *
+ * @type {(function(string):string)|undefined}
+ */
+goog.global.CLOSURE_CSS_NAME_MAP_FN;
+
+
 /**
  * Handles strings that are intended to be used as CSS class names.
  *
@@ -1998,7 +2275,7 @@ goog.cssNameMappingStyle_;
  *     var x = goog.getCssName('foo');
  *     var y = goog.getCssName(this.baseClass, 'active');
  *  becomes:
- *     var x= 'foo';
+ *     var x = 'foo';
  *     var y = this.baseClass + '-active';
  *
  * If one argument is passed it will be processed, if two are passed only the
@@ -2011,6 +2288,14 @@ goog.cssNameMappingStyle_;
  *     the modifier.
  */
 goog.getCssName = function(className, opt_modifier) {
+  // String() is used for compatibility with compiled soy where the passed
+  // className can be non-string objects.
+  if (String(className).charAt(0) == '.') {
+    throw new Error(
+        'className passed in goog.getCssName must not start with ".".' +
+        ' You passed: ' + className);
+  }
+
   var getMapping = function(cssName) {
     return goog.cssNameMapping_[cssName] || cssName;
   };
@@ -2027,19 +2312,24 @@ goog.getCssName = function(className, opt_modifier) {
 
   var rename;
   if (goog.cssNameMapping_) {
-    rename = goog.cssNameMappingStyle_ == 'BY_WHOLE' ?
-        getMapping : renameByParts;
+    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);
+  var result =
+      opt_modifier ? className + '-' + rename(opt_modifier) : rename(className);
+
+  // The special CLOSURE_CSS_NAME_MAP_FN allows users to specify further
+  // processing of the class name.
+  if (goog.global.CLOSURE_CSS_NAME_MAP_FN) {
+    return goog.global.CLOSURE_CSS_NAME_MAP_FN(result);
   }
+
+  return result;
 };
 
 
@@ -2106,6 +2396,10 @@ if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
  * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'});
  * </code>
  *
+ * This function produces a string which should be treated as plain text. Use
+ * {@link goog.html.SafeHtmlFormatter} in conjunction with goog.getMsg to
+ * produce SafeHtml.
+ *
  * @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.
@@ -2113,7 +2407,8 @@ if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
 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 (opt_values != null && key in opt_values) ? opt_values[key] :
+                                                         match;
     });
   }
   return str;
@@ -2201,7 +2496,7 @@ goog.exportProperty = function(object, publicName, symbol) {
  */
 goog.inherits = function(childCtor, parentCtor) {
   /** @constructor */
-  function tempCtor() {};
+  function tempCtor() {}
   tempCtor.prototype = parentCtor.prototype;
   childCtor.superClass_ = parentCtor.prototype;
   childCtor.prototype = new tempCtor();
@@ -2266,9 +2561,10 @@ 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');
+    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_) {
@@ -2289,8 +2585,8 @@ goog.base = function(me, opt_methodName, var_args) {
     args[i - 2] = arguments[i];
   }
   var foundCaller = false;
-  for (var ctor = me.constructor;
-       ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
+  for (var ctor = me.constructor; ctor;
+       ctor = ctor.superClass_ && ctor.superClass_.constructor) {
     if (ctor.prototype[opt_methodName] === caller) {
       foundCaller = true;
     } else if (foundCaller) {
@@ -2324,6 +2620,9 @@ goog.base = function(me, opt_methodName, var_args) {
  *     (e.g. "var Timer = goog.Timer").
  */
 goog.scope = function(fn) {
+  if (goog.isInModuleLoader_()) {
+    throw Error('goog.scope is not supported within a goog.module.');
+  }
   fn.call(goog.global);
 };
 
@@ -2343,7 +2642,6 @@ if (!COMPILED) {
 }
 
 
-
 //==============================================================================
 // goog.defineClass implementation
 //==============================================================================
@@ -2404,18 +2702,20 @@ goog.defineClass = function(superClass, def) {
 
 
 /**
- * @typedef {
- *     !Object|
- *     {constructor:!Function}|
- *     {constructor:!Function, statics:(Object|function(Function):void)}}
- * @suppress {missingProvide}
+ * @typedef {{
+ *   constructor: (!Function|undefined),
+ *   statics: (Object|undefined|function(Function):void)
+ * }}
  */
 goog.defineClass.ClassDescriptor;
 
 
 /**
- * @define {boolean} Whether the instances returned by
- * goog.defineClass should be sealed when possible.
+ * @define {boolean} Whether the instances returned by goog.defineClass should
+ *     be sealed when possible.
+ *
+ * When sealing is disabled the constructor function will not be wrapped by
+ * goog.defineClass, making it incompatible with ES6 class methods.
  */
 goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG);
 
@@ -2431,30 +2731,46 @@ goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG);
  * @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;
+  if (!goog.defineClass.SEAL_CLASS_INSTANCES) {
+    // Do now wrap the constructor when sealing is disabled. Angular code
+    // depends on this for injection to work properly.
+    return ctr;
   }
-  return ctr;
+
+  // Compute whether the constructor is sealable at definition time, rather
+  // than when the instance is being constructed.
+  var superclassSealable = !goog.defineClass.isUnsealable_(superClass);
+
+  /**
+   * @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 && superclassSealable &&
+        Object.seal instanceof Function) {
+      Object.seal(instance);
+    }
+    return instance;
+  };
+
+  return wrappedCtr;
+};
+
+
+/**
+ * @param {Function} ctr The constructor to test.
+ * @return {boolean} Whether the constructor has been tagged as unsealable
+ *     using goog.tagUnsealableClass.
+ * @private
+ */
+goog.defineClass.isUnsealable_ = function(ctr) {
+  return ctr && ctr.prototype &&
+      ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_];
 };
 
 
@@ -2466,13 +2782,8 @@ goog.defineClass.createSealingConstructor_ = function(ctr, superClass) {
  * @const
  */
 goog.defineClass.OBJECT_PROTOTYPE_FIELDS_ = [
-  'constructor',
-  'hasOwnProperty',
-  'isPrototypeOf',
-  'propertyIsEnumerable',
-  'toLocaleString',
-  'toString',
-  'valueOf'
+  'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
+  'toLocaleString', 'toString', 'valueOf'
 ];
 
 
@@ -2508,7 +2819,7 @@ goog.defineClass.applyProperties_ = function(target, source) {
 
 /**
  * Sealing classes breaks the older idiom of assigning properties on the
- * prototype rather than in the constructor.  As such, goog.defineClass
+ * 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.
@@ -2527,6 +2838,347 @@ goog.tagUnsealableClass = function(ctr) {
  */
 goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_ = 'goog_defineClass_legacy_unsealable';
 
+
+/**
+ * Returns a newly created map from language mode string to a boolean
+ * indicating whether transpilation should be done for that mode.
+ *
+ * Guaranteed invariant:
+ * For any two modes, l1 and l2 where l2 is a newer mode than l1,
+ * `map[l1] == true` implies that `map[l2] == true`.
+ * @private
+ * @return {!Object<string, boolean>}
+ */
+goog.createRequiresTranspilation_ = function() {
+  var /** !Object<string, boolean> */ requiresTranspilation = {'es3': false};
+  var transpilationRequiredForAllLaterModes = false;
+
+  /**
+   * Adds an entry to requiresTranspliation for the given language mode.
+   *
+   * IMPORTANT: Calls must be made in order from oldest to newest language
+   * mode.
+   * @param {string} modeName
+   * @param {function(): boolean} isSupported Returns true if the JS engine
+   *     supports the given mode.
+   */
+  function addNewerLanguageTranspilationCheck(modeName, isSupported) {
+    if (transpilationRequiredForAllLaterModes) {
+      requiresTranspilation[modeName] = true;
+    } else if (isSupported()) {
+      requiresTranspilation[modeName] = false;
+    } else {
+      requiresTranspilation[modeName] = true;
+      transpilationRequiredForAllLaterModes = true;
+    }
+  }
+
+  /**
+   * Does the given code evaluate without syntax errors and return a truthy
+   * result?
+   */
+  function /** boolean */ evalCheck(/** string */ code) {
+    try {
+      return !!eval(code);
+    } catch (ignored) {
+      return false;
+    }
+  }
+
+  var userAgent = goog.global.navigator && goog.global.navigator.userAgent ?
+      goog.global.navigator.userAgent :
+      '';
+
+  // Identify ES3-only browsers by their incorrect treatment of commas.
+  addNewerLanguageTranspilationCheck('es5', function() {
+    return evalCheck('[1,].length==1');
+  });
+  addNewerLanguageTranspilationCheck('es6', function() {
+    // Edge has a non-deterministic (i.e., not reproducible) bug with ES6:
+    // https://github.com/Microsoft/ChakraCore/issues/1496.
+    var re = /Edge\/(\d+)(\.\d)*/i;
+    var edgeUserAgent = userAgent.match(re);
+    if (edgeUserAgent && Number(edgeUserAgent[1]) < 15) {
+      return false;
+    }
+    // Test es6: [FF50 (?), Edge 14 (?), Chrome 50]
+    //   (a) default params (specifically shadowing locals),
+    //   (b) destructuring, (c) block-scoped functions,
+    //   (d) for-of (const), (e) new.target/Reflect.construct
+    var es6fullTest =
+        'class X{constructor(){if(new.target!=String)throw 1;this.x=42}}' +
+        'let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof ' +
+        'String))throw 1;for(const a of[2,3]){if(a==2)continue;function ' +
+        'f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()' +
+        '==3}';
+
+    return evalCheck('(()=>{"use strict";' + es6fullTest + '})()');
+  });
+  // TODO(joeltine): Remove es6-impl references for b/31340605.
+  // Consider es6-impl (widely-implemented es6 features) to be supported
+  // whenever es6 is supported. Technically es6-impl is a lower level of
+  // support than es6, but we don't have tests specifically for it.
+  addNewerLanguageTranspilationCheck('es6-impl', function() {
+    return true;
+  });
+  // ** and **= are the only new features in 'es7'
+  addNewerLanguageTranspilationCheck('es7', function() {
+    return evalCheck('2 ** 2 == 4');
+  });
+  // async functions are the only new features in 'es8'
+  addNewerLanguageTranspilationCheck('es8', function() {
+    return evalCheck('async () => 1, true');
+  });
+  return requiresTranspilation;
+};
+
+goog.provide('ol.array');
+
+
+/**
+ * Performs a binary search on the provided sorted list and returns the index of the item if found. If it can't be found it'll return -1.
+ * https://github.com/darkskyapp/binary-search
+ *
+ * @param {Array.<*>} haystack Items to search through.
+ * @param {*} needle The item to look for.
+ * @param {Function=} opt_comparator Comparator function.
+ * @return {number} The index of the item if found, -1 if not.
+ */
+ol.array.binarySearch = function(haystack, needle, opt_comparator) {
+  var mid, cmp;
+  var comparator = opt_comparator || ol.array.numberSafeCompareFunction;
+  var low = 0;
+  var high = haystack.length;
+  var found = false;
+
+  while (low < high) {
+    /* Note that "(low + high) >>> 1" may overflow, and results in a typecast
+     * to double (which gives the wrong results). */
+    mid = low + (high - low >> 1);
+    cmp = +comparator(haystack[mid], needle);
+
+    if (cmp < 0.0) { /* Too low. */
+      low  = mid + 1;
+
+    } else { /* Key found or too high */
+      high = mid;
+      found = !cmp;
+    }
+  }
+
+  /* Key not found. */
+  return found ? low : ~low;
+};
+
+
+/**
+ * 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;
+          }
+        }
+      }
+    }
+    return n - 1;
+  }
+};
+
+
+/**
+ * @param {Array.<*>} arr Array.
+ * @param {number} begin Begin index.
+ * @param {number} end End index.
+ */
+ol.array.reverseSubArray = function(arr, begin, end) {
+  while (begin < end) {
+    var tmp = arr[begin];
+    arr[begin] = arr[end];
+    arr[end] = tmp;
+    ++begin;
+    --end;
+  }
+};
+
+
+/**
+ * @param {Array.<VALUE>} arr The array to modify.
+ * @param {Array.<VALUE>|VALUE} data The elements or arrays of elements
+ *     to add to arr.
+ * @template VALUE
+ */
+ol.array.extend = function(arr, data) {
+  var i;
+  var extension = Array.isArray(data) ? data : [data];
+  var length = extension.length;
+  for (i = 0; i < length; i++) {
+    arr[arr.length] = extension[i];
+  }
+};
+
+
+/**
+ * @param {Array.<VALUE>} arr The array to modify.
+ * @param {VALUE} obj The element to remove.
+ * @template VALUE
+ * @return {boolean} If the element was removed.
+ */
+ol.array.remove = function(arr, obj) {
+  var i = arr.indexOf(obj);
+  var found = i > -1;
+  if (found) {
+    arr.splice(i, 1);
+  }
+  return found;
+};
+
+
+/**
+ * @param {Array.<VALUE>} arr The array to search in.
+ * @param {function(VALUE, number, ?) : boolean} func The function to compare.
+ * @template VALUE
+ * @return {VALUE} The element found.
+ */
+ol.array.find = function(arr, func) {
+  var length = arr.length >>> 0;
+  var value;
+
+  for (var i = 0; i < length; i++) {
+    value = arr[i];
+    if (func(value, i, arr)) {
+      return value;
+    }
+  }
+  return null;
+};
+
+
+/**
+ * @param {Array|Uint8ClampedArray} arr1 The first array to compare.
+ * @param {Array|Uint8ClampedArray} arr2 The second array to compare.
+ * @return {boolean} Whether the two arrays are equal.
+ */
+ol.array.equals = function(arr1, arr2) {
+  var len1 = arr1.length;
+  if (len1 !== arr2.length) {
+    return false;
+  }
+  for (var i = 0; i < len1; i++) {
+    if (arr1[i] !== arr2[i]) {
+      return false;
+    }
+  }
+  return true;
+};
+
+
+/**
+ * @param {Array.<*>} arr The array to sort (modifies original).
+ * @param {Function} compareFnc Comparison function.
+ */
+ol.array.stableSort = function(arr, compareFnc) {
+  var length = arr.length;
+  var tmp = Array(arr.length);
+  var i;
+  for (i = 0; i < length; i++) {
+    tmp[i] = {index: i, value: arr[i]};
+  }
+  tmp.sort(function(a, b) {
+    return compareFnc(a.value, b.value) || a.index - b.index;
+  });
+  for (i = 0; i < arr.length; i++) {
+    arr[i] = tmp[i].value;
+  }
+};
+
+
+/**
+ * @param {Array.<*>} arr The array to search in.
+ * @param {Function} func Comparison function.
+ * @return {number} Return index.
+ */
+ol.array.findIndex = function(arr, func) {
+  var index;
+  var found = !arr.every(function(el, idx) {
+    index = idx;
+    return !func(el, idx, arr);
+  });
+  return found ? index : -1;
+};
+
+
+/**
+ * @param {Array.<*>} arr The array to test.
+ * @param {Function=} opt_func Comparison function.
+ * @param {boolean=} opt_strict Strictly sorted (default false).
+ * @return {boolean} Return index.
+ */
+ol.array.isSorted = function(arr, opt_func, opt_strict) {
+  var compare = opt_func || ol.array.numberSafeCompareFunction;
+  return arr.every(function(currentVal, index) {
+    if (index === 0) {
+      return true;
+    }
+    var res = compare(arr[index - 1], currentVal);
+    return !(res > 0 || opt_strict && res === 0);
+  });
+};
+
 goog.provide('ol');
 
 
@@ -2545,7 +3197,7 @@ ol.ASSUME_TOUCH = false;
 
 /**
  * TODO: rename this to something having to do with tile grids
- * see https://github.com/openlayers/ol3/issues/2076
+ * see https://github.com/openlayers/openlayers/issues/2076
  * @define {number} Default maximum zoom for default tile grids.
  */
 ol.DEFAULT_MAX_ZOOM = 42;
@@ -2558,9 +3210,10 @@ ol.DEFAULT_MIN_ZOOM = 0;
 
 
 /**
- * @define {number} Default high water mark.
+ * @define {number} Default maximum allowed threshold  (in pixels) for
+ *     reprojection triangulation. Default is `0.5`.
  */
-ol.DEFAULT_TILE_CACHE_HIGH_WATER_MARK = 2048;
+ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD = 0.5;
 
 
 /**
@@ -2575,12 +3228,6 @@ ol.DEFAULT_TILE_SIZE = 256;
 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
@@ -2589,33 +3236,6 @@ ol.DRAG_BOX_HYSTERESIS_PIXELS = 8;
 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`.
@@ -2624,19 +3244,10 @@ ol.ENABLE_PROJ4JS = 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.
+ * @define {boolean} Enable automatic reprojection of raster sources. Default is
+ *     `true`.
  */
-ol.ENABLE_VECTOR = true;
+ol.ENABLE_RASTER_REPROJECTION = true;
 
 
 /**
@@ -2647,6 +3258,14 @@ ol.ENABLE_VECTOR = true;
 ol.ENABLE_WEBGL = true;
 
 
+/**
+ * @define {boolean} Include debuggable shader sources.  Default is `true`.
+ *     This should be set to `false` for production builds (if `ol.ENABLE_WEBGL`
+ *     is `true`).
+ */
+ol.DEBUG_WEBGL = true;
+
+
 /**
  * @define {number} The size in pixels of the first atlas image. Default is
  * `256`.
@@ -2668,12 +3287,6 @@ ol.MAX_ATLAS_SIZE = -1;
 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.
@@ -2688,6 +3301,41 @@ ol.OVERVIEWMAP_MAX_RATIO = 0.75;
 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.
  */
@@ -2700,6 +3348,12 @@ ol.SIMPLIFY_TOLERANCE = 0.5;
 ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024;
 
 
+/**
+ * @define {string} OpenLayers version.
+ */
+ol.VERSION = '';
+
+
 /**
  * The maximum supported WebGL texture size in pixels. If WebGL is not
  * supported, the value is set to `undefined`.
@@ -2734,22 +3388,15 @@ ol.WEBGL_EXTENSIONS; // value is set in `ol.has`
  *     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
+ol.inherits = function(childCtor, parentCtor) {
+  childCtor.prototype = Object.create(parentCtor.prototype);
+  childCtor.prototype.constructor = childCtor;
+};
 
 
 /**
@@ -2759,87035 +3406,47952 @@ ol.inherits =
  */
 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
+ * 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. Unique IDs are generated
+ * as a strictly increasing sequence. Adapted from goog.getUid.
+ *
+ * @param {Object} obj The object to get the unique ID for.
+ * @return {number} The unique ID for the object.
  */
-ol.PreRenderFunction;
+ol.getUid = function(obj) {
+  return obj.ol_uid ||
+      (obj.ol_uid = ++ol.uidCounter_);
+};
 
-// 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.
- *
+ * Counter for getUid.
+ * @type {number}
+ * @private
  */
+ol.uidCounter_ = 0;
 
-goog.provide('goog.debug.Error');
-
+goog.provide('ol.AssertionError');
 
+goog.require('ol');
 
 /**
- * Base class for custom error objects.
- * @param {*=} opt_msg The message associated with the error.
+ * Error object thrown when an assertion failed. This is an ECMA-262 Error,
+ * extended with a `code` property.
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error}
  * @constructor
  * @extends {Error}
+ * @implements {oli.AssertionError}
+ * @param {number} code Error code.
  */
-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;
-    }
-  }
+ol.AssertionError = function(code) {
 
-  if (opt_msg) {
-    this.message = String(opt_msg);
-  }
+  var path = ol.VERSION ? ol.VERSION.split('-')[0] : 'latest';
 
   /**
-   * 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}
+   * @type {string}
    */
-  this.reportErrorToServer = true;
-};
-goog.inherits(goog.debug.Error, Error);
+  this.message = 'Assertion failed. See https://openlayers.org/en/' + path +
+      '/doc/errors/#' + code + ' for details.';
 
+  /**
+   * Error code. The meaning of the code can be found on
+   * {@link https://openlayers.org/en/latest/doc/errors/} (replace `latest` with
+   * the version found in the OpenLayers script's header comment if a version
+   * other than the latest is used).
+   * @type {number}
+   * @api
+   */
+  this.code = code;
 
-/** @override */
-goog.debug.Error.prototype.name = 'CustomError';
+  this.name = 'AssertionError';
 
-// 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.
+};
+ol.inherits(ol.AssertionError, Error);
 
-/**
- * @fileoverview Definition of goog.dom.NodeType.
- */
+goog.provide('ol.asserts');
 
-goog.provide('goog.dom.NodeType');
+goog.require('ol.AssertionError');
 
 
 /**
- * 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}
+ * @param {*} assertion Assertion we expected to be truthy.
+ * @param {number} errorCode Error code.
  */
-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
+ol.asserts.assert = function(assertion, errorCode) {
+  if (!assertion) {
+    throw new ol.AssertionError(errorCode);
+  }
 };
 
-// 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)
- */
+goog.provide('ol.TileRange');
 
 
 /**
- * Namespace for string utilities
+ * 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
  */
-goog.provide('goog.string');
-goog.provide('goog.string.Unicode');
-
+ol.TileRange = function(minX, maxX, minY, maxY) {
 
-/**
- * @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);
+  /**
+   * @type {number}
+   */
+  this.minX = minX;
 
+  /**
+   * @type {number}
+   */
+  this.maxX = maxX;
 
-/**
- * @define {boolean} Whether to force non-dom html unescaping.
- */
-goog.define('goog.string.FORCE_NON_DOM_HTML_UNESCAPING', false);
+  /**
+   * @type {number}
+   */
+  this.minY = minY;
 
+  /**
+   * @type {number}
+   */
+  this.maxY = maxY;
 
-/**
- * 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}.
+ * @param {number} minX Minimum X.
+ * @param {number} maxX Maximum X.
+ * @param {number} minY Minimum Y.
+ * @param {number} maxY Maximum Y.
+ * @param {ol.TileRange|undefined} tileRange TileRange.
+ * @return {ol.TileRange} Tile range.
  */
-goog.string.startsWith = function(str, prefix) {
-  return str.lastIndexOf(prefix, 0) == 0;
+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);
+  }
 };
 
 
 /**
- * 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}.
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @return {boolean} Contains tile coordinate.
  */
-goog.string.endsWith = function(str, suffix) {
-  var l = str.length - suffix.length;
-  return l >= 0 && str.indexOf(suffix, l) == l;
+ol.TileRange.prototype.contains = function(tileCoord) {
+  return this.containsXY(tileCoord[1], tileCoord[2]);
 };
 
 
 /**
- * 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).
+ * @param {ol.TileRange} tileRange Tile range.
+ * @return {boolean} Contains.
  */
-goog.string.caseInsensitiveStartsWith = function(str, prefix) {
-  return goog.string.caseInsensitiveCompare(
-      prefix, str.substr(0, prefix.length)) == 0;
+ol.TileRange.prototype.containsTileRange = function(tileRange) {
+  return this.minX <= tileRange.minX && tileRange.maxX <= this.maxX &&
+      this.minY <= tileRange.minY && tileRange.maxY <= this.maxY;
 };
 
 
 /**
- * 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).
+ * @param {number} x Tile coordinate x.
+ * @param {number} y Tile coordinate y.
+ * @return {boolean} Contains coordinate.
  */
-goog.string.caseInsensitiveEndsWith = function(str, suffix) {
-  return goog.string.caseInsensitiveCompare(
-      suffix, str.substr(str.length - suffix.length, suffix.length)) == 0;
+ol.TileRange.prototype.containsXY = function(x, y) {
+  return this.minX <= x && x <= this.maxX && this.minY <= y && y <= this.maxY;
 };
 
 
 /**
- * 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.
+ * @param {ol.TileRange} tileRange Tile range.
+ * @return {boolean} Equals.
  */
-goog.string.caseInsensitiveEquals = function(str1, str2) {
-  return str1.toLowerCase() == str2.toLowerCase();
+ol.TileRange.prototype.equals = function(tileRange) {
+  return this.minX == tileRange.minX && this.minY == tileRange.minY &&
+      this.maxX == tileRange.maxX && this.maxY == tileRange.maxY;
 };
 
 
 /**
- * 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}.
+ * @param {ol.TileRange} tileRange Tile range.
  */
-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();
+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 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.
+ * @return {number} Height.
  */
-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, '');
+ol.TileRange.prototype.getHeight = function() {
+  return this.maxY - this.minY + 1;
 };
 
 
 /**
- * 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.
+ * @return {ol.Size} Size.
  */
-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);
+ol.TileRange.prototype.getSize = function() {
+  return [this.getWidth(), this.getHeight()];
 };
 
 
 /**
- * Checks if a string is empty.
- * @param {string} str The string to check.
- * @return {boolean} Whether {@code str} is empty.
+ * @return {number} Width.
  */
-goog.string.isEmptyString = function(str) {
-  return str.length == 0;
+ol.TileRange.prototype.getWidth = function() {
+  return this.maxX - this.minX + 1;
 };
 
 
 /**
- * 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.
+ * @param {ol.TileRange} tileRange Tile range.
+ * @return {boolean} Intersects.
  */
-goog.string.isEmpty = goog.string.isEmptyOrWhitespace;
+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.math');
+
+goog.require('ol.asserts');
 
 
 /**
- * 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.
+ * 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.string.isEmptyOrWhitespaceSafe = function(str) {
-  return goog.string.isEmptyOrWhitespace(goog.string.makeSafe(str));
+ol.math.clamp = function(value, min, max) {
+  return Math.min(Math.max(value, min), max);
 };
 
 
 /**
- * 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.
+ * 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 {*} str The string to check.
- * @return {boolean} Whether {@code str} is null, undefined, empty, or
- *     whitespace only.
+ * @param {number} x X.
+ * @return {number} Hyperbolic cosine of x.
  */
-goog.string.isEmptySafe = goog.string.isEmptyOrWhitespaceSafe;
+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;
+}());
 
 
 /**
- * Checks if a string is all breaking whitespace.
- * @param {string} str The string to check.
- * @return {boolean} Whether the string is all breaking whitespace.
+ * @param {number} x X.
+ * @return {number} The smallest power of two greater than or equal to x.
  */
-goog.string.isBreakingWhitespace = function(str) {
-  return !/[^\t\n\r ]/.test(str);
+ol.math.roundUpToPowerOfTwo = function(x) {
+  ol.asserts.assert(0 < x, 29); // `x` must be greater than `0`
+  return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2));
 };
 
 
 /**
- * Checks if a string contains all letters.
- * @param {string} str string to check.
- * @return {boolean} True if {@code str} consists entirely of letters.
+ * 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.
  */
-goog.string.isAlpha = function(str) {
-  return !/[^a-zA-Z]/.test(str);
+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);
 };
 
 
 /**
- * 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.
+ * 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.
  */
-goog.string.isNumeric = function(str) {
-  return !/[^0-9]/.test(str);
+ol.math.squaredDistance = function(x1, y1, x2, y2) {
+  var dx = x2 - x1;
+  var dy = y2 - y1;
+  return dx * dx + dy * dy;
 };
 
 
 /**
- * Checks if a string contains only numbers or letters.
- * @param {string} str string to check.
- * @return {boolean} True if {@code str} is alphanumeric.
+ * 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.
  */
-goog.string.isAlphaNumeric = function(str) {
-  return !/[^a-zA-Z0-9]/.test(str);
+ol.math.solveLinearSystem = function(mat) {
+  var n = mat.length;
+
+  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;
 };
 
 
 /**
- * Checks if a character is a space character.
- * @param {string} ch Character to check.
- * @return {boolean} True if {@code ch} is a space.
+ * Converts radians to to degrees.
+ *
+ * @param {number} angleInRadians Angle in radians.
+ * @return {number} Angle in degrees.
  */
-goog.string.isSpace = function(ch) {
-  return ch == ' ';
+ol.math.toDegrees = function(angleInRadians) {
+  return angleInRadians * 180 / Math.PI;
 };
 
 
 /**
- * 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.
+ * Converts degrees to radians.
+ *
+ * @param {number} angleInDegrees Angle in degrees.
+ * @return {number} Angle in radians.
  */
-goog.string.isUnicodeChar = function(ch) {
-  return ch.length == 1 && ch >= ' ' && ch <= '~' ||
-         ch >= '\u0080' && ch <= '\uFFFD';
+ol.math.toRadians = function(angleInDegrees) {
+  return angleInDegrees * Math.PI / 180;
 };
 
-
 /**
- * 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.
+ * Returns the modulo of a / b, depending on the sign of b.
+ *
+ * @param {number} a Dividend.
+ * @param {number} b Divisor.
+ * @return {number} Modulo.
  */
-goog.string.stripNewlines = function(str) {
-  return str.replace(/(\r\n|\r|\n)+/g, ' ');
+ol.math.modulo = function(a, b) {
+  var r = a % b;
+  return r * b < 0 ? r + b : r;
 };
 
-
 /**
- * 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.
+ * Calculates the linearly interpolated value of x between a and b.
+ *
+ * @param {number} a Number
+ * @param {number} b Number
+ * @param {number} x Value to be interpolated.
+ * @return {number} Interpolated value.
  */
-goog.string.canonicalizeNewlines = function(str) {
-  return str.replace(/(\r\n|\r|\n)/g, '\n');
+ol.math.lerp = function(a, b, x) {
+  return a + x * (b - a);
 };
 
+goog.provide('ol.size');
+
 
 /**
- * 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.
+ * 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} The buffered size.
  */
-goog.string.normalizeWhitespace = function(str) {
-  return str.replace(/\xa0|\s/g, ' ');
+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;
 };
 
 
 /**
- * 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.
+ * Determines if a size has a positive area.
+ * @param {ol.Size} size The size to test.
+ * @return {boolean} The size has a positive area.
  */
-goog.string.normalizeSpaces = function(str) {
-  return str.replace(/\xa0|[ \t]+/g, ' ');
+ol.size.hasArea = function(size) {
+  return size[0] > 0 && size[1] > 0;
 };
 
 
 /**
- * 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.
+ * 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} The scaled size.
  */
-goog.string.collapseBreakingSpaces = function(str) {
-  return str.replace(/[\t\r\n ]+/g, ' ').replace(
-      /^[\t\r\n ]+|[\t\r\n ]+$/g, '');
+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;
 };
 
 
 /**
- * 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}.
+ * 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
  */
-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, '');
-    };
+ol.size.toSize = function(size, opt_size) {
+  if (Array.isArray(size)) {
+    return size;
+  } else {
+    if (opt_size === undefined) {
+      opt_size = [size, size];
+    } else {
+      opt_size[0] = opt_size[1] = /** @type {number} */ (size);
+    }
+    return opt_size;
+  }
+};
 
+goog.provide('ol.extent.Corner');
 
 /**
- * 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}.
+ * Extent corner.
+ * @enum {string}
  */
-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]+/, '');
+ol.extent.Corner = {
+  BOTTOM_LEFT: 'bottom-left',
+  BOTTOM_RIGHT: 'bottom-right',
+  TOP_LEFT: 'top-left',
+  TOP_RIGHT: 'top-right'
 };
 
+goog.provide('ol.extent.Relationship');
+
 
 /**
- * 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}.
+ * Relationship to an extent.
+ * @enum {number}
  */
-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]+$/, '');
+ol.extent.Relationship = {
+  UNKNOWN: 0,
+  INTERSECTING: 1,
+  ABOVE: 2,
+  RIGHT: 4,
+  BELOW: 8,
+  LEFT: 16
 };
 
+goog.provide('ol.extent');
+
+goog.require('ol.asserts');
+goog.require('ol.extent.Corner');
+goog.require('ol.extent.Relationship');
+
 
 /**
- * A string comparator that ignores case.
- * -1 = str1 less than str2
- *  0 = str1 equals str2
- *  1 = str1 greater than str2
+ * Build an extent that includes all given coordinates.
  *
- * @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.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @return {ol.Extent} Bounding extent.
+ * @api
  */
-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;
+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;
 };
 
 
 /**
- * Regular expression used for splitting a string into substrings of fractional
- * numbers, integers, and non-numeric characters.
- * @type {RegExp}
+ * @param {Array.<number>} xs Xs.
+ * @param {Array.<number>} ys Ys.
+ * @param {ol.Extent=} opt_extent Destination extent.
  * @private
+ * @return {ol.Extent} Extent.
  */
-goog.string.numerateCompareRegExp_ = /(\.\d+)|(\d+)|(\D+)/g;
+ol.extent.boundingExtentXYs_ = function(xs, ys, opt_extent) {
+  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);
+};
 
 
 /**
- * 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.
+ * 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
  */
-goog.string.numerateCompare = function(str1, str2) {
-  if (str1 == str2) {
-    return 0;
-  }
-  if (!str1) {
-    return -1;
-  }
-  if (!str2) {
-    return 1;
+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
+    ];
   }
+};
 
-  // 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;
+/**
+ * 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();
   }
-
-  // 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.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @return {number} Closest squared distance.
  */
-goog.string.urlEncode = function(str) {
-  return encodeURIComponent(String(str));
+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;
 };
 
 
 /**
- * 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}.
+ * 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
  */
-goog.string.urlDecode = function(str) {
-  return decodeURIComponent(str.replace(/\+/g, ' '));
+ol.extent.containsCoordinate = function(extent, coordinate) {
+  return ol.extent.containsXY(extent, coordinate[0], coordinate[1]);
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.string.newLineToBr = function(str, opt_xml) {
-  return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>');
+ol.extent.containsExtent = function(extent1, extent2) {
+  return extent1[0] <= extent2[0] && extent2[2] <= extent1[2] &&
+      extent1[1] <= extent2[1] && extent2[3] <= extent1[3];
 };
 
 
 /**
- * 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.
+ * Check if the passed coordinate is contained or on the edge of the extent.
  *
- * 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}.
+ * @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
  */
-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;
-  }
+ol.extent.containsXY = function(extent, x, y) {
+  return extent[0] <= x && x <= extent[2] && extent[1] <= y && y <= extent[3];
 };
 
 
 /**
- * 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
+ * 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).
  */
-goog.string.ALL_RE_ = (goog.string.DETECT_DOUBLE_ESCAPING ?
-    /[\x00&<>"'e]/ :
-    /[\x00&<>"']/);
+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;
+};
 
 
 /**
- * Unescapes an HTML string.
- *
- * @param {string} str The string to unescape.
- * @return {string} An unescaped copy of {@code str}.
+ * Create an empty extent.
+ * @return {ol.Extent} Empty extent.
+ * @api
  */
-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;
+ol.extent.createEmpty = function() {
+  return [Infinity, Infinity, -Infinity, -Infinity];
 };
 
 
 /**
- * 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}.
+ * 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.
  */
-goog.string.unescapeEntitiesWithDocument = function(str, document) {
-  if (goog.string.contains(str, '&')) {
-    return goog.string.unescapeEntitiesUsingDom_(str, document);
+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];
   }
-  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.
+ * Create a new empty extent or make the provided one empty.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
  */
-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;
-  });
+ol.extent.createOrUpdateEmpty = function(opt_extent) {
+  return ol.extent.createOrUpdate(
+      Infinity, Infinity, -Infinity, -Infinity, opt_extent);
 };
 
 
 /**
- * Unescapes XML entities.
- * @private
- * @param {string} str The string to unescape.
- * @return {string} An unescaped copy of {@code str}.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
  */
-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;
-    }
-  });
+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);
 };
 
 
 /**
- * Regular expression that matches an HTML entity.
- * See also HTML5: Tokenization / Tokenizing character references.
- * @private
- * @type {!RegExp}
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
  */
-goog.string.HTML_ENTITY_PATTERN_ = /&([^;\s<&]+);?/g;
+ol.extent.createOrUpdateFromCoordinates = function(coordinates, opt_extent) {
+  var extent = ol.extent.createOrUpdateEmpty(opt_extent);
+  return ol.extent.extendCoordinates(extent, coordinates);
+};
 
 
 /**
- * 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}.
+ * @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.
  */
-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);
+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);
 };
 
 
 /**
- * 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.
+ * @param {Array.<Array.<ol.Coordinate>>} rings Rings.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
  */
-goog.string.preserveSpaces = function(str) {
-  return str.replace(/(^|[\n ]) /g, '$1' + goog.string.Unicode.NBSP);
+ol.extent.createOrUpdateFromRings = function(rings, opt_extent) {
+  var extent = ol.extent.createOrUpdateEmpty(opt_extent);
+  return ol.extent.extendRings(extent, rings);
 };
 
 
 /**
- * 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.
+ * 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
  */
-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;
+ol.extent.equals = function(extent1, extent2) {
+  return extent1[0] == extent2[0] && extent1[2] == extent2[2] &&
+      extent1[1] == extent2[1] && extent1[3] == extent2[3];
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) {
-  if (opt_protectEscapedCharacters) {
-    str = goog.string.unescapeEntities(str);
+ol.extent.extend = function(extent1, extent2) {
+  if (extent2[0] < extent1[0]) {
+    extent1[0] = extent2[0];
   }
-
-  if (str.length > chars) {
-    str = str.substring(0, chars - 3) + '...';
+  if (extent2[2] > extent1[2]) {
+    extent1[2] = extent2[2];
   }
-
-  if (opt_protectEscapedCharacters) {
-    str = goog.string.htmlEscape(str);
+  if (extent2[1] < extent1[1]) {
+    extent1[1] = extent2[1];
   }
-
-  return str;
+  if (extent2[3] > extent1[3]) {
+    extent1[3] = extent2[3];
+  }
+  return extent1;
 };
 
 
 /**
- * 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}.
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.Coordinate} coordinate Coordinate.
  */
-goog.string.truncateMiddle = function(str, chars,
-    opt_protectEscapedCharacters, opt_trailingChars) {
-  if (opt_protectEscapedCharacters) {
-    str = goog.string.unescapeEntities(str);
+ol.extent.extendCoordinate = function(extent, coordinate) {
+  if (coordinate[0] < extent[0]) {
+    extent[0] = coordinate[0];
   }
-
-  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 (coordinate[0] > extent[2]) {
+    extent[2] = coordinate[0];
   }
-
-  if (opt_protectEscapedCharacters) {
-    str = goog.string.htmlEscape(str);
+  if (coordinate[1] < extent[1]) {
+    extent[1] = coordinate[1];
+  }
+  if (coordinate[1] > extent[3]) {
+    extent[3] = coordinate[1];
   }
-
-  return str;
 };
 
 
 /**
- * Special chars that need to be escaped for goog.string.quote.
- * @private {!Object<string, string>}
+ * @param {ol.Extent} extent Extent.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @return {ol.Extent} Extent.
  */
-goog.string.specialEscapeChars_ = {
-  '\0': '\\0',
-  '\b': '\\b',
-  '\f': '\\f',
-  '\n': '\\n',
-  '\r': '\\r',
-  '\t': '\\t',
-  '\x0B': '\\x0B', // '\v' is not supported in JScript
-  '"': '\\"',
-  '\\': '\\\\'
+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;
 };
 
 
 /**
- * Character mappings used internally for goog.string.escapeChar.
- * @private {!Object<string, string>}
+ * @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.
  */
-goog.string.jsEscapeCache_ = {
-  '\'': '\\\''
+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;
 };
 
 
 /**
- * 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.
+ * @param {ol.Extent} extent Extent.
+ * @param {Array.<Array.<ol.Coordinate>>} rings Rings.
+ * @return {ol.Extent} Extent.
  */
-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('');
+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;
 };
 
 
 /**
- * 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}.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} x X.
+ * @param {number} y Y.
  */
-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('');
+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);
 };
 
 
 /**
- * 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}.
+ * 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
  */
-goog.string.escapeChar = function(c) {
-  if (c in goog.string.jsEscapeCache_) {
-    return goog.string.jsEscapeCache_[c];
+ol.extent.forEachCorner = function(extent, callback, opt_this) {
+  var val;
+  val = callback.call(opt_this, ol.extent.getBottomLeft(extent));
+  if (val) {
+    return val;
   }
-
-  if (c in goog.string.specialEscapeChars_) {
-    return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c];
+  val = callback.call(opt_this, ol.extent.getBottomRight(extent));
+  if (val) {
+    return val;
   }
-
-  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();
+  val = callback.call(opt_this, ol.extent.getTopRight(extent));
+  if (val) {
+    return val;
   }
-
-  return goog.string.jsEscapeCache_[c] = rv;
+  val = callback.call(opt_this, ol.extent.getTopLeft(extent));
+  if (val) {
+    return val;
+  }
+  return false;
 };
 
 
 /**
- * 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}.
+ * Get the size of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {number} Area.
+ * @api
  */
-goog.string.contains = function(str, subString) {
-  return str.indexOf(subString) != -1;
+ol.extent.getArea = function(extent) {
+  var area = 0;
+  if (!ol.extent.isEmpty(extent)) {
+    area = ol.extent.getWidth(extent) * ol.extent.getHeight(extent);
+  }
+  return area;
 };
 
 
 /**
- * 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}.
+ * Get the bottom left coordinate of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.Coordinate} Bottom left coordinate.
+ * @api
  */
-goog.string.caseInsensitiveContains = function(str, subString) {
-  return goog.string.contains(str.toLowerCase(), subString.toLowerCase());
+ol.extent.getBottomLeft = function(extent) {
+  return [extent[0], extent[1]];
 };
 
 
 /**
- * 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.
+ * Get the bottom right coordinate of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.Coordinate} Bottom right coordinate.
+ * @api
  */
-goog.string.countOf = function(s, ss) {
-  return s && ss ? s.split(ss).length - 1 : 0;
+ol.extent.getBottomRight = function(extent) {
+  return [extent[2], extent[1]];
 };
 
 
 /**
- * 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.
+ * Get the center coordinate of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.Coordinate} Center.
+ * @api
  */
-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;
+ol.extent.getCenter = function(extent) {
+  return [(extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2];
 };
 
 
 /**
- *  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.
+ * Get a corner coordinate of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.extent.Corner} corner Corner.
+ * @return {ol.Coordinate} Corner coordinate.
  */
-goog.string.remove = function(s, ss) {
-  var re = new RegExp(goog.string.regExpEscape(ss), '');
-  return s.replace(re, '');
+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 {
+    ol.asserts.assert(false, 13); // Invalid corner
+  }
+  return /** @type {!ol.Coordinate} */ (coordinate);
 };
 
 
 /**
- *  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.
+ * @param {ol.Extent} extent1 Extent 1.
+ * @param {ol.Extent} extent2 Extent 2.
+ * @return {number} Enlarged area.
  */
-goog.string.removeAll = function(s, ss) {
-  var re = new RegExp(goog.string.regExpEscape(ss), 'g');
-  return s.replace(re, '');
+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);
 };
 
 
 /**
- * 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}.
+ * @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.
  */
-goog.string.regExpEscape = function(s) {
-  return String(s).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
-      replace(/\x08/g, '\\x08');
+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);
+  var xCos = dx * cosRotation;
+  var xSin = dx * sinRotation;
+  var yCos = dy * cosRotation;
+  var ySin = dy * sinRotation;
+  var x = center[0];
+  var y = center[1];
+  var x0 = x - xCos + ySin;
+  var x1 = x - xCos - ySin;
+  var x2 = x + xCos - ySin;
+  var x3 = x + xCos + ySin;
+  var y0 = y - xSin - yCos;
+  var y1 = y - xSin + yCos;
+  var y2 = y + xSin + yCos;
+  var y3 = y + xSin - yCos;
+  return ol.extent.createOrUpdate(
+      Math.min(x0, x1, x2, x3), Math.min(y0, y1, y2, y3),
+      Math.max(x0, x1, x2, x3), Math.max(y0, y1, y2, y3),
+      opt_extent);
 };
 
 
 /**
- * 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.
+ * Get the height of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {number} Height.
+ * @api
  */
-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;
+ol.extent.getHeight = function(extent) {
+  return extent[3] - extent[1];
 };
 
 
 /**
- * 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}.
+ * @param {ol.Extent} extent1 Extent 1.
+ * @param {ol.Extent} extent2 Extent 2.
+ * @return {number} Intersection area.
  */
-goog.string.makeSafe = function(obj) {
-  return obj == null ? '' : String(obj);
+ol.extent.getIntersectionArea = function(extent1, extent2) {
+  var intersection = ol.extent.getIntersection(extent1, extent2);
+  return ol.extent.getArea(intersection);
 };
 
 
 /**
- * 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}.
+ * 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
  */
-goog.string.buildString = function(var_args) {
-  return Array.prototype.join.call(arguments, '');
+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;
 };
 
 
 /**
- * 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.
+ * @param {ol.Extent} extent Extent.
+ * @return {number} Margin.
  */
-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);
+ol.extent.getMargin = function(extent) {
+  return ol.extent.getWidth(extent) + ol.extent.getHeight(extent);
 };
 
 
 /**
- * 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.
+ * Get the size (width, height) of an extent.
+ * @param {ol.Extent} extent The extent.
+ * @return {ol.Size} The extent size.
+ * @api
  */
-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;
+ol.extent.getSize = function(extent) {
+  return [extent[2] - extent[0], extent[3] - extent[1]];
 };
 
 
 /**
- * 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
+ * Get the top left coordinate of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.Coordinate} Top left coordinate.
+ * @api
  */
-goog.string.compareElements_ = function(left, right) {
-  if (left < right) {
-    return -1;
-  } else if (left > right) {
-    return 1;
-  }
-  return 0;
+ol.extent.getTopLeft = function(extent) {
+  return [extent[0], extent[3]];
 };
 
 
 /**
- * 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.
+ * Get the top right coordinate of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.Coordinate} Top right coordinate.
+ * @api
  */
-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;
+ol.extent.getTopRight = function(extent) {
+  return [extent[2], extent[3]];
 };
 
 
 /**
- * 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.
+ * Get the width of an extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {number} Width.
+ * @api
  */
-goog.string.createUniqueString = function() {
-  return 'goog_' + goog.string.uniqueStringCounter_++;
+ol.extent.getWidth = function(extent) {
+  return extent[2] - extent[0];
 };
 
 
 /**
- * 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.
+ * Determine if one extent intersects another.
+ * @param {ol.Extent} extent1 Extent 1.
+ * @param {ol.Extent} extent2 Extent.
+ * @return {boolean} The two extents intersect.
+ * @api
  */
-goog.string.toNumber = function(str) {
-  var num = Number(str);
-  if (num == 0 && goog.string.isEmptyOrWhitespace(str)) {
-    return NaN;
-  }
-  return num;
+ol.extent.intersects = function(extent1, extent2) {
+  return extent1[0] <= extent2[2] &&
+      extent1[2] >= extent2[0] &&
+      extent1[1] <= extent2[3] &&
+      extent1[3] >= extent2[1];
 };
 
 
 /**
- * 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.
+ * Determine if an extent is empty.
+ * @param {ol.Extent} extent Extent.
+ * @return {boolean} Is empty.
+ * @api
  */
-goog.string.isLowerCamelCase = function(str) {
-  return /^[a-z]+([A-Z][a-z]*)*$/.test(str);
+ol.extent.isEmpty = function(extent) {
+  return extent[2] < extent[0] || extent[3] < extent[1];
 };
 
 
 /**
- * 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.
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
  */
-goog.string.isUpperCamelCase = function(str) {
-  return /^([A-Z][a-z]*)+$/.test(str);
+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;
+  }
 };
 
 
 /**
- * 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.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} value Value.
  */
-goog.string.toCamelCase = function(str) {
-  return String(str).replace(/\-([a-z])/g, function(all, match) {
-    return match.toUpperCase();
-  });
+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;
 };
 
 
 /**
- * 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.
+ * 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.
  */
-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 + ']+' : '';
+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;
+    }
 
-  var regexp = new RegExp('(^' + delimiters + ')([a-z])', 'g');
-  return str.replace(regexp, function(all, p1, p2) {
-    return p1 + p2.toUpperCase();
-  });
+  }
+  return intersects;
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.string.capitalize = function(str) {
-  return String(str.charAt(0)).toUpperCase() +
-      String(str.substr(1)).toLowerCase();
+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);
 };
 
+goog.provide('ol.obj');
+
 
 /**
- * Parse a string in decimal or hexidecimal ('0xFFFF') form.
+ * Polyfill for Object.assign().  Assigns enumerable and own properties from
+ * one or more source objects to a target object.
  *
- * 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.
+ * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
+ * @param {!Object} target The target object.
+ * @param {...Object} var_sources The source object(s).
+ * @return {!Object} The modified target object.
  */
-goog.string.parseInt = function(value) {
-  // Force finite numbers to strings.
-  if (isFinite(value)) {
-    value = String(value);
+ol.obj.assign = (typeof Object.assign === 'function') ? Object.assign : function(target, var_sources) {
+  if (target === undefined || target === null) {
+    throw new TypeError('Cannot convert undefined or null to object');
   }
 
-  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);
+  var output = Object(target);
+  for (var i = 1, ii = arguments.length; i < ii; ++i) {
+    var source = arguments[i];
+    if (source !== undefined && source !== null) {
+      for (var key in source) {
+        if (source.hasOwnProperty(key)) {
+          output[key] = source[key];
+        }
+      }
+    }
   }
-
-  return NaN;
+  return output;
 };
 
 
 /**
- * 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.
+ * Removes all properties from an object.
+ * @param {Object} object The object to clear.
  */
-
-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--;
+ol.obj.clear = function(object) {
+  for (var property in object) {
+    delete object[property];
   }
+};
 
-  // If there are remaining parts, append them to the end.
-  if (parts.length) {
-    returnVal.push(parts.join(separator));
-  }
 
-  return returnVal;
+/**
+ * Get an array of property values from an object.
+ * @param {Object<K,V>} object The object from which to get the values.
+ * @return {!Array<V>} The property values.
+ * @template K,V
+ */
+ol.obj.getValues = function(object) {
+  var values = [];
+  for (var property in object) {
+    values.push(object[property]);
+  }
+  return values;
 };
 
 
 /**
- * Computes the Levenshtein edit distance between two strings.
- * @param {string} a
- * @param {string} b
- * @return {number} The edit distance between the two strings.
+ * Determine if an object has any properties.
+ * @param {Object} object The object to check.
+ * @return {boolean} The object is empty.
  */
-goog.string.editDistance = function(a, b) {
-  var v0 = [];
-  var v1 = [];
-
-  if (a == b) {
-    return 0;
+ol.obj.isEmpty = function(object) {
+  var property;
+  for (property in object) {
+    return false;
   }
+  return !property;
+};
 
-  if (!a.length || !b.length) {
-    return Math.max(a.length, b.length);
-  }
+/**
+ * @license
+ * Latitude/longitude spherical geodesy formulae taken from
+ * http://www.movable-type.co.uk/scripts/latlong.html
+ * Licensed under CC-BY-3.0.
+ */
 
-  for (var i = 0; i < b.length + 1; i++) {
-    v0[i] = i;
-  }
+goog.provide('ol.Sphere');
 
-  for (var i = 0; i < a.length; i++) {
-    v1[0] = i + 1;
+goog.require('ol.math');
 
-    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];
-    }
-  }
+/**
+ * @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;
 
-  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.
+ * Returns the geodesic area for a list of coordinates.
  *
- * 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.
+ * [Reference](https://trs-new.jpl.nasa.gov/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
  *
- * @author agrieve@google.com (Andrew Grieve)
+ * @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;
+};
 
-goog.provide('goog.asserts');
-goog.provide('goog.asserts.AssertionError');
 
-goog.require('goog.debug.Error');
-goog.require('goog.dom.NodeType');
-goog.require('goog.string');
+/**
+ * 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));
+};
 
 
 /**
- * @define {boolean} Whether to strip out asserts or to leave them in.
+ * 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.
  */
-goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG);
+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');
 
 
 /**
- * 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
+ * The normal sphere.
+ * @const
+ * @type {ol.Sphere}
  */
-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);
+ol.sphere.NORMAL = new ol.Sphere(6370997);
 
+goog.provide('ol.proj.Units');
 
-/** @override */
-goog.asserts.AssertionError.prototype.name = 'AssertionError';
+goog.require('ol.sphere.NORMAL');
 
 
 /**
- * The default error handler.
- * @param {!goog.asserts.AssertionError} e The exception to be handled.
+ * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, `'tile-pixels'` or
+ * `'us-ft'`.
+ * @enum {string}
  */
-goog.asserts.DEFAULT_ERROR_HANDLER = function(e) { throw e; };
+ol.proj.Units = {
+  DEGREES: 'degrees',
+  FEET: 'ft',
+  METERS: 'm',
+  PIXELS: 'pixels',
+  TILE_PIXELS: 'tile-pixels',
+  USFEET: 'us-ft'
+};
 
 
 /**
- * The handler responsible for throwing or logging assertion errors.
- * @private {function(!goog.asserts.AssertionError)}
+ * Meters per unit lookup table.
+ * @const
+ * @type {Object.<ol.proj.Units, number>}
+ * @api
  */
-goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER;
+ol.proj.Units.METERS_PER_UNIT = {};
+ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.DEGREES] =
+    2 * Math.PI * ol.sphere.NORMAL.radius / 360;
+ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.FEET] = 0.3048;
+ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.METERS] = 1;
+ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.USFEET] = 1200 / 3937;
+
+goog.provide('ol.proj.proj4');
 
 
 /**
- * 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
+ * @type {Proj4}
  */
-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);
-};
+ol.proj.proj4.cache_ = null;
 
 
 /**
- * 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
+ * Store the proj4 function.
+ * @param {Proj4} proj4 The proj4 function.
  */
-goog.asserts.setErrorHandler = function(errorHandler) {
-  if (goog.asserts.ENABLE_ASSERTS) {
-    goog.asserts.errorHandler_ = errorHandler;
-  }
+ol.proj.proj4.set = function(proj4) {
+  ol.proj.proj4.cache_ = proj4;
 };
 
 
 /**
- * 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.
+ * Get proj4.
+ * @return {Proj4} The proj4 function set above or available globally.
  */
-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;
+ol.proj.proj4.get = function() {
+  return ol.proj.proj4.cache_ || window['proj4'];
 };
 
+goog.provide('ol.proj.Projection');
+
+goog.require('ol');
+goog.require('ol.proj.Units');
+goog.require('ol.proj.proj4');
+
 
 /**
- * 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:
+ * @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.ProjectionLike} which means the simple string
+ * code will suffice.
  *
- * <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>
+ * 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). To set an alternative
+ * namespace for proj4, use {@link ol.proj.setProj4}.
  *
- * @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.
+ * @constructor
+ * @param {olx.ProjectionOptions} options Projection options.
+ * @struct
+ * @api
  */
-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)));
-  }
-};
+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;
 
-/**
- * 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);
-};
+ /**
+  * @private
+  * @type {ol.Extent}
+  */
+  this.worldExtent_ = options.worldExtent !== undefined ?
+     options.worldExtent : null;
 
+ /**
+  * @private
+  * @type {string}
+  */
+  this.axisOrientation_ = options.axisOrientation !== undefined ?
+     options.axisOrientation : 'enu';
 
-/**
- * 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.
+ /**
+  * @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|undefined}
  */
-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));
+  this.getPointResolutionFunc_ = options.getPointResolution;
+
+ /**
+  * @private
+  * @type {ol.tilegrid.TileGrid}
+  */
+  this.defaultTileGrid_ = null;
+
+ /**
+  * @private
+  * @type {number|undefined}
+  */
+  this.metersPerUnit_ = options.metersPerUnit;
+
+  var code = options.code;
+  if (ol.ENABLE_PROJ4JS) {
+    var proj4js = ol.proj.proj4.get();
+    if (typeof proj4js == 'function') {
+      var def = proj4js.defs(code);
+      if (def !== undefined) {
+        if (def.axis !== undefined && options.axisOrientation === undefined) {
+          this.axisOrientation_ = def.axis;
+        }
+        if (options.metersPerUnit === undefined) {
+          this.metersPerUnit_ = def.to_meter;
+        }
+        if (options.units === undefined) {
+          this.units_ = def.units;
+        }
+      }
+    }
   }
-  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.
+ * @return {boolean} The projection is suitable for wrapping the x-axis
  */
-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);
+ol.proj.Projection.prototype.canWrapX = function() {
+  return this.canWrapX_;
 };
 
 
 /**
- * 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.
+ * Get the code for this projection, e.g. 'EPSG:4326'.
+ * @return {string} Code.
+ * @api
  */
-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);
+ol.proj.Projection.prototype.getCode = function() {
+  return this.code_;
 };
 
 
 /**
- * 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.
+ * Get the validity extent for this projection.
+ * @return {ol.Extent} Extent.
+ * @api
  */
-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);
+ol.proj.Projection.prototype.getExtent = function() {
+  return this.extent_;
 };
 
 
 /**
- * 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.
+ * Get the units of this projection.
+ * @return {ol.proj.Units} Units.
+ * @api
  */
-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);
+ol.proj.Projection.prototype.getUnits = function() {
+  return this.units_;
 };
 
 
 /**
- * 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.
+ * Get the amount of meters per unit of this projection.  If the projection is
+ * not configured with `metersPerUnit` or a units identifier, the return is
+ * `undefined`.
+ * @return {number|undefined} Meters.
+ * @api
  */
-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);
+ol.proj.Projection.prototype.getMetersPerUnit = function() {
+  return this.metersPerUnit_ || ol.proj.Units.METERS_PER_UNIT[this.units_];
 };
 
 
 /**
- * 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
+ * Get the world extent for this projection.
+ * @return {ol.Extent} Extent.
+ * @api
  */
-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;
+ol.proj.Projection.prototype.getWorldExtent = function() {
+  return this.worldExtent_;
 };
 
 
 /**
- * Checks that no enumerable keys are present in Object.prototype. Such keys
- * would break most code that use {@code for (var ... in ...)} loops.
+ * 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.
  */
-goog.asserts.assertObjectPrototypeIsIntact = function() {
-  for (var key in Object.prototype) {
-    goog.asserts.fail(key + ' should not be enumerable in Object.prototype.');
-  }
+ol.proj.Projection.prototype.getAxisOrientation = function() {
+  return this.axisOrientation_;
 };
 
 
 /**
- * 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
+ * Is this projection a global projection which spans the whole world?
+ * @return {boolean} Whether the projection is global.
+ * @api
  */
-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;
-  }
+ol.proj.Projection.prototype.isGlobal = function() {
+  return this.global_;
 };
 
-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);
+* Set if the projection is a global projection which spans the whole world
+* @param {boolean} global Whether the projection is global.
+* @api
+*/
+ol.proj.Projection.prototype.setGlobal = function(global) {
+  this.global_ = global;
+  this.canWrapX_ = !!(global && this.extent_);
 };
 
 
 /**
- * @param {number} x X.
- * @return {number} Hyperbolic cosine of x.
+ * @return {ol.tilegrid.TileGrid} The default tile grid.
  */
-ol.math.cosh = function(x) {
-  return (Math.exp(x) + Math.exp(-x)) / 2;
+ol.proj.Projection.prototype.getDefaultTileGrid = function() {
+  return this.defaultTileGrid_;
 };
 
 
 /**
- * @param {number} x X.
- * @return {number} Hyperbolic cotangent of x.
+ * @param {ol.tilegrid.TileGrid} tileGrid The default tile grid.
  */
-ol.math.coth = function(x) {
-  var expMinusTwoX = Math.exp(-2 * x);
-  return (1 + expMinusTwoX) / (1 - expMinusTwoX);
+ol.proj.Projection.prototype.setDefaultTileGrid = function(tileGrid) {
+  this.defaultTileGrid_ = tileGrid;
 };
 
 
 /**
- * @param {number} x X.
- * @return {number} Hyperbolic cosecant of x.
+ * Set the validity extent for this projection.
+ * @param {ol.Extent} extent Extent.
+ * @api
  */
-ol.math.csch = function(x) {
-  return 2 / (Math.exp(x) - Math.exp(-x));
+ol.proj.Projection.prototype.setExtent = function(extent) {
+  this.extent_ = extent;
+  this.canWrapX_ = !!(this.global_ && extent);
 };
 
 
 /**
- * @param {number} x X.
- * @return {number} The smallest power of two greater than or equal to x.
+ * Set the world extent for this projection.
+ * @param {ol.Extent} worldExtent World extent
+ *     [minlon, minlat, maxlon, maxlat].
+ * @api
  */
-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));
+ol.proj.Projection.prototype.setWorldExtent = function(worldExtent) {
+  this.worldExtent_ = worldExtent;
 };
 
 
 /**
- * @param {number} x X.
- * @return {number} Hyperbolic secant of x.
+ * Set the getPointResolution function (see {@link ol.proj#getPointResolution}
+ * for this projection.
+ * @param {function(number, ol.Coordinate):number} func Function
+ * @api
  */
-ol.math.sech = function(x) {
-  return 2 / (Math.exp(x) + Math.exp(-x));
+ol.proj.Projection.prototype.setGetPointResolution = function(func) {
+  this.getPointResolutionFunc_ = func;
 };
 
 
 /**
- * @param {number} x X.
- * @return {number} Hyperbolic sine of x.
+ * Get the custom point resolution function for this projection (if set).
+ * @return {function(number, ol.Coordinate):number|undefined} The custom point
+ * resolution function (if set).
  */
-ol.math.sinh = function(x) {
-  return (Math.exp(x) - Math.exp(-x)) / 2;
+ol.proj.Projection.prototype.getPointResolutionFunc = function() {
+  return this.getPointResolutionFunc_;
 };
 
+goog.provide('ol.proj.EPSG3857');
+
+goog.require('ol');
+goog.require('ol.math');
+goog.require('ol.proj.Projection');
+goog.require('ol.proj.Units');
+
 
 /**
- * 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.
+ * @classdesc
+ * Projection object for web/spherical Mercator (EPSG:3857).
+ *
+ * @constructor
+ * @extends {ol.proj.Projection}
+ * @param {string} code Code.
+ * @private
  */
-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;
+ol.proj.EPSG3857.Projection_ = function(code) {
+  ol.proj.Projection.call(this, {
+    code: code,
+    units: ol.proj.Units.METERS,
+    extent: ol.proj.EPSG3857.EXTENT,
+    global: true,
+    worldExtent: ol.proj.EPSG3857.WORLD_EXTENT,
+    getPointResolution: function(resolution, point) {
+      return resolution / ol.math.cosh(point[1] / ol.proj.EPSG3857.RADIUS);
     }
-  }
-  return ol.math.squaredDistance(x, y, x1, y1);
+  });
 };
+ol.inherits(ol.proj.EPSG3857.Projection_, ol.proj.Projection);
 
 
 /**
- * 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.
+ * @const
+ * @type {number}
  */
-ol.math.squaredDistance = function(x1, y1, x2, y2) {
-  var dx = x2 - x1;
-  var dy = y2 - y1;
-  return dx * dx + dy * dy;
-};
+ol.proj.EPSG3857.RADIUS = 6378137;
 
 
 /**
- * @param {number} x X.
- * @return {number} Hyperbolic tangent of x.
+ * @const
+ * @type {number}
  */
-ol.math.tanh = function(x) {
-  var expMinusTwoX = Math.exp(-2 * x);
-  return (1 - expMinusTwoX) / (1 + expMinusTwoX);
-};
+ol.proj.EPSG3857.HALF_SIZE = Math.PI * ol.proj.EPSG3857.RADIUS;
 
-goog.provide('ol.CenterConstraint');
-goog.provide('ol.CenterConstraintType');
 
-goog.require('ol.math');
+/**
+ * @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
+];
 
 
 /**
- * @typedef {function((ol.Coordinate|undefined)): (ol.Coordinate|undefined)}
+ * @const
+ * @type {ol.Extent}
  */
-ol.CenterConstraintType;
+ol.proj.EPSG3857.WORLD_EXTENT = [-180, -85, 180, 85];
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @return {ol.CenterConstraintType}
+ * Lists several projection codes with the same meaning as EPSG:3857.
+ *
+ * @type {Array.<string>}
  */
-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;
-        }
-      });
-};
+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'
+];
 
 
 /**
- * @param {ol.Coordinate|undefined} center Center.
- * @return {ol.Coordinate|undefined} Center.
+ * Projections equal to EPSG:3857.
+ *
+ * @const
+ * @type {Array.<ol.proj.Projection>}
  */
-ol.CenterConstraint.none = function(center) {
-  return center;
-};
+ol.proj.EPSG3857.PROJECTIONS = ol.proj.EPSG3857.CODES.map(function(code) {
+  return new ol.proj.EPSG3857.Projection_(code);
+});
 
-// 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.
+ * Transformation from EPSG:4326 to EPSG:3857.
  *
- * @author arv@google.com (Erik Arvidsson)
+ * @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.
  */
-
-
-goog.provide('goog.array');
-goog.provide('goog.array.ArrayLike');
-
-goog.require('goog.asserts');
+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);
+    }
+  }
+  var halfSize = ol.proj.EPSG3857.HALF_SIZE;
+  for (var i = 0; i < length; i += dimension) {
+    output[i] = halfSize * input[i] / 180;
+    var y = ol.proj.EPSG3857.RADIUS *
+        Math.log(Math.tan(Math.PI * (input[i + 1] + 90) / 360));
+    if (y > halfSize) {
+      y = halfSize;
+    } else if (y < -halfSize) {
+      y = -halfSize;
+    }
+    output[i + 1] = y;
+  }
+  return output;
+};
 
 
 /**
- * @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.
+ * Transformation from EPSG:3857 to EPSG:4326.
  *
- * Setting goog.TRUSTED_SITE to false will automatically set
- * NATIVE_ARRAY_PROTOTYPES to false.
+ * @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.
  */
-goog.define('goog.NATIVE_ARRAY_PROTOTYPES', goog.TRUSTED_SITE);
+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);
+    }
+  }
+  for (var i = 0; i < length; i += dimension) {
+    output[i] = 180 * input[i] / ol.proj.EPSG3857.HALF_SIZE;
+    output[i + 1] = 360 * Math.atan(
+        Math.exp(input[i + 1] / ol.proj.EPSG3857.RADIUS)) / Math.PI - 90;
+  }
+  return output;
+};
 
+goog.provide('ol.sphere.WGS84');
 
-/**
- * @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);
+goog.require('ol.Sphere');
 
 
 /**
- * @typedef {Array|NodeList|Arguments|{length: number}}
+ * A sphere with radius equal to the semi-major axis of the WGS84 ellipsoid.
+ * @const
+ * @type {ol.Sphere}
  */
-goog.array.ArrayLike;
+ol.sphere.WGS84 = new ol.Sphere(6378137);
 
+goog.provide('ol.proj.EPSG4326');
 
-/**
- * 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];
-};
+goog.require('ol');
+goog.require('ol.proj.Projection');
+goog.require('ol.proj.Units');
+goog.require('ol.sphere.WGS84');
 
 
 /**
- * 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
+ * @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
  */
-goog.array.last = goog.array.peek;
+ol.proj.EPSG4326.Projection_ = function(code, opt_axisOrientation) {
+  ol.proj.Projection.call(this, {
+    code: code,
+    units: ol.proj.Units.DEGREES,
+    extent: ol.proj.EPSG4326.EXTENT,
+    axisOrientation: opt_axisOrientation,
+    global: true,
+    metersPerUnit: ol.proj.EPSG4326.METERS_PER_UNIT,
+    worldExtent: ol.proj.EPSG4326.EXTENT
+  });
+};
+ol.inherits(ol.proj.EPSG4326.Projection_, ol.proj.Projection);
 
 
 /**
- * Reference to the original {@code Array.prototype}.
- * @private
+ * Extent of the EPSG:4326 projection which is the whole world.
+ *
+ * @const
+ * @type {ol.Extent}
  */
-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.
+ol.proj.EPSG4326.EXTENT = [-180, -90, 180, 90];
 
 
 /**
- * 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
+ * @const
+ * @type {number}
  */
-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;
-    };
+ol.proj.EPSG4326.METERS_PER_UNIT = Math.PI * ol.sphere.WGS84.radius / 180;
 
 
 /**
- * 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}
+ * Projections equal to EPSG:4326.
  *
- * @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
+ * @const
+ * @type {Array.<ol.proj.Projection>}
  */
-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);
-      }
+ol.proj.EPSG4326.PROJECTIONS = [
+  new ol.proj.EPSG4326.Projection_('CRS:84'),
+  new ol.proj.EPSG4326.Projection_('EPSG:4326', 'neu'),
+  new ol.proj.EPSG4326.Projection_('urn:ogc:def:crs:EPSG::4326', 'neu'),
+  new ol.proj.EPSG4326.Projection_('urn:ogc:def:crs:EPSG:6.6:4326', 'neu'),
+  new ol.proj.EPSG4326.Projection_('urn:ogc:def:crs:OGC:1.3:CRS84'),
+  new ol.proj.EPSG4326.Projection_('urn:ogc:def:crs:OGC:2:84'),
+  new ol.proj.EPSG4326.Projection_('http://www.opengis.net/gml/srs/epsg.xml#4326', 'neu'),
+  new ol.proj.EPSG4326.Projection_('urn:x-ogc:def:crs:EPSG:4326', 'neu')
+];
 
-      for (var i = fromIndex; i >= 0; i--) {
-        if (i in arr && arr[i] === obj)
-          return i;
-      }
-      return -1;
-    };
+goog.provide('ol.proj.projections');
 
 
 /**
- * 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
+ * @private
+ * @type {Object.<string, ol.proj.Projection>}
  */
-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);
-        }
-      }
-    };
+ol.proj.projections.cache_ = {};
 
 
 /**
- * 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
+ * Clear the projections cache.
  */
-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);
-    }
-  }
+ol.proj.projections.clear = function() {
+  ol.proj.projections.cache_ = {};
 };
 
 
 /**
- * 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
+ * Get a cached projection by code.
+ * @param {string} code The code for the projection.
+ * @return {ol.proj.Projection} The projection (if cached).
  */
-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;
-    };
+ol.proj.projections.get = function(code) {
+  var projections = ol.proj.projections.cache_;
+  return projections[code] || null;
+};
 
 
 /**
- * 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;
-    };
+ * Add a projection to the cache.
+ * @param {string} code The projection code.
+ * @param {ol.proj.Projection} projection The projection to cache.
+ */
+ol.proj.projections.add = function(code, projection) {
+  var projections = ol.proj.projections.cache_;
+  projections[code] = projection;
+};
 
+goog.provide('ol.proj.transforms');
 
-/**
- * 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;
-    };
+goog.require('ol.obj');
 
 
 /**
- * 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
+ * @private
+ * @type {Object.<string, Object.<string, ol.TransformFunction>>}
  */
-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;
-    };
+ol.proj.transforms.cache_ = {};
 
 
 /**
- * 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
+ * Clear the transform cache.
  */
-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;
-    };
+ol.proj.transforms.clear = function() {
+  ol.proj.transforms.cache_ = {};
+};
 
 
 /**
- * Counts the array elements that fulfill the predicate, i.e. for which the
- * callback function returns true. Skips holes in the array.
+ * Registers a conversion function to convert coordinates from the source
+ * projection to the destination projection.
  *
- * @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
+ * @param {ol.proj.Projection} source Source.
+ * @param {ol.proj.Projection} destination Destination.
+ * @param {ol.TransformFunction} transformFn Transform.
  */
-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;
+ol.proj.transforms.add = function(source, destination, transformFn) {
+  var sourceCode = source.getCode();
+  var destinationCode = destination.getCode();
+  var transforms = ol.proj.transforms.cache_;
+  if (!(sourceCode in transforms)) {
+    transforms[sourceCode] = {};
+  }
+  transforms[sourceCode][destinationCode] = transformFn;
 };
 
 
 /**
- * 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
+ * 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.
  */
-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];
+ol.proj.transforms.remove = function(source, destination) {
+  var sourceCode = source.getCode();
+  var destinationCode = destination.getCode();
+  var transforms = ol.proj.transforms.cache_;
+  var transform = transforms[sourceCode][destinationCode];
+  delete transforms[sourceCode][destinationCode];
+  if (ol.obj.isEmpty(transforms[sourceCode])) {
+    delete transforms[sourceCode];
+  }
+  return transform;
 };
 
 
 /**
- * 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
+ * Get a transform given a source code and a destination code.
+ * @param {string} sourceCode The code for the source projection.
+ * @param {string} destinationCode The code for the destination projection.
+ * @return {ol.TransformFunction|undefined} The transform function (if found).
  */
-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;
-    }
+ol.proj.transforms.get = function(sourceCode, destinationCode) {
+  var transform;
+  var transforms = ol.proj.transforms.cache_;
+  if (sourceCode in transforms && destinationCode in transforms[sourceCode]) {
+    transform = transforms[sourceCode][destinationCode];
   }
-  return -1;
+  return transform;
 };
 
+goog.provide('ol.proj');
+
+goog.require('ol');
+goog.require('ol.extent');
+goog.require('ol.proj.EPSG3857');
+goog.require('ol.proj.EPSG4326');
+goog.require('ol.proj.Projection');
+goog.require('ol.proj.Units');
+goog.require('ol.proj.proj4');
+goog.require('ol.proj.projections');
+goog.require('ol.proj.transforms');
+goog.require('ol.sphere.NORMAL');
+
 
 /**
- * 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
+ * Meters per unit lookup table.
+ * @const
+ * @type {Object.<ol.proj.Units, number>}
+ * @api
  */
-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];
-};
+ol.proj.METERS_PER_UNIT = ol.proj.Units.METERS_PER_UNIT;
+
+
+if (ol.ENABLE_PROJ4JS) {
+  /**
+   * Register proj4. If not explicitly registered, it will be assumed that
+   * proj4js will be loaded in the global namespace. For example in a
+   * browserify ES6 environment you could use:
+   *
+   *     import ol from 'openlayers';
+   *     import proj4 from 'proj4';
+   *     ol.proj.setProj4(proj4);
+   *
+   * @param {Proj4} proj4 Proj4.
+   * @api
+   */
+  ol.proj.setProj4 = function(proj4) {
+    ol.proj.proj4.set(proj4);
+  };
+}
 
 
 /**
- * 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
+ * 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
+ * by default 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.
+ * A custom function can be provided for a specific projection, either
+ * by setting the `getPointResolution` option in the
+ * {@link ol.proj.Projection} constructor or by using
+ * {@link ol.proj.Projection#setGetPointResolution} to change an existing
+ * projection object.
+ * @param {ol.ProjectionLike} projection The projection.
+ * @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.
+ * @api
  */
-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;
+ol.proj.getPointResolution = function(projection, resolution, point) {
+  projection = ol.proj.get(projection);
+  var pointResolution;
+  var getter = projection.getPointResolutionFunc();
+  if (getter) {
+    pointResolution = getter(resolution, point);
+  } else {
+    var units = projection.getUnits();
+    if (units == ol.proj.Units.DEGREES) {
+      pointResolution = 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(projection, 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));
+      pointResolution = (width + height) / 2;
+      var metersPerUnit = projection.getMetersPerUnit();
+      if (metersPerUnit !== undefined) {
+        pointResolution /= metersPerUnit;
+      }
     }
   }
-  return -1;
+  return pointResolution;
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.array.contains = function(arr, obj) {
-  return goog.array.indexOf(arr, obj) >= 0;
+ol.proj.addEquivalentProjections = function(projections) {
+  ol.proj.addProjections(projections);
+  projections.forEach(function(source) {
+    projections.forEach(function(destination) {
+      if (source !== destination) {
+        ol.proj.transforms.add(source, destination, ol.proj.cloneTransform);
+      }
+    });
+  });
 };
 
 
 /**
- * Whether the array is empty.
- * @param {goog.array.ArrayLike} arr The array to test.
- * @return {boolean} true if empty.
+ * 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..
  */
-goog.array.isEmpty = function(arr) {
-  return arr.length == 0;
+ol.proj.addEquivalentTransforms = function(projections1, projections2, forwardTransform, inverseTransform) {
+  projections1.forEach(function(projection1) {
+    projections2.forEach(function(projection2) {
+      ol.proj.transforms.add(projection1, projection2, forwardTransform);
+      ol.proj.transforms.add(projection2, projection1, inverseTransform);
+    });
+  });
 };
 
 
 /**
- * Clears the array.
- * @param {goog.array.ArrayLike} arr Array or array like object to clear.
+ * 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
  */
-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;
+ol.proj.addProjection = function(projection) {
+  ol.proj.projections.add(projection.getCode(), projection);
+  ol.proj.transforms.add(projection, projection, ol.proj.cloneTransform);
 };
 
 
 /**
- * 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
+ * @param {Array.<ol.proj.Projection>} projections Projections.
  */
-goog.array.insert = function(arr, obj) {
-  if (!goog.array.contains(arr, obj)) {
-    arr.push(obj);
-  }
+ol.proj.addProjections = function(projections) {
+  projections.forEach(ol.proj.addProjection);
 };
 
 
 /**
- * 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.
+ * Clear all cached projections and transforms.
  */
-goog.array.insertAt = function(arr, obj, opt_i) {
-  goog.array.splice(arr, opt_i, 0, obj);
+ol.proj.clearAllProjections = function() {
+  ol.proj.projections.clear();
+  ol.proj.transforms.clear();
 };
 
 
 /**
- * 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.
+ * @param {ol.proj.Projection|string|undefined} projection Projection.
+ * @param {string} defaultCode Default code.
+ * @return {ol.proj.Projection} Projection.
  */
-goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) {
-  goog.partial(goog.array.splice, arr, opt_i, 0).apply(null, elementsToAdd);
+ol.proj.createProjection = function(projection, defaultCode) {
+  if (!projection) {
+    return ol.proj.get(defaultCode);
+  } else if (typeof projection === 'string') {
+    return ol.proj.get(projection);
+  } else {
+    return /** @type {ol.proj.Projection} */ (projection);
+  }
 };
 
 
 /**
- * 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
+ * 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.ProjectionLike} source Source projection.
+ * @param {ol.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
  */
-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);
-  }
+ol.proj.addCoordinateTransforms = function(source, destination, forward, inverse) {
+  var sourceProj = ol.proj.get(source);
+  var destProj = ol.proj.get(destination);
+  ol.proj.transforms.add(sourceProj, destProj,
+      ol.proj.createTransformFromCoordinateTransform(forward));
+  ol.proj.transforms.add(destProj, sourceProj,
+      ol.proj.createTransformFromCoordinateTransform(inverse));
 };
 
 
 /**
- * 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
+ * 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.
  */
-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;
+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;
+      });
 };
 
 
 /**
- * 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.
+ * 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.ProjectionLike=} opt_projection Target projection. The
+ *     default is Web Mercator, i.e. 'EPSG:3857'.
+ * @return {ol.Coordinate} Coordinate projected to the target projection.
+ * @api
  */
-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;
+ol.proj.fromLonLat = function(coordinate, opt_projection) {
+  return ol.proj.transform(coordinate, 'EPSG:4326',
+      opt_projection !== undefined ? opt_projection : 'EPSG:3857');
 };
 
 
 /**
- * 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
+ * Transforms a coordinate to longitude/latitude.
+ * @param {ol.Coordinate} coordinate Projected coordinate.
+ * @param {ol.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
  */
-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;
+ol.proj.toLonLat = function(coordinate, opt_projection) {
+  return ol.proj.transform(coordinate,
+      opt_projection !== undefined ? opt_projection : 'EPSG:3857', 'EPSG:4326');
 };
 
 
 /**
- * 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
+ * Fetches a Projection object for the code specified.
+ *
+ * @param {ol.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
  */
-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++;
+ol.proj.get = function(projectionLike) {
+  var projection = null;
+  if (projectionLike instanceof ol.proj.Projection) {
+    projection = projectionLike;
+  } else if (typeof projectionLike === 'string') {
+    var code = projectionLike;
+    projection = ol.proj.projections.get(code);
+    if (ol.ENABLE_PROJ4JS) {
+      var proj4js = ol.proj.proj4.get();
+      if (!projection && typeof proj4js == 'function' &&
+          proj4js.defs(code) !== undefined) {
+        projection = new ol.proj.Projection({code: code});
+        ol.proj.addProjection(projection);
       }
     }
-  });
-  return removedCount;
+  }
+  return projection;
 };
 
 
 /**
- * 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.
+ * 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 {...*} 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.
+ * @param {ol.proj.Projection} projection1 Projection 1.
+ * @param {ol.proj.Projection} projection2 Projection 2.
+ * @return {boolean} Equivalent.
+ * @api
  */
-goog.array.concat = function(var_args) {
-  return goog.array.ARRAY_PROTOTYPE_.concat.apply(
-      goog.array.ARRAY_PROTOTYPE_, arguments);
+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;
+  }
 };
 
 
 /**
- * Returns a new array that contains the contents of all the arrays passed.
- * @param {...!Array<T>} var_args
- * @return {!Array<T>}
- * @template T
+ * 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.ProjectionLike} source Source.
+ * @param {ol.ProjectionLike} destination Destination.
+ * @return {ol.TransformFunction} Transform function.
+ * @api
  */
-goog.array.join = function(var_args) {
-  return goog.array.ARRAY_PROTOTYPE_.concat.apply(
-      goog.array.ARRAY_PROTOTYPE_, arguments);
+ol.proj.getTransform = function(source, destination) {
+  var sourceProjection = ol.proj.get(source);
+  var destinationProjection = ol.proj.get(destination);
+  return ol.proj.getTransformFromProjections(
+      sourceProjection, destinationProjection);
 };
 
 
 /**
- * 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
+ * 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.
  */
-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];
+ol.proj.getTransformFromProjections = function(sourceProjection, destinationProjection) {
+  var sourceCode = sourceProjection.getCode();
+  var destinationCode = destinationProjection.getCode();
+  var transform = ol.proj.transforms.get(sourceCode, destinationCode);
+  if (ol.ENABLE_PROJ4JS && !transform) {
+    var proj4js = ol.proj.proj4.get();
+    if (typeof proj4js == 'function') {
+      var sourceDef = proj4js.defs(sourceCode);
+      var destinationDef = proj4js.defs(destinationCode);
+
+      if (sourceDef !== undefined && destinationDef !== undefined) {
+        if (sourceDef === destinationDef) {
+          ol.proj.addEquivalentProjections([destinationProjection, sourceProjection]);
+        } else {
+          var proj4Transform = proj4js(destinationCode, sourceCode);
+          ol.proj.addCoordinateTransforms(destinationProjection, sourceProjection,
+              proj4Transform.forward, proj4Transform.inverse);
+        }
+        transform = ol.proj.transforms.get(sourceCode, destinationCode);
+      }
     }
-    return rv;
   }
-  return [];
+  if (!transform) {
+    transform = ol.proj.identityTransform;
+  }
+  return transform;
 };
 
 
 /**
- * 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
+ * @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).
  */
-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);
+ol.proj.identityTransform = function(input, opt_output, opt_dimension) {
+  if (opt_output !== undefined && input !== opt_output) {
+    for (var i = 0, ii = input.length; i < ii; ++i) {
+      opt_output[i] = input[i];
     }
+    input = opt_output;
   }
+  return input;
 };
 
 
 /**
- * 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
+ * @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).
  */
-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);
+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 {
-    return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start, opt_end);
+    output = input.slice();
   }
+  return output;
 };
 
 
 /**
- * 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.
+ * Transforms a coordinate from source projection to destination projection.
+ * This returns a new coordinate (and does not modify the original).
  *
- * Runtime: N,
- * Worstcase space: 2N (no dupes)
+ * See {@link ol.proj.transformExtent} for extent transformation.
+ * See the transform method of {@link ol.geom.Geometry} and its subclasses for
+ * geometry transforms.
  *
- * @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
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {ol.ProjectionLike} source Source projection-like.
+ * @param {ol.ProjectionLike} destination Destination projection-like.
+ * @return {ol.Coordinate} Coordinate.
+ * @api
  */
-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;
+ol.proj.transform = function(coordinate, source, destination) {
+  var transformFn = ol.proj.getTransform(source, destination);
+  return transformFn(coordinate, undefined, coordinate.length);
 };
 
 
 /**
- * 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>
+ * Transforms an extent from source projection to destination projection.  This
+ * returns a new extent (and does not modify the original).
  *
- * @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
+ * @param {ol.Extent} extent The extent to transform.
+ * @param {ol.ProjectionLike} source Source projection-like.
+ * @param {ol.ProjectionLike} destination Destination projection-like.
+ * @return {ol.Extent} The transformed extent.
+ * @api
  */
-goog.array.sort = function(arr, opt_compareFn) {
-  // TODO(arv): Update type annotation since null is not accepted.
-  arr.sort(opt_compareFn || goog.array.defaultCompare);
+ol.proj.transformExtent = function(extent, source, destination) {
+  var transformFn = ol.proj.getTransform(source, destination);
+  return ol.extent.applyTransform(extent, transformFn);
 };
 
 
 /**
- * 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.
+ * Transforms the given point to the destination projection.
  *
- * @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
+ * @param {ol.Coordinate} point Point.
+ * @param {ol.proj.Projection} sourceProjection Source projection.
+ * @param {ol.proj.Projection} destinationProjection Destination projection.
+ * @return {ol.Coordinate} Point.
  */
-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;
-  }
+ol.proj.transformWithProjections = function(point, sourceProjection, destinationProjection) {
+  var transformFn = ol.proj.getTransformFromProjections(
+      sourceProjection, destinationProjection);
+  return transformFn(point);
 };
 
-
 /**
- * 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
+ * Add transforms to and from EPSG:4326 and EPSG:3857.  This function is called
+ * by when this module is executed and should only need to be called again after
+ * `ol.proj.clearAllProjections()` is called (e.g. in tests).
  */
-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));
-  });
+ol.proj.addCommon = function() {
+  // Add transformations that don't alter coordinates to convert within set of
+  // projections with equal meaning.
+  ol.proj.addEquivalentProjections(ol.proj.EPSG3857.PROJECTIONS);
+  ol.proj.addEquivalentProjections(ol.proj.EPSG4326.PROJECTIONS);
+  // Add transformations to convert EPSG:4326 like coordinates to EPSG:3857 like
+  // coordinates and back.
+  ol.proj.addEquivalentTransforms(
+      ol.proj.EPSG4326.PROJECTIONS,
+      ol.proj.EPSG3857.PROJECTIONS,
+      ol.proj.EPSG3857.fromEPSG4326,
+      ol.proj.EPSG3857.toEPSG4326);
 };
 
+ol.proj.addCommon();
+
+goog.provide('ol.tilecoord');
+
 
 /**
- * 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.
+ * @param {number} z Z.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @param {ol.TileCoord=} opt_tileCoord Tile coordinate.
+ * @return {ol.TileCoord} Tile coordinate.
  */
-goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) {
-  goog.array.sortByKey(arr,
-      function(obj) { return obj[key]; },
-      opt_compareFn);
+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];
+  }
 };
 
 
 /**
- * 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
+ * @param {number} z Z.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @return {string} Key.
  */
-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;
+ol.tilecoord.getKeyZXY = function(z, x, y) {
+  return z + '/' + x + '/' + y;
 };
 
 
 /**
- * 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.
+ * @param {ol.TileCoord} tileCoord Tile coord.
+ * @return {number} Hash.
  */
-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;
+ol.tilecoord.hash = function(tileCoord) {
+  return (tileCoord[1] << tileCoord[0]) + tileCoord[2];
 };
 
 
 /**
- * 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
+ * @param {ol.TileCoord} tileCoord Tile coord.
+ * @return {string} Quad key.
  */
-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;
+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) {
+    // 48 is charCode for 0 - '0'.charCodeAt(0)
+    charCode = 48;
+    if (tileCoord[1] & mask) {
+      charCode += 1;
     }
+    if (tileCoord[2] & mask) {
+      charCode += 2;
+    }
+    digits[i] = String.fromCharCode(charCode);
+    mask >>= 1;
   }
-  return goog.array.defaultCompare(arr1.length, arr2.length);
+  return digits.join('');
 };
 
 
 /**
- * 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
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid.
+ * @return {boolean} Tile coordinate is within extent and zoom level range.
  */
-goog.array.defaultCompare = function(a, b) {
-  return a > b ? 1 : a < b ? -1 : 0;
+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.tilegrid.TileGrid');
 
-/**
- * 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);
-};
+goog.require('ol');
+goog.require('ol.asserts');
+goog.require('ol.TileRange');
+goog.require('ol.array');
+goog.require('ol.extent');
+goog.require('ol.math');
+goog.require('ol.size');
+goog.require('ol.tilecoord');
 
 
 /**
- * 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.
+ * @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
  */
-goog.array.defaultCompareEquality = function(a, b) {
-  return a === b;
-};
+ol.tilegrid.TileGrid = function(options) {
 
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.minZoom = options.minZoom !== undefined ? options.minZoom : 0;
 
-/**
- * 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;
-};
+  /**
+   * @private
+   * @type {!Array.<number>}
+   */
+  this.resolutions_ = options.resolutions;
+  ol.asserts.assert(ol.array.isSorted(this.resolutions_, function(a, b) {
+    return b - a;
+  }, true), 17); // `resolutions` must be sorted in descending order
 
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.maxZoom = this.resolutions_.length - 1;
 
-/**
- * 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;
-};
+  /**
+   * @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;
+    ol.asserts.assert(this.origins_.length == this.resolutions_.length,
+        20); // Number of `origins` and `resolutions` must be equal
+  }
 
-/**
- * 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 = {};
+  var extent = options.extent;
 
-  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);
-    }
+  if (extent !== undefined &&
+      !this.origin_ && !this.origins_) {
+    this.origin_ = ol.extent.getTopLeft(extent);
   }
 
-  return buckets;
-};
+  ol.asserts.assert(
+      (!this.origin_ && this.origins_) || (this.origin_ && !this.origins_),
+      18); // 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;
+    ol.asserts.assert(this.tileSizes_.length == this.resolutions_.length,
+        19); // 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;
+  ol.asserts.assert(
+      (!this.tileSize_ && this.tileSizes_) ||
+      (this.tileSize_ && !this.tileSizes_),
+      22); // Either `tileSize` or `tileSizes` must be configured, never both
 
-/**
- * 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;
-};
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.extent_ = extent !== undefined ? extent : null;
 
 
-/**
- * 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;
-  }
+  /**
+   * @private
+   * @type {Array.<ol.TileRange>}
+   */
+  this.fullTileRanges_ = null;
 
-  if (step * (end - start) < 0) {
-    // Sign mismatch: start + step will never reach the end value.
-    return [];
-  }
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.tmpSize_ = [0, 0];
 
-  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);
-    }
+  if (options.sizes !== undefined) {
+    this.fullTileRanges_ = options.sizes.map(function(size, z) {
+      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));
+      return tileRange;
+    }, this);
+  } else if (extent) {
+    this.calculateTileRanges_(extent);
   }
-  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
+ * @private
+ * @type {ol.TileCoord}
  */
-goog.array.repeat = function(value, n) {
-  var array = [];
-  for (var i = 0; i < n; i++) {
-    array[i] = value;
-  }
-  return array;
-};
+ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0];
 
 
 /**
- * Returns an array consisting of every argument with all arrays
- * expanded in-place recursively.
+ * Call a function with each tile coordinate for a given extent and zoom level.
  *
- * @param {...*} var_args The values to flatten.
- * @return {!Array<?>} An array containing the flattened values.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} zoom Zoom level.
+ * @param {function(ol.TileCoord)} callback Function called with each tile coordinate.
+ * @api
  */
-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);
+ol.tilegrid.TileGrid.prototype.forEachTileCoord = function(extent, zoom, callback) {
+  var tileRange = this.getTileRangeForExtentAndZ(extent, zoom);
+  for (var i = tileRange.minX, ii = tileRange.maxX; i <= ii; ++i) {
+    for (var j = tileRange.minY, jj = tileRange.maxY; j <= jj; ++j) {
+      callback([zoom, i, j]);
     }
   }
-  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.
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {function(this: T, number, ol.TileRange): boolean} callback Callback.
+ * @param {T=} opt_this The object to use as `this` in `callback`.
+ * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
+ * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
+ * @return {boolean} Callback succeeded.
  * @template T
  */
-goog.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));
+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 array;
+  return false;
 };
 
 
 /**
- * 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}.
+ * Get the extent for this tile grid, if it was configured.
+ * @return {ol.Extent} Extent.
  */
-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.
+ol.tilegrid.TileGrid.prototype.getExtent = function() {
+  return this.extent_;
 };
 
 
 /**
- * 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.
+ * Get the maximum zoom level for the grid.
+ * @return {number} Max zoom.
+ * @api
  */
-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);
-  }
+ol.tilegrid.TileGrid.prototype.getMaxZoom = function() {
+  return this.maxZoom;
 };
 
 
 /**
- * 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.
+ * Get the minimum zoom level for the grid.
+ * @return {number} Min zoom.
+ * @api
  */
-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;
-  }
+ol.tilegrid.TileGrid.prototype.getMinZoom = function() {
+  return this.minZoom;
 };
 
 
 /**
- * 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
+ * Get the origin for the grid at the given zoom level.
+ * @param {number} z Z.
+ * @return {ol.Coordinate} Origin.
+ * @api
  */
-goog.array.copyByIndex = function(arr, index_arr) {
-  var result = [];
-  goog.array.forEach(index_arr, function(index) {
-    result.push(arr[index]);
-  });
-  return result;
+ol.tilegrid.TileGrid.prototype.getOrigin = function(z) {
+  if (this.origin_) {
+    return this.origin_;
+  } else {
+    return this.origins_[z];
+  }
 };
 
-goog.provide('ol.array');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-
 
 /**
- * @param {Array.<number>} arr Array.
- * @param {number} target Target.
- * @return {number} Index.
+ * Get the resolution for the given zoom level.
+ * @param {number} z Z.
+ * @return {number} Resolution.
+ * @api
  */
-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;
-    }
-  }
+ol.tilegrid.TileGrid.prototype.getResolution = function(z) {
+  return this.resolutions_[z];
 };
 
 
 /**
- * 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.
+ * Get the list of resolutions for the tile grid.
+ * @return {Array.<number>} Resolutions.
+ * @api
  */
-ol.array.includes = function(arr, obj) {
-  return arr.indexOf(obj) >= 0;
+ol.tilegrid.TileGrid.prototype.getResolutions = function() {
+  return this.resolutions_;
 };
 
 
 /**
- * @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.
+ * @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.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;
+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 {
-    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;
+    return null;
   }
 };
 
 
 /**
- * @param {Array.<*>} arr Array.
- * @param {number} begin Begin index.
- * @param {number} end End index.
+ * @param {number} z Z.
+ * @param {ol.TileRange} tileRange Tile range.
+ * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
+ * @return {ol.Extent} Extent.
  */
-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;
-  }
+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);
 };
 
-goog.provide('ol.ResolutionConstraint');
-goog.provide('ol.ResolutionConstraintType');
 
-goog.require('ol.array');
-goog.require('ol.math');
+/**
+ * @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);
+};
 
 
 /**
- * @typedef {function((number|undefined), number, number): (number|undefined)}
+ * @param {ol.Extent} extent Extent.
+ * @param {number} z Z.
+ * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
+ * @return {ol.TileRange} Tile range.
  */
-ol.ResolutionConstraintType;
+ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ = function(extent, z, opt_tileRange) {
+  var resolution = this.getResolution(z);
+  return this.getTileRangeForExtentAndResolution(
+      extent, resolution, opt_tileRange);
+};
 
 
 /**
- * @param {Array.<number>} resolutions Resolutions.
- * @return {ol.ResolutionConstraintType} Zoom function.
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @return {ol.Coordinate} Tile center.
  */
-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;
-        }
-      });
+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
+  ];
 };
 
 
 /**
- * @param {number} power Power.
- * @param {number} maxResolution Maximum resolution.
- * @param {number=} opt_maxLevel Maximum level.
- * @return {ol.ResolutionConstraintType} Zoom function.
+ * 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.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;
-        }
-      });
+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);
 };
 
-// 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.
+ * Get the tile coordinate for the given map coordinate and resolution.  This
+ * method considers that coordinates that intersect tile boundaries should be
+ * assigned the higher tile coordinate.
+ *
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} resolution Resolution.
+ * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object.
+ * @return {ol.TileCoord} Tile coordinate.
+ * @api
  */
-
-goog.provide('goog.math');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
+ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function(coordinate, resolution, opt_tileCoord) {
+  return this.getTileCoordForXYAndResolution_(
+      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
+};
 
 
 /**
- * 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.
+ * @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
  */
-goog.math.randomInt = function(a) {
-  return Math.floor(Math.random() * a);
-};
+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];
 
-/**
- * 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);
+  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);
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.math.clamp = function(value, min, max) {
-  return Math.min(Math.max(value, min), max);
+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);
 };
 
 
 /**
- * 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).
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @return {number} Tile resolution.
  */
-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;
+ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) {
+  return this.resolutions_[tileCoord[0]];
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.math.lerp = function(a, b, x) {
-  return a + x * (b - a);
+ol.tilegrid.TileGrid.prototype.getTileSize = function(z) {
+  if (this.tileSize_) {
+    return this.tileSize_;
+  } else {
+    return this.tileSizes_[z];
+  }
 };
 
 
 /**
- * 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.
+ * @param {number} z Zoom level.
+ * @return {ol.TileRange} Extent tile range for the specified zoom level.
  */
-goog.math.nearlyEquals = function(a, b, opt_tolerance) {
-  return Math.abs(a - b) <= (opt_tolerance || 0.000001);
+ol.tilegrid.TileGrid.prototype.getFullTileRange = function(z) {
+  if (!this.fullTileRanges_) {
+    return null;
+  } else {
+    return this.fullTileRanges_[z];
+  }
 };
 
 
-// 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.
+ * @param {number} resolution Resolution.
+ * @param {number=} opt_direction If 0, the nearest resolution will be used.
+ *     If 1, the nearest lower resolution will be used. If -1, the nearest
+ *     higher resolution will be used. Default is 0.
+ * @return {number} Z.
+ * @api
  */
-goog.math.standardAngle = function(angle) {
-  return goog.math.modulo(angle, 360);
+ol.tilegrid.TileGrid.prototype.getZForResolution = function(
+    resolution, opt_direction) {
+  var z = ol.array.linearFindNearest(this.resolutions_, resolution,
+      opt_direction || 0);
+  return ol.math.clamp(z, this.minZoom, this.maxZoom);
 };
 
 
 /**
- * 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.
+ * @param {!ol.Extent} extent Extent for this tile grid.
+ * @private
  */
-goog.math.standardAngleInRadians = function(angle) {
-  return goog.math.modulo(angle, 2 * Math.PI);
+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;
 };
 
+goog.provide('ol.tilegrid');
+
+goog.require('ol');
+goog.require('ol.size');
+goog.require('ol.extent');
+goog.require('ol.extent.Corner');
+goog.require('ol.obj');
+goog.require('ol.proj');
+goog.require('ol.proj.Units');
+goog.require('ol.tilegrid.TileGrid');
+
 
 /**
- * Converts degrees to radians.
- * @param {number} angleDegrees Angle in degrees.
- * @return {number} Angle in radians.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {!ol.tilegrid.TileGrid} Default tile grid for the passed projection.
  */
-goog.math.toRadians = function(angleDegrees) {
-  return angleDegrees * Math.PI / 180;
+ol.tilegrid.getForProjection = function(projection) {
+  var tileGrid = projection.getDefaultTileGrid();
+  if (!tileGrid) {
+    tileGrid = ol.tilegrid.createForProjection(projection);
+    projection.setDefaultTileGrid(tileGrid);
+  }
+  return tileGrid;
 };
 
 
 /**
- * Converts radians to degrees.
- * @param {number} angleRadians Angle in radians.
- * @return {number} Angle in degrees.
+ * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {ol.TileCoord} Tile coordinate.
  */
-goog.math.toDegrees = function(angleRadians) {
-  return angleRadians * 180 / Math.PI;
+ol.tilegrid.wrapX = function(tileGrid, tileCoord, 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;
+  }
 };
 
 
 /**
- * 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.
+ * @param {ol.Extent} extent Extent.
+ * @param {number=} opt_maxZoom Maximum zoom level (default is
+ *     ol.DEFAULT_MAX_ZOOM).
+ * @param {number|ol.Size=} opt_tileSize Tile size (default uses
+ *     ol.DEFAULT_TILE_SIZE).
+ * @param {ol.extent.Corner=} opt_corner Extent corner (default is
+ *     ol.extent.Corner.TOP_LEFT).
+ * @return {!ol.tilegrid.TileGrid} TileGrid instance.
  */
-goog.math.angleDx = function(degrees, radius) {
-  return radius * Math.cos(goog.math.toRadians(degrees));
+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
+  });
 };
 
 
 /**
- * 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.
+ * Creates a tile grid with a standard XYZ tiling scheme.
+ * @param {olx.tilegrid.XYZOptions=} opt_options Tile grid options.
+ * @return {!ol.tilegrid.TileGrid} Tile grid instance.
+ * @api
  */
-goog.math.angleDy = function(degrees, radius) {
-  return radius * Math.sin(goog.math.toRadians(degrees));
+ol.tilegrid.createXYZ = function(opt_options) {
+  var options = /** @type {olx.tilegrid.TileGridOptions} */ ({});
+  ol.obj.assign(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);
 };
 
 
 /**
- * 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.
+ * Create a resolutions array from an extent.  A zoom factor of 2 is assumed.
+ * @param {ol.Extent} extent Extent.
+ * @param {number=} opt_maxZoom Maximum zoom level (default is
+ *     ol.DEFAULT_MAX_ZOOM).
+ * @param {number|ol.Size=} opt_tileSize Tile size (default uses
+ *     ol.DEFAULT_TILE_SIZE).
+ * @return {!Array.<number>} Resolutions array.
  */
-goog.math.angle = function(x1, y1, x2, y2) {
-  return goog.math.standardAngle(goog.math.toDegrees(Math.atan2(y2 - y1,
-                                                                x2 - x1)));
+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;
 };
 
 
 /**
- * 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.
+ * @param {ol.ProjectionLike} projection Projection.
+ * @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.BOTTOM_LEFT).
+ * @return {!ol.tilegrid.TileGrid} TileGrid instance.
  */
-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;
+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);
 };
 
 
 /**
- * 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.
+ * 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.ProjectionLike} projection Projection.
+ * @return {ol.Extent} Extent.
  */
-goog.math.sign = Math.sign || function(x) {
-  if (x > 0) {
-    return 1;
-  }
-  if (x < 0) {
-    return -1;
+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 x;  // Preserves signed zeros and NaN.
+  return extent;
 };
 
+goog.provide('ol.Attribution');
+
+goog.require('ol.TileRange');
+goog.require('ol.math');
+goog.require('ol.tilegrid');
+
 
 /**
- * JavaScript implementation of Longest Common Subsequence problem.
- * http://en.wikipedia.org/wiki/Longest_common_subsequence
+ * @classdesc
+ * An attribution for a layer source.
  *
- * Returns the longest possible array that is subarray of both of given arrays.
+ * Example:
+ *
+ *     source: new ol.source.OSM({
+ *       attributions: [
+ *         new ol.Attribution({
+ *           html: 'All maps &copy; ' +
+ *               '<a href="https://www.opencyclemap.org/">OpenCycleMap</a>'
+ *         }),
+ *         ol.source.OSM.ATTRIBUTION
+ *       ],
+ *     ..
  *
- * @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.
+ * @constructor
+ * @param {olx.AttributionOptions} options Attribution options.
+ * @struct
+ * @api
  */
-goog.math.longestCommonSubsequence = function(
-    array1, array2, opt_compareFn, opt_collectorFn) {
+ol.Attribution = function(options) {
 
-  var compare = opt_compareFn || function(a, b) {
-    return a == b;
-  };
+  /**
+   * @private
+   * @type {string}
+   */
+  this.html_ = options.html;
 
-  var collect = opt_collectorFn || function(i1, i2) {
-    return array1[i1];
-  };
+  /**
+   * @private
+   * @type {Object.<string, Array.<ol.TileRange>>}
+   */
+  this.tileRanges_ = options.tileRanges ? options.tileRanges : null;
 
-  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;
-  }
+/**
+ * Get the attribution markup.
+ * @return {string} The attribution HTML.
+ * @api
+ */
+ol.Attribution.prototype.getHTML = function() {
+  return this.html_;
+};
 
-  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--;
+/**
+ * @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(
+          ol.tilegrid.extentFromProjection(projection), parseInt(zKey, 10));
+      var width = extentTileRange.getWidth();
+      if (tileRange.minX < extentTileRange.minX ||
+          tileRange.maxX > extentTileRange.maxX) {
+        if (testTileRange.intersects(new ol.TileRange(
+            ol.math.modulo(tileRange.minX, width),
+            ol.math.modulo(tileRange.maxX, width),
+            tileRange.minY, tileRange.maxY))) {
+          return true;
+        }
+        if (tileRange.getWidth() > width &&
+            testTileRange.intersects(extentTileRange)) {
+          return true;
+        }
       }
     }
   }
-
-  return result;
+  return false;
 };
 
+goog.provide('ol.CenterConstraint');
+
+goog.require('ol.math');
+
 
 /**
- * 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).
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.CenterConstraintType} The constraint.
  */
-goog.math.sum = function(var_args) {
-  return /** @type {number} */ (goog.array.reduce(arguments,
-      function(sum, value) {
-        return sum + value;
-      }, 0));
+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;
+        }
+      });
 };
 
 
 /**
- * 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).
+ * @param {ol.Coordinate|undefined} center Center.
+ * @return {ol.Coordinate|undefined} Center.
  */
-goog.math.average = function(var_args) {
-  return goog.math.sum.apply(null, arguments) / arguments.length;
+ol.CenterConstraint.none = function(center) {
+  return center;
 };
 
+goog.provide('ol.CollectionEventType');
 
 /**
- * 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).
+ * @enum {string}
  */
-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;
+ol.CollectionEventType = {
+  /**
+   * Triggered when an item is added to the collection.
+   * @event ol.Collection.Event#add
+   * @api
+   */
+  ADD: 'add',
+  /**
+   * Triggered when an item is removed from the collection.
+   * @event ol.Collection.Event#remove
+   * @api
+   */
+  REMOVE: 'remove'
 };
 
+goog.provide('ol.ObjectEventType');
 
 /**
- * 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).
+ * @enum {string}
  */
-goog.math.standardDeviation = function(var_args) {
-  return Math.sqrt(goog.math.sampleVariance.apply(null, arguments));
+ol.ObjectEventType = {
+  /**
+   * Triggered when a property is changed.
+   * @event ol.Object.Event#propertychange
+   * @api
+   */
+  PROPERTYCHANGE: 'propertychange'
 };
 
+goog.provide('ol.events');
+
+goog.require('ol.obj');
+
 
 /**
- * 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.
+ * @param {ol.EventsKey} listenerObj Listener object.
+ * @return {ol.EventsListenerFunctionType} Bound listener.
  */
-goog.math.isInt = function(num) {
-  return isFinite(num) && num % 1 == 0;
+ol.events.bindListener_ = function(listenerObj) {
+  var boundListener = function(evt) {
+    var listener = listenerObj.listener;
+    var bindTo = listenerObj.bindTo || listenerObj.target;
+    if (listenerObj.callOnce) {
+      ol.events.unlistenByKey(listenerObj);
+    }
+    return listener.call(bindTo, evt);
+  };
+  listenerObj.boundListener = boundListener;
+  return boundListener;
 };
 
 
 /**
- * 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.
+ * Finds the matching {@link ol.EventsKey} in the given listener
+ * array.
+ *
+ * @param {!Array<!ol.EventsKey>} listeners Array of listeners.
+ * @param {!Function} listener The listener function.
+ * @param {Object=} opt_this The `this` value inside the listener.
+ * @param {boolean=} opt_setDeleteIndex Set the deleteIndex on the matching
+ *     listener, for {@link ol.events.unlistenByKey}.
+ * @return {ol.EventsKey|undefined} The matching listener object.
+ * @private
  */
-goog.math.isFiniteNumber = function(num) {
-  return isFinite(num) && !isNaN(num);
+ol.events.findListener_ = function(listeners, listener, opt_this,
+    opt_setDeleteIndex) {
+  var listenerObj;
+  for (var i = 0, ii = listeners.length; i < ii; ++i) {
+    listenerObj = listeners[i];
+    if (listenerObj.listener === listener &&
+        listenerObj.bindTo === opt_this) {
+      if (opt_setDeleteIndex) {
+        listenerObj.deleteIndex = i;
+      }
+      return listenerObj;
+    }
+  }
+  return undefined;
 };
 
 
 /**
- * @param {number} num The number to test.
- * @return {boolean} Whether it is negative zero.
+ * @param {ol.EventTargetLike} target Target.
+ * @param {string} type Type.
+ * @return {Array.<ol.EventsKey>|undefined} Listeners.
  */
-goog.math.isNegativeZero = function(num) {
-  return num == 0 && 1 / num < 0;
+ol.events.getListeners = function(target, type) {
+  var listenerMap = target.ol_lm;
+  return listenerMap ? listenerMap[type] : undefined;
 };
 
 
 /**
- * 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.
+ * Get the lookup of listeners.  If one does not exist on the target, it is
+ * created.
+ * @param {ol.EventTargetLike} target Target.
+ * @return {!Object.<string, Array.<ol.EventsKey>>} Map of
+ *     listeners by event type.
+ * @private
  */
-goog.math.log10Floor = function(num) {
-  if (num > 0) {
-    var x = Math.round(Math.log(num) * Math.LOG10E);
-    return x - (parseFloat('1e' + x) > num);
+ol.events.getListenerMap_ = function(target) {
+  var listenerMap = target.ol_lm;
+  if (!listenerMap) {
+    listenerMap = target.ol_lm = {};
   }
-  return num == 0 ? -Infinity : NaN;
+  return listenerMap;
 };
 
 
 /**
- * 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}.
+ * Clean up all listener objects of the given type.  All properties on the
+ * listener objects will be removed, and if no listeners remain in the listener
+ * map, it will be removed from the target.
+ * @param {ol.EventTargetLike} target Target.
+ * @param {string} type Type.
+ * @private
  */
-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));
+ol.events.removeListeners_ = function(target, type) {
+  var listeners = ol.events.getListeners(target, type);
+  if (listeners) {
+    for (var i = 0, ii = listeners.length; i < ii; ++i) {
+      target.removeEventListener(type, listeners[i].boundListener);
+      ol.obj.clear(listeners[i]);
+    }
+    listeners.length = 0;
+    var listenerMap = target.ol_lm;
+    if (listenerMap) {
+      delete listenerMap[type];
+      if (Object.keys(listenerMap).length === 0) {
+        delete target.ol_lm;
+      }
+    }
+  }
 };
 
 
 /**
- * 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));
+ * Registers an event listener on an event target. Inspired by
+ * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
+ *
+ * This function efficiently binds a `listener` to a `this` object, and returns
+ * a key for use with {@link ol.events.unlistenByKey}.
+ *
+ * @param {ol.EventTargetLike} target Event target.
+ * @param {string} type Event type.
+ * @param {ol.EventsListenerFunctionType} listener Listener.
+ * @param {Object=} opt_this Object referenced by the `this` keyword in the
+ *     listener. Default is the `target`.
+ * @param {boolean=} opt_once If true, add the listener as one-off listener.
+ * @return {ol.EventsKey} Unique key for the listener.
+ */
+ol.events.listen = function(target, type, listener, opt_this, opt_once) {
+  var listenerMap = ol.events.getListenerMap_(target);
+  var listeners = listenerMap[type];
+  if (!listeners) {
+    listeners = listenerMap[type] = [];
+  }
+  var listenerObj = ol.events.findListener_(listeners, listener, opt_this,
+      false);
+  if (listenerObj) {
+    if (!opt_once) {
+      // Turn one-off listener into a permanent one.
+      listenerObj.callOnce = false;
+    }
+  } else {
+    listenerObj = /** @type {ol.EventsKey} */ ({
+      bindTo: opt_this,
+      callOnce: !!opt_once,
+      listener: listener,
+      target: target,
+      type: type
+    });
+    target.addEventListener(type, ol.events.bindListener_(listenerObj));
+    listeners.push(listenerObj);
+  }
+
+  return listenerObj;
 };
 
-goog.provide('ol.RotationConstraint');
-goog.provide('ol.RotationConstraintType');
 
-goog.require('goog.math');
+/**
+ * Registers a one-off event listener on an event target. Inspired by
+ * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
+ *
+ * This function efficiently binds a `listener` as self-unregistering listener
+ * to a `this` object, and returns a key for use with
+ * {@link ol.events.unlistenByKey} in case the listener needs to be unregistered
+ * before it is called.
+ *
+ * When {@link ol.events.listen} is called with the same arguments after this
+ * function, the self-unregistering listener will be turned into a permanent
+ * listener.
+ *
+ * @param {ol.EventTargetLike} target Event target.
+ * @param {string} type Event type.
+ * @param {ol.EventsListenerFunctionType} listener Listener.
+ * @param {Object=} opt_this Object referenced by the `this` keyword in the
+ *     listener. Default is the `target`.
+ * @return {ol.EventsKey} Key for unlistenByKey.
+ */
+ol.events.listenOnce = function(target, type, listener, opt_this) {
+  return ol.events.listen(target, type, listener, opt_this, true);
+};
 
 
 /**
- * @typedef {function((number|undefined), number): (number|undefined)}
- */
-ol.RotationConstraintType;
+ * Unregisters an event listener on an event target. Inspired by
+ * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
+ *
+ * To return a listener, this function needs to be called with the exact same
+ * arguments that were used for a previous {@link ol.events.listen} call.
+ *
+ * @param {ol.EventTargetLike} target Event target.
+ * @param {string} type Event type.
+ * @param {ol.EventsListenerFunctionType} listener Listener.
+ * @param {Object=} opt_this Object referenced by the `this` keyword in the
+ *     listener. Default is the `target`.
+ */
+ol.events.unlisten = function(target, type, listener, opt_this) {
+  var listeners = ol.events.getListeners(target, type);
+  if (listeners) {
+    var listenerObj = ol.events.findListener_(listeners, listener, opt_this,
+        true);
+    if (listenerObj) {
+      ol.events.unlistenByKey(listenerObj);
+    }
+  }
+};
 
 
 /**
- * @param {number|undefined} rotation Rotation.
- * @param {number} delta Delta.
- * @return {number|undefined} Rotation.
+ * Unregisters event listeners on an event target. Inspired by
+ * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
+ *
+ * The argument passed to this function is the key returned from
+ * {@link ol.events.listen} or {@link ol.events.listenOnce}.
+ *
+ * @param {ol.EventsKey} key The key.
  */
-ol.RotationConstraint.disable = function(rotation, delta) {
-  if (rotation !== undefined) {
-    return 0;
-  } else {
-    return undefined;
+ol.events.unlistenByKey = function(key) {
+  if (key && key.target) {
+    key.target.removeEventListener(key.type, key.boundListener);
+    var listeners = ol.events.getListeners(key.target, key.type);
+    if (listeners) {
+      var i = 'deleteIndex' in key ? key.deleteIndex : listeners.indexOf(key);
+      if (i !== -1) {
+        listeners.splice(i, 1);
+      }
+      if (listeners.length === 0) {
+        ol.events.removeListeners_(key.target, key.type);
+      }
+    }
+    ol.obj.clear(key);
   }
 };
 
 
 /**
- * @param {number|undefined} rotation Rotation.
- * @param {number} delta Delta.
- * @return {number|undefined} Rotation.
+ * Unregisters all event listeners on an event target. Inspired by
+ * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
+ *
+ * @param {ol.EventTargetLike} target Target.
  */
-ol.RotationConstraint.none = function(rotation, delta) {
-  if (rotation !== undefined) {
-    return rotation + delta;
-  } else {
-    return undefined;
+ol.events.unlistenAll = function(target) {
+  var listenerMap = ol.events.getListenerMap_(target);
+  for (var type in listenerMap) {
+    ol.events.removeListeners_(target, type);
   }
 };
 
+goog.provide('ol.Disposable');
+
+goog.require('ol');
 
 /**
- * @param {number} n N.
- * @return {ol.RotationConstraintType} Rotation constraint.
+ * Objects that need to clean up after themselves.
+ * @constructor
  */
-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;
-        }
-      });
-};
+ol.Disposable = function() {};
 
+/**
+ * The object has already been disposed.
+ * @type {boolean}
+ * @private
+ */
+ol.Disposable.prototype.disposed_ = false;
 
 /**
- * @param {number=} opt_tolerance Tolerance.
- * @return {ol.RotationConstraintType} Rotation constraint.
+ * Clean up.
  */
-ol.RotationConstraint.createSnapToZero = function(opt_tolerance) {
-  var tolerance = opt_tolerance || goog.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;
-        }
-      });
+ol.Disposable.prototype.dispose = function() {
+  if (!this.disposed_) {
+    this.disposed_ = true;
+    this.disposeInternal();
+  }
 };
 
-goog.provide('ol.Constraints');
-
-goog.require('ol.CenterConstraintType');
-goog.require('ol.ResolutionConstraintType');
-goog.require('ol.RotationConstraintType');
+/**
+ * Extension point for disposable objects.
+ * @protected
+ */
+ol.Disposable.prototype.disposeInternal = ol.nullFunction;
 
+goog.provide('ol.events.Event');
 
 
 /**
+ * @classdesc
+ * Stripped down implementation of the W3C DOM Level 2 Event interface.
+ * @see {@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface}
+ *
+ * This implementation only provides `type` and `target` properties, and
+ * `stopPropagation` and `preventDefault` methods. It is meant as base class
+ * for higher level events defined in the library, and works with
+ * {@link ol.events.EventTarget}.
+ *
  * @constructor
- * @param {ol.CenterConstraintType} centerConstraint Center constraint.
- * @param {ol.ResolutionConstraintType} resolutionConstraint
- *     Resolution constraint.
- * @param {ol.RotationConstraintType} rotationConstraint
- *     Rotation constraint.
+ * @implements {oli.events.Event}
+ * @param {string} type Type.
  */
-ol.Constraints =
-    function(centerConstraint, resolutionConstraint, rotationConstraint) {
+ol.events.Event = function(type) {
 
   /**
-   * @type {ol.CenterConstraintType}
+   * @type {boolean}
    */
-  this.center = centerConstraint;
+  this.propagationStopped;
 
   /**
-   * @type {ol.ResolutionConstraintType}
+   * The event type.
+   * @type {string}
+   * @api
    */
-  this.resolution = resolutionConstraint;
+  this.type = type;
 
   /**
-   * @type {ol.RotationConstraintType}
+   * The event target.
+   * @type {Object}
+   * @api
    */
-  this.rotation = rotationConstraint;
+  this.target = 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 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)
+ * Stop event propagation.
+ * @function
+ * @override
+ * @api
  */
+ol.events.Event.prototype.preventDefault =
 
-goog.provide('goog.debug.EntryPointMonitor');
-goog.provide('goog.debug.entryPointRegistry');
-
-goog.require('goog.asserts');
-
+/**
+ * Stop event propagation.
+ * @function
+ * @override
+ * @api
+ */
+ol.events.Event.prototype.stopPropagation = function() {
+  this.propagationStopped = true;
+};
 
 
 /**
- * @interface
+ * @param {Event|ol.events.Event} evt Event
  */
-goog.debug.EntryPointMonitor = function() {};
+ol.events.Event.stopPropagation = function(evt) {
+  evt.stopPropagation();
+};
 
 
 /**
- * Instruments a function.
- *
- * @param {!Function} fn A function to instrument.
- * @return {!Function} The instrumented function.
+ * @param {Event|ol.events.Event} evt Event
  */
-goog.debug.EntryPointMonitor.prototype.wrap;
+ol.events.Event.preventDefault = function(evt) {
+  evt.preventDefault();
+};
+
+goog.provide('ol.events.EventTarget');
+
+goog.require('ol');
+goog.require('ol.Disposable');
+goog.require('ol.events');
+goog.require('ol.events.Event');
 
 
 /**
- * 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.
+ * @classdesc
+ * A simplified implementation of the W3C DOM Level 2 EventTarget interface.
+ * @see {@link https://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html#Events-EventTarget}
  *
- * 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.
+ * There are two important simplifications compared to the specification:
  *
- * So it is important that entry points are unwrapped in the reverse
- * order that they were wrapped.
+ * 1. The handling of `useCapture` in `addEventListener` and
+ *    `removeEventListener`. There is no real capture model.
+ * 2. The handling of `stopPropagation` and `preventDefault` on `dispatchEvent`.
+ *    There is no event target hierarchy. When a listener calls
+ *    `stopPropagation` or `preventDefault` on an event object, it means that no
+ *    more listeners after this one will be called. Same as when the listener
+ *    returns false.
  *
- * @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.
+ * @constructor
+ * @extends {ol.Disposable}
  */
-goog.debug.EntryPointMonitor.prototype.unwrap;
+ol.events.EventTarget = function() {
 
+  ol.Disposable.call(this);
 
-/**
- * An array of entry point callbacks.
- * @type {!Array<function(!Function)>}
- * @private
- */
-goog.debug.entryPointRegistry.refList_ = [];
+  /**
+   * @private
+   * @type {!Object.<string, number>}
+   */
+  this.pendingRemovals_ = {};
+
+  /**
+   * @private
+   * @type {!Object.<string, number>}
+   */
+  this.dispatching_ = {};
 
+  /**
+   * @private
+   * @type {!Object.<string, Array.<ol.EventsListenerFunctionType>>}
+   */
+  this.listeners_ = {};
 
-/**
- * Monitors that should wrap all the entry points.
- * @type {!Array<!goog.debug.EntryPointMonitor>}
- * @private
- */
-goog.debug.entryPointRegistry.monitors_ = [];
+};
+ol.inherits(ol.events.EventTarget, ol.Disposable);
 
 
 /**
- * Whether goog.debug.entryPointRegistry.monitorAll has ever been called.
- * Checking this allows the compiler to optimize out the registrations.
- * @type {boolean}
- * @private
+ * @param {string} type Type.
+ * @param {ol.EventsListenerFunctionType} listener Listener.
  */
-goog.debug.entryPointRegistry.monitorsMayExist_ = false;
+ol.events.EventTarget.prototype.addEventListener = function(type, listener) {
+  var listeners = this.listeners_[type];
+  if (!listeners) {
+    listeners = this.listeners_[type] = [];
+  }
+  if (listeners.indexOf(listener) === -1) {
+    listeners.push(listener);
+  }
+};
 
 
 /**
- * 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.
+ * @param {{type: string,
+ *     target: (EventTarget|ol.events.EventTarget|undefined)}|ol.events.Event|
+ *     string} event Event or event type.
+ * @return {boolean|undefined} `false` if anyone called preventDefault on the
+ *     event object or if any of the listeners returned false.
  */
-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]));
+ol.events.EventTarget.prototype.dispatchEvent = function(event) {
+  var evt = typeof event === 'string' ? new ol.events.Event(event) : event;
+  var type = evt.type;
+  evt.target = this;
+  var listeners = this.listeners_[type];
+  var propagate;
+  if (listeners) {
+    if (!(type in this.dispatching_)) {
+      this.dispatching_[type] = 0;
+      this.pendingRemovals_[type] = 0;
+    }
+    ++this.dispatching_[type];
+    for (var i = 0, ii = listeners.length; i < ii; ++i) {
+      if (listeners[i].call(this, evt) === false || evt.propagationStopped) {
+        propagate = false;
+        break;
+      }
+    }
+    --this.dispatching_[type];
+    if (this.dispatching_[type] === 0) {
+      var pendingRemovals = this.pendingRemovals_[type];
+      delete this.pendingRemovals_[type];
+      while (pendingRemovals--) {
+        this.removeEventListener(type, ol.nullFunction);
+      }
+      delete this.dispatching_[type];
     }
+    return propagate;
   }
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-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);
+ol.events.EventTarget.prototype.disposeInternal = function() {
+  ol.events.unlistenAll(this);
 };
 
 
 /**
- * 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.
+ * Get the listeners for a specified event type. Listeners are returned in the
+ * order that they will be called in.
  *
- * @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.
+ * @param {string} type Type.
+ * @return {Array.<ol.EventsListenerFunctionType>} Listeners.
  */
-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--;
+ol.events.EventTarget.prototype.getListeners = function(type) {
+  return this.listeners_[type];
 };
 
-// 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)
+ * @param {string=} opt_type Type. If not provided,
+ *     `true` will be returned if this EventTarget has any listeners.
+ * @return {boolean} Has listeners.
  */
-
-goog.provide('goog.labs.userAgent.util');
-
-goog.require('goog.string');
+ol.events.EventTarget.prototype.hasListener = function(opt_type) {
+  return opt_type ?
+      opt_type in this.listeners_ :
+      Object.keys(this.listeners_).length > 0;
+};
 
 
 /**
- * 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;
+ * @param {string} type Type.
+ * @param {ol.EventsListenerFunctionType} listener Listener.
+ */
+ol.events.EventTarget.prototype.removeEventListener = function(type, listener) {
+  var listeners = this.listeners_[type];
+  if (listeners) {
+    var index = listeners.indexOf(listener);
+    if (type in this.pendingRemovals_) {
+      // make listener a no-op, and remove later in #dispatchEvent()
+      listeners[index] = ol.nullFunction;
+      ++this.pendingRemovals_[type];
+    } else {
+      listeners.splice(index, 1);
+      if (listeners.length === 0) {
+        delete this.listeners_[type];
+      }
     }
   }
-  return '';
 };
 
+goog.provide('ol.events.EventType');
 
 /**
- * Getter for the native navigator.
- * This is a separate function so it can be stubbed out in testing.
- * @return {Navigator}
- * @private
+ * @enum {string}
+ * @const
  */
-goog.labs.userAgent.util.getNavigator_ = function() {
-  return goog.global.navigator;
+ol.events.EventType = {
+  /**
+   * Generic change event. Triggered when the revision counter is increased.
+   * @event ol.events.Event#change
+   * @api
+   */
+  CHANGE: 'change',
+
+  CLICK: 'click',
+  DBLCLICK: 'dblclick',
+  DRAGENTER: 'dragenter',
+  DRAGOVER: 'dragover',
+  DROP: 'drop',
+  ERROR: 'error',
+  KEYDOWN: 'keydown',
+  KEYPRESS: 'keypress',
+  LOAD: 'load',
+  MOUSEDOWN: 'mousedown',
+  MOUSEMOVE: 'mousemove',
+  MOUSEOUT: 'mouseout',
+  MOUSEUP: 'mouseup',
+  MOUSEWHEEL: 'mousewheel',
+  MSPOINTERDOWN: 'MSPointerDown',
+  RESIZE: 'resize',
+  TOUCHSTART: 'touchstart',
+  TOUCHMOVE: 'touchmove',
+  TOUCHEND: 'touchend',
+  WHEEL: 'wheel'
 };
 
+goog.provide('ol.Observable');
 
-/**
- * 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_();
+goog.require('ol');
+goog.require('ol.events');
+goog.require('ol.events.EventTarget');
+goog.require('ol.events.EventType');
 
 
 /**
- * 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.
+ * @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 {ol.events.EventTarget}
+ * @fires ol.events.Event
+ * @struct
+ * @api
  */
-goog.labs.userAgent.util.setUserAgent = function(opt_userAgent) {
-  goog.labs.userAgent.util.userAgent_ = opt_userAgent ||
-      goog.labs.userAgent.util.getNativeUserAgentString_();
-};
-
+ol.Observable = function() {
 
-/**
- * @return {string} The user agent string.
- */
-goog.labs.userAgent.util.getUserAgent = function() {
-  return goog.labs.userAgent.util.userAgent_;
-};
+  ol.events.EventTarget.call(this);
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.revision_ = 0;
 
-/**
- * @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);
 };
+ol.inherits(ol.Observable, ol.events.EventTarget);
 
 
 /**
- * @param {string} str
- * @return {boolean} Whether the user agent contains the given string.
+ * Removes an event listener using the key returned by `on()` or `once()`.
+ * @param {ol.EventsKey|Array.<ol.EventsKey>} key The key returned by `on()`
+ *     or `once()` (or an array of keys).
+ * @api
  */
-goog.labs.userAgent.util.matchUserAgentIgnoreCase = function(str) {
-  var userAgent = goog.labs.userAgent.util.getUserAgent();
-  return goog.string.caseInsensitiveContains(userAgent, str);
+ol.Observable.unByKey = function(key) {
+  if (Array.isArray(key)) {
+    for (var i = 0, ii = key.length; i < ii; ++i) {
+      ol.events.unlistenByKey(key[i]);
+    }
+  } else {
+    ol.events.unlistenByKey(/** @type {ol.EventsKey} */ (key));
+  }
 };
 
 
 /**
- * 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.
+ * Increases the revision counter and dispatches a 'change' event.
+ * @api
  */
-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;
+ol.Observable.prototype.changed = function() {
+  ++this.revision_;
+  this.dispatchEvent(ol.events.EventType.CHANGE);
 };
 
 
-// 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)
+ * 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 {{type: string,
+ *     target: (EventTarget|ol.events.EventTarget|undefined)}|ol.events.Event|
+ *     string} event Event object.
+ * @function
+ * @api
  */
-
-goog.provide('goog.object');
+ol.Observable.prototype.dispatchEvent;
 
 
 /**
- * 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
+ * Get the version number for this object.  Each time the object is modified,
+ * its version number will be incremented.
+ * @return {number} Revision.
+ * @api
  */
-goog.object.forEach = function(obj, f, opt_obj) {
-  for (var key in obj) {
-    f.call(opt_obj, obj[key], key, obj);
-  }
+ol.Observable.prototype.getRevision = function() {
+  return this.revision_;
 };
 
 
 /**
- * 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
+ * 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 {ol.EventsKey|Array.<ol.EventsKey>} Unique key for the listener. If
+ *     called with an array of event types as the first argument, the return
+ *     will be an array of keys.
+ * @api
  */
-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];
+ol.Observable.prototype.on = function(type, listener, opt_this) {
+  if (Array.isArray(type)) {
+    var len = type.length;
+    var keys = new Array(len);
+    for (var i = 0; i < len; ++i) {
+      keys[i] = ol.events.listen(this, type[i], listener, opt_this);
     }
+    return keys;
+  } else {
+    return ol.events.listen(
+        this, /** @type {string} */ (type), listener, opt_this);
   }
-  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
+ * 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 {ol.EventsKey|Array.<ol.EventsKey>} Unique key for the listener. If
+ *     called with an array of event types as the first argument, the return
+ *     will be an array of keys.
+ * @api
  */
-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);
+ol.Observable.prototype.once = function(type, listener, opt_this) {
+  if (Array.isArray(type)) {
+    var len = type.length;
+    var keys = new Array(len);
+    for (var i = 0; i < len; ++i) {
+      keys[i] = ol.events.listenOnce(this, type[i], listener, opt_this);
+    }
+    return keys;
+  } else {
+    return ol.events.listenOnce(
+        this, /** @type {string} */ (type), listener, opt_this);
   }
-  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
+ * 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
  */
-goog.object.some = function(obj, f, opt_obj) {
-  for (var key in obj) {
-    if (f.call(opt_obj, obj[key], key, obj)) {
-      return true;
+ol.Observable.prototype.un = function(type, listener, opt_this) {
+  if (Array.isArray(type)) {
+    for (var i = 0, ii = type.length; i < ii; ++i) {
+      ol.events.unlisten(this, type[i], listener, opt_this);
     }
+    return;
+  } else {
+    ol.events.unlisten(this, /** @type {string} */ (type), listener, opt_this);
   }
-  return false;
 };
 
+goog.provide('ol.Object');
 
-/**
- * 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;
-};
+goog.require('ol');
+goog.require('ol.ObjectEventType');
+goog.require('ol.Observable');
+goog.require('ol.events.Event');
+goog.require('ol.obj');
 
 
 /**
- * Returns the number of key-value pairs in the object map.
+ * @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').
  *
- * @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.
+ * @constructor
+ * @extends {ol.Observable}
+ * @param {Object.<string, *>=} opt_values An object with key-value pairs.
+ * @fires ol.Object.Event
+ * @api
  */
-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;
-};
+ol.Object = function(opt_values) {
+  ol.Observable.call(this);
 
+  // Call ol.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.
+  ol.getUid(this);
 
-/**
- * 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;
+  /**
+   * @private
+   * @type {!Object.<string, *>}
+   */
+  this.values_ = {};
+
+  if (opt_values !== undefined) {
+    this.setProperties(opt_values);
   }
 };
+ol.inherits(ol.Object, ol.Observable);
 
 
 /**
- * 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
+ * @private
+ * @type {Object.<string, string>}
  */
-goog.object.getAnyValue = function(obj) {
-  for (var key in obj) {
-    return obj[key];
-  }
-};
+ol.Object.changeEventTypeCache_ = {};
 
 
 /**
- * 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
+ * @param {string} key Key name.
+ * @return {string} Change name.
  */
-goog.object.contains = function(obj, val) {
-  return goog.object.containsValue(obj, val);
+ol.Object.getChangeEventType = function(key) {
+  return ol.Object.changeEventTypeCache_.hasOwnProperty(key) ?
+      ol.Object.changeEventTypeCache_[key] :
+      (ol.Object.changeEventTypeCache_[key] = 'change:' + key);
 };
 
 
 /**
- * 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
+ * Gets a value.
+ * @param {string} key Key name.
+ * @return {*} Value.
+ * @api
  */
-goog.object.getValues = function(obj) {
-  var res = [];
-  var i = 0;
-  for (var key in obj) {
-    res[i++] = obj[key];
+ol.Object.prototype.get = function(key) {
+  var value;
+  if (this.values_.hasOwnProperty(key)) {
+    value = this.values_[key];
   }
-  return res;
+  return value;
 };
 
 
 /**
- * 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.
+ * Get a list of object property names.
+ * @return {Array.<string>} List of property names.
+ * @api
  */
-goog.object.getKeys = function(obj) {
-  var res = [];
-  var i = 0;
-  for (var key in obj) {
-    res[i++] = key;
-  }
-  return res;
+ol.Object.prototype.getKeys = function() {
+  return Object.keys(this.values_);
 };
 
 
 /**
- * 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.
+ * Get an object of all property names and values.
+ * @return {Object.<string, *>} Object.
+ * @api
  */
-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;
+ol.Object.prototype.getProperties = function() {
+  return ol.obj.assign({}, this.values_);
 };
 
 
 /**
- * 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.
+ * @param {string} key Key name.
+ * @param {*} oldValue Old value.
  */
-goog.object.containsKey = function(obj, key) {
-  return key in obj;
+ol.Object.prototype.notify = function(key, oldValue) {
+  var eventType;
+  eventType = ol.Object.getChangeEventType(key);
+  this.dispatchEvent(new ol.Object.Event(eventType, key, oldValue));
+  eventType = ol.ObjectEventType.PROPERTYCHANGE;
+  this.dispatchEvent(new ol.Object.Event(eventType, key, oldValue));
 };
 
 
 /**
- * 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
+ * Sets a value.
+ * @param {string} key Key name.
+ * @param {*} value Value.
+ * @param {boolean=} opt_silent Update without triggering an event.
+ * @api
  */
-goog.object.containsValue = function(obj, val) {
-  for (var key in obj) {
-    if (obj[key] == val) {
-      return true;
+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);
     }
   }
-  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
+ * 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
  */
-goog.object.findKey = function(obj, f, opt_this) {
-  for (var key in obj) {
-    if (f.call(opt_this, obj[key], key, obj)) {
-      return key;
-    }
+ol.Object.prototype.setProperties = function(values, opt_silent) {
+  var key;
+  for (key in values) {
+    this.set(key, values[key], opt_silent);
   }
-  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
+ * Unsets a property.
+ * @param {string} key Key name.
+ * @param {boolean=} opt_silent Unset without triggering an event.
+ * @api
  */
-goog.object.findValue = function(obj, f, opt_this) {
-  var key = goog.object.findKey(obj, f, opt_this);
-  return key && obj[key];
+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);
+    }
+  }
 };
 
 
 /**
- * Whether the object/map/hash is empty.
+ * @classdesc
+ * Events emitted by {@link ol.Object} instances are instances of this type.
  *
- * @param {Object} obj The object to test.
- * @return {boolean} true if obj is empty.
+ * @param {string} type The event type.
+ * @param {string} key The property name.
+ * @param {*} oldValue The old value for `key`.
+ * @extends {ol.events.Event}
+ * @implements {oli.Object.Event}
+ * @constructor
  */
-goog.object.isEmpty = function(obj) {
-  for (var key in obj) {
-    return false;
-  }
-  return true;
-};
+ol.Object.Event = function(type, key, oldValue) {
+  ol.events.Event.call(this, type);
+
+  /**
+   * The name of the property whose value is changing.
+   * @type {string}
+   * @api
+   */
+  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
+   */
+  this.oldValue = oldValue;
 
+};
+ol.inherits(ol.Object.Event, ol.events.Event);
 
 /**
- * Removes all key value pairs from the object/map/hash.
- *
- * @param {Object} obj The object to clear.
+ * An implementation of Google Maps' MVCArray.
+ * @see https://developers.google.com/maps/documentation/javascript/reference
  */
-goog.object.clear = function(obj) {
-  for (var i in obj) {
-    delete obj[i];
-  }
-};
+
+goog.provide('ol.Collection');
+
+goog.require('ol');
+goog.require('ol.AssertionError');
+goog.require('ol.CollectionEventType');
+goog.require('ol.Object');
+goog.require('ol.events.Event');
 
 
 /**
- * Removes a key-value pair based on the key.
+ * @classdesc
+ * An expanded version of standard JS Array, adding convenience methods for
+ * manipulation. Add and remove changes to the Collection trigger a Collection
+ * event. Note that this does not cover changes to the objects _within_ the
+ * Collection; they trigger events on the appropriate object, not on the
+ * Collection as a whole.
  *
- * @param {Object} obj The object from which to remove the key.
- * @param {*} key The key to remove.
- * @return {boolean} Whether an element was removed.
+ * @constructor
+ * @extends {ol.Object}
+ * @fires ol.Collection.Event
+ * @param {!Array.<T>=} opt_array Array.
+ * @param {olx.CollectionOptions=} opt_options Collection options.
+ * @template T
+ * @api
  */
-goog.object.remove = function(obj, key) {
-  var rv;
-  if ((rv = key in obj)) {
-    delete obj[key];
+ol.Collection = function(opt_array, opt_options) {
+
+  ol.Object.call(this);
+
+  var options = opt_options || {};
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.unique_ = !!options.unique;
+
+  /**
+   * @private
+   * @type {!Array.<T>}
+   */
+  this.array_ = opt_array ? opt_array : [];
+
+  if (this.unique_) {
+    for (var i = 0, ii = this.array_.length; i < ii; ++i) {
+      this.assertUnique_(this.array_[i], i);
+    }
   }
-  return rv;
+
+  this.updateLength_();
+
 };
+ol.inherits(ol.Collection, ol.Object);
 
 
 /**
- * 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
+ * Remove all elements from the collection.
+ * @api
  */
-goog.object.add = function(obj, key, val) {
-  if (key in obj) {
-    throw Error('The object already contains the key "' + key + '"');
+ol.Collection.prototype.clear = function() {
+  while (this.getLength() > 0) {
+    this.pop();
   }
-  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
+ * 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
  */
-goog.object.get = function(obj, key, opt_val) {
-  if (key in obj) {
-    return obj[key];
+ol.Collection.prototype.extend = function(arr) {
+  var i, ii;
+  for (i = 0, ii = arr.length; i < ii; ++i) {
+    this.push(arr[i]);
   }
-  return opt_val;
+  return this;
 };
 
 
 /**
- * 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
+ * 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
  */
-goog.object.set = function(obj, key, value) {
-  obj[key] = value;
+ol.Collection.prototype.forEach = function(f, opt_this) {
+  this.array_.forEach(f, opt_this);
 };
 
 
 /**
- * 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
+ * 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
  */
-goog.object.setIfUndefined = function(obj, key, value) {
-  return key in obj ? obj[key] : (obj[key] = value);
+ol.Collection.prototype.getArray = function() {
+  return this.array_;
 };
 
 
 /**
- * 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
+ * Get the element at the provided index.
+ * @param {number} index Index.
+ * @return {T} Element.
+ * @api
  */
-goog.object.setWithReturnValueIfNotSet = function(obj, key, f) {
-  if (key in obj) {
-    return obj[key];
-  }
-
-  var val = f();
-  obj[key] = val;
-  return val;
+ol.Collection.prototype.item = function(index) {
+  return this.array_[index];
 };
 
 
 /**
- * Compares two objects for equality using === on the values.
- *
- * @param {!Object<K,V>} a
- * @param {!Object<K,V>} b
- * @return {boolean}
- * @template K,V
+ * Get the length of this collection.
+ * @return {number} The length of the array.
+ * @observable
+ * @api
  */
-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;
+ol.Collection.prototype.getLength = function() {
+  return /** @type {number} */ (this.get(ol.Collection.Property_.LENGTH));
 };
 
 
 /**
- * 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
+ * Insert an element at the provided index.
+ * @param {number} index Index.
+ * @param {T} elem Element.
+ * @api
  */
-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];
+ol.Collection.prototype.insertAt = function(index, elem) {
+  if (this.unique_) {
+    this.assertUnique_(elem);
   }
-  return res;
-  // We could also use goog.mixin but I wanted this to be independent from that.
+  this.array_.splice(index, 0, elem);
+  this.updateLength_();
+  this.dispatchEvent(
+      new ol.Collection.Event(ol.CollectionEventType.ADD, elem));
 };
 
 
 /**
- * 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.
+ * Remove the last element of the collection and return it.
+ * Return `undefined` if the collection is empty.
+ * @return {T|undefined} Element.
+ * @api
  */
-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;
+ol.Collection.prototype.pop = function() {
+  return this.removeAt(this.getLength() - 1);
 };
 
 
 /**
- * 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.
+ * Insert the provided element at the end of the collection.
+ * @param {T} elem Element.
+ * @return {number} New length of the collection.
+ * @api
  */
-goog.object.transpose = function(obj) {
-  var transposed = {};
-  for (var key in obj) {
-    transposed[obj[key]] = key;
+ol.Collection.prototype.push = function(elem) {
+  if (this.unique_) {
+    this.assertUnique_(elem);
   }
-  return transposed;
+  var n = this.getLength();
+  this.insertAt(n, elem);
+  return this.getLength();
 };
 
 
 /**
- * The names of the fields that are defined on Object.prototype.
- * @type {Array<string>}
- * @private
+ * Remove the first occurrence of an element from the collection.
+ * @param {T} elem Element.
+ * @return {T|undefined} The removed element or undefined if none found.
+ * @api
  */
-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];
-      }
+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;
 };
 
 
 /**
- * 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.
+ * 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
  */
-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]);
-  }
+ol.Collection.prototype.removeAt = function(index) {
+  var prev = this.array_[index];
+  this.array_.splice(index, 1);
+  this.updateLength_();
+  this.dispatchEvent(
+      new ol.Collection.Event(ol.CollectionEventType.REMOVE, prev));
+  return prev;
+};
 
-  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];
+/**
+ * Set the element at the provided index.
+ * @param {number} index Index.
+ * @param {T} elem Element.
+ * @api
+ */
+ol.Collection.prototype.setAt = function(index, elem) {
+  var n = this.getLength();
+  if (index < n) {
+    if (this.unique_) {
+      this.assertUnique_(elem, index);
+    }
+    var prev = this.array_[index];
+    this.array_[index] = elem;
+    this.dispatchEvent(
+        new ol.Collection.Event(ol.CollectionEventType.REMOVE, prev));
+    this.dispatchEvent(
+        new ol.Collection.Event(ol.CollectionEventType.ADD, elem));
+  } else {
+    var j;
+    for (j = n; j < index; ++j) {
+      this.insertAt(j, undefined);
+    }
+    this.insertAt(index, elem);
   }
-  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.
+ * @private
  */
-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;
+ol.Collection.prototype.updateLength_ = function() {
+  this.set(ol.Collection.Property_.LENGTH, this.array_.length);
 };
 
 
 /**
- * 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
+ * @private
+ * @param {T} elem Element.
+ * @param {number=} opt_except Optional index to ignore.
  */
-goog.object.createImmutableView = function(obj) {
-  var result = obj;
-  if (Object.isFrozen && !Object.isFrozen(obj)) {
-    result = Object.create(obj);
-    Object.freeze(result);
+ol.Collection.prototype.assertUnique_ = function(elem, opt_except) {
+  for (var i = 0, ii = this.array_.length; i < ii; ++i) {
+    if (this.array_[i] === elem && i !== opt_except) {
+      throw new ol.AssertionError(58);
+    }
   }
-  return result;
 };
 
 
 /**
- * @param {!Object} obj An object.
- * @return {boolean} Whether this is an immutable view of the object.
+ * @enum {string}
+ * @private
  */
-goog.object.isImmutableView = function(obj) {
-  return !!Object.isFrozen && Object.isFrozen(obj);
+ol.Collection.Property_ = {
+  LENGTH: '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 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.)
+ * @classdesc
+ * Events emitted by {@link ol.Collection} instances are instances of this
+ * type.
  *
- * @author martone@google.com (Andy Martone)
+ * @constructor
+ * @extends {ol.events.Event}
+ * @implements {oli.Collection.Event}
+ * @param {ol.CollectionEventType} type Type.
+ * @param {*=} opt_element Element.
  */
+ol.Collection.Event = function(type, opt_element) {
+
+  ol.events.Event.call(this, type);
 
-goog.provide('goog.labs.userAgent.browser');
+  /**
+   * The element that is added to or removed from the collection.
+   * @type {*}
+   * @api
+   */
+  this.element = opt_element;
 
-goog.require('goog.array');
-goog.require('goog.labs.userAgent.util');
-goog.require('goog.object');
-goog.require('goog.string');
+};
+ol.inherits(ol.Collection.Event, ol.events.Event);
 
+goog.provide('ol.color');
 
-// TODO(nnaze): Refactor to remove excessive exclusion logic in matching
-// functions.
+goog.require('ol.asserts');
+goog.require('ol.math');
 
 
 /**
- * @return {boolean} Whether the user's browser is Opera.
+ * This RegExp matches # followed by 3 or 6 hex digits.
+ * @const
+ * @type {RegExp}
  * @private
  */
-goog.labs.userAgent.browser.matchOpera_ = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Opera') ||
-      goog.labs.userAgent.util.matchUserAgent('OPR');
-};
+ol.color.HEX_COLOR_RE_ = /^#(?:[0-9a-f]{3}){1,2}$/i;
 
 
 /**
- * @return {boolean} Whether the user's browser is IE.
+ * Regular expression for matching potential named color style strings.
+ * @const
+ * @type {RegExp}
  * @private
  */
-goog.labs.userAgent.browser.matchIE_ = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Trident') ||
-      goog.labs.userAgent.util.matchUserAgent('MSIE');
-};
+ol.color.NAMED_COLOR_RE_ = /^([a-z]*)$/i;
 
 
 /**
- * @return {boolean} Whether the user's browser is Edge.
- * @private
+ * Return the color as an array. This function maintains a cache of calculated
+ * arrays which means the result should not be modified.
+ * @param {ol.Color|string} color Color.
+ * @return {ol.Color} Color.
+ * @api
  */
-goog.labs.userAgent.browser.matchEdge_ = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Edge');
+ol.color.asArray = function(color) {
+  if (Array.isArray(color)) {
+    return color;
+  } else {
+    return ol.color.fromString(/** @type {string} */ (color));
+  }
 };
 
 
 /**
- * @return {boolean} Whether the user's browser is Firefox.
- * @private
+ * Return the color as an rgba string.
+ * @param {ol.Color|string} color Color.
+ * @return {string} Rgba string.
+ * @api
  */
-goog.labs.userAgent.browser.matchFirefox_ = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Firefox');
+ol.color.asString = function(color) {
+  if (typeof color === 'string') {
+    return color;
+  } else {
+    return ol.color.toString(color);
+  }
 };
 
-
 /**
- * @return {boolean} Whether the user's browser is Safari.
- * @private
+ * Return named color as an rgba string.
+ * @param {string} color Named color.
+ * @return {string} Rgb string.
  */
-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'));
+ol.color.fromNamed = function(color) {
+  var el = document.createElement('div');
+  el.style.color = color;
+  document.body.appendChild(el);
+  var rgb = getComputedStyle(el).color;
+  document.body.removeChild(el);
+  return rgb;
 };
 
 
 /**
- * @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
- *     iOS browser).
- * @private
+ * @param {string} s String.
+ * @return {ol.Color} Color.
  */
-goog.labs.userAgent.browser.matchCoast_ = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Coast');
-};
+ol.color.fromString = (
+    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.
 
-/**
- * @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');
-};
+      /**
+       * @const
+       * @type {number}
+       */
+      var MAX_CACHE_SIZE = 1024;
+
+      /**
+       * @type {Object.<string, ol.Color>}
+       */
+      var cache = {};
 
+      /**
+       * @type {number}
+       */
+      var cacheSize = 0;
 
-/**
- * @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 (
+          /**
+           * @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;
+          });
+
+    })();
 
 
 /**
- * @return {boolean} Whether the user's browser is the Android browser.
+ * @param {string} s String.
  * @private
+ * @return {ol.Color} Color.
  */
-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());
+ol.color.fromStringInternal_ = function(s) {
+  var r, g, b, a, color, parts;
+
+  if (ol.color.NAMED_COLOR_RE_.exec(s)) {
+    s = ol.color.fromNamed(s);
+  }
+
+  if (ol.color.HEX_COLOR_RE_.exec(s)) { // hex
+    var n = s.length - 1; // number of hex digits
+    ol.asserts.assert(n == 3 || n == 6, 54); // Hex color should have 3 or 6 digits
+    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];
+  } else if (s.indexOf('rgba(') == 0) { // rgba()
+    parts = s.slice(5, -1).split(',').map(Number);
+    color = ol.color.normalize(parts);
+  } else if (s.indexOf('rgb(') == 0) { // rgb()
+    parts = s.slice(4, -1).split(',').map(Number);
+    parts.push(1);
+    color = ol.color.normalize(parts);
+  } else {
+    ol.asserts.assert(false, 14); // Invalid color
+  }
+  return /** @type {ol.Color} */ (color);
 };
 
 
 /**
- * @return {boolean} Whether the user's browser is Opera.
+ * @param {ol.Color} color Color.
+ * @param {ol.Color=} opt_color Color.
+ * @return {ol.Color} Clamped color.
  */
-goog.labs.userAgent.browser.isOpera = goog.labs.userAgent.browser.matchOpera_;
+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;
+};
 
 
 /**
- * @return {boolean} Whether the user's browser is IE.
+ * @param {ol.Color} color Color.
+ * @return {string} String.
  */
-goog.labs.userAgent.browser.isIE = goog.labs.userAgent.browser.matchIE_;
+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] === undefined ? 1 : color[3];
+  return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
+};
 
+goog.provide('ol.colorlike');
 
-/**
- * @return {boolean} Whether the user's browser is Edge.
- */
-goog.labs.userAgent.browser.isEdge = goog.labs.userAgent.browser.matchEdge_;
+goog.require('ol.color');
 
 
 /**
- * @return {boolean} Whether the user's browser is Firefox.
+ * @param {ol.Color|ol.ColorLike} color Color.
+ * @return {ol.ColorLike} The color as an ol.ColorLike
+ * @api
  */
-goog.labs.userAgent.browser.isFirefox =
-    goog.labs.userAgent.browser.matchFirefox_;
+ol.colorlike.asColorLike = function(color) {
+  if (ol.colorlike.isColorLike(color)) {
+    return /** @type {string|CanvasPattern|CanvasGradient} */ (color);
+  } else {
+    return ol.color.asString(/** @type {ol.Color} */ (color));
+  }
+};
 
 
 /**
- * @return {boolean} Whether the user's browser is Safari.
+ * @param {?} color The value that is potentially an ol.ColorLike
+ * @return {boolean} Whether the color is an ol.ColorLike
  */
-goog.labs.userAgent.browser.isSafari =
-    goog.labs.userAgent.browser.matchSafari_;
+ol.colorlike.isColorLike = function(color) {
+  return (
+      typeof color === 'string' ||
+      color instanceof CanvasPattern ||
+      color instanceof CanvasGradient
+  );
+};
+
+goog.provide('ol.dom');
 
 
 /**
- * @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
- *     iOS browser).
+ * Create an html canvas element and returns its 2d context.
+ * @param {number=} opt_width Canvas width.
+ * @param {number=} opt_height Canvas height.
+ * @return {CanvasRenderingContext2D} The context.
  */
-goog.labs.userAgent.browser.isCoast =
-    goog.labs.userAgent.browser.matchCoast_;
+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');
+};
 
 
 /**
- * @return {boolean} Whether the user's browser is iOS Webview.
+ * 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} The width.
  */
-goog.labs.userAgent.browser.isIosWebview =
-    goog.labs.userAgent.browser.matchIosWebview_;
+ol.dom.outerWidth = function(element) {
+  var width = element.offsetWidth;
+  var style = getComputedStyle(element);
+  width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);
+
+  return width;
+};
 
 
 /**
- * @return {boolean} Whether the user's browser is Chrome.
+ * 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} The height.
  */
-goog.labs.userAgent.browser.isChrome =
-    goog.labs.userAgent.browser.matchChrome_;
+ol.dom.outerHeight = function(element) {
+  var height = element.offsetHeight;
+  var style = getComputedStyle(element);
+  height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);
 
+  return height;
+};
 
 /**
- * @return {boolean} Whether the user's browser is the Android browser.
+ * @param {Node} newNode Node to replace old node
+ * @param {Node} oldNode The node to be replaced
  */
-goog.labs.userAgent.browser.isAndroidBrowser =
-    goog.labs.userAgent.browser.matchAndroidBrowser_;
-
+ol.dom.replaceNode = function(newNode, oldNode) {
+  var parent = oldNode.parentNode;
+  if (parent) {
+    parent.replaceChild(newNode, oldNode);
+  }
+};
 
 /**
- * For more information, see:
- * http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
- * @return {boolean} Whether the user's browser is Silk.
+ * @param {Node} node The node to remove.
+ * @returns {Node} The node that was removed or null.
  */
-goog.labs.userAgent.browser.isSilk = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Silk');
+ol.dom.removeNode = function(node) {
+  return node && node.parentNode ? node.parentNode.removeChild(node) : null;
 };
 
-
 /**
- * @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.)
+ * @param {Node} node The node to remove the children from.
  */
-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);
+ol.dom.removeChildren = function(node) {
+  while (node.lastChild) {
+    node.removeChild(node.lastChild);
   }
+};
 
-  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);
+goog.provide('ol.MapEventType');
 
-  // 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] || '';
-  }
+/**
+ * @enum {string}
+ */
+ol.MapEventType = {
 
-  // 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']);
-  }
+  /**
+   * Triggered after a map frame is rendered.
+   * @event ol.MapEvent#postrender
+   * @api
+   */
+  POSTRENDER: 'postrender',
 
-  // Check Edge before Chrome since it has Chrome in the string.
-  if (goog.labs.userAgent.browser.isEdge()) {
-    return lookUpValueWithKeys(['Edge']);
-  }
+  /**
+   * Triggered when the map starts moving.
+   * @event ol.MapEvent#movestart
+   * @api
+   */
+  MOVESTART: 'movestart',
 
-  if (goog.labs.userAgent.browser.isChrome()) {
-    return lookUpValueWithKeys(['Chrome', 'CriOS']);
-  }
+  /**
+   * Triggered after the map is moved.
+   * @event ol.MapEvent#moveend
+   * @api
+   */
+  MOVEEND: 'moveend'
 
-  // Usually products browser versions are in the third tuple after "Mozilla"
-  // and the engine.
-  var tuple = versionTuples[2];
-  return tuple && tuple[1] || '';
 };
 
+goog.provide('ol.control.Control');
 
-/**
- * @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;
-};
+goog.require('ol');
+goog.require('ol.MapEventType');
+goog.require('ol.Object');
+goog.require('ol.dom');
+goog.require('ol.events');
 
 
 /**
- * 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
+ * @classdesc
+ * A control is a visible widget with a DOM element in a fixed position on the
+ * screen. They can involve user input (buttons), or be informational only;
+ * the position is determined using CSS. By default these are placed in the
+ * container with CSS class name `ol-overlaycontainer-stopevent`, but can use
+ * any outside DOM element.
  *
- * @param {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).
+ * 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
  */
+ol.control.Control = function(options) {
 
-goog.provide('goog.labs.userAgent.engine');
+  ol.Object.call(this);
 
-goog.require('goog.array');
-goog.require('goog.labs.userAgent.util');
-goog.require('goog.string');
+  /**
+   * @protected
+   * @type {Element}
+   */
+  this.element = options.element ? options.element : null;
 
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.target_ = null;
 
-/**
- * @return {boolean} Whether the rendering engine is Presto.
- */
-goog.labs.userAgent.engine.isPresto = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Presto');
-};
+  /**
+   * @private
+   * @type {ol.Map}
+   */
+  this.map_ = null;
 
+  /**
+   * @protected
+   * @type {!Array.<ol.EventsKey>}
+   */
+  this.listenerKeys = [];
 
-/**
- * @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');
-};
+  /**
+   * @type {function(ol.MapEvent)}
+   */
+  this.render = options.render ? options.render : ol.nullFunction;
 
+  if (options.target) {
+    this.setTarget(options.target);
+  }
 
-/**
- * @return {boolean} Whether the rendering engine is Edge.
- */
-goog.labs.userAgent.engine.isEdge = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Edge');
 };
+ol.inherits(ol.control.Control, ol.Object);
 
 
 /**
- * @return {boolean} Whether the rendering engine is WebKit.
+ * @inheritDoc
  */
-goog.labs.userAgent.engine.isWebKit = function() {
-  return goog.labs.userAgent.util.matchUserAgentIgnoreCase('WebKit') &&
-      !goog.labs.userAgent.engine.isEdge();
+ol.control.Control.prototype.disposeInternal = function() {
+  ol.dom.removeNode(this.element);
+  ol.Object.prototype.disposeInternal.call(this);
 };
 
 
 /**
- * @return {boolean} Whether the rendering engine is Gecko.
+ * Get the map associated with this control.
+ * @return {ol.Map} Map.
+ * @api
  */
-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();
+ol.control.Control.prototype.getMap = function() {
+  return this.map_;
 };
 
 
 /**
- * @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
+ * 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.
+ * @override
+ * @api
  */
-goog.labs.userAgent.engine.getEngineTuple_ = function(tuples) {
-  if (!goog.labs.userAgent.engine.isEdge()) {
-    return tuples[1];
+ol.control.Control.prototype.setMap = function(map) {
+  if (this.map_) {
+    ol.dom.removeNode(this.element);
+  }
+  for (var i = 0, ii = this.listenerKeys.length; i < ii; ++i) {
+    ol.events.unlistenByKey(this.listenerKeys[i]);
   }
-  for (var i = 0; i < tuples.length; i++) {
-    var tuple = tuples[i];
-    if (tuple[0] == 'Edge') {
-      return tuple;
+  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(ol.events.listen(map,
+          ol.MapEventType.POSTRENDER, this.render, this));
     }
+    map.render();
   }
 };
 
 
 /**
- * @param {string|number} version The version to check.
- * @return {boolean} Whether the rendering engine version is higher or the same
- *     as the given version.
+ * This function is used to set a target element for the control. It has no
+ * effect if it is called after the control has been added to the map (i.e.
+ * after `setMap` is called on the control). If no `target` is set in the
+ * options passed to the control constructor and if `setTarget` is not called
+ * then the control is added to the map's overlay container.
+ * @param {Element|string} target Target.
+ * @api
  */
-goog.labs.userAgent.engine.isVersionOrHigher = function(version) {
-  return goog.string.compareVersions(goog.labs.userAgent.engine.getVersion(),
-                                     version) >= 0;
+ol.control.Control.prototype.setTarget = function(target) {
+  this.target_ = typeof target === 'string' ?
+    document.getElementById(target) :
+    target;
 };
 
+goog.provide('ol.css');
 
-/**
- * @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).
+ * The CSS class for hidden feature.
  *
+ * @const
+ * @type {string}
  */
-
-goog.provide('goog.labs.userAgent.platform');
-
-goog.require('goog.labs.userAgent.util');
-goog.require('goog.string');
+ol.css.CLASS_HIDDEN = 'ol-hidden';
 
 
 /**
- * @return {boolean} Whether the platform is Android.
+ * The CSS class that we'll give the DOM elements to have them selectable.
+ *
+ * @const
+ * @type {string}
  */
-goog.labs.userAgent.platform.isAndroid = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Android');
-};
-
+ol.css.CLASS_SELECTABLE = 'ol-selectable';
 
 /**
- * @return {boolean} Whether the platform is iPod.
+ * The CSS class that we'll give the DOM elements to have them unselectable.
+ *
+ * @const
+ * @type {string}
  */
-goog.labs.userAgent.platform.isIpod = function() {
-  return goog.labs.userAgent.util.matchUserAgent('iPod');
-};
+ol.css.CLASS_UNSELECTABLE = 'ol-unselectable';
 
 
 /**
- * @return {boolean} Whether the platform is iPhone.
+ * The CSS class for unsupported feature.
+ *
+ * @const
+ * @type {string}
  */
-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');
-};
+ol.css.CLASS_UNSUPPORTED = 'ol-unsupported';
 
 
 /**
- * @return {boolean} Whether the platform is iPad.
+ * The CSS class for controls.
+ *
+ * @const
+ * @type {string}
  */
-goog.labs.userAgent.platform.isIpad = function() {
-  return goog.labs.userAgent.util.matchUserAgent('iPad');
-};
-
+ol.css.CLASS_CONTROL = 'ol-control';
 
-/**
- * @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();
-};
+// FIXME handle date line wrap
 
+goog.provide('ol.control.Attribution');
 
-/**
- * @return {boolean} Whether the platform is Mac.
- */
-goog.labs.userAgent.platform.isMacintosh = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Macintosh');
-};
+goog.require('ol');
+goog.require('ol.dom');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.obj');
 
 
 /**
- * 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.
+ * @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
  */
-goog.labs.userAgent.platform.isLinux = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Linux');
-};
+ol.control.Attribution = function(opt_options) {
 
+  var options = opt_options ? opt_options : {};
 
-/**
- * @return {boolean} Whether the platform is Windows.
- */
-goog.labs.userAgent.platform.isWindows = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Windows');
-};
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.ulElement_ = document.createElement('UL');
 
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.logoLi_ = document.createElement('LI');
 
-/**
- * @return {boolean} Whether the platform is ChromeOS.
- */
-goog.labs.userAgent.platform.isChromeOS = function() {
-  return goog.labs.userAgent.util.matchUserAgent('CrOS');
-};
+  this.ulElement_.appendChild(this.logoLi_);
+  this.logoLi_.style.display = 'none';
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;
 
-/**
- * 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];
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.collapsible_ = options.collapsible !== undefined ?
+      options.collapsible : true;
+
+  if (!this.collapsible_) {
+    this.collapsed_ = false;
   }
-  return version || '';
-};
 
+  var className = options.className !== undefined ? options.className : 'ol-attribution';
 
-/**
- * @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;
-};
+  var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Attributions';
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+  var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00BB';
 
-/**
- * @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
- */
+  if (typeof collapseLabel === 'string') {
+    /**
+     * @private
+     * @type {Node}
+     */
+    this.collapseLabel_ = document.createElement('span');
+    this.collapseLabel_.textContent = collapseLabel;
+  } else {
+    this.collapseLabel_ = collapseLabel;
+  }
 
-goog.provide('goog.userAgent');
+  var label = options.label !== undefined ? options.label : 'i';
 
-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');
+  if (typeof label === 'string') {
+    /**
+     * @private
+     * @type {Node}
+     */
+    this.label_ = document.createElement('span');
+    this.label_.textContent = label;
+  } else {
+    this.label_ = label;
+  }
 
 
-/**
- * @define {boolean} Whether we know at compile-time that the browser is IE.
- */
-goog.define('goog.userAgent.ASSUME_IE', false);
+  var activeLabel = (this.collapsible_ && !this.collapsed_) ?
+      this.collapseLabel_ : this.label_;
+  var button = document.createElement('button');
+  button.setAttribute('type', 'button');
+  button.title = tipLabel;
+  button.appendChild(activeLabel);
 
+  ol.events.listen(button, ol.events.EventType.CLICK, this.handleClick_, this);
 
-/**
- * @define {boolean} Whether we know at compile-time that the browser is EDGE.
- */
-goog.define('goog.userAgent.ASSUME_EDGE', false);
+  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
+      ol.css.CLASS_CONTROL +
+      (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') +
+      (this.collapsible_ ? '' : ' ol-uncollapsible');
+  var element = document.createElement('div');
+  element.className = cssClasses;
+  element.appendChild(this.ulElement_);
+  element.appendChild(button);
 
+  var render = options.render ? options.render : ol.control.Attribution.render;
 
-/**
- * @define {boolean} Whether we know at compile-time that the browser is GECKO.
- */
-goog.define('goog.userAgent.ASSUME_GECKO', false);
+  ol.control.Control.call(this, {
+    element: element,
+    render: render,
+    target: options.target
+  });
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.renderedVisible_ = true;
 
+  /**
+   * @private
+   * @type {Object.<string, Element>}
+   */
+  this.attributionElements_ = {};
 
-/**
- * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
- */
-goog.define('goog.userAgent.ASSUME_WEBKIT', false);
+  /**
+   * @private
+   * @type {Object.<string, boolean>}
+   */
+  this.attributionElementRenderedVisible_ = {};
 
+  /**
+   * @private
+   * @type {Object.<string, Element>}
+   */
+  this.logoElements_ = {};
 
-/**
- * @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);
+};
+ol.inherits(ol.control.Attribution, ol.control.Control);
 
 
 /**
- * @define {boolean} Whether we know at compile-time that the browser is OPERA.
+ * @param {?olx.FrameState} frameState Frame state.
+ * @return {Array.<Object.<string, ol.Attribution>>} Attributions.
  */
-goog.define('goog.userAgent.ASSUME_OPERA', false);
+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 = ol.obj.assign({}, frameState.attributions);
+  /** @type {Object.<string, ol.Attribution>} */
+  var hiddenAttributions = {};
+  var uniqueAttributions = {};
+  var projection = /** @type {!ol.proj.Projection} */ (frameState.viewState.projection);
+  for (i = 0, ii = layerStatesArray.length; i < ii; i++) {
+    source = layerStatesArray[i].layer.getSource();
+    if (!source) {
+      continue;
+    }
+    sourceKey = ol.getUid(source).toString();
+    sourceAttributions = source.getAttributions();
+    if (!sourceAttributions) {
+      continue;
+    }
+    for (j = 0, jj = sourceAttributions.length; j < jj; j++) {
+      sourceAttribution = sourceAttributions[j];
+      sourceAttributionKey = ol.getUid(sourceAttribution).toString();
+      if (sourceAttributionKey in attributions) {
+        continue;
+      }
+      tileRanges = frameState.usedTiles[sourceKey];
+      if (tileRanges) {
+        var tileGrid = /** @type {ol.source.Tile} */ (source).getTileGridForProjection(projection);
+        intersectsTileRange = sourceAttribution.intersectsAnyTileRange(
+            tileRanges, tileGrid, projection);
+      } else {
+        intersectsTileRange = false;
+      }
+      if (intersectsTileRange) {
+        if (sourceAttributionKey in hiddenAttributions) {
+          delete hiddenAttributions[sourceAttributionKey];
+        }
+        var html = sourceAttribution.getHTML();
+        if (!(html in uniqueAttributions)) {
+          uniqueAttributions[html] = true;
+          attributions[sourceAttributionKey] = sourceAttribution;
+        }
+      } else {
+        hiddenAttributions[sourceAttributionKey] = sourceAttribution;
+      }
+    }
+  }
+  return [attributions, hiddenAttributions];
+};
 
 
 /**
- * @define {boolean} Whether the
- *     {@code goog.userAgent.isVersionOrHigher}
- *     function will return true for any version.
+ * Update the attribution element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.Attribution}
+ * @api
  */
-goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);
+ol.control.Attribution.render = function(mapEvent) {
+  this.updateElement_(mapEvent.frameState);
+};
 
 
 /**
- * Whether we know the browser engine at compile-time.
- * @type {boolean}
  * @private
+ * @param {?olx.FrameState} frameState Frame state.
  */
-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;
+ol.control.Attribution.prototype.updateElement_ = function(frameState) {
 
+  if (!frameState) {
+    if (this.renderedVisible_) {
+      this.element.style.display = 'none';
+      this.renderedVisible_ = false;
+    }
+    return;
+  }
 
-/**
- * Returns the userAgent string for the current browser.
- *
- * @return {string} The userAgent string.
- */
-goog.userAgent.getUserAgentString = function() {
-  return goog.labs.userAgent.util.getUserAgent();
-};
+  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]) {
+        this.attributionElements_[attributionKey].style.display = '';
+        this.attributionElementRenderedVisible_[attributionKey] = true;
+      }
+      delete visibleAttributions[attributionKey];
+    } else if (attributionKey in hiddenAttributions) {
+      if (this.attributionElementRenderedVisible_[attributionKey]) {
+        this.attributionElements_[attributionKey].style.display = 'none';
+        delete this.attributionElementRenderedVisible_[attributionKey];
+      }
+      delete hiddenAttributions[attributionKey];
+    } else {
+      ol.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();
+    attributionElement.style.display = 'none';
+    this.ulElement_.appendChild(attributionElement);
+    this.attributionElements_[attributionKey] = attributionElement;
+  }
 
-/**
- * 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;
-};
+  var renderVisible =
+      !ol.obj.isEmpty(this.attributionElementRenderedVisible_) ||
+      !ol.obj.isEmpty(frameState.logos);
+  if (this.renderedVisible_ != renderVisible) {
+    this.element.style.display = renderVisible ? '' : 'none';
+    this.renderedVisible_ = renderVisible;
+  }
+  if (renderVisible &&
+      ol.obj.isEmpty(this.attributionElementRenderedVisible_)) {
+    this.element.classList.add('ol-logo-only');
+  } else {
+    this.element.classList.remove('ol-logo-only');
+  }
 
+  this.insertLogos_(frameState);
 
-/**
- * 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}
+ * @param {?olx.FrameState} frameState Frame state.
+ * @private
  */
-goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
-    goog.userAgent.ASSUME_IE :
-    goog.labs.userAgent.browser.isIE();
-
+ol.control.Attribution.prototype.insertLogos_ = function(frameState) {
 
-/**
- * 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();
+  var logo;
+  var logos = frameState.logos;
+  var logoElements = this.logoElements_;
 
+  for (logo in logoElements) {
+    if (!(logo in logos)) {
+      ol.dom.removeNode(logoElements[logo]);
+      delete logoElements[logo];
+    }
+  }
 
-/**
- * Whether the user agent is MS Internet Explorer or MS Edge.
- * @type {boolean}
- */
-goog.userAgent.EDGE_OR_IE = goog.userAgent.EDGE || goog.userAgent.IE;
+  var image, logoElement, logoKey;
+  for (logoKey in logos) {
+    var logoValue = logos[logoKey];
+    if (logoValue instanceof HTMLElement) {
+      this.logoLi_.appendChild(logoValue);
+      logoElements[logoKey] = logoValue;
+    }
+    if (!(logoKey in logoElements)) {
+      image = new Image();
+      image.src = logoKey;
+      if (logoValue === '') {
+        logoElement = image;
+      } else {
+        logoElement = document.createElement('a');
+        logoElement.href = logoValue;
+        logoElement.appendChild(image);
+      }
+      this.logoLi_.appendChild(logoElement);
+      logoElements[logoKey] = logoElement;
+    }
+  }
 
+  this.logoLi_.style.display = !ol.obj.isEmpty(logos) ? '' : 'none';
 
-/**
- * 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}
+ * @param {Event} event The event to handle
+ * @private
  */
-goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
-    goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
-    goog.labs.userAgent.engine.isWebKit();
+ol.control.Attribution.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  this.handleToggle_();
+};
 
 
 /**
- * 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');
+ol.control.Attribution.prototype.handleToggle_ = function() {
+  this.element.classList.toggle('ol-collapsed');
+  if (this.collapsed_) {
+    ol.dom.replaceNode(this.collapseLabel_, this.label_);
+  } else {
+    ol.dom.replaceNode(this.label_, this.collapseLabel_);
+  }
+  this.collapsed_ = !this.collapsed_;
 };
 
 
 /**
- * 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}
+ * Return `true` if the attribution is collapsible, `false` otherwise.
+ * @return {boolean} True if the widget is collapsible.
+ * @api
  */
-goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT ||
-                        goog.userAgent.isMobile_();
+ol.control.Attribution.prototype.getCollapsible = function() {
+  return this.collapsible_;
+};
 
 
 /**
- * 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.
+ * Set whether the attribution should be collapsible.
+ * @param {boolean} collapsible True if the widget is collapsible.
+ * @api
  */
-goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
+ol.control.Attribution.prototype.setCollapsible = function(collapsible) {
+  if (this.collapsible_ === collapsible) {
+    return;
+  }
+  this.collapsible_ = collapsible;
+  this.element.classList.toggle('ol-uncollapsible');
+  if (!collapsible && this.collapsed_) {
+    this.handleToggle_();
+  }
+};
 
 
 /**
- * @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
+ * 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
  */
-goog.userAgent.determinePlatform_ = function() {
-  var navigator = goog.userAgent.getNavigator();
-  return navigator && navigator.platform || '';
+ol.control.Attribution.prototype.setCollapsed = function(collapsed) {
+  if (!this.collapsible_ || this.collapsed_ === collapsed) {
+    return;
+  }
+  this.handleToggle_();
 };
 
 
 /**
- * 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}
+ * Return `true` when the attribution is currently collapsed or `false`
+ * otherwise.
+ * @return {boolean} True if the widget is collapsed.
+ * @api
  */
-goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
-
+ol.control.Attribution.prototype.getCollapsed = function() {
+  return this.collapsed_;
+};
 
-/**
- * @define {boolean} Whether the user agent is running on a Macintosh operating
- *     system.
- */
-goog.define('goog.userAgent.ASSUME_MAC', false);
+goog.provide('ol.easing');
 
 
 /**
- * @define {boolean} Whether the user agent is running on a Windows operating
- *     system.
+ * Start slow and speed up.
+ * @param {number} t Input between 0 and 1.
+ * @return {number} Output between 0 and 1.
+ * @api
  */
-goog.define('goog.userAgent.ASSUME_WINDOWS', false);
+ol.easing.easeIn = function(t) {
+  return Math.pow(t, 3);
+};
 
 
 /**
- * @define {boolean} Whether the user agent is running on a Linux operating
- *     system.
+ * Start fast and slow down.
+ * @param {number} t Input between 0 and 1.
+ * @return {number} Output between 0 and 1.
+ * @api
  */
-goog.define('goog.userAgent.ASSUME_LINUX', false);
+ol.easing.easeOut = function(t) {
+  return 1 - ol.easing.easeIn(1 - t);
+};
 
 
 /**
- * @define {boolean} Whether the user agent is running on a X11 windowing
- *     system.
+ * 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
  */
-goog.define('goog.userAgent.ASSUME_X11', false);
+ol.easing.inAndOut = function(t) {
+  return 3 * t * t - 2 * t * t * t;
+};
 
 
 /**
- * @define {boolean} Whether the user agent is running on Android.
+ * Maintain a constant speed over time.
+ * @param {number} t Input between 0 and 1.
+ * @return {number} Output between 0 and 1.
+ * @api
  */
-goog.define('goog.userAgent.ASSUME_ANDROID', false);
+ol.easing.linear = function(t) {
+  return t;
+};
 
 
 /**
- * @define {boolean} Whether the user agent is running on an iPhone.
+ * 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
  */
-goog.define('goog.userAgent.ASSUME_IPHONE', false);
+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.control.Rotate');
 
-/**
- * @define {boolean} Whether the user agent is running on an iPad.
- */
-goog.define('goog.userAgent.ASSUME_IPAD', false);
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.easing');
 
 
 /**
- * @type {boolean}
- * @private
+ * @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
  */
-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;
+ol.control.Rotate = function(opt_options) {
 
+  var options = opt_options ? opt_options : {};
 
-/**
- * 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();
+  var className = options.className !== undefined ? options.className : 'ol-rotate';
 
+  var label = options.label !== undefined ? options.label : '\u21E7';
 
-/**
- * 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();
+  /**
+   * @type {Element}
+   * @private
+   */
+  this.label_ = null;
 
+  if (typeof label === 'string') {
+    this.label_ = document.createElement('span');
+    this.label_.className = 'ol-compass';
+    this.label_.textContent = label;
+  } else {
+    this.label_ = label;
+    this.label_.classList.add('ol-compass');
+  }
 
-/**
- * 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();
-};
+  var tipLabel = options.tipLabel ? options.tipLabel : 'Reset rotation';
 
+  var button = document.createElement('button');
+  button.className = className + '-reset';
+  button.setAttribute('type', 'button');
+  button.title = tipLabel;
+  button.appendChild(this.label_);
 
-/**
- * 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_();
+  ol.events.listen(button, ol.events.EventType.CLICK,
+      ol.control.Rotate.prototype.handleClick_, this);
 
+  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
+      ol.css.CLASS_CONTROL;
+  var element = document.createElement('div');
+  element.className = cssClasses;
+  element.appendChild(button);
 
-/**
- * @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');
-};
-
+  var render = options.render ? options.render : ol.control.Rotate.render;
 
-/**
- * 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_();
+  this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined;
 
+  ol.control.Control.call(this, {
+    element: element,
+    render: render,
+    target: options.target
+  });
 
-/**
- * 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();
+  /**
+   * @type {number}
+   * @private
+   */
+  this.duration_ = options.duration !== undefined ? options.duration : 250;
 
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true;
 
-/**
- * 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();
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.rotation_ = undefined;
 
+  if (this.autoHide_) {
+    this.element.classList.add(ol.css.CLASS_HIDDEN);
+  }
 
-/**
- * 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();
+};
+ol.inherits(ol.control.Rotate, ol.control.Control);
 
 
 /**
- * @return {string} The string that describes the version number of the user
- *     agent.
- * Assumes user agent is opera.
+ * @param {Event} event The event to handle
  * @private
  */
-goog.userAgent.operaVersion_ = function() {
-  var version = goog.global.opera.version;
-  try {
-    return version();
-  } catch (e) {
-    return version;
+ol.control.Rotate.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  if (this.callResetNorth_ !== undefined) {
+    this.callResetNorth_();
+  } else {
+    this.resetNorth_();
   }
 };
 
 
 /**
- * @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] : '';
+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;
   }
-
-  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);
+  if (view.getRotation() !== undefined) {
+    if (this.duration_ > 0) {
+      view.animate({
+        rotation: 0,
+        duration: this.duration_,
+        easing: ol.easing.easeOut
+      });
+    } else {
+      view.setRotation(0);
     }
   }
-
-  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
+ * Update the rotate control element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.Rotate}
+ * @api
  */
-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);
+ol.control.Rotate.render = function(mapEvent) {
+  var frameState = mapEvent.frameState;
+  if (!frameState) {
+    return;
   }
-  if (goog.userAgent.WEBKIT) {
-    // WebKit/125.4
-    return /WebKit\/(\S+)/.exec(userAgent);
+  var rotation = frameState.viewState.rotation;
+  if (rotation != this.rotation_) {
+    var transform = 'rotate(' + rotation + 'rad)';
+    if (this.autoHide_) {
+      var contains = this.element.classList.contains(ol.css.CLASS_HIDDEN);
+      if (!contains && rotation === 0) {
+        this.element.classList.add(ol.css.CLASS_HIDDEN);
+      } else if (contains && rotation !== 0) {
+        this.element.classList.remove(ol.css.CLASS_HIDDEN);
+      }
+    }
+    this.label_.style.msTransform = transform;
+    this.label_.style.webkitTransform = transform;
+    this.label_.style.transform = transform;
   }
+  this.rotation_ = rotation;
 };
 
+goog.provide('ol.control.Zoom');
 
-/**
- * @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;
-};
+goog.require('ol');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.easing');
 
 
 /**
- * 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}
+ * @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
  */
-goog.userAgent.VERSION = goog.userAgent.determineVersion_();
+ol.control.Zoom = function(opt_options) {
 
+  var options = opt_options ? opt_options : {};
+
+  var className = options.className !== undefined ? options.className : 'ol-zoom';
+
+  var delta = options.delta !== undefined ? options.delta : 1;
+
+  var zoomInLabel = options.zoomInLabel !== undefined ? options.zoomInLabel : '+';
+  var zoomOutLabel = options.zoomOutLabel !== undefined ? options.zoomOutLabel : '\u2212';
+
+  var zoomInTipLabel = options.zoomInTipLabel !== undefined ?
+      options.zoomInTipLabel : 'Zoom in';
+  var zoomOutTipLabel = options.zoomOutTipLabel !== undefined ?
+      options.zoomOutTipLabel : 'Zoom out';
+
+  var inElement = document.createElement('button');
+  inElement.className = className + '-in';
+  inElement.setAttribute('type', 'button');
+  inElement.title = zoomInTipLabel;
+  inElement.appendChild(
+    typeof zoomInLabel === 'string' ? document.createTextNode(zoomInLabel) : zoomInLabel
+  );
+
+  ol.events.listen(inElement, ol.events.EventType.CLICK,
+      ol.control.Zoom.prototype.handleClick_.bind(this, delta));
+
+  var outElement = document.createElement('button');
+  outElement.className = className + '-out';
+  outElement.setAttribute('type', 'button');
+  outElement.title = zoomOutTipLabel;
+  outElement.appendChild(
+    typeof zoomOutLabel === 'string' ? document.createTextNode(zoomOutLabel) : zoomOutLabel
+  );
+
+  ol.events.listen(outElement, ol.events.EventType.CLICK,
+      ol.control.Zoom.prototype.handleClick_.bind(this, -delta));
+
+  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
+      ol.css.CLASS_CONTROL;
+  var element = document.createElement('div');
+  element.className = cssClasses;
+  element.appendChild(inElement);
+  element.appendChild(outElement);
+
+  ol.control.Control.call(this, {
+    element: element,
+    target: options.target
+  });
+
+  /**
+   * @type {number}
+   * @private
+   */
+  this.duration_ = options.duration !== undefined ? options.duration : 250;
 
-/**
- * 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);
 };
+ol.inherits(ol.control.Zoom, ol.control.Control);
 
 
 /**
- * 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
+ * @param {number} delta Zoom delta.
+ * @param {Event} event The event to handle
  * @private
  */
-goog.userAgent.isVersionOrHigherCache_ = {};
+ol.control.Zoom.prototype.handleClick_ = function(delta, event) {
+  event.preventDefault();
+  this.zoomByDelta_(delta);
+};
 
 
 /**
- * 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.
+ * @param {number} delta Zoom delta.
+ * @private
  */
-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);
+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) {
+    var newResolution = view.constrainResolution(currentResolution, delta);
+    if (this.duration_ > 0) {
+      if (view.getAnimating()) {
+        view.cancelAnimations();
+      }
+      view.animate({
+        resolution: newResolution,
+        duration: this.duration_,
+        easing: ol.easing.easeOut
+      });
+    } else {
+      view.setResolution(newResolution);
+    }
+  }
 };
 
+goog.provide('ol.control');
 
-/**
- * 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;
+goog.require('ol.Collection');
+goog.require('ol.control.Attribution');
+goog.require('ol.control.Rotate');
+goog.require('ol.control.Zoom');
 
 
 /**
- * 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.
+ * 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 {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.
+ * @param {olx.control.DefaultsOptions=} opt_options Defaults options.
+ * @return {ol.Collection.<ol.control.Control>} Controls.
+ * @api
  */
-goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
-  return goog.userAgent.DOCUMENT_MODE >= documentMode;
-};
+ol.control.defaults = function(opt_options) {
 
+  var options = opt_options ? opt_options : {};
 
-/**
- * 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;
+  var controls = new ol.Collection();
 
+  var zoomControl = options.zoom !== undefined ? options.zoom : true;
+  if (zoomControl) {
+    controls.push(new ol.control.Zoom(options.zoomOptions));
+  }
 
-/**
- * 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;
+  var rotateControl = options.rotate !== undefined ? options.rotate : true;
+  if (rotateControl) {
+    controls.push(new ol.control.Rotate(options.rotateOptions));
   }
-  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.
+  var attributionControl = options.attribution !== undefined ?
+      options.attribution : true;
+  if (attributionControl) {
+    controls.push(new ol.control.Attribution(options.attributionOptions));
+  }
 
-/**
- * @fileoverview Browser capability checks for the events package.
- *
- */
+  return controls;
 
+};
 
-goog.provide('goog.events.BrowserFeature');
+goog.provide('ol.control.FullScreen');
 
-goog.require('goog.userAgent');
+goog.require('ol');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
 
 
 /**
- * Enum of browser capabilities.
- * @enum {boolean}
+ * @classdesc
+ * Provides a button that when clicked fills up the full screen with the map.
+ * The full screen source element is by default the element containing the map viewport unless
+ * overridden by providing the `source` option. In which case, the dom
+ * element introduced using this parameter will be displayed in full screen.
+ *
+ * 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
  */
-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),
+ol.control.FullScreen = function(opt_options) {
 
-  /**
-   * Whether the browser supports full W3C event model.
-   */
-  HAS_W3C_EVENT_SUPPORT: !goog.userAgent.IE ||
-      goog.userAgent.isDocumentModeOrHigher(9),
+  var options = opt_options ? opt_options : {};
 
   /**
-   * To prevent default in IE7-8 for certain keydown events we need set the
-   * keyCode to -1.
+   * @private
+   * @type {string}
    */
-  SET_KEY_CODE_TO_PREVENT_DEFAULT: goog.userAgent.IE &&
-      !goog.userAgent.isVersionOrHigher('9'),
+  this.cssClassName_ = options.className !== undefined ? options.className :
+      'ol-full-screen';
+
+  var label = options.label !== undefined ? options.label : '\u2922';
 
   /**
-   * Whether the {@code navigator.onLine} property is supported.
+   * @private
+   * @type {Node}
    */
-  HAS_NAVIGATOR_ONLINE_PROPERTY: !goog.userAgent.WEBKIT ||
-      goog.userAgent.isVersionOrHigher('528'),
+  this.labelNode_ = typeof label === 'string' ?
+      document.createTextNode(label) : label;
+
+  var labelActive = options.labelActive !== undefined ? options.labelActive : '\u00d7';
 
   /**
-   * Whether HTML5 network online/offline events are supported.
+   * @private
+   * @type {Node}
    */
-  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'),
+  this.labelActiveNode_ = typeof labelActive === 'string' ?
+      document.createTextNode(labelActive) : labelActive;
+
+  var tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen';
+  var button = document.createElement('button');
+  button.className = this.cssClassName_ + '-' + ol.control.FullScreen.isFullScreen();
+  button.setAttribute('type', 'button');
+  button.title = tipLabel;
+  button.appendChild(this.labelNode_);
+
+  ol.events.listen(button, ol.events.EventType.CLICK,
+      this.handleClick_, this);
+
+  var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE +
+      ' ' + ol.css.CLASS_CONTROL + ' ' +
+      (!ol.control.FullScreen.isFullScreenSupported() ? ol.css.CLASS_UNSUPPORTED : '');
+  var element = document.createElement('div');
+  element.className = cssClasses;
+  element.appendChild(button);
+
+  ol.control.Control.call(this, {
+    element: element,
+    target: options.target
+  });
 
   /**
-   * Whether HTML5 network events fire on document.body, or otherwise the
-   * window.
+   * @private
+   * @type {boolean}
    */
-  HTML5_NETWORK_EVENTS_FIRE_ON_BODY:
-      goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('8') ||
-      goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'),
+  this.keys_ = options.keys !== undefined ? options.keys : false;
 
   /**
-   * Whether touch is enabled in the browser.
+   * @private
+   * @type {Element|string|undefined}
    */
-  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']))
+  this.source_ = options.source;
+
 };
+ol.inherits(ol.control.FullScreen, ol.control.Control);
 
-// 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)
+ * @param {Event} event The event to handle
+ * @private
  */
+ol.control.FullScreen.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  this.handleFullScreen_();
+};
 
 
-goog.provide('goog.disposable.IDisposable');
+/**
+ * @private
+ */
+ol.control.FullScreen.prototype.handleFullScreen_ = function() {
+  if (!ol.control.FullScreen.isFullScreenSupported()) {
+    return;
+  }
+  var map = this.getMap();
+  if (!map) {
+    return;
+  }
+  if (ol.control.FullScreen.isFullScreen()) {
+    ol.control.FullScreen.exitFullScreen();
+  } else {
+    var element;
+    if (this.source_) {
+      element = typeof this.source_ === 'string' ?
+        document.getElementById(this.source_) :
+        this.source_;
+    } else {
+      element = map.getTargetElement();
+    }
+    if (this.keys_) {
+      ol.control.FullScreen.requestFullScreenWithKeys(element);
 
+    } else {
+      ol.control.FullScreen.requestFullScreen(element);
+    }
+  }
+};
 
 
 /**
- * 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
+ * @private
  */
-goog.disposable.IDisposable = function() {};
+ol.control.FullScreen.prototype.handleFullScreenChange_ = function() {
+  var button = this.element.firstElementChild;
+  var map = this.getMap();
+  if (ol.control.FullScreen.isFullScreen()) {
+    button.className = this.cssClassName_ + '-true';
+    ol.dom.replaceNode(this.labelActiveNode_, this.labelNode_);
+  } else {
+    button.className = this.cssClassName_ + '-false';
+    ol.dom.replaceNode(this.labelNode_, this.labelActiveNode_);
+  }
+  if (map) {
+    map.updateSize();
+  }
+};
 
 
 /**
- * Disposes of the object and its resources.
- * @return {void} Nothing.
+ * @inheritDoc
+ * @api
+ */
+ol.control.FullScreen.prototype.setMap = function(map) {
+  ol.control.Control.prototype.setMap.call(this, map);
+  if (map) {
+    this.listenerKeys.push(ol.events.listen(document,
+        ol.control.FullScreen.getChangeType_(),
+        this.handleFullScreenChange_, this)
+    );
+  }
+};
+
+/**
+ * @return {boolean} Fullscreen is supported by the current platform.
  */
-goog.disposable.IDisposable.prototype.dispose = goog.abstractMethod;
+ol.control.FullScreen.isFullScreenSupported = function() {
+  var body = document.body;
+  return !!(
+    body.webkitRequestFullscreen ||
+    (body.mozRequestFullScreen && document.mozFullScreenEnabled) ||
+    (body.msRequestFullscreen && document.msFullscreenEnabled) ||
+    (body.requestFullscreen && document.fullscreenEnabled)
+  );
+};
 
+/**
+ * @return {boolean} Element is currently in fullscreen.
+ */
+ol.control.FullScreen.isFullScreen = function() {
+  return !!(
+    document.webkitIsFullScreen || document.mozFullScreen ||
+    document.msFullscreenElement || document.fullscreenElement
+  );
+};
 
 /**
- * @return {boolean} Whether the object has been disposed of.
+ * Request to fullscreen an element.
+ * @param {Node} element Element to request fullscreen
  */
-goog.disposable.IDisposable.prototype.isDisposed = goog.abstractMethod;
+ol.control.FullScreen.requestFullScreen = function(element) {
+  if (element.requestFullscreen) {
+    element.requestFullscreen();
+  } else if (element.msRequestFullscreen) {
+    element.msRequestFullscreen();
+  } else if (element.mozRequestFullScreen) {
+    element.mozRequestFullScreen();
+  } else if (element.webkitRequestFullscreen) {
+    element.webkitRequestFullscreen();
+  }
+};
 
-// 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.
+/**
+ * Request to fullscreen an element with keyboard input.
+ * @param {Node} element Element to request fullscreen
+ */
+ol.control.FullScreen.requestFullScreenWithKeys = function(element) {
+  if (element.mozRequestFullScreenWithKeys) {
+    element.mozRequestFullScreenWithKeys();
+  } else if (element.webkitRequestFullscreen) {
+    element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+  } else {
+    ol.control.FullScreen.requestFullScreen(element);
+  }
+};
 
 /**
- * @fileoverview Implements the disposable interface. The dispose method is used
- * to clean up references and resources.
- * @author arv@google.com (Erik Arvidsson)
+ * Exit fullscreen.
  */
+ol.control.FullScreen.exitFullScreen = function() {
+  if (document.exitFullscreen) {
+    document.exitFullscreen();
+  } else if (document.msExitFullscreen) {
+    document.msExitFullscreen();
+  } else if (document.mozCancelFullScreen) {
+    document.mozCancelFullScreen();
+  } else if (document.webkitExitFullscreen) {
+    document.webkitExitFullscreen();
+  }
+};
 
+/**
+ * @return {string} Change type.
+ * @private
+ */
+ol.control.FullScreen.getChangeType_ = (function() {
+  var changeType;
+  return function() {
+    if (!changeType) {
+      var body = document.body;
+      if (body.webkitRequestFullscreen) {
+        changeType = 'webkitfullscreenchange';
+      } else if (body.mozRequestFullScreen) {
+        changeType = 'mozfullscreenchange';
+      } else if (body.msRequestFullscreen) {
+        changeType = 'MSFullscreenChange';
+      } else if (body.requestFullscreen) {
+        changeType = 'fullscreenchange';
+      }
+    }
+    return changeType;
+  };
+})();
 
-goog.provide('goog.Disposable');
-/** @suppress {extraProvide} */
-goog.provide('goog.dispose');
-/** @suppress {extraProvide} */
-goog.provide('goog.disposeAll');
+// FIXME should listen on appropriate pane, once it is defined
 
-goog.require('goog.disposable.IDisposable');
+goog.provide('ol.control.MousePosition');
 
+goog.require('ol');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.Object');
+goog.require('ol.control.Control');
+goog.require('ol.proj');
 
 
 /**
- * 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).
+ * @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
- * @implements {goog.disposable.IDisposable}
+ * @extends {ol.control.Control}
+ * @param {olx.control.MousePositionOptions=} opt_options Mouse position
+ *     options.
+ * @api
  */
-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;
+ol.control.MousePosition = function(opt_options) {
+
+  var options = opt_options ? opt_options : {};
+
+  var element = document.createElement('DIV');
+  element.className = options.className !== undefined ? options.className : 'ol-mouse-position';
+
+  var render = options.render ?
+      options.render : ol.control.MousePosition.render;
+
+  ol.control.Control.call(this, {
+    element: element,
+    render: render,
+    target: options.target
+  });
+
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.control.MousePosition.Property_.PROJECTION),
+      this.handleProjectionChanged_, this);
+
+  if (options.coordinateFormat) {
+    this.setCoordinateFormat(options.coordinateFormat);
+  }
+  if (options.projection) {
+    this.setProjection(options.projection);
   }
-  // Support sealing
-  this.disposed_ = this.disposed_;
-  this.onDisposeCallbacks_ = this.onDisposeCallbacks_;
-};
 
+  /**
+   * @private
+   * @type {string}
+   */
+  this.undefinedHTML_ = options.undefinedHTML !== undefined ? options.undefinedHTML : '';
 
-/**
- * @enum {number} Different monitoring modes for Disposable.
- */
-goog.Disposable.MonitoringMode = {
   /**
-   * No monitoring.
+   * @private
+   * @type {string}
    */
-  OFF: 0,
+  this.renderedHTML_ = element.innerHTML;
+
   /**
-   * 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.
+   * @private
+   * @type {ol.proj.Projection}
    */
-  PERMANENT: 1,
+  this.mapProjection_ = null;
+
   /**
-   * 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.
+   * @private
+   * @type {?ol.TransformFunction}
    */
-  INTERACTIVE: 2
-};
+  this.transform_ = null;
 
+  /**
+   * @private
+   * @type {ol.Pixel}
+   */
+  this.lastMouseMovePixel_ = null;
 
-/**
- * @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);
+};
+ol.inherits(ol.control.MousePosition, ol.control.Control);
 
 
 /**
- * @define {boolean} Whether to attach creation stack to each created disposable
- *     instance; This is only relevant for when MonitoringMode != OFF.
+ * Update the mouseposition element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.MousePosition}
+ * @api
  */
-goog.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true);
+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_);
+};
 
 
 /**
- * Maps the unique ID of every undisposed {@code goog.Disposable} object to
- * the object itself.
- * @type {!Object<number, !goog.Disposable>}
  * @private
  */
-goog.Disposable.instances_ = {};
+ol.control.MousePosition.prototype.handleProjectionChanged_ = function() {
+  this.transform_ = null;
+};
 
 
 /**
- * @return {!Array<!goog.Disposable>} All {@code goog.Disposable} objects that
- *     haven't been disposed of.
+ * 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
  */
-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;
+ol.control.MousePosition.prototype.getCoordinateFormat = function() {
+  return /** @type {ol.CoordinateFormatType|undefined} */ (
+      this.get(ol.control.MousePosition.Property_.COORDINATE_FORMAT));
 };
 
 
 /**
- * Clears the registry of undisposed objects but doesn't dispose of them.
+ * 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
  */
-goog.Disposable.clearUndisposedObjects = function() {
-  goog.Disposable.instances_ = {};
+ol.control.MousePosition.prototype.getProjection = function() {
+  return /** @type {ol.proj.Projection|undefined} */ (
+      this.get(ol.control.MousePosition.Property_.PROJECTION));
 };
 
 
 /**
- * Whether the object has been disposed of.
- * @type {boolean}
- * @private
+ * @param {Event} event Browser event.
+ * @protected
  */
-goog.Disposable.prototype.disposed_ = false;
+ol.control.MousePosition.prototype.handleMouseMove = function(event) {
+  var map = this.getMap();
+  this.lastMouseMovePixel_ = map.getEventPixel(event);
+  this.updateHTML_(this.lastMouseMovePixel_);
+};
 
 
 /**
- * Callbacks to invoke when this object is disposed.
- * @type {Array<!Function>}
- * @private
+ * @param {Event} event Browser event.
+ * @protected
  */
-goog.Disposable.prototype.onDisposeCallbacks_;
+ol.control.MousePosition.prototype.handleMouseOut = function(event) {
+  this.updateHTML_(null);
+  this.lastMouseMovePixel_ = null;
+};
 
 
 /**
- * If monitoring the goog.Disposable instances is enabled, stores the creation
- * stack trace of the Disposable instance.
- * @const {string}
+ * @inheritDoc
+ * @api
  */
-goog.Disposable.prototype.creationStack;
+ol.control.MousePosition.prototype.setMap = function(map) {
+  ol.control.Control.prototype.setMap.call(this, map);
+  if (map) {
+    var viewport = map.getViewport();
+    this.listenerKeys.push(
+        ol.events.listen(viewport, ol.events.EventType.MOUSEMOVE,
+            this.handleMouseMove, this),
+        ol.events.listen(viewport, ol.events.EventType.MOUSEOUT,
+            this.handleMouseOut, this)
+    );
+  }
+};
 
 
 /**
- * @return {boolean} Whether the object has been disposed of.
- * @override
+ * 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
  */
-goog.Disposable.prototype.isDisposed = function() {
-  return this.disposed_;
+ol.control.MousePosition.prototype.setCoordinateFormat = function(format) {
+  this.set(ol.control.MousePosition.Property_.COORDINATE_FORMAT, format);
 };
 
 
 /**
- * @return {boolean} Whether the object has been disposed of.
- * @deprecated Use {@link #isDisposed} instead.
+ * Set the projection that is used to report the mouse position.
+ * @param {ol.ProjectionLike} projection The projection to report mouse
+ *     position in.
+ * @observable
+ * @api
  */
-goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed;
+ol.control.MousePosition.prototype.setProjection = function(projection) {
+  this.set(ol.control.MousePosition.Property_.PROJECTION, ol.proj.get(projection));
+};
 
 
 /**
- * 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
+ * @param {?ol.Pixel} pixel Pixel.
+ * @private
  */
-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];
+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;
+      }
     }
-  }
-};
-
-
-/**
- * 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()();
+    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();
+      }
     }
   }
-};
-
-
-/**
- * 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();
+  if (!this.renderedHTML_ || html != this.renderedHTML_) {
+    this.element.innerHTML = html;
+    this.renderedHTML_ = html;
   }
 };
 
 
 /**
- * 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.
+ * @enum {string}
+ * @private
  */
-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);
-    }
-  }
+ol.control.MousePosition.Property_ = {
+  PROJECTION: 'projection',
+  COORDINATE_FORMAT: 'coordinateFormat'
 };
 
-// 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');
+goog.provide('ol.MapEvent');
 
+goog.require('ol');
+goog.require('ol.events.Event');
 
 
 /**
- * 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>
+ * @classdesc
+ * Events emitted as map events are instances of this type.
+ * See {@link ol.Map} for which events trigger a map event.
  *
- * @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.
- *
+ * @extends {ol.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) {
 
+  ol.events.Event.call(this, type);
 
-goog.provide('goog.events.Event');
-goog.provide('goog.events.EventLike');
+  /**
+   * The map where the event occurred.
+   * @type {ol.Map}
+   * @api
+   */
+  this.map = map;
 
-/**
- * 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');
+  /**
+   * The frame state at the time of the event.
+   * @type {?olx.FrameState}
+   * @api
+   */
+  this.frameState = opt_frameState !== undefined ? opt_frameState : null;
 
+};
+ol.inherits(ol.MapEvent, ol.events.Event);
 
-/**
- * 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;
+goog.provide('ol.MapBrowserEvent');
 
+goog.require('ol');
+goog.require('ol.MapEvent');
 
 
 /**
- * A base class for event objects, so that they can support preventDefault and
- * stopPropagation.
+ * @classdesc
+ * Events emitted as map browser events are instances of this type.
+ * See {@link ol.Map} for which events trigger a map browser event.
  *
- * @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
+ * @extends {ol.MapEvent}
+ * @implements {oli.MapBrowserEvent}
+ * @param {string} type Event type.
+ * @param {ol.Map} map Map.
+ * @param {Event} browserEvent Browser event.
+ * @param {boolean=} opt_dragging Is the map currently being dragged?
+ * @param {?olx.FrameState=} opt_frameState Frame state.
  */
-goog.events.Event = function(type, opt_target) {
-  /**
-   * Event type.
-   * @type {string}
-   */
-  this.type = type instanceof goog.events.EventId ? String(type) : type;
+ol.MapBrowserEvent = function(type, map, browserEvent, opt_dragging,
+    opt_frameState) {
 
-  /**
-   * TODO(tbreisacher): The type should probably be
-   * EventTarget|goog.events.EventTarget.
-   *
-   * Target of the event.
-   * @type {Object|undefined}
-   */
-  this.target = opt_target;
+  ol.MapEvent.call(this, type, map, opt_frameState);
 
   /**
-   * Object that had the listener attached.
-   * @type {Object|undefined}
+   * The original browser event.
+   * @const
+   * @type {Event}
+   * @api
    */
-  this.currentTarget = this.target;
+  this.originalEvent = browserEvent;
 
   /**
-   * 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.
+   * The map pixel relative to the viewport corresponding to the original browser event.
+   * @type {ol.Pixel}
+   * @api
    */
-  this.propagationStopped_ = false;
+  this.pixel = map.getEventPixel(browserEvent);
 
   /**
-   * 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}
+   * The coordinate in view projection corresponding to the original browser event.
+   * @type {ol.Coordinate}
+   * @api
    */
-  this.defaultPrevented = false;
+  this.coordinate = map.getCoordinateFromPixel(this.pixel);
 
   /**
-   * Return value for in internal capture/bubble processing for IE.
+   * Indicates if the map is currently being dragged. Only set for
+   * `POINTERDRAG` and `POINTERMOVE` events. Default is `false`.
+   *
    * @type {boolean}
-   * @public
-   * @suppress {underscore|visibility} Technically public, but referencing this
-   *     outside this package is strongly discouraged.
+   * @api
    */
-  this.returnValue_ = true;
-};
-
+  this.dragging = opt_dragging !== undefined ? opt_dragging : false;
 
-/**
- * Stops event propagation.
- */
-goog.events.Event.prototype.stopPropagation = function() {
-  this.propagationStopped_ = true;
 };
+ol.inherits(ol.MapBrowserEvent, ol.MapEvent);
 
 
 /**
- * Prevents the default action, for example a link redirecting to a url.
+ * Prevents the default browser action.
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault
+ * @override
+ * @api
  */
-goog.events.Event.prototype.preventDefault = function() {
-  this.defaultPrevented = true;
-  this.returnValue_ = false;
+ol.MapBrowserEvent.prototype.preventDefault = function() {
+  ol.MapEvent.prototype.preventDefault.call(this);
+  this.originalEvent.preventDefault();
 };
 
 
 /**
- * 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.
+ * Prevents further propagation of the current event.
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation
+ * @override
+ * @api
  */
-goog.events.Event.stopPropagation = function(e) {
-  e.stopPropagation();
+ol.MapBrowserEvent.prototype.stopPropagation = function() {
+  ol.MapEvent.prototype.stopPropagation.call(this);
+  this.originalEvent.stopPropagation();
 };
 
+goog.provide('ol.webgl');
 
-/**
- * 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();
-};
+goog.require('ol');
 
-// 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)
- */
+if (ol.ENABLE_WEBGL) {
 
+  /** Constants taken from goog.webgl
+   */
 
-goog.provide('goog.events.EventType');
 
-goog.require('goog.userAgent');
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.ONE = 1;
 
 
-/**
- * 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());
-};
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.SRC_ALPHA = 0x0302;
 
 
-/**
- * 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
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.COLOR_ATTACHMENT0 = 0x8CE0;
 
-  // Wheel events
-  // http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents
-  WHEEL: 'wheel',
 
-  // Key events
-  KEYPRESS: 'keypress',
-  KEYDOWN: 'keydown',
-  KEYUP: 'keyup',
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.COLOR_BUFFER_BIT = 0x00004000;
 
-  // 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',
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.TRIANGLES = 0x0004;
 
-  // 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)
- */
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.TRIANGLE_STRIP = 0x0005;
 
-goog.provide('goog.reflect');
 
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.ONE_MINUS_SRC_ALPHA = 0x0303;
 
-/**
- * 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;
-};
 
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.ARRAY_BUFFER = 0x8892;
 
-/**
- * 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;
-};
 
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.ELEMENT_ARRAY_BUFFER = 0x8893;
 
-/**
- * The compiler should optimize this function away iff no one ever uses
- * goog.reflect.sinkValue.
- */
-goog.reflect.sinkValue[' '] = goog.nullFunction;
 
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.STREAM_DRAW = 0x88E0;
 
-/**
- * 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.
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.STATIC_DRAW = 0x88E4;
 
-/**
- * @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');
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.DYNAMIC_DRAW = 0x88E8;
+
 
-goog.require('goog.events.BrowserFeature');
-goog.require('goog.events.Event');
-goog.require('goog.events.EventType');
-goog.require('goog.reflect');
-goog.require('goog.userAgent');
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.CULL_FACE = 0x0B44;
 
 
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.BLEND = 0x0BE2;
 
-/**
- * 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}
+   * @const
+   * @type {number}
    */
-  this.target = null;
+  ol.webgl.STENCIL_TEST = 0x0B90;
+
 
   /**
-   * Node that had the listener attached.
-   * @override
-   * @type {Node|undefined}
+   * @const
+   * @type {number}
    */
-  this.currentTarget = null;
+  ol.webgl.DEPTH_TEST = 0x0B71;
+
 
   /**
-   * For mouseover and mouseout events, the related object for the event.
-   * @type {Node}
+   * @const
+   * @type {number}
    */
-  this.relatedTarget = null;
+  ol.webgl.SCISSOR_TEST = 0x0C11;
+
 
   /**
-   * X-coordinate relative to target.
+   * @const
    * @type {number}
    */
-  this.offsetX = 0;
+  ol.webgl.UNSIGNED_BYTE = 0x1401;
+
 
   /**
-   * Y-coordinate relative to target.
+   * @const
    * @type {number}
    */
-  this.offsetY = 0;
+  ol.webgl.UNSIGNED_SHORT = 0x1403;
+
 
   /**
-   * X-coordinate relative to the window.
+   * @const
    * @type {number}
    */
-  this.clientX = 0;
+  ol.webgl.UNSIGNED_INT = 0x1405;
+
 
   /**
-   * Y-coordinate relative to the window.
+   * @const
    * @type {number}
    */
-  this.clientY = 0;
+  ol.webgl.FLOAT = 0x1406;
+
 
   /**
-   * X-coordinate relative to the monitor.
+   * @const
    * @type {number}
    */
-  this.screenX = 0;
+  ol.webgl.RGBA = 0x1908;
+
 
   /**
-   * Y-coordinate relative to the monitor.
+   * @const
    * @type {number}
    */
-  this.screenY = 0;
+  ol.webgl.FRAGMENT_SHADER = 0x8B30;
+
 
   /**
-   * Which mouse button was pressed.
+   * @const
    * @type {number}
    */
-  this.button = 0;
+  ol.webgl.VERTEX_SHADER = 0x8B31;
+
 
   /**
-   * Keycode of key press.
+   * @const
    * @type {number}
    */
-  this.keyCode = 0;
+  ol.webgl.LINK_STATUS = 0x8B82;
+
 
   /**
-   * Keycode of key press.
+   * @const
    * @type {number}
    */
-  this.charCode = 0;
+  ol.webgl.LINEAR = 0x2601;
+
 
   /**
-   * Whether control was pressed at time of event.
-   * @type {boolean}
+   * @const
+   * @type {number}
    */
-  this.ctrlKey = false;
+  ol.webgl.TEXTURE_MAG_FILTER = 0x2800;
+
 
   /**
-   * Whether alt was pressed at time of event.
-   * @type {boolean}
+   * @const
+   * @type {number}
    */
-  this.altKey = false;
+  ol.webgl.TEXTURE_MIN_FILTER = 0x2801;
+
 
   /**
-   * Whether shift was pressed at time of event.
-   * @type {boolean}
+   * @const
+   * @type {number}
    */
-  this.shiftKey = false;
+  ol.webgl.TEXTURE_WRAP_S = 0x2802;
+
 
   /**
-   * Whether the meta key was pressed at time of event.
-   * @type {boolean}
+   * @const
+   * @type {number}
    */
-  this.metaKey = false;
+  ol.webgl.TEXTURE_WRAP_T = 0x2803;
+
 
   /**
-   * History state object, only set for PopState events where it's a copy of the
-   * state object provided to pushState or replaceState.
-   * @type {Object}
+   * @const
+   * @type {number}
    */
-  this.state = null;
+  ol.webgl.TEXTURE_2D = 0x0DE1;
+
 
   /**
-   * 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}
+   * @const
+   * @type {number}
    */
-  this.platformModifierKey = false;
+  ol.webgl.TEXTURE0 = 0x84C0;
+
 
   /**
-   * The browser event object.
-   * @private {Event}
+   * @const
+   * @type {number}
    */
-  this.event_ = null;
+  ol.webgl.CLAMP_TO_EDGE = 0x812F;
 
-  if (opt_e) {
-    this.init(opt_e, opt_currentTarget);
-  }
-};
-goog.inherits(goog.events.BrowserEvent, goog.events.Event);
 
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.COMPILE_STATUS = 0x8B81;
 
-/**
- * Normalized button constants for the mouse.
- * @enum {number}
- */
-goog.events.BrowserEvent.MouseButton = {
-  LEFT: 0,
-  MIDDLE: 1,
-  RIGHT: 2
-};
 
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.webgl.FRAMEBUFFER = 0x8D40;
 
-/**
- * Static data for mapping mouse buttons.
- * @type {!Array<number>}
- */
-goog.events.BrowserEvent.IEButtonMap = [
-  1, // LEFT
-  4, // MIDDLE
-  2  // RIGHT
-];
 
+  /** end of goog.webgl constants
+   */
 
-/**
- * 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}
+   * @const
+   * @private
+   * @type {Array.<string>}
    */
-  var relevantTouch = e.changedTouches ? e.changedTouches[0] : null;
-
-  // TODO(nicksantos): Change this.target to type EventTarget.
-  this.target = /** @type {Node} */ (e.target) || e.srcElement;
+  ol.webgl.CONTEXT_IDS_ = [
+    'experimental-webgl',
+    'webgl',
+    'webkit-3d',
+    'moz-webgl'
+  ];
 
-  // 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;
+  /**
+   * @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) {
+        // pass
       }
     }
-    // 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;
-  }
+    return null;
+  };
 
-  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;
-  }
+goog.provide('ol.has');
 
-  this.button = e.button;
+goog.require('ol');
+goog.require('ol.webgl');
 
-  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();
-  }
-};
+var ua = typeof navigator !== 'undefined' ?
+    navigator.userAgent.toLowerCase() : '';
 
+/**
+ * User agent string says we are dealing with Firefox as browser.
+ * @type {boolean}
+ */
+ol.has.FIREFOX = ua.indexOf('firefox') !== -1;
 
 /**
- * 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.
+ * User agent string says we are dealing with Safari as browser.
+ * @type {boolean}
  */
-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;
-  }
-};
+ol.has.SAFARI = ua.indexOf('safari') !== -1 && ua.indexOf('chrom') == -1;
 
+/**
+ * User agent string says we are dealing with a WebKit engine.
+ * @type {boolean}
+ */
+ol.has.WEBKIT = ua.indexOf('webkit') !== -1 && ua.indexOf('edge') == -1;
 
 /**
- * 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.
+ * User agent string says we are dealing with a Mac as platform.
+ * @type {boolean}
  */
-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);
-};
+ol.has.MAC = ua.indexOf('macintosh') !== -1;
 
 
 /**
- * @override
+ * The ratio between physical pixels and device-independent pixels
+ * (dips) on the device (`window.devicePixelRatio`).
+ * @const
+ * @type {number}
+ * @api
  */
-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;
-  }
-};
+ol.has.DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1;
 
 
 /**
- * @override
+ * True if the browser's Canvas implementation implements {get,set}LineDash.
+ * @type {boolean}
  */
-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();
-  }
-};
+ol.has.CANVAS_LINE_DASH = false;
 
 
 /**
- * @return {Event} The underlying browser event object.
+ * 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
  */
-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');
-
+ol.has.CANVAS = ol.ENABLE_CANVAS && (
+    /**
+     * @return {boolean} Canvas supported.
+     */
+    function() {
+      if (!('HTMLCanvasElement' in window)) {
+        return false;
+      }
+      try {
+        var context = document.createElement('CANVAS').getContext('2d');
+        if (!context) {
+          return false;
+        } else {
+          if (context.setLineDash !== undefined) {
+            ol.has.CANVAS_LINE_DASH = true;
+          }
+          return true;
+        }
+      } catch (e) {
+        return false;
+      }
+    })();
 
 
 /**
- * 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
+ * Indicates if DeviceOrientation is supported in the user's browser.
+ * @const
+ * @type {boolean}
+ * @api
  */
-goog.events.Listenable = function() {};
+ol.has.DEVICE_ORIENTATION = 'DeviceOrientationEvent' in window;
 
 
 /**
- * An expando property to indicate that an object implements
- * goog.events.Listenable.
- *
- * See addImplementation/isImplementedBy.
- *
- * @type {string}
+ * Is HTML5 geolocation supported in the current browser?
  * @const
+ * @type {boolean}
+ * @api
  */
-goog.events.Listenable.IMPLEMENTED_BY_PROP =
-    'closure_listenable_' + ((Math.random() * 1e6) | 0);
+ol.has.GEOLOCATION = 'geolocation' in navigator;
 
 
 /**
- * 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.
+ * True if browser supports touch events.
+ * @const
+ * @type {boolean}
+ * @api
  */
-goog.events.Listenable.addImplementation = function(cls) {
-  cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true;
-};
+ol.has.TOUCH = ol.ASSUME_TOUCH || 'ontouchstart' in window;
 
 
 /**
- * @param {Object} obj The object to check.
- * @return {boolean} Whether a given instance implements Listenable. The
- *     class/superclass of the instance must call addImplementation.
+ * True if browser supports pointer events.
+ * @const
+ * @type {boolean}
  */
-goog.events.Listenable.isImplementedBy = function(obj) {
-  return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]);
-};
+ol.has.POINTER = 'PointerEvent' in window;
 
 
 /**
- * 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
+ * True if browser supports ms pointer events (IE 10).
+ * @const
+ * @type {boolean}
  */
-goog.events.Listenable.prototype.listen;
+ol.has.MSPOINTER = !!(navigator.msPointerEnabled);
 
 
 /**
- * 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
+ * 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
  */
-goog.events.Listenable.prototype.listenOnce;
-
+ol.has.WEBGL;
 
-/**
- * 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;
 
+(function() {
+  if (ol.ENABLE_WEBGL) {
+    var hasWebGL = false;
+    var textureSize;
+    var /** @type {Array.<string>} */ extensions = [];
 
-/**
- * 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;
+    if ('WebGLRenderingContext' in window) {
+      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) {
+        // pass
+      }
+    }
+    ol.has.WEBGL = hasWebGL;
+    ol.WEBGL_EXTENSIONS = extensions;
+    ol.WEBGL_MAX_TEXTURE_SIZE = textureSize;
+  }
+})();
 
+goog.provide('ol.MapBrowserEventType');
 
-/**
- * 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;
+goog.require('ol.events.EventType');
 
 
 /**
- * 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.
+ * Constants for event names.
+ * @enum {string}
  */
-goog.events.Listenable.prototype.removeAllListeners;
+ol.MapBrowserEventType = {
 
+  /**
+   * 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
+   */
+  SINGLECLICK: 'singleclick',
 
-/**
- * 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;
+  /**
+   * A click with no dragging. A double click will fire two of this.
+   * @event ol.MapBrowserEvent#click
+   * @api
+   */
+  CLICK: ol.events.EventType.CLICK,
 
+  /**
+   * A true double click, with no dragging.
+   * @event ol.MapBrowserEvent#dblclick
+   * @api
+   */
+  DBLCLICK: ol.events.EventType.DBLCLICK,
 
-/**
- * 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;
+  /**
+   * 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
+   */
+  POINTERMOVE: 'pointermove',
 
-/**
- * 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;
+  POINTERDOWN: 'pointerdown',
+  POINTERUP: 'pointerup',
+  POINTEROVER: 'pointerover',
+  POINTEROUT: 'pointerout',
+  POINTERENTER: 'pointerenter',
+  POINTERLEAVE: 'pointerleave',
+  POINTERCANCEL: 'pointercancel'
+};
 
+goog.provide('ol.MapBrowserPointerEvent');
 
-/**
- * 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;
+goog.require('ol');
+goog.require('ol.MapBrowserEvent');
 
 
 /**
- * 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
+ * @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.
  */
-goog.events.Listenable.prototype.hasListener;
-
+ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_dragging,
+    opt_frameState) {
 
+  ol.MapBrowserEvent.call(this, type, map, pointerEvent.originalEvent, opt_dragging,
+      opt_frameState);
 
-/**
- * An interface that describes a single registered listener.
- * @interface
- */
-goog.events.ListenableKey = function() {};
+  /**
+   * @const
+   * @type {ol.pointer.PointerEvent}
+   */
+  this.pointerEvent = pointerEvent;
 
+};
+ol.inherits(ol.MapBrowserPointerEvent, ol.MapBrowserEvent);
 
-/**
- * Counter used to create a unique key
- * @type {number}
- * @private
- */
-goog.events.ListenableKey.counter_ = 0;
+goog.provide('ol.pointer.EventType');
 
 
 /**
- * Reserves a key to be used for ListenableKey#key field.
- * @return {number} A number to be used to fill ListenableKey#key
- *     field.
+ * Constants for event names.
+ * @enum {string}
  */
-goog.events.ListenableKey.reserveKey = function() {
-  return ++goog.events.ListenableKey.counter_;
+ol.pointer.EventType = {
+  POINTERMOVE: 'pointermove',
+  POINTERDOWN: 'pointerdown',
+  POINTERUP: 'pointerup',
+  POINTEROVER: 'pointerover',
+  POINTEROUT: 'pointerout',
+  POINTERENTER: 'pointerenter',
+  POINTERLEAVE: 'pointerleave',
+  POINTERCANCEL: 'pointercancel'
 };
 
-
-/**
- * The source event target.
- * @type {!(Object|goog.events.Listenable|goog.events.EventTarget)}
- */
-goog.events.ListenableKey.prototype.src;
+goog.provide('ol.pointer.EventSource');
 
 
 /**
- * The event type the listener is listening to.
- * @type {string}
+ * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
+ * @param {!Object.<string, function(Event)>} mapping Event
+ *     mapping.
+ * @constructor
  */
-goog.events.ListenableKey.prototype.type;
-
+ol.pointer.EventSource = function(dispatcher, mapping) {
+  /**
+   * @type {ol.pointer.PointerEventHandler}
+   */
+  this.dispatcher = dispatcher;
 
-/**
- * The listener function.
- * @type {function(?):?|{handleEvent:function(?):?}|null}
- */
-goog.events.ListenableKey.prototype.listener;
+  /**
+   * @private
+   * @const
+   * @type {!Object.<string, function(Event)>}
+   */
+  this.mapping_ = mapping;
+};
 
 
 /**
- * Whether the listener works on capture phase.
- * @type {boolean}
+ * List of events supported by this source.
+ * @return {Array.<string>} Event names
  */
-goog.events.ListenableKey.prototype.capture;
+ol.pointer.EventSource.prototype.getEvents = function() {
+  return Object.keys(this.mapping_);
+};
 
 
 /**
- * The 'this' object for the listener function's scope.
- * @type {Object}
+ * Returns the handler that should handle a given event type.
+ * @param {string} eventType The event type.
+ * @return {function(Event)} Handler
  */
-goog.events.ListenableKey.prototype.handler;
-
+ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) {
+  return this.mapping_[eventType];
+};
 
-/**
- * A globally unique number to identify the key.
- * @type {number}
- */
-goog.events.ListenableKey.prototype.key;
+// Based on https://github.com/Polymer/PointerEvents
 
-// Copyright 2005 The Closure Library Authors. All Rights Reserved.
+// Copyright (c) 2013 The Polymer 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
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
 //
-//      http://www.apache.org/licenses/LICENSE-2.0
+// * 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.
 //
-// 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');
+// 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.require('goog.events.ListenableKey');
+goog.provide('ol.pointer.MouseSource');
 
+goog.require('ol');
+goog.require('ol.pointer.EventSource');
 
 
 /**
- * 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}
+ * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
  * @constructor
+ * @extends {ol.pointer.EventSource}
  */
-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();
+ol.pointer.MouseSource = function(dispatcher) {
+  var mapping = {
+    'mousedown': this.mousedown,
+    'mousemove': this.mousemove,
+    'mouseup': this.mouseup,
+    'mouseover': this.mouseover,
+    'mouseout': this.mouseout
+  };
+  ol.pointer.EventSource.call(this, dispatcher, mapping);
 
   /**
-   * Whether to remove the listener after it has been called.
-   * @type {boolean}
+   * @const
+   * @type {!Object.<string, Event|Object>}
    */
-  this.callOnce = false;
+  this.pointerMap = dispatcher.pointerMap;
 
   /**
-   * Whether the listener has been removed.
-   * @type {boolean}
+   * @const
+   * @type {Array.<ol.Pixel>}
    */
-  this.removed = false;
+  this.lastTouches = [];
 };
+ol.inherits(ol.pointer.MouseSource, ol.pointer.EventSource);
 
 
 /**
- * @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.
+ * @const
+ * @type {number}
  */
-goog.define('goog.events.Listener.ENABLE_MONITORING', false);
+ol.pointer.MouseSource.POINTER_ID = 1;
 
 
 /**
- * If monitoring the goog.events.Listener instances is enabled, stores the
- * creation stack trace of the Disposable instance.
+ * @const
  * @type {string}
  */
-goog.events.Listener.prototype.creationStack;
+ol.pointer.MouseSource.POINTER_TYPE = 'mouse';
 
 
 /**
- * Marks this listener as removed. This also remove references held by
- * this listener object (such as listener and event source).
+ * Radius around touchend that swallows mouse events.
+ *
+ * @const
+ * @type {number}
  */
-goog.events.Listener.prototype.markAsRemoved = function() {
-  this.removed = true;
-  this.listener = null;
-  this.proxy = null;
-  this.src = null;
-  this.handler = null;
-};
+ol.pointer.MouseSource.DEDUP_DIST = 25;
 
-// 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}.
+ * Detect if a mouse event was simulated from a touch by
+ * checking if previously there was a touch event at the
+ * same position.
  *
- * WARNING: Do not use this class from outside goog.events package.
+ * 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.
  *
- * @visibility {//closure/goog/bin/sizetests:__pkg__}
- * @visibility {//closure/goog/events:__pkg__}
- * @visibility {//closure/goog/labs/events:__pkg__}
+ * @private
+ * @param {Event} inEvent The in event.
+ * @return {boolean} True, if the event was generated by a touch.
  */
-
-goog.provide('goog.events.ListenerMap');
-
-goog.require('goog.array');
-goog.require('goog.events.Listener');
-goog.require('goog.object');
-
+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 new listener map.
- * @param {EventTarget|goog.events.Listenable} src The src object.
- * @constructor
- * @final
+ * Creates a copy of the original event that will be used
+ * for the fake pointer event.
+ *
+ * @param {Event} inEvent The in event.
+ * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
+ * @return {Object} The copied event.
  */
-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 = {};
+ol.pointer.MouseSource.prepareEvent = function(inEvent, dispatcher) {
+  var e = dispatcher.cloneEvent(inEvent, inEvent);
 
-  /**
-   * The count of types in this map that have registered listeners.
-   * @private {number}
-   */
-  this.typeCount_ = 0;
-};
+  // 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 {number} The count of event types in this map that actually
- *     have registered listeners.
- */
-goog.events.ListenerMap.prototype.getTypeCount = function() {
-  return this.typeCount_;
+  return e;
 };
 
 
 /**
- * @return {number} Total number of registered listeners.
+ * Handler for `mousedown`.
+ *
+ * @param {Event} inEvent The in event.
  */
-goog.events.ListenerMap.prototype.getListenerCount = function() {
-  var count = 0;
-  for (var type in this.listeners) {
-    count += this.listeners[type].length;
+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);
   }
-  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.
+ * Handler for `mousemove`.
  *
- * @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.
+ * @param {Event} inEvent The in event.
  */
-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);
+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);
   }
-  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.
+ * Handler for `mouseup`.
+ *
+ * @param {Event} inEvent The in event.
  */
-goog.events.ListenerMap.prototype.remove = function(
-    type, listener, opt_useCapture, opt_listenerScope) {
-  var typeStr = type.toString();
-  if (!(typeStr in this.listeners)) {
-    return false;
-  }
+ol.pointer.MouseSource.prototype.mouseup = function(inEvent) {
+  if (!this.isEventSimulatedFromTouch_(inEvent)) {
+    var p = this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()];
 
-  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_--;
+    if (p && p.button === inEvent.button) {
+      var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
+      this.dispatcher.up(e, inEvent);
+      this.cleanupMouse();
     }
-    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.
+ * Handler for `mouseover`.
+ *
+ * @param {Event} inEvent The in event.
  */
-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_--;
-    }
+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);
   }
-  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.
+ * Handler for `mouseout`.
+ *
+ * @param {Event} inEvent The in event.
  */
-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_--;
-    }
+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);
   }
-  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.
+ * Dispatches a `pointercancel` event.
+ *
+ * @param {Event} inEvent The in event.
  */
-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;
+ol.pointer.MouseSource.prototype.cancel = function(inEvent) {
+  var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
+  this.dispatcher.cancel(e, inEvent);
+  this.cleanupMouse();
 };
 
 
 /**
- * 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.
+ * Remove the mouse from the list of active pointers.
  */
-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;
+ol.pointer.MouseSource.prototype.cleanupMouse = function() {
+  delete this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()];
 };
 
+// Based on https://github.com/Polymer/PointerEvents
 
-/**
- * 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);
+// 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.
 
-  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;
-          }
-        }
+goog.provide('ol.pointer.MsSource');
 
-        return false;
-      });
-};
+goog.require('ol');
+goog.require('ol.pointer.EventSource');
 
 
 /**
- * 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
+ * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
+ * @constructor
+ * @extends {ol.pointer.EventSource}
  */
-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,
+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
+  };
+  ol.pointer.EventSource.call(this, dispatcher, mapping);
 
   /**
-   * Does not perform capture simulation, silently ignore capture
-   * listeners.
+   * @const
+   * @type {!Object.<string, Event|Object>}
    */
-  OFF_AND_SILENT: 1,
+  this.pointerMap = dispatcher.pointerMap;
 
   /**
-   * Performs capture simulation.
+   * @const
+   * @type {Array.<string>}
    */
-  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);
-  }
+  this.POINTER_TYPES = [
+    '',
+    'unavailable',
+    'touch',
+    'pen',
+    'mouse'
+  ];
 };
+ol.inherits(ol.pointer.MsSource, ol.pointer.EventSource);
 
 
 /**
- * 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.
+ * Creates a copy of the original event that will be used
+ * for the fake pointer event.
  *
- * @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
+ * @param {Event} inEvent The in event.
+ * @return {Object} The copied event.
  */
-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.');
+ol.pointer.MsSource.prototype.prepareEvent_ = function(inEvent) {
+  var e = inEvent;
+  if (typeof inEvent.pointerType === 'number') {
+    e = this.dispatcher.cloneEvent(inEvent, inEvent);
+    e.pointerType = this.POINTER_TYPES[inEvent.pointerType];
   }
 
-  goog.events.listenerCountEstimate_++;
-  return listenerObj;
+  return e;
 };
 
 
 /**
- * Helper function for returning a proxy function.
- * @return {!Function} A new or reused function object.
+ * Remove this pointer from the list of active pointers.
+ * @param {number} pointerId Pointer identifier.
  */
-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;
+ol.pointer.MsSource.prototype.cleanup = function(pointerId) {
+  delete this.pointerMap[pointerId.toString()];
 };
 
 
 /**
- * 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).
+ * Handler for `msPointerDown`.
  *
- * @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
+ * @param {Event} inEvent The in event.
  */
-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);
-  }
+ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) {
+  this.pointerMap[inEvent.pointerId.toString()] = inEvent;
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.down(e, inEvent);
 };
 
 
 /**
- * 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.
+ * Handler for `msPointerMove`.
  *
- * @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
+ * @param {Event} inEvent The in event.
  */
-goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt,
-    opt_handler) {
-  wrapper.listen(src, listener, opt_capt, opt_handler);
+ol.pointer.MsSource.prototype.msPointerMove = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.move(e, inEvent);
 };
 
 
 /**
- * Removes an event listener which was added with listen().
+ * Handler for `msPointerUp`.
  *
- * @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
+ * @param {Event} inEvent The in event.
  */
-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;
+ol.pointer.MsSource.prototype.msPointerUp = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.up(e, inEvent);
+  this.cleanup(inEvent.pointerId);
 };
 
 
 /**
- * Removes an event listener which was added with listen() by the key
- * returned by listen().
+ * Handler for `msPointerOut`.
  *
- * @param {goog.events.Key} key The key returned by listen() for this
- *     event listener.
- * @return {boolean} indicating whether the listener was there to remove.
+ * @param {Event} inEvent The in event.
  */
-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;
+ol.pointer.MsSource.prototype.msPointerOut = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.leaveOut(e, inEvent);
 };
 
 
 /**
- * Removes an event listener which was added with listenWithWrapper().
+ * Handler for `msPointerOver`.
  *
- * @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.
+ * @param {Event} inEvent The in event.
  */
-goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt,
-    opt_handler) {
-  wrapper.unlisten(src, listener, opt_capt, opt_handler);
+ol.pointer.MsSource.prototype.msPointerOver = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.enterOver(e, inEvent);
 };
 
 
 /**
- * Removes all listeners from an object. You can also optionally
- * remove listeners of a particular type.
+ * Handler for `msPointerCancel`.
  *
- * @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.
+ * @param {Event} inEvent The in event.
  */
-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;
+ol.pointer.MsSource.prototype.msPointerCancel = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.cancel(e, inEvent);
+  this.cleanup(inEvent.pointerId);
 };
 
 
 /**
- * Gets the listeners for a given object, type and capture phase.
+ * Handler for `msLostPointerCapture`.
  *
- * @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.
+ * @param {Event} inEvent The in event.
  */
-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) : [];
-  }
+ol.pointer.MsSource.prototype.msLostPointerCapture = function(inEvent) {
+  var e = this.dispatcher.makeEvent('lostpointercapture',
+      inEvent, inEvent);
+  this.dispatcher.dispatchEvent(e);
 };
 
 
 /**
- * Gets the goog.events.Listener for the event or null if no such listener is
- * in use.
+ * Handler for `msGotPointerCapture`.
  *
- * @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
+ * @param {Event} inEvent The in event.
  */
-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;
+ol.pointer.MsSource.prototype.msGotPointerCapture = function(inEvent) {
+  var e = this.dispatcher.makeEvent('gotpointercapture',
+      inEvent, inEvent);
+  this.dispatcher.dispatchEvent(e);
 };
 
+// Based on https://github.com/Polymer/PointerEvents
 
-/**
- * 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);
-};
+// 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');
 
-/**
- * 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');
-};
+goog.require('ol');
+goog.require('ol.pointer.EventSource');
 
 
 /**
- * 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
+ * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
+ * @constructor
+ * @extends {ol.pointer.EventSource}
  */
-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;
+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
+  };
+  ol.pointer.EventSource.call(this, dispatcher, mapping);
 };
+ol.inherits(ol.pointer.NativeSource, ol.pointer.EventSource);
 
 
 /**
- * Fires an object's listeners of a particular type and phase
+ * Handler for `pointerdown`.
  *
- * @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
+ * @param {Event} inEvent The in event.
  */
-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;
+ol.pointer.NativeSource.prototype.pointerDown = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * Fires a listener with a set of arguments
+ * Handler for `pointermove`.
  *
- * @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.
+ * @param {Event} inEvent The in event.
  */
-goog.events.getTotalListenerCount = function() {
-  return goog.events.listenerCountEstimate_;
+ol.pointer.NativeSource.prototype.pointerMove = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * 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.
+ * Handler for `pointerup`.
  *
- * @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.
+ * @param {Event} inEvent The in event.
  */
-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);
+ol.pointer.NativeSource.prototype.pointerUp = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * Installs exception protection for the browser event entry point using the
- * given error handler.
+ * Handler for `pointerout`.
  *
- * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
- *     protect the entry point.
+ * @param {Event} inEvent The in event.
  */
-goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
-  goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint(
-      goog.events.handleBrowserEvent_);
+ol.pointer.NativeSource.prototype.pointerOut = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * Handles an event and dispatches it to the correct listeners. This
- * function is a proxy for the real listener the user specified.
+ * Handler for `pointerover`.
  *
- * @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
+ * @param {Event} inEvent The in event.
  */
-goog.events.isMarkedIeEvent_ = function(e) {
-  return e.keyCode < 0 || e.returnValue != undefined;
+ol.pointer.NativeSource.prototype.pointerOver = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * Counter to create unique event ids.
- * @private {number}
- */
-goog.events.uniqueIdCounter_ = 0;
-
-
-/**
- * Creates a unique event id.
+ * Handler for `pointercancel`.
  *
- * @param {string} identifier The identifier.
- * @return {string} A unique identifier.
- * @idGenerator
+ * @param {Event} inEvent The in event.
  */
-goog.events.getUniqueId = function(identifier) {
-  return identifier + '_' + goog.events.uniqueIdCounter_++;
+ol.pointer.NativeSource.prototype.pointerCancel = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * @param {EventTarget} src The source object.
- * @return {goog.events.ListenerMap} A listener map for the given
- *     source object, or null if none exists.
- * @private
+ * Handler for `lostpointercapture`.
+ *
+ * @param {Event} inEvent The in event.
  */
-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;
+ol.pointer.NativeSource.prototype.lostPointerCapture = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * 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.
+ * Handler for `gotpointercapture`.
+ *
+ * @param {Event} inEvent The in event.
  */
-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_];
+ol.pointer.NativeSource.prototype.gotPointerCapture = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
+// Based on https://github.com/Polymer/PointerEvents
 
-// 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.
+// Copyright (c) 2013 The Polymer 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
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
 //
-//      http://www.apache.org/licenses/LICENSE-2.0
+// * 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.
 //
-// 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
- */
+// 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('goog.events.EventTarget');
+goog.provide('ol.pointer.PointerEvent');
 
-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');
 
+goog.require('ol');
+goog.require('ol.events.Event');
 
 
 /**
- * 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.
+ * A class for pointer events.
  *
- * 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>
+ * This class is used as an abstraction for mouse events,
+ * touch events and even native pointer events.
  *
  * @constructor
- * @extends {goog.Disposable}
- * @implements {goog.events.Listenable}
+ * @extends {ol.events.Event}
+ * @param {string} type The type of the event to create.
+ * @param {Event} originalEvent The event.
+ * @param {Object.<string, ?>=} opt_eventDict An optional dictionary of
+ *    initial event properties.
  */
-goog.events.EventTarget = function() {
-  goog.Disposable.call(this);
+ol.pointer.PointerEvent = function(type, originalEvent, opt_eventDict) {
+  ol.events.Event.call(this, type);
 
   /**
-   * Maps of event type to an array of listeners.
-   * @private {!goog.events.ListenerMap}
+   * @const
+   * @type {Event}
    */
-  this.eventTargetListeners_ = new goog.events.ListenerMap(this);
+  this.originalEvent = originalEvent;
+
+  var eventDict = opt_eventDict ? opt_eventDict : {};
 
   /**
-   * The object to use for event.target. Useful when mixing in an
-   * EventTarget to another object.
-   * @private {!Object}
+   * @type {number}
    */
-  this.actualEventTarget_ = this;
+  this.buttons = this.getButtons_(eventDict);
 
   /**
-   * 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}
+   * @type {number}
    */
-  this.parentEventTarget_ = null;
-};
-goog.inherits(goog.events.EventTarget, goog.Disposable);
-goog.events.Listenable.addImplementation(goog.events.EventTarget);
+  this.pressure = this.getPressure_(eventDict, this.buttons);
 
+  // MouseEvent related properties
 
-/**
- * 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;
+  /**
+   * @type {boolean}
+   */
+  this.bubbles = 'bubbles' in eventDict ? eventDict['bubbles'] : false;
 
+  /**
+   * @type {boolean}
+   */
+  this.cancelable = 'cancelable' in eventDict ? eventDict['cancelable'] : false;
 
-/**
- * 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_;
-};
+  /**
+   * @type {Object}
+   */
+  this.view = 'view' in eventDict ? eventDict['view'] : null;
 
+  /**
+   * @type {number}
+   */
+  this.detail = 'detail' in eventDict ? eventDict['detail'] : null;
 
-/**
- * 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;
-};
+  /**
+   * @type {number}
+   */
+  this.screenX = 'screenX' in eventDict ? eventDict['screenX'] : 0;
 
+  /**
+   * @type {number}
+   */
+  this.screenY = 'screenY' in eventDict ? eventDict['screenY'] : 0;
 
-/**
- * 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);
-};
+  /**
+   * @type {number}
+   */
+  this.clientX = 'clientX' in eventDict ? eventDict['clientX'] : 0;
 
+  /**
+   * @type {number}
+   */
+  this.clientY = 'clientY' in eventDict ? eventDict['clientY'] : 0;
 
-/**
- * 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);
-};
+  /**
+   * @type {boolean}
+   */
+  this.ctrlKey = 'ctrlKey' in eventDict ? eventDict['ctrlKey'] : false;
 
+  /**
+   * @type {boolean}
+   */
+  this.altKey = 'altKey' in eventDict ? eventDict['altKey'] : false;
 
-/** @override */
-goog.events.EventTarget.prototype.dispatchEvent = function(e) {
-  this.assertInitialized_();
+  /**
+   * @type {boolean}
+   */
+  this.shiftKey = 'shiftKey' in eventDict ? eventDict['shiftKey'] : false;
 
-  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');
-    }
-  }
+  /**
+   * @type {boolean}
+   */
+  this.metaKey = 'metaKey' in eventDict ? eventDict['metaKey'] : false;
 
-  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);
+  /**
+   * @type {number}
+   */
+  this.button = 'button' in eventDict ? eventDict['button'] : 0;
 
-  this.removeAllListeners();
-  this.parentEventTarget_ = null;
-};
+  /**
+   * @type {Node}
+   */
+  this.relatedTarget = 'relatedTarget' in eventDict ?
+      eventDict['relatedTarget'] : null;
 
+  // PointerEvent related properties
 
-/** @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);
-};
+  /**
+   * @const
+   * @type {number}
+   */
+  this.pointerId = 'pointerId' in eventDict ? eventDict['pointerId'] : 0;
 
+  /**
+   * @type {number}
+   */
+  this.width = 'width' in eventDict ? eventDict['width'] : 0;
 
-/** @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);
-};
+  /**
+   * @type {number}
+   */
+  this.height = 'height' in eventDict ? eventDict['height'] : 0;
 
+  /**
+   * @type {number}
+   */
+  this.tiltX = 'tiltX' in eventDict ? eventDict['tiltX'] : 0;
 
-/** @override */
-goog.events.EventTarget.prototype.unlisten = function(
-    type, listener, opt_useCapture, opt_listenerScope) {
-  return this.eventTargetListeners_.remove(
-      String(type), listener, opt_useCapture, opt_listenerScope);
-};
+  /**
+   * @type {number}
+   */
+  this.tiltY = 'tiltY' in eventDict ? eventDict['tiltY'] : 0;
 
+  /**
+   * @type {string}
+   */
+  this.pointerType = 'pointerType' in eventDict ? eventDict['pointerType'] : '';
 
-/** @override */
-goog.events.EventTarget.prototype.unlistenByKey = function(key) {
-  return this.eventTargetListeners_.removeByKey(key);
-};
+  /**
+   * @type {number}
+   */
+  this.hwTimestamp = 'hwTimestamp' in eventDict ? eventDict['hwTimestamp'] : 0;
 
+  /**
+   * @type {boolean}
+   */
+  this.isPrimary = 'isPrimary' in eventDict ? eventDict['isPrimary'] : false;
 
-/** @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;
+  // keep the semantics of preventDefault
+  if (originalEvent.preventDefault) {
+    this.preventDefault = function() {
+      originalEvent.preventDefault();
+    };
   }
-  return this.eventTargetListeners_.removeAll(opt_type);
 };
+ol.inherits(ol.pointer.PointerEvent, ol.events.Event);
 
 
-/** @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;
+/**
+ * @private
+ * @param {Object.<string, ?>} eventDict The event dictionary.
+ * @return {number} Button indicator.
+ */
+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 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);
+  return buttons;
 };
 
 
 /**
- * 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.
+ * @private
+ * @param {Object.<string, ?>} eventDict The event dictionary.
+ * @param {number} buttons Button indicator.
+ * @return {number} The pressure.
  */
-goog.events.EventTarget.prototype.setTargetForTesting = function(target) {
-  this.actualEventTarget_ = target;
+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;
 };
 
 
 /**
- * Asserts that the event target instance is initialized properly.
- * @private
+ * Is the `buttons` property supported?
+ * @type {boolean}
  */
-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?');
-};
+ol.pointer.PointerEvent.HAS_BUTTONS = false;
 
 
 /**
- * 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
+ * Checks if the `buttons` property is supported.
  */
-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;
-    }
+(function() {
+  try {
+    var ev = new MouseEvent('click', {buttons: 1});
+    ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1;
+  } catch (e) {
+    // pass
   }
+})();
 
-  return rv;
-};
+// Based on https://github.com/Polymer/PointerEvents
 
-goog.provide('ol.Observable');
+// 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.require('goog.events');
-goog.require('goog.events.EventTarget');
-goog.require('goog.events.EventType');
+goog.provide('ol.pointer.TouchSource');
 
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.pointer.EventSource');
+goog.require('ol.pointer.MouseSource');
 
 
 /**
- * @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
+ * @param {ol.pointer.PointerEventHandler} dispatcher The event handler.
+ * @param {ol.pointer.MouseSource} mouseSource Mouse source.
+ * @extends {ol.pointer.EventSource}
  */
-ol.Observable = function() {
+ol.pointer.TouchSource = function(dispatcher, mouseSource) {
+  var mapping = {
+    'touchstart': this.touchstart,
+    'touchmove': this.touchmove,
+    'touchend': this.touchend,
+    'touchcancel': this.touchcancel
+  };
+  ol.pointer.EventSource.call(this, dispatcher, mapping);
+
+  /**
+   * @const
+   * @type {!Object.<string, Event|Object>}
+   */
+  this.pointerMap = dispatcher.pointerMap;
+
+  /**
+   * @const
+   * @type {ol.pointer.MouseSource}
+   */
+  this.mouseSource = mouseSource;
 
-  goog.base(this);
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.firstTouchId_ = undefined;
 
   /**
    * @private
    * @type {number}
    */
-  this.revision_ = 0;
+  this.clickCount_ = 0;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.resetId_ = undefined;
 };
-goog.inherits(ol.Observable, goog.events.EventTarget);
+ol.inherits(ol.pointer.TouchSource, ol.pointer.EventSource);
 
 
 /**
- * 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
+ * Mouse event timeout: This should be long enough to
+ * ignore compat mouse events made by touch.
+ * @const
+ * @type {number}
  */
-ol.Observable.unByKey = function(key) {
-  goog.events.unlistenByKey(key);
-};
+ol.pointer.TouchSource.DEDUP_TIMEOUT = 2500;
 
 
 /**
- * Increases the revision counter and dispatches a 'change' event.
- * @api
+ * @const
+ * @type {number}
  */
-ol.Observable.prototype.changed = function() {
-  ++this.revision_;
-  this.dispatchEvent(goog.events.EventType.CHANGE);
-};
+ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT = 200;
 
 
 /**
- * Triggered when the revision counter is increased.
- * @event change
- * @api
+ * @const
+ * @type {string}
  */
+ol.pointer.TouchSource.POINTER_TYPE = 'touch';
 
 
 /**
- * Get the version number for this object.  Each time the object is modified,
- * its version number will be incremented.
- * @return {number} Revision.
- * @api
+ * @private
+ * @param {Touch} inTouch The in touch.
+ * @return {boolean} True, if this is the primary touch.
  */
-ol.Observable.prototype.getRevision = function() {
-  return this.revision_;
+ol.pointer.TouchSource.prototype.isPrimaryTouch_ = function(inTouch) {
+  return this.firstTouchId_ === inTouch.identifier;
 };
 
 
 /**
- * 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
+ * Set primary touch if there are no pointers, or the only pointer is the mouse.
+ * @param {Touch} inTouch The in touch.
+ * @private
  */
-ol.Observable.prototype.on = function(type, listener, opt_this) {
-  return goog.events.listen(this, type, listener, false, opt_this);
+ol.pointer.TouchSource.prototype.setPrimaryTouch_ = function(inTouch) {
+  var count = Object.keys(this.pointerMap).length;
+  if (count === 0 || (count === 1 &&
+      ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap)) {
+    this.firstTouchId_ = inTouch.identifier;
+    this.cancelResetClickCount_();
+  }
 };
 
 
 /**
- * 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
+ * @private
+ * @param {Object} inPointer The in pointer object.
  */
-ol.Observable.prototype.once = function(type, listener, opt_this) {
-  return goog.events.listenOnce(this, type, listener, false, opt_this);
+ol.pointer.TouchSource.prototype.removePrimaryPointer_ = function(inPointer) {
+  if (inPointer.isPrimary) {
+    this.firstTouchId_ = undefined;
+    this.resetClickCount_();
+  }
 };
 
 
 /**
- * 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
+ * @private
  */
-ol.Observable.prototype.un = function(type, listener, opt_this) {
-  goog.events.unlisten(this, type, listener, false, opt_this);
+ol.pointer.TouchSource.prototype.resetClickCount_ = function() {
+  this.resetId_ = setTimeout(
+      this.resetClickCountHandler_.bind(this),
+      ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT);
 };
 
 
 /**
- * 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
+ * @private
  */
-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');
+ol.pointer.TouchSource.prototype.resetClickCountHandler_ = function() {
+  this.clickCount_ = 0;
+  this.resetId_ = undefined;
+};
 
 
 /**
- * @enum {string}
+ * @private
  */
-ol.ObjectEventType = {
-  /**
-   * Triggered when a property is changed.
-   * @event ol.ObjectEvent#propertychange
-   * @api stable
-   */
-  PROPERTYCHANGE: 'propertychange'
+ol.pointer.TouchSource.prototype.cancelResetClickCount_ = function() {
+  if (this.resetId_ !== undefined) {
+    clearTimeout(this.resetId_);
+  }
 };
 
 
-
 /**
- * @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
+ * @private
+ * @param {Event} browserEvent Browser event
+ * @param {Touch} inTouch Touch event
+ * @return {Object} A pointer object.
  */
-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;
+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;
 
-  /**
-   * 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;
+  // 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;
 };
-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
+ * @private
+ * @param {Event} inEvent Touch event
+ * @param {function(Event, Object)} inFunction In function.
  */
-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);
+ol.pointer.TouchSource.prototype.processTouches_ = function(inEvent, inFunction) {
+  var touches = Array.prototype.slice.call(
+      inEvent.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);
   }
 };
-goog.inherits(ol.Object, ol.Observable);
 
 
 /**
  * @private
- * @type {Object.<string, string>}
+ * @param {TouchList} touchList The touch list.
+ * @param {number} searchId Search identifier.
+ * @return {boolean} True, if the `Touch` with the given id is in the list.
  */
-ol.Object.changeEventTypeCache_ = {};
+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;
+};
 
 
 /**
- * @param {string} key Key name.
- * @return {string} Change name.
+ * 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 {Event} inEvent The in event.
  */
-ol.Object.getChangeEventType = function(key) {
-  return ol.Object.changeEventTypeCache_.hasOwnProperty(key) ?
-      ol.Object.changeEventTypeCache_[key] :
-      (ol.Object.changeEventTypeCache_[key] = 'change:' + key);
+ol.pointer.TouchSource.prototype.vacuumTouches_ = function(inEvent) {
+  var touchList = inEvent.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]);
+    }
+  }
 };
 
 
 /**
- * Gets a value.
- * @param {string} key Key name.
- * @return {*} Value.
- * @api stable
+ * Handler for `touchstart`, triggers `pointerover`,
+ * `pointerenter` and `pointerdown` events.
+ *
+ * @param {Event} inEvent The in event.
  */
-ol.Object.prototype.get = function(key) {
-  var value;
-  if (this.values_.hasOwnProperty(key)) {
-    value = this.values_[key];
-  }
-  return value;
+ol.pointer.TouchSource.prototype.touchstart = function(inEvent) {
+  this.vacuumTouches_(inEvent);
+  this.setPrimaryTouch_(inEvent.changedTouches[0]);
+  this.dedupSynthMouse_(inEvent);
+  this.clickCount_++;
+  this.processTouches_(inEvent, this.overDown_);
 };
 
 
 /**
- * Get a list of object property names.
- * @return {Array.<string>} List of property names.
- * @api stable
+ * @private
+ * @param {Event} browserEvent The event.
+ * @param {Object} inPointer The in pointer object.
  */
-ol.Object.prototype.getKeys = function() {
-  return Object.keys(this.values_);
+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);
 };
 
 
 /**
- * Get an object of all property names and values.
- * @return {Object.<string, *>} Object.
- * @api stable
+ * Handler for `touchmove`.
+ *
+ * @param {Event} inEvent The in event.
  */
-ol.Object.prototype.getProperties = function() {
-  var properties = {};
-  var key;
-  for (key in this.values_) {
-    properties[key] = this.values_[key];
-  }
-  return properties;
+ol.pointer.TouchSource.prototype.touchmove = function(inEvent) {
+  inEvent.preventDefault();
+  this.processTouches_(inEvent, this.moveOverOut_);
 };
 
 
 /**
- * @param {string} key Key name.
- * @param {*} oldValue Old value.
+ * @private
+ * @param {Event} browserEvent The event.
+ * @param {Object} inPointer The in pointer.
  */
-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));
+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;
 };
 
 
 /**
- * Sets a value.
- * @param {string} key Key name.
- * @param {*} value Value.
- * @api stable
+ * Handler for `touchend`, triggers `pointerup`,
+ * `pointerout` and `pointerleave` events.
+ *
+ * @param {Event} inEvent The event.
  */
-ol.Object.prototype.set = function(key, value) {
-  var oldValue = this.values_[key];
-  this.values_[key] = value;
-  this.notify(key, oldValue);
+ol.pointer.TouchSource.prototype.touchend = function(inEvent) {
+  this.dedupSynthMouse_(inEvent);
+  this.processTouches_(inEvent, this.upOut_);
 };
 
 
 /**
- * Sets a collection of key-value pairs.  Note that this changes any existing
- * properties and adds new ones (it does not remove any existing properties).
- * @param {Object.<string, *>} values Values.
- * @api stable
+ * @private
+ * @param {Event} browserEvent An event.
+ * @param {Object} inPointer The inPointer object.
  */
-ol.Object.prototype.setProperties = function(values) {
-  var key;
-  for (key in values) {
-    this.set(key, values[key]);
-  }
+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);
 };
 
 
 /**
- * Unsets a property.
- * @param {string} key Key name.
- * @api stable
+ * Handler for `touchcancel`, triggers `pointercancel`,
+ * `pointerout` and `pointerleave` events.
+ *
+ * @param {Event} inEvent The in event.
  */
-ol.Object.prototype.unset = function(key) {
-  if (key in this.values_) {
-    var oldValue = this.values_[key];
-    delete this.values_[key];
-    this.notify(key, oldValue);
-  }
+ol.pointer.TouchSource.prototype.touchcancel = function(inEvent) {
+  this.processTouches_(inEvent, this.cancelOut_);
 };
 
-goog.provide('ol.Size');
-goog.provide('ol.size');
-
 
-goog.require('goog.asserts');
+/**
+ * @private
+ * @param {Event} browserEvent The event.
+ * @param {Object} inPointer The in pointer.
+ */
+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);
+};
 
 
 /**
- * An array of numbers representing a size: `[width, height]`.
- * @typedef {Array.<number>}
- * @api stable
+ * @private
+ * @param {Object} inPointer The inPointer object.
  */
-ol.Size;
+ol.pointer.TouchSource.prototype.cleanUpPointer_ = function(inPointer) {
+  delete this.pointerMap[inPointer.pointerId];
+  this.removePrimaryPointer_(inPointer);
+};
 
 
 /**
- * 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}
+ * Prevent synth mouse events from creating pointer events.
+ *
+ * @private
+ * @param {Event} inEvent The in event.
  */
-ol.size.buffer = function(size, buffer, opt_size) {
-  if (opt_size === undefined) {
-    opt_size = [0, 0];
+ol.pointer.TouchSource.prototype.dedupSynthMouse_ = function(inEvent) {
+  var lts = this.mouseSource.lastTouches;
+  var t = inEvent.changedTouches[0];
+  // only the primary finger will synth mouse events
+  if (this.isPrimaryTouch_(t)) {
+    // remember x/y of last touch
+    var lt = [t.clientX, t.clientY];
+    lts.push(lt);
+
+    setTimeout(function() {
+      // remove touch after timeout
+      ol.array.remove(lts, lt);
+    }, ol.pointer.TouchSource.DEDUP_TIMEOUT);
   }
-  opt_size[0] = size[0] + 2 * buffer;
-  opt_size[1] = size[1] + 2 * buffer;
-  return opt_size;
 };
 
+// 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('ol');
+goog.require('ol.events');
+goog.require('ol.events.EventTarget');
+
+goog.require('ol.has');
+goog.require('ol.pointer.EventType');
+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');
+
 
 /**
- * Compares sizes for equality.
- * @param {ol.Size} a Size.
- * @param {ol.Size} b Size.
- * @return {boolean} Equals.
+ * @constructor
+ * @extends {ol.events.EventTarget}
+ * @param {Element|HTMLDocument} element Viewport element.
  */
-ol.size.equals = function(a, b) {
-  return a[0] == b[0] && a[1] == b[1];
+ol.pointer.PointerEventHandler = function(element) {
+  ol.events.EventTarget.call(this);
+
+  /**
+   * @const
+   * @private
+   * @type {Element|HTMLDocument}
+   */
+  this.element_ = element;
+
+  /**
+   * @const
+   * @type {!Object.<string, Event|Object>}
+   */
+  this.pointerMap = {};
+
+  /**
+   * @type {Object.<string, function(Event)>}
+   * @private
+   */
+  this.eventMap_ = {};
+
+  /**
+   * @type {Array.<ol.pointer.EventSource>}
+   * @private
+   */
+  this.eventSourceList_ = [];
+
+  this.registerSources();
 };
+ol.inherits(ol.pointer.PointerEventHandler, ol.events.EventTarget);
 
 
 /**
- * Determines if a size has a positive area.
- * @param {ol.Size} size The size to test.
- * @return {boolean} The size has a positive area.
+ * Set up the event sources (mouse, touch and native pointers)
+ * that generate pointer events.
  */
-ol.size.hasArea = function(size) {
-  return size[0] > 0 && size[1] > 0;
+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_();
 };
 
 
 /**
- * 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}
+ * Add a new event source that will generate pointer events.
+ *
+ * @param {string} name A name for the event source
+ * @param {ol.pointer.EventSource} source The source event.
  */
-ol.size.scale = function(size, ratio, opt_size) {
-  if (opt_size === undefined) {
-    opt_size = [0, 0];
+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] = handler.bind(s);
+      }
+    }, this);
+    this.eventSourceList_.push(s);
   }
-  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
+ * Set up the events for all registered event sources.
+ * @private
  */
-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;
+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());
   }
 };
 
-goog.provide('ol.Coordinate');
-goog.provide('ol.CoordinateFormatType');
-goog.provide('ol.coordinate');
 
-goog.require('goog.math');
-goog.require('goog.string');
+/**
+ * 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());
+  }
+};
 
 
 /**
- * A function that takes a {@link ol.Coordinate} and transforms it into a
- * `{string}`.
- *
- * @typedef {function((ol.Coordinate|undefined)): string}
- * @api stable
+ * Calls the right handler for a new event.
+ * @private
+ * @param {Event} inEvent Browser event.
  */
-ol.CoordinateFormatType;
+ol.pointer.PointerEventHandler.prototype.eventHandler_ = function(inEvent) {
+  var type = inEvent.type;
+  var handler = this.eventMap_[type];
+  if (handler) {
+    handler(inEvent);
+  }
+};
 
 
 /**
- * An array of numbers representing an xy coordinate. Example: `[16, 48]`.
- * @typedef {Array.<number>} ol.Coordinate
- * @api stable
+ * Setup listeners for the given events.
+ * @private
+ * @param {Array.<string>} events List of events.
  */
-ol.Coordinate;
+ol.pointer.PointerEventHandler.prototype.addEvents_ = function(events) {
+  events.forEach(function(eventName) {
+    ol.events.listen(this.element_, eventName, this.eventHandler_, this);
+  }, this);
+};
 
 
 /**
- * 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
+ * Unregister listeners for the given events.
+ * @private
+ * @param {Array.<string>} events List of events.
  */
-ol.coordinate.add = function(coordinate, delta) {
-  coordinate[0] += delta[0];
-  coordinate[1] += delta[1];
-  return coordinate;
+ol.pointer.PointerEventHandler.prototype.removeEvents_ = function(events) {
+  events.forEach(function(e) {
+    ol.events.unlisten(this.element_, e, this.eventHandler_, this);
+  }, this);
 };
 
 
 /**
- * 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.
+ * Returns a snapshot of inEvent, with writable properties.
  *
- * @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.
+ * @param {Event} event 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.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;
+ol.pointer.PointerEventHandler.prototype.cloneEvent = function(event, inEvent) {
+  var eventCopy = {}, p;
+  for (var i = 0, ii = ol.pointer.PointerEventHandler.CLONE_PROPS.length; i < ii; i++) {
+    p = ol.pointer.PointerEventHandler.CLONE_PROPS[i][0];
+    eventCopy[p] = event[p] || inEvent[p] || ol.pointer.PointerEventHandler.CLONE_PROPS[i][1];
   }
-  return [x, y];
+
+  return eventCopy;
 };
 
 
+// EVENTS
+
+
 /**
- * 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
+ * Triggers a 'pointerdown' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-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);
-      });
+ol.pointer.PointerEventHandler.prototype.down = function(data, event) {
+  this.fireEvent(ol.pointer.EventType.POINTERDOWN, data, event);
 };
 
 
 /**
- * @private
- * @param {number} degrees Degrees.
- * @param {string} hemispheres Hemispheres.
- * @return {string} String.
+ * Triggers a 'pointermove' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-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);
+ol.pointer.PointerEventHandler.prototype.move = function(data, event) {
+  this.fireEvent(ol.pointer.EventType.POINTERMOVE, data, event);
 };
 
 
 /**
- * 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
+ * Triggers a 'pointerup' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-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 '';
-  }
+ol.pointer.PointerEventHandler.prototype.up = function(data, event) {
+  this.fireEvent(ol.pointer.EventType.POINTERUP, data, event);
 };
 
 
 /**
- * @param {ol.Coordinate} coordinate1 First coordinate.
- * @param {ol.Coordinate} coordinate2 Second coordinate.
- * @return {boolean} Whether the passed coordinates are equal.
+ * Triggers a 'pointerenter' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-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;
+ol.pointer.PointerEventHandler.prototype.enter = function(data, event) {
+  data.bubbles = false;
+  this.fireEvent(ol.pointer.EventType.POINTERENTER, data, event);
 };
 
 
 /**
- * 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
+ * Triggers a 'pointerleave' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-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;
+ol.pointer.PointerEventHandler.prototype.leave = function(data, event) {
+  data.bubbles = false;
+  this.fireEvent(ol.pointer.EventType.POINTERLEAVE, data, event);
 };
 
 
 /**
- * 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.
+ * Triggers a 'pointerover' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.coordinate.scale = function(coordinate, scale) {
-  coordinate[0] *= scale;
-  coordinate[1] *= scale;
-  return coordinate;
+ol.pointer.PointerEventHandler.prototype.over = function(data, event) {
+  data.bubbles = true;
+  this.fireEvent(ol.pointer.EventType.POINTEROVER, data, event);
 };
 
 
 /**
- * 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.
+ * Triggers a 'pointerout' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.coordinate.sub = function(coordinate, delta) {
-  coordinate[0] -= delta[0];
-  coordinate[1] -= delta[1];
-  return coordinate;
+ol.pointer.PointerEventHandler.prototype.out = function(data, event) {
+  data.bubbles = true;
+  this.fireEvent(ol.pointer.EventType.POINTEROUT, data, event);
 };
 
 
 /**
- * @param {ol.Coordinate} coord1 First coordinate.
- * @param {ol.Coordinate} coord2 Second coordinate.
- * @return {number} Squared distance between coord1 and coord2.
+ * Triggers a 'pointercancel' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.coordinate.squaredDistance = function(coord1, coord2) {
-  var dx = coord1[0] - coord2[0];
-  var dy = coord1[1] - coord2[1];
-  return dx * dx + dy * dy;
+ol.pointer.PointerEventHandler.prototype.cancel = function(data, event) {
+  this.fireEvent(ol.pointer.EventType.POINTERCANCEL, data, event);
 };
 
 
 /**
- * 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.
+ * Triggers a combination of 'pointerout' and 'pointerleave' events.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.coordinate.squaredDistanceToSegment = function(coordinate, segment) {
-  return ol.coordinate.squaredDistance(coordinate,
-      ol.coordinate.closestOnSegment(coordinate, segment));
+ol.pointer.PointerEventHandler.prototype.leaveOut = function(data, event) {
+  this.out(data, event);
+  if (!this.contains_(data.target, data.relatedTarget)) {
+    this.leave(data, event);
+  }
 };
 
 
 /**
- * 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
+ * Triggers a combination of 'pointerover' and 'pointerevents' events.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.coordinate.toStringHDMS = function(coordinate) {
-  if (coordinate) {
-    return ol.coordinate.degreesToStringHDMS_(coordinate[1], 'NS') + ' ' +
-        ol.coordinate.degreesToStringHDMS_(coordinate[0], 'EW');
-  } else {
-    return '';
+ol.pointer.PointerEventHandler.prototype.enterOver = function(data, event) {
+  this.over(data, event);
+  if (!this.contains_(data.target, data.relatedTarget)) {
+    this.enter(data, event);
   }
 };
 
 
 /**
- * Format a coordinate as a comma delimited string.
+ * @private
+ * @param {Element} container The container element.
+ * @param {Element} contained The contained element.
+ * @return {boolean} Returns true if the container element
+ *   contains the other element.
+ */
+ol.pointer.PointerEventHandler.prototype.contains_ = function(container, contained) {
+  if (!container || !contained) {
+    return false;
+  }
+  return container.contains(contained);
+};
+
+
+// EVENT CREATION AND TRACKING
+/**
+ * Creates a new Event of type `inType`, based on the information in
+ * `data`.
  *
- * 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
+ * @param {string} inType A string representing the type of event to create.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
+ * @return {ol.pointer.PointerEvent} A PointerEvent of type `inType`.
  */
-ol.coordinate.toStringXY = function(coordinate, opt_fractionDigits) {
-  return ol.coordinate.format(coordinate, '{x}, {y}', opt_fractionDigits);
+ol.pointer.PointerEventHandler.prototype.makeEvent = function(inType, data, event) {
+  return new ol.pointer.PointerEvent(inType, event, data);
 };
 
 
 /**
- * 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.
+ * Make and dispatch an event in one call.
+ * @param {string} inType A string representing the type of event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.coordinate.fromProjectedArray = function(array, axis) {
-  var firstAxis = axis.charAt(0);
-  if (firstAxis === 'n' || firstAxis === 's') {
-    return [array[1], array[0]];
-  } else {
-    return array;
-  }
+ol.pointer.PointerEventHandler.prototype.fireEvent = function(inType, data, event) {
+  var e = this.makeEvent(inType, data, event);
+  this.dispatchEvent(e);
 };
 
-// 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.
- *
+ * Creates a pointer event from a native pointer event
+ * and dispatches this event.
+ * @param {Event} event A platform event with a target.
  */
-goog.provide('goog.vec.Float32Array');
-
+ol.pointer.PointerEventHandler.prototype.fireNativeEvent = function(event) {
+  var e = this.makeEvent(event.type, event, event);
+  this.dispatchEvent(e);
+};
 
 
 /**
- * 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
+ * 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 {Event} event The event.
+ * @return {ol.pointer.PointerEvent} The wrapped event.
  */
-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;
-  }
+ol.pointer.PointerEventHandler.prototype.wrapMouseEvent = function(eventType, event) {
+  var pointerEvent = this.makeEvent(
+      eventType, ol.pointer.MouseSource.prepareEvent(event, this), event);
+  return pointerEvent;
 };
 
 
 /**
- * The number of bytes in an element (as defined by the Typed Array
- * specification).
- *
- * @type {number}
+ * @inheritDoc
  */
-goog.vec.Float32Array.BYTES_PER_ELEMENT = 4;
+ol.pointer.PointerEventHandler.prototype.disposeInternal = function() {
+  this.unregister_();
+  ol.events.EventTarget.prototype.disposeInternal.call(this);
+};
 
 
 /**
- * The number of bytes in an element (as defined by the Typed Array
- * specification).
- *
- * @type {number}
+ * Properties to copy when cloning an event, with default values.
+ * @type {Array.<Array>}
  */
-goog.vec.Float32Array.prototype.BYTES_PER_ELEMENT = 4;
+ol.pointer.PointerEventHandler.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.MapBrowserEventHandler');
 
-/**
- * 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];
-  }
-};
+goog.require('ol');
+goog.require('ol.has');
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.MapBrowserPointerEvent');
+goog.require('ol.events');
+goog.require('ol.events.EventTarget');
+goog.require('ol.pointer.EventType');
+goog.require('ol.pointer.PointerEventHandler');
 
 
 /**
- * Creates a string representation of this array.
- * @return {string} The string version of this array.
- * @override
+ * @param {ol.Map} map The map with the viewport to listen to events on.
+ * @param {number|undefined} moveTolerance The minimal distance the pointer must travel to trigger a move.
+ * @constructor
+ * @extends {ol.events.EventTarget}
  */
-goog.vec.Float32Array.prototype.toString = Array.prototype.join;
+ol.MapBrowserEventHandler = function(map, moveTolerance) {
 
+  ol.events.EventTarget.call(this);
 
-/**
- * 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.
- */
+  /**
+   * 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;
 
-/**
- * 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);
-}
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.dragging_ = false;
 
-// 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.
+  /**
+   * @type {!Array.<ol.EventsKey>}
+   * @private
+   */
+  this.dragListenerKeys_ = [];
 
+  /**
+   * @type {number}
+   * @private
+   */
+  this.moveTolerance_ = moveTolerance ?
+      moveTolerance * ol.has.DEVICE_PIXEL_RATIO : ol.has.DEVICE_PIXEL_RATIO;
 
-/**
- * @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');
+  /**
+   * 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;
+
+  /**
+   * @type {?ol.EventsKey}
+   * @private
+   */
+  this.pointerdownListenerKey_ = ol.events.listen(this.pointerEventHandler_,
+      ol.pointer.EventType.POINTERDOWN,
+      this.handlePointerDown_, this);
+
+  /**
+   * @type {?ol.EventsKey}
+   * @private
+   */
+  this.relayedListenerKey_ = ol.events.listen(this.pointerEventHandler_,
+      ol.pointer.EventType.POINTERMOVE,
+      this.relayEvent_, this);
 
+};
+ol.inherits(ol.MapBrowserEventHandler, ol.events.EventTarget);
 
 
 /**
- * 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
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
  */
-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;
+ol.MapBrowserEventHandler.prototype.emulateClick_ = function(pointerEvent) {
+  var newEvent = new ol.MapBrowserPointerEvent(
+      ol.MapBrowserEventType.CLICK, this.map_, pointerEvent);
+  this.dispatchEvent(newEvent);
+  if (this.clickTimeoutId_ !== 0) {
+    // double-click
+    clearTimeout(this.clickTimeoutId_);
+    this.clickTimeoutId_ = 0;
+    newEvent = new ol.MapBrowserPointerEvent(
+        ol.MapBrowserEventType.DBLCLICK, this.map_, pointerEvent);
+    this.dispatchEvent(newEvent);
+  } else {
+    // click
+    this.clickTimeoutId_ = setTimeout(function() {
+      this.clickTimeoutId_ = 0;
+      var newEvent = new ol.MapBrowserPointerEvent(
+          ol.MapBrowserEventType.SINGLECLICK, this.map_, pointerEvent);
+      this.dispatchEvent(newEvent);
+    }.bind(this), 250);
   }
 };
 
 
 /**
- * The number of bytes in an element (as defined by the Typed Array
- * specification).
+ * Keeps track on how many pointers are currently active.
  *
- * @type {number}
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
  */
-goog.vec.Float64Array.BYTES_PER_ELEMENT = 8;
+ol.MapBrowserEventHandler.prototype.updateActivePointers_ = function(pointerEvent) {
+  var event = pointerEvent;
+
+  if (event.type == ol.MapBrowserEventType.POINTERUP ||
+      event.type == ol.MapBrowserEventType.POINTERCANCEL) {
+    delete this.trackedTouches_[event.pointerId];
+  } else if (event.type == ol.MapBrowserEventType.POINTERDOWN) {
+    this.trackedTouches_[event.pointerId] = true;
+  }
+  this.activePointers_ = Object.keys(this.trackedTouches_).length;
+};
 
 
 /**
- * The number of bytes in an element (as defined by the Typed Array
- * specification).
- *
- * @type {number}
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
  */
-goog.vec.Float64Array.prototype.BYTES_PER_ELEMENT = 8;
+ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) {
+  this.updateActivePointers_(pointerEvent);
+  var newEvent = new ol.MapBrowserPointerEvent(
+     ol.MapBrowserEventType.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)) {
+    this.emulateClick_(this.down_);
+  }
 
-/**
- * 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];
+  if (this.activePointers_ === 0) {
+    this.dragListenerKeys_.forEach(ol.events.unlistenByKey);
+    this.dragListenerKeys_.length = 0;
+    this.dragging_ = false;
+    this.down_ = null;
+    this.documentPointerEventHandler_.dispose();
+    this.documentPointerEventHandler_ = null;
   }
 };
 
 
 /**
- * Creates a string representation of this array.
- * @return {string} The string version of this array.
- * @override
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @return {boolean} If the left mouse button was pressed.
+ * @private
  */
-goog.vec.Float64Array.prototype.toString = Array.prototype.join;
+ol.MapBrowserEventHandler.prototype.isMouseActionButton_ = function(pointerEvent) {
+  return pointerEvent.button === 0;
+};
 
 
 /**
- * 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.
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
  */
+ol.MapBrowserEventHandler.prototype.handlePointerDown_ = function(pointerEvent) {
+  this.updateActivePointers_(pointerEvent);
+  var newEvent = new ol.MapBrowserPointerEvent(
+      ol.MapBrowserEventType.POINTERDOWN, this.map_, pointerEvent);
+  this.dispatchEvent(newEvent);
 
+  this.down_ = pointerEvent;
 
-/**
- * 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);
-}
+  if (this.dragListenerKeys_.length === 0) {
+    /* 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);
 
-// 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.
+    this.dragListenerKeys_.push(
+      ol.events.listen(this.documentPointerEventHandler_,
+          ol.MapBrowserEventType.POINTERMOVE,
+          this.handlePointerMove_, this),
+      ol.events.listen(this.documentPointerEventHandler_,
+          ol.MapBrowserEventType.POINTERUP,
+          this.handlePointerUp_, 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.
+       */
+      ol.events.listen(this.pointerEventHandler_,
+          ol.MapBrowserEventType.POINTERCANCEL,
+          this.handlePointerUp_, this)
+    );
+  }
+};
 
 
 /**
- * @fileoverview Supplies global data types and constants for the vector math
- *     library.
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
  */
-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');
+ol.MapBrowserEventHandler.prototype.handlePointerMove_ = function(pointerEvent) {
+  // Between pointerdown and pointerup, pointermove events are triggered.
+  // To avoid a 'false' touchmove event to be dispatched, we test if the pointer
+  // moved a significant distance.
+  if (this.isMoving_(pointerEvent)) {
+    this.dragging_ = true;
+    var newEvent = new ol.MapBrowserPointerEvent(
+        ol.MapBrowserEventType.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();
+};
 
 
 /**
- * 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}
+ * 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
  */
-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;
+ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) {
+  var dragging = !!(this.down_ && this.isMoving_(pointerEvent));
+  this.dispatchEvent(new ol.MapBrowserPointerEvent(
+      pointerEvent.type, this.map_, pointerEvent, dragging));
+};
 
 
-/** @typedef {!Float64Array} */
-goog.vec.Float64;
+/**
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @return {boolean} Is moving.
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.isMoving_ = function(pointerEvent) {
+  return Math.abs(pointerEvent.clientX - this.down_.clientX) > this.moveTolerance_ ||
+      Math.abs(pointerEvent.clientY - this.down_.clientY) > this.moveTolerance_;
+};
 
 
-/** @typedef {!Array<number>} */
-goog.vec.Number;
+/**
+ * @inheritDoc
+ */
+ol.MapBrowserEventHandler.prototype.disposeInternal = function() {
+  if (this.relayedListenerKey_) {
+    ol.events.unlistenByKey(this.relayedListenerKey_);
+    this.relayedListenerKey_ = null;
+  }
+  if (this.pointerdownListenerKey_) {
+    ol.events.unlistenByKey(this.pointerdownListenerKey_);
+    this.pointerdownListenerKey_ = null;
+  }
 
+  this.dragListenerKeys_.forEach(ol.events.unlistenByKey);
+  this.dragListenerKeys_.length = 0;
 
-/** @typedef {!goog.vec.Float32|!goog.vec.Float64|!goog.vec.Number} */
-goog.vec.AnyType;
+  if (this.documentPointerEventHandler_) {
+    this.documentPointerEventHandler_.dispose();
+    this.documentPointerEventHandler_ = null;
+  }
+  if (this.pointerEventHandler_) {
+    this.pointerEventHandler_.dispose();
+    this.pointerEventHandler_ = null;
+  }
+  ol.events.EventTarget.prototype.disposeInternal.call(this);
+};
 
+goog.provide('ol.MapProperty');
 
 /**
- * @deprecated Use AnyType.
- * @typedef {!Float32Array|!Array<number>}
+ * @enum {string}
  */
-goog.vec.ArrayType;
+ol.MapProperty = {
+  LAYERGROUP: 'layergroup',
+  SIZE: 'size',
+  TARGET: 'target',
+  VIEW: 'view'
+};
 
+goog.provide('ol.TileState');
 
 /**
- * For graphics work, 6 decimal places of accuracy are typically all that is
- * required.
- *
- * @type {number}
- * @const
+ * @enum {number}
  */
-goog.vec.EPSILON = 1e-6;
+ol.TileState = {
+  IDLE: 0,
+  LOADING: 1,
+  LOADED: 2,
+  ERROR: 3,
+  EMPTY: 4,
+  ABORT: 5
+};
 
-// 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.
+goog.provide('ol.structs.PriorityQueue');
+
+goog.require('ol.asserts');
+goog.require('ol.obj');
 
 
 /**
- * @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.
+ * 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
  */
-goog.provide('goog.vec.Vec3');
+ol.structs.PriorityQueue = function(priorityFunction, keyFunction) {
+
+  /**
+   * @type {function(T): number}
+   * @private
+   */
+  this.priorityFunction_ = priorityFunction;
 
-/** @suppress {extraRequire} */
-goog.require('goog.vec');
+  /**
+   * @type {function(T): string}
+   * @private
+   */
+  this.keyFunction_ = keyFunction;
 
-/** @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;
+  /**
+   * @type {Array.<T>}
+   * @private
+   */
+  this.elements_ = [];
 
-// 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;
+  /**
+   * @type {Array.<number>}
+   * @private
+   */
+  this.priorities_ = [];
 
+  /**
+   * @type {Object.<string, boolean>}
+   * @private
+   */
+  this.queuedElements_ = {};
 
-/**
- * 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.
+ * @const
+ * @type {number}
  */
-goog.vec.Vec3.createFloat64 = function() {
-  return new Float64Array(3);
-};
+ol.structs.PriorityQueue.DROP = Infinity;
 
 
 /**
- * Creates a 3 element vector of Number. The array is initialized to zero.
- *
- * @return {!goog.vec.Vec3.Number} The new 3 element array.
+ * FIXME empty description for jsdoc
  */
-goog.vec.Vec3.createNumber = function() {
-  var a = new Array(3);
-  goog.vec.Vec3.setFromValues(a, 0, 0, 0);
-  return a;
+ol.structs.PriorityQueue.prototype.clear = function() {
+  this.elements_.length = 0;
+  this.priorities_.length = 0;
+  ol.obj.clear(this.queuedElements_);
 };
 
 
 /**
- * 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.
+ * Remove and return the highest-priority element. O(log N).
+ * @return {T} Element.
  */
-goog.vec.Vec3.create = function() {
-  return new Float32Array(3);
+ol.structs.PriorityQueue.prototype.dequeue = function() {
+  var elements = this.elements_;
+  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);
+  delete this.queuedElements_[elementKey];
+  return element;
 };
 
 
 /**
- * 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.
+ * Enqueue an element. O(log N).
+ * @param {T} element Element.
+ * @return {boolean} The element was added to the queue.
  */
-goog.vec.Vec3.createFloat32FromArray = function(vec) {
-  var newVec = goog.vec.Vec3.createFloat32();
-  goog.vec.Vec3.setFromArray(newVec, vec);
-  return newVec;
+ol.structs.PriorityQueue.prototype.enqueue = function(element) {
+  ol.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_),
+      31); // Tried to enqueue an `element` that was already added to the queue
+  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;
 };
 
 
 /**
- * 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.
+ * @return {number} Count.
  */
-goog.vec.Vec3.createFloat32FromValues = function(v0, v1, v2) {
-  var a = goog.vec.Vec3.createFloat32();
-  goog.vec.Vec3.setFromValues(a, v0, v1, v2);
-  return a;
+ol.structs.PriorityQueue.prototype.getCount = function() {
+  return this.elements_.length;
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.vec.Vec3.cloneFloat32 = goog.vec.Vec3.createFloat32FromArray;
+ol.structs.PriorityQueue.prototype.getLeftChildIndex_ = function(index) {
+  return index * 2 + 1;
+};
 
 
 /**
- * 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.
+ * 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
  */
-goog.vec.Vec3.createFloat64FromArray = function(vec) {
-  var newVec = goog.vec.Vec3.createFloat64();
-  goog.vec.Vec3.setFromArray(newVec, vec);
-  return newVec;
+ol.structs.PriorityQueue.prototype.getRightChildIndex_ = function(index) {
+  return index * 2 + 2;
 };
 
 
 /**
-* 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;
+ * 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;
 };
 
 
 /**
- * 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.
+ * Make this a heap. O(N).
+ * @private
  */
-goog.vec.Vec3.cloneFloat64 = goog.vec.Vec3.createFloat64FromArray;
+ol.structs.PriorityQueue.prototype.heapify_ = function() {
+  var i;
+  for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) {
+    this.siftUp_(i);
+  }
+};
 
 
 /**
- * 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.
+ * @return {boolean} Is empty.
  */
-goog.vec.Vec3.createFromArray = function(vec) {
-  var newVec = goog.vec.Vec3.create();
-  goog.vec.Vec3.setFromArray(newVec, vec);
-  return newVec;
+ol.structs.PriorityQueue.prototype.isEmpty = function() {
+  return this.elements_.length === 0;
 };
 
 
 /**
- * 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.
+ * @param {string} key Key.
+ * @return {boolean} Is key queued.
  */
-goog.vec.Vec3.createFromValues = function(v0, v1, v2) {
-  var vec = goog.vec.Vec3.create();
-  goog.vec.Vec3.setFromValues(vec, v0, v1, v2);
-  return vec;
+ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) {
+  return key in this.queuedElements_;
 };
 
 
 /**
- * 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.
+ * @param {T} element Element.
+ * @return {boolean} Is queued.
  */
-goog.vec.Vec3.clone = function(vec) {
-  var newVec = goog.vec.Vec3.create();
-  goog.vec.Vec3.setFromArray(newVec, vec);
-  return newVec;
+ol.structs.PriorityQueue.prototype.isQueued = function(element) {
+  return this.isKeyQueued(this.keyFunction_(element));
 };
 
 
 /**
- * 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.
+ * @param {number} index The index of the node to move down.
+ * @private
  */
-goog.vec.Vec3.setFromValues = function(vec, v0, v1, v2) {
-  vec[0] = v0;
-  vec[1] = v1;
-  vec[2] = v2;
-  return vec;
-};
+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);
 
-/**
- * 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;
-};
+    var smallerChildIndex = rIndex < count &&
+        priorities[rIndex] < priorities[lIndex] ?
+        rIndex : lIndex;
 
+    elements[index] = elements[smallerChildIndex];
+    priorities[index] = priorities[smallerChildIndex];
+    index = smallerChildIndex;
+  }
 
-/**
- * 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;
+  elements[index] = element;
+  priorities[index] = priority;
+  this.siftDown_(startIndex, index);
 };
 
 
 /**
- * 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.
+ * @param {number} startIndex The index of the root.
+ * @param {number} index The index of the node to move up.
+ * @private
  */
-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;
+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;
 };
 
 
 /**
- * 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.
+ * FIXME empty description for jsdoc
  */
-goog.vec.Vec3.negate = function(vec0, resultVec) {
-  resultVec[0] = -vec0[0];
-  resultVec[1] = -vec0[1];
-  resultVec[2] = -vec0[2];
-  return resultVec;
+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.TileQueue');
 
-/**
- * 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;
-};
+goog.require('ol');
+goog.require('ol.TileState');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.structs.PriorityQueue');
 
 
 /**
- * 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.
+ * @constructor
+ * @extends {ol.structs.PriorityQueue.<Array>}
+ * @param {ol.TilePriorityFunction} tilePriorityFunction
+ *     Tile priority function.
+ * @param {function(): ?} tileChangeCallback
+ *     Function called on each tile change event.
+ * @struct
  */
-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;
-};
+ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) {
 
+  ol.structs.PriorityQueue.call(
+      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();
+      });
 
-/**
- * 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;
-};
+  /**
+   * @private
+   * @type {function(): ?}
+   */
+  this.tileChangeCallback_ = tileChangeCallback;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.tilesLoading_ = 0;
+
+  /**
+   * @private
+   * @type {!Object.<string,boolean>}
+   */
+  this.tilesLoadingKeys_ = {};
 
-/**
- * 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);
 };
+ol.inherits(ol.TileQueue, ol.structs.PriorityQueue);
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-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;
+ol.TileQueue.prototype.enqueue = function(element) {
+  var added = ol.structs.PriorityQueue.prototype.enqueue.call(this, element);
+  if (added) {
+    var tile = element[0];
+    ol.events.listen(tile, ol.events.EventType.CHANGE,
+        this.handleTileChange, this);
+  }
+  return added;
 };
 
 
 /**
- * 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.
+ * @return {number} Number of tiles loading.
  */
-goog.vec.Vec3.dot = function(v0, v1) {
-  return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2];
+ol.TileQueue.prototype.getTilesLoading = function() {
+  return this.tilesLoading_;
 };
 
 
 /**
- * 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.
+ * @param {ol.events.Event} event Event.
+ * @protected
  */
-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;
+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 || state === ol.TileState.ABORT) {
+    ol.events.unlisten(tile, ol.events.EventType.CHANGE,
+        this.handleTileChange, this);
+    var tileKey = tile.getKey();
+    if (tileKey in this.tilesLoadingKeys_) {
+      delete this.tilesLoadingKeys_[tileKey];
+      --this.tilesLoading_;
+    }
+    this.tileChangeCallback_();
+  }
 };
 
 
 /**
- * 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.
+ * @param {number} maxTotalLoading Maximum number tiles to load simultaneously.
+ * @param {number} maxNewLoads Maximum number of new tiles to load.
  */
-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;
+ol.TileQueue.prototype.loadMoreTiles = function(maxTotalLoading, maxNewLoads) {
+  var newLoads = 0;
+  var tile, tileKey;
+  while (this.tilesLoading_ < maxTotalLoading && newLoads < maxNewLoads &&
+         this.getCount() > 0) {
+    tile = /** @type {ol.Tile} */ (this.dequeue()[0]);
+    tileKey = tile.getKey();
+    if (tile.getState() === ol.TileState.IDLE && !(tileKey in this.tilesLoadingKeys_)) {
+      this.tilesLoadingKeys_[tileKey] = true;
+      ++this.tilesLoading_;
+      ++newLoads;
+      tile.load();
+    }
+  }
 };
 
+goog.provide('ol.ResolutionConstraint');
 
-/**
- * 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));
-};
+goog.require('ol.array');
+goog.require('ol.math');
 
 
 /**
- * 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.
+ * @param {Array.<number>} resolutions Resolutions.
+ * @return {ol.ResolutionConstraintType} Zoom function.
  */
-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;
+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);
+          var index = Math.floor(z);
+          if (z != index && index < resolutions.length - 1) {
+            var power = resolutions[index] / resolutions[index + 1];
+            return resolutions[index] / Math.pow(power, z - index);
+          } else {
+            return resolutions[index];
+          }
+        } else {
+          return undefined;
+        }
+      });
 };
 
 
 /**
- * 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.
+ * @param {number} power Power.
+ * @param {number} maxResolution Maximum resolution.
+ * @param {number=} opt_maxLevel Maximum level.
+ * @return {ol.ResolutionConstraintType} Zoom function.
  */
-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;
+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 = -direction / 2 + 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.require('ol.math');
+
 
 /**
- * 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.
+ * @param {number|undefined} rotation Rotation.
+ * @param {number} delta Delta.
+ * @return {number|undefined} Rotation.
  */
-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);
+ol.RotationConstraint.disable = function(rotation, delta) {
+  if (rotation !== undefined) {
+    return 0;
   } 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 undefined;
   }
-  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.
+ * @param {number|undefined} rotation Rotation.
+ * @param {number} delta Delta.
+ * @return {number|undefined} Rotation.
  */
-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);
+ol.RotationConstraint.none = function(rotation, delta) {
+  if (rotation !== undefined) {
+    return rotation + delta;
   } 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 undefined;
   }
-  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.
+ * @param {number} n N.
+ * @return {ol.RotationConstraintType} Rotation constraint.
  */
-goog.vec.Vec3.equals = function(v0, v1) {
-  return v0.length == v1.length &&
-      v0[0] == v1[0] && v0[1] == v1[1] && v0[2] == v1[2];
+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;
+        }
+      });
 };
 
-// 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.
- *
+ * @param {number=} opt_tolerance Tolerance.
+ * @return {ol.RotationConstraintType} Rotation constraint.
  */
-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;
+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.ViewHint');
 
 /**
- * Creates a 4 element vector of Float32. The array is initialized to zero.
- *
- * @return {!goog.vec.Vec4.Float32} The new 3 element array.
+ * @enum {number}
  */
-goog.vec.Vec4.createFloat32 = function() {
-  return new Float32Array(4);
+ol.ViewHint = {
+  ANIMATING: 0,
+  INTERACTING: 1
 };
 
+goog.provide('ol.ViewProperty');
 
 /**
- * Creates a 4 element vector of Float64. The array is initialized to zero.
- *
- * @return {!goog.vec.Vec4.Float64} The new 4 element array.
+ * @enum {string}
  */
-goog.vec.Vec4.createFloat64 = function() {
-  return new Float64Array(4);
+ol.ViewProperty = {
+  CENTER: 'center',
+  RESOLUTION: 'resolution',
+  ROTATION: 'rotation'
 };
 
+goog.provide('ol.string');
 
 /**
- * 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;
+ * @param {number} number Number to be formatted
+ * @param {number} width The desired width
+ * @param {number=} opt_precision Precision of the output string (i.e. number of decimal places)
+ * @returns {string} Formatted string
+*/
+ol.string.padNumber = function(number, width, opt_precision) {
+  var numberString = opt_precision !== undefined ? number.toFixed(opt_precision) : '' + number;
+  var decimal = numberString.indexOf('.');
+  decimal = decimal === -1 ? numberString.length : decimal;
+  return decimal > width ? numberString : new Array(1 + width - decimal).join('0') + numberString;
 };
 
-
 /**
- * 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.
+ * Adapted from https://github.com/omichelsen/compare-versions/blob/master/index.js
+ * @param {string|number} v1 First version
+ * @param {string|number} v2 Second version
+ * @returns {number} Value
  */
-goog.vec.Vec4.create = function() {
-  return new Float32Array(4);
-};
+ol.string.compareVersions = function(v1, v2) {
+  var s1 = ('' + v1).split('.');
+  var s2 = ('' + v2).split('.');
 
+  for (var i = 0; i < Math.max(s1.length, s2.length); i++) {
+    var n1 = parseInt(s1[i] || '0', 10);
+    var n2 = parseInt(s2[i] || '0', 10);
 
-/**
- * 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;
+    if (n1 > n2) {
+      return 1;
+    }
+    if (n2 > n1) {
+      return -1;
+    }
+  }
+
+  return 0;
 };
 
+goog.provide('ol.coordinate');
 
-/**
- * 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;
-};
+goog.require('ol.math');
+goog.require('ol.string');
 
 
 /**
- * Creates a new 4 element Float32 vector initialized with the supplied values.
+ * Add `delta` to `coordinate`. `coordinate` is modified in place and returned
+ * by the function.
+ *
+ * Example:
  *
- * @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.
+ *     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
  */
-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;
+ol.coordinate.add = function(coordinate, delta) {
+  coordinate[0] += delta[0];
+  coordinate[1] += delta[1];
+  return coordinate;
 };
 
 
 /**
- * Creates a clone of the given 4 element Float32 vector.
+ * Calculates the point closest to the passed coordinate on the passed circle.
  *
- * @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;
-
+ * @param {ol.Coordinate} coordinate The coordinate.
+ * @param {ol.geom.Circle} circle The circle.
+ * @return {ol.Coordinate} Closest point on the circumference
+ */
+ol.coordinate.closestOnCircle = function(coordinate, circle) {
+  var r = circle.getRadius();
+  var center = circle.getCenter();
+  var x0 = center[0];
+  var y0 = center[1];
+  var x1 = coordinate[0];
+  var y1 = coordinate[1];
+
+  var dx = x1 - x0;
+  var dy = y1 - y0;
+  if (dx === 0 && dy === 0) {
+    dx = 1;
+  }
+  var d = Math.sqrt(dx * dx + dy * dy);
 
-/**
- * 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;
-};
+  var x, y;
 
+  x = x0 + r * dx / d;
+  y = y0 + r * dy / d;
 
-/**
-* 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;
+  return [x, y];
 };
 
 
 /**
- * Creates a clone of the given 4 element vector.
+ * 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 {goog.vec.Vec4.Float64} vec The source 4 element vector.
- * @return {!goog.vec.Vec4.Float64} The new cloned vector.
+ * @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.
  */
-goog.vec.Vec4.cloneFloat64 = goog.vec.Vec4.createFloat64FromArray;
+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];
+};
 
 
 /**
- * Creates a new 4 element vector initialized with the supplied values.
+ * Returns a {@link ol.CoordinateFormatType} function that can be used to format
+ * a {ol.Coordinate} to a string.
+ *
+ * Example without specifying the fractional digits:
  *
- * @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.
+ *     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
  */
-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;
+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);
+      });
 };
 
 
 /**
- * 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.
+ * @param {string} hemispheres Hemispheres.
+ * @param {number} degrees Degrees.
+ * @param {number=} opt_fractionDigits The number of digits to include
+ *    after the decimal point. Default is `0`.
+ * @return {string} String.
  */
-goog.vec.Vec4.clone = goog.vec.Vec4.createFromArray;
+ol.coordinate.degreesToStringHDMS = function(hemispheres, degrees, opt_fractionDigits) {
+  var normalizedDegrees = ol.math.modulo(degrees + 180, 360) - 180;
+  var x = Math.abs(3600 * normalizedDegrees);
+  var dflPrecision = opt_fractionDigits || 0;
+  var precision = Math.pow(10, dflPrecision);
 
+  var deg = Math.floor(x / 3600);
+  var min = Math.floor((x - deg * 3600) / 60);
+  var sec = x - (deg * 3600) - (min * 60);
+  sec = Math.ceil(sec * precision) / precision;
 
-/**
- * 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;
-};
+  if (sec >= 60) {
+    sec = 0;
+    min += 1;
+  }
 
+  if (min >= 60) {
+    min = 0;
+    deg += 1;
+  }
 
-/**
- * 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;
+  return deg + '\u00b0 ' + ol.string.padNumber(min, 2) + '\u2032 ' +
+    ol.string.padNumber(sec, 2, dflPrecision) + '\u2033' +
+    (normalizedDegrees == 0 ? '' : ' ' + hemispheres.charAt(normalizedDegrees < 0 ? 1 : 0));
 };
 
 
 /**
- * Performs a component-wise addition of vec0 and vec1 together storing the
- * result into resultVec.
+ * 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.
  *
- * @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.
+ * Example without specifying the fractional digits:
  *
- * @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.
+ *     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
  */
-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;
+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 '';
+  }
 };
 
 
 /**
- * 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.
+ * @param {ol.Coordinate} coordinate1 First coordinate.
+ * @param {ol.Coordinate} coordinate2 Second coordinate.
+ * @return {boolean} Whether the passed coordinates are equal.
  */
-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;
+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;
 };
 
 
 /**
- * Takes the absolute value of each component of vec0 storing the result in
- * resultVec.
+ * Rotate `coordinate` by `angle`. `coordinate` is modified in place and
+ * returned by the function.
  *
- * @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.
+ * Example:
  *
- * @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.
+ *     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 {goog.vec.Vec4.AnyType} vec0 The vector.
- * @return {number} The magnitude of the vector.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} angle Angle in radian.
+ * @return {ol.Coordinate} Coordinate.
+ * @api
  */
-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;
+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;
 };
 
 
 /**
- * Returns the magnitude of the given vector.
+ * 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 {goog.vec.Vec4.AnyType} vec0 The vector.
- * @return {number} The magnitude of the vector.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} scale Scale factor.
+ * @return {ol.Coordinate} Coordinate.
  */
-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);
+ol.coordinate.scale = function(coordinate, scale) {
+  coordinate[0] *= scale;
+  coordinate[1] *= scale;
+  return coordinate;
 };
 
 
 /**
- * Normalizes the given vector storing the result into resultVec.
+ * Subtract `delta` to `coordinate`. `coordinate` is modified in place and
+ * returned by the function.
  *
- * @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.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {ol.Coordinate} delta Delta.
+ * @return {ol.Coordinate} Coordinate.
  */
-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;
+ol.coordinate.sub = function(coordinate, delta) {
+  coordinate[0] -= delta[0];
+  coordinate[1] -= delta[1];
+  return coordinate;
 };
 
 
 /**
- * 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.
+ * @param {ol.Coordinate} coord1 First coordinate.
+ * @param {ol.Coordinate} coord2 Second coordinate.
+ * @return {number} Squared distance between coord1 and coord2.
  */
-goog.vec.Vec4.dot = function(v0, v1) {
-  return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2] + v0[3] * v1[3];
+ol.coordinate.squaredDistance = function(coord1, coord2) {
+  var dx = coord1[0] - coord2[0];
+  var dy = coord1[1] - coord2[1];
+  return dx * dx + dy * dy;
 };
 
 
 /**
- * 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.
+ * @param {ol.Coordinate} coord1 First coordinate.
+ * @param {ol.Coordinate} coord2 Second coordinate.
+ * @return {number} Distance between coord1 and coord2.
  */
-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;
+ol.coordinate.distance = function(coord1, coord2) {
+  return Math.sqrt(ol.coordinate.squaredDistance(coord1, coord2));
 };
 
 
 /**
- * Compares the components of vec0 with the components of another vector or
- * scalar, storing the larger values in resultVec.
+ * Calculate the squared distance from a coordinate to a line segment.
  *
- * @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.
+ * @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.
  */
-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;
+ol.coordinate.squaredDistanceToSegment = function(coordinate, segment) {
+  return ol.coordinate.squaredDistance(coordinate,
+      ol.coordinate.closestOnSegment(coordinate, segment));
 };
 
 
 /**
- * Compares the components of vec0 with the components of another vector or
- * scalar, storing the smaller values in resultVec.
+ * Format a geographic coordinate with the hemisphere, degrees, minutes, and
+ * seconds.
  *
- * @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.
+ * Example without specifying fractional digits:
+ *
+ *     var coord = [7.85, 47.983333];
+ *     var out = ol.coordinate.toStringHDMS(coord);
+ *     // out is now '47° 58′ 60″ N 7° 50′ 60″ E'
+ *
+ * Example explicitly specifying 1 fractional digit:
+ *
+ *     var coord = [7.85, 47.983333];
+ *     var out = ol.coordinate.toStringHDMS(coord, 1);
+ *     // out is now '47° 58′ 60.0″ N 7° 50′ 60.0″ E'
+ *
+ * @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} Hemisphere, degrees, minutes and seconds.
+ * @api
  */
-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);
+ol.coordinate.toStringHDMS = function(coordinate, opt_fractionDigits) {
+  if (coordinate) {
+    return ol.coordinate.degreesToStringHDMS('NS', coordinate[1], opt_fractionDigits) + ' ' +
+        ol.coordinate.degreesToStringHDMS('EW', coordinate[0], opt_fractionDigits);
   } 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 '';
   }
-  return resultVec;
 };
 
 
 /**
- * Returns true if the components of v0 are equal to the components of v1.
+ * Format a coordinate as a comma delimited string.
  *
- * @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.
+ * 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:
  *
- * The matrices are stored in column-major order.
+ *     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
  */
-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;
+ol.coordinate.toStringXY = function(coordinate, opt_fractionDigits) {
+  return ol.coordinate.format(coordinate, '{x}, {y}', opt_fractionDigits);
+};
 
-// 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;
+goog.provide('ol.geom.GeometryType');
 
 
 /**
- * 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.
+ * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`,
+ * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`,
+ * `'GeometryCollection'`, `'Circle'`.
+ * @enum {string}
  */
-goog.vec.Mat4.createFloat32 = function() {
-  return new Float32Array(16);
+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'
 };
 
+goog.provide('ol.geom.GeometryLayout');
+
 
 /**
- * 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.
+ * 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}
  */
-goog.vec.Mat4.createFloat64 = function() {
-  return new Float64Array(16);
+ol.geom.GeometryLayout = {
+  XY: 'XY',
+  XYZ: 'XYZ',
+  XYM: 'XYM',
+  XYZM: 'XYZM'
 };
 
+goog.provide('ol.functions');
 
 /**
- * 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.
+ * Always returns true.
+ * @returns {boolean} true.
  */
-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;
+ol.functions.TRUE = function() {
+  return true;
 };
 
-
 /**
- * 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.
+ * Always returns false.
+ * @returns {boolean} false.
  */
-goog.vec.Mat4.create = function() {
-  return goog.vec.Mat4.createFloat32();
+ol.functions.FALSE = function() {
+  return false;
 };
 
+goog.provide('ol.geom.Geometry');
 
-/**
- * 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;
-};
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.extent');
+goog.require('ol.functions');
+goog.require('ol.proj');
 
 
 /**
- * Creates a 4x4 identity matrix of Float64.
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for vector geometries.
  *
- * @return {!goog.vec.Mat4.Float64} The new 16 element array.
+ * To get notified of changes to the geometry, register a listener for the
+ * generic `change` event on your geometry instance.
+ *
+ * @constructor
+ * @abstract
+ * @extends {ol.Object}
+ * @api
  */
-goog.vec.Mat4.createFloat64Identity = function() {
-  var mat = goog.vec.Mat4.createFloat64();
-  mat[0] = mat[5] = mat[10] = mat[15] = 1;
-  return mat;
+ol.geom.Geometry = function() {
+
+  ol.Object.call(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;
+
 };
+ol.inherits(ol.geom.Geometry, ol.Object);
 
 
 /**
- * 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.
+ * Make a complete copy of the geometry.
+ * @abstract
+ * @return {!ol.geom.Geometry} Clone.
  */
-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;
-};
+ol.geom.Geometry.prototype.clone = function() {};
 
 
 /**
- * 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.
+ * @abstract
+ * @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.
  */
-goog.vec.Mat4.createIdentity = function() {
-  return goog.vec.Mat4.createFloat32Identity();
-};
+ol.geom.Geometry.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {};
 
 
 /**
- * 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.
+ * 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
  */
-goog.vec.Mat4.createFloat32FromArray = function(matrix) {
-  var newMatrix = goog.vec.Mat4.createFloat32();
-  goog.vec.Mat4.setFromArray(newMatrix, matrix);
-  return newMatrix;
+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;
 };
 
 
 /**
- * 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.
+ * Returns true if this geometry includes the specified coordinate. If the
+ * coordinate is on the boundary of the geometry, returns false.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {boolean} Contains coordinate.
+ * @api
  */
-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;
+ol.geom.Geometry.prototype.intersectsCoordinate = function(coordinate) {
+  return this.containsXY(coordinate[0], coordinate[1]);
 };
 
 
 /**
- * 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.
+ * @abstract
+ * @param {ol.Extent} extent Extent.
+ * @protected
+ * @return {ol.Extent} extent Extent.
  */
-goog.vec.Mat4.cloneFloat32 = goog.vec.Mat4.createFloat32FromArray;
+ol.geom.Geometry.prototype.computeExtent = function(extent) {};
 
 
 /**
- * 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.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @return {boolean} Contains (x, y).
  */
-goog.vec.Mat4.createFloat64FromArray = function(matrix) {
-  var newMatrix = goog.vec.Mat4.createFloat64();
-  goog.vec.Mat4.setFromArray(newMatrix, matrix);
-  return newMatrix;
-};
+ol.geom.Geometry.prototype.containsXY = ol.functions.FALSE;
 
 
 /**
- * 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.
+ * Get the extent of the geometry.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} extent Extent.
+ * @api
  */
-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;
+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);
 };
 
 
 /**
- * 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.
+ * Rotate the geometry around a given coordinate. This modifies the geometry
+ * coordinates in place.
+ * @abstract
+ * @param {number} angle Rotation angle in radians.
+ * @param {ol.Coordinate} anchor The rotation center.
+ * @api
  */
-goog.vec.Mat4.cloneFloat64 = goog.vec.Mat4.createFloat64FromArray;
+ol.geom.Geometry.prototype.rotate = function(angle, anchor) {};
 
 
 /**
- * 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.
+ * Scale the geometry (with an optional origin).  This modifies the geometry
+ * coordinates in place.
+ * @abstract
+ * @param {number} sx The scaling factor in the x-direction.
+ * @param {number=} opt_sy The scaling factor in the y-direction (defaults to
+ *     sx).
+ * @param {ol.Coordinate=} opt_anchor The scale origin (defaults to the center
+ *     of the geometry extent).
+ * @api
  */
-goog.vec.Mat4.createFromArray = function(matrix) {
-  var newMatrix = goog.vec.Mat4.createFloat32();
-  goog.vec.Mat4.setFromArray(newMatrix, matrix);
-  return newMatrix;
-};
+ol.geom.Geometry.prototype.scale = function(sx, opt_sy, opt_anchor) {};
 
 
 /**
- * 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.
+ * 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
  */
-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);
+ol.geom.Geometry.prototype.simplify = function(tolerance) {
+  return this.getSimplifiedGeometry(tolerance * tolerance);
 };
 
 
 /**
- * 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.
+ * Create a simplified version of this geometry using the Douglas Peucker
+ * algorithm.
+ * @see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
+ * @abstract
+ * @param {number} squaredTolerance Squared tolerance.
+ * @return {ol.geom.Geometry} Simplified geometry.
  */
-goog.vec.Mat4.clone = goog.vec.Mat4.createFromArray;
+ol.geom.Geometry.prototype.getSimplifiedGeometry = function(squaredTolerance) {};
 
 
 /**
- * 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.
+ * Get the type of this geometry.
+ * @abstract
+ * @return {ol.geom.GeometryType} Geometry type.
  */
-goog.vec.Mat4.getElement = function(mat, row, column) {
-  return mat[row + column * 4];
-};
+ol.geom.Geometry.prototype.getType = function() {};
 
 
 /**
- * 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.
+ * 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.
+ * @abstract
+ * @param {ol.TransformFunction} transformFn Transform.
  */
-goog.vec.Mat4.setFromRowMajorArray = function(mat, values) {
-  mat[0] = values[0];
-  mat[1] = values[4];
-  mat[2] = values[8];
-  mat[3] = values[12];
+ol.geom.Geometry.prototype.applyTransform = function(transformFn) {};
 
-  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];
+/**
+ * Test if the geometry and the passed extent intersect.
+ * @abstract
+ * @param {ol.Extent} extent Extent.
+ * @return {boolean} `true` if the geometry and the extent intersect.
+ */
+ol.geom.Geometry.prototype.intersectsExtent = function(extent) {};
 
-  mat[12] = values[3];
-  mat[13] = values[7];
-  mat[14] = values[11];
-  mat[15] = values[15];
 
-  return mat;
-};
+/**
+ * Translate the geometry.  This modifies the geometry coordinates in place.  If
+ * instead you want a new geometry, first `clone()` this geometry.
+ * @abstract
+ * @param {number} deltaX Delta X.
+ * @param {number} deltaY Delta Y.
+ */
+ol.geom.Geometry.prototype.translate = function(deltaX, deltaY) {};
 
 
 /**
- * Sets the diagonal values of the matrix from the given values.
+ * 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 {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.
+ * @param {ol.ProjectionLike} source The current projection.  Can be a
+ *     string identifier or a {@link ol.proj.Projection} object.
+ * @param {ol.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
  */
-goog.vec.Mat4.setDiagonalValues = function(mat, v00, v11, v22, v33) {
-  mat[0] = v00;
-  mat[5] = v11;
-  mat[10] = v22;
-  mat[15] = v33;
-  return mat;
+ol.geom.Geometry.prototype.transform = function(source, destination) {
+  this.applyTransform(ol.proj.getTransform(source, destination));
+  return this;
 };
 
+goog.provide('ol.geom.flat.transform');
+
 
 /**
- * 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.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {ol.Transform} transform Transform.
+ * @param {Array.<number>=} opt_dest Destination.
+ * @return {Array.<number>} Transformed coordinates.
  */
-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;
+ol.geom.flat.transform.transform2D = function(flatCoordinates, offset, end, stride, transform, opt_dest) {
+  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++] = transform[0] * x + transform[2] * y + transform[4];
+    dest[i++] = transform[1] * x + transform[3] * y + transform[5];
+  }
+  if (opt_dest && dest.length != i) {
+    dest.length = i;
+  }
+  return dest;
 };
 
 
 /**
- * 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.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {number} angle Angle.
+ * @param {Array.<number>} anchor Rotation anchor point.
+ * @param {Array.<number>=} opt_dest Destination.
+ * @return {Array.<number>} Transformed coordinates.
  */
-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];
+ol.geom.flat.transform.rotate = function(flatCoordinates, offset, end, stride, angle, anchor, opt_dest) {
+  var dest = opt_dest ? opt_dest : [];
+  var cos = Math.cos(angle);
+  var sin = Math.sin(angle);
+  var anchorX = anchor[0];
+  var anchorY = anchor[1];
+  var i = 0;
+  for (var j = offset; j < end; j += stride) {
+    var deltaX = flatCoordinates[j] - anchorX;
+    var deltaY = flatCoordinates[j + 1] - anchorY;
+    dest[i++] = anchorX + deltaX * cos - deltaY * sin;
+    dest[i++] = anchorY + deltaX * sin + deltaY * cos;
+    for (var k = j + 2; k < j + stride; ++k) {
+      dest[i++] = flatCoordinates[k];
     }
   }
-  return vec;
+  if (opt_dest && dest.length != i) {
+    dest.length = i;
+  }
+  return dest;
 };
 
 
 /**
- * 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.
+ * Scale the coordinates.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {number} sx Scale factor in the x-direction.
+ * @param {number} sy Scale factor in the y-direction.
+ * @param {Array.<number>} anchor Scale anchor point.
+ * @param {Array.<number>=} opt_dest Destination.
+ * @return {Array.<number>} Transformed coordinates.
  */
-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;
+ol.geom.flat.transform.scale = function(flatCoordinates, offset, end, stride, sx, sy, anchor, opt_dest) {
+  var dest = opt_dest ? opt_dest : [];
+  var anchorX = anchor[0];
+  var anchorY = anchor[1];
+  var i = 0;
+  for (var j = offset; j < end; j += stride) {
+    var deltaX = flatCoordinates[j] - anchorX;
+    var deltaY = flatCoordinates[j + 1] - anchorY;
+    dest[i++] = anchorX + sx * deltaX;
+    dest[i++] = anchorY + sy * deltaY;
+    for (var k = j + 2; k < j + stride; ++k) {
+      dest[i++] = flatCoordinates[k];
+    }
+  }
+  if (opt_dest && dest.length != i) {
+    dest.length = i;
+  }
+  return dest;
 };
 
 
 /**
- * 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.
+ * @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.
  */
-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;
+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('ol');
+goog.require('ol.functions');
+goog.require('ol.extent');
+goog.require('ol.geom.Geometry');
+goog.require('ol.geom.GeometryLayout');
+goog.require('ol.geom.flat.transform');
+goog.require('ol.obj');
+
 
 /**
- * Retrieves the specified column from the matrix into the given vector.
+ * @classdesc
+ * Abstract base class; only used for creating subclasses; do not instantiate
+ * in apps, as cannot be rendered.
  *
- * @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.
+ * @constructor
+ * @abstract
+ * @extends {ol.geom.Geometry}
+ * @api
  */
-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;
+ol.geom.SimpleGeometry = function() {
+
+  ol.geom.Geometry.call(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;
+
 };
+ol.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry);
 
 
 /**
- * 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.
+ * @param {number} stride Stride.
+ * @private
+ * @return {ol.geom.GeometryLayout} layout Layout.
  */
-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;
+ol.geom.SimpleGeometry.getLayoutForStride_ = function(stride) {
+  var layout;
+  if (stride == 2) {
+    layout = ol.geom.GeometryLayout.XY;
+  } else if (stride == 3) {
+    layout = ol.geom.GeometryLayout.XYZ;
+  } else if (stride == 4) {
+    layout = ol.geom.GeometryLayout.XYZM;
+  }
+  return /** @type {ol.geom.GeometryLayout} */ (layout);
 };
 
 
 /**
- * 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.
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @return {number} Stride.
  */
-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);
+ol.geom.SimpleGeometry.getStrideForLayout = function(layout) {
+  var stride;
+  if (layout == ol.geom.GeometryLayout.XY) {
+    stride = 2;
+  } else if (layout == ol.geom.GeometryLayout.XYZ || layout == ol.geom.GeometryLayout.XYM) {
+    stride = 3;
+  } else if (layout == ol.geom.GeometryLayout.XYZM) {
+    stride = 4;
+  }
+  return /** @type {number} */ (stride);
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-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;
-};
+ol.geom.SimpleGeometry.prototype.containsXY = ol.functions.FALSE;
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-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;
+ol.geom.SimpleGeometry.prototype.computeExtent = function(extent) {
+  return ol.extent.createOrUpdateFromFlatCoordinates(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
+      extent);
 };
 
 
 /**
- * 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.
+ * @abstract
+ * @return {Array} Coordinates.
  */
-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;
-};
+ol.geom.SimpleGeometry.prototype.getCoordinates = function() {};
 
 
 /**
- * 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.
+ * Return the first coordinate of the geometry.
+ * @return {ol.Coordinate} First coordinate.
+ * @api
  */
-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;
+ol.geom.SimpleGeometry.prototype.getFirstCoordinate = function() {
+  return this.flatCoordinates.slice(0, this.stride);
 };
 
 
 /**
- * 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.
+ * @return {Array.<number>} Flat coordinates.
  */
-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);
+ol.geom.SimpleGeometry.prototype.getFlatCoordinates = function() {
+  return this.flatCoordinates;
 };
 
 
 /**
- * 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.
+ * Return the last coordinate of the geometry.
+ * @return {ol.Coordinate} Last point.
+ * @api
  */
-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;
+ol.geom.SimpleGeometry.prototype.getLastCoordinate = function() {
+  return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride);
 };
 
 
 /**
- * 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.
+ * Return the {@link ol.geom.GeometryLayout layout} of the geometry.
+ * @return {ol.geom.GeometryLayout} Layout.
+ * @api
  */
-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;
+ol.geom.SimpleGeometry.prototype.getLayout = function() {
+  return this.layout;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-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;
+ol.geom.SimpleGeometry.prototype.getSimplifiedGeometry = function(squaredTolerance) {
+  if (this.simplifiedGeometryRevision != this.getRevision()) {
+    ol.obj.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 {
-    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 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;
+    }
   }
-
-  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.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @return {ol.geom.SimpleGeometry} Simplified geometry.
+ * @protected
  */
-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];
+ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
+  return this;
 };
 
 
 /**
- * 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.
+ * @return {number} Stride.
  */
-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;
+ol.geom.SimpleGeometry.prototype.getStride = function() {
+  return this.stride;
 };
 
 
 /**
- * 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.
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @protected
  */
-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;
+ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal = function(layout, flatCoordinates) {
+  this.stride = ol.geom.SimpleGeometry.getStrideForLayout(layout);
+  this.layout = layout;
+  this.flatCoordinates = flatCoordinates;
 };
 
 
 /**
- * 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.
+ * @abstract
+ * @param {Array} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
  */
-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;
-};
+ol.geom.SimpleGeometry.prototype.setCoordinates = function(coordinates, opt_layout) {};
 
 
 /**
- * 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.
+ * @param {ol.geom.GeometryLayout|undefined} layout Layout.
+ * @param {Array} coordinates Coordinates.
+ * @param {number} nesting Nesting.
+ * @protected
  */
-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;
+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 = coordinates.length;
+    layout = ol.geom.SimpleGeometry.getLayoutForStride_(stride);
+  }
+  this.layout = layout;
+  this.stride = stride;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
+ * @api
  */
-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);
+ol.geom.SimpleGeometry.prototype.applyTransform = function(transformFn) {
+  if (this.flatCoordinates) {
+    transformFn(this.flatCoordinates, this.flatCoordinates, this.stride);
+    this.changed();
+  }
 };
 
 
 /**
- * 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.
+ * @inheritDoc
+ * @api
  */
-goog.vec.Mat4.makeScale = function(mat, x, y, z) {
-  goog.vec.Mat4.makeIdentity(mat);
-  return goog.vec.Mat4.setDiagonalValues(mat, x, y, z, 1);
+ol.geom.SimpleGeometry.prototype.rotate = function(angle, anchor) {
+  var flatCoordinates = this.getFlatCoordinates();
+  if (flatCoordinates) {
+    var stride = this.getStride();
+    ol.geom.flat.transform.rotate(
+        flatCoordinates, 0, flatCoordinates.length,
+        stride, angle, anchor, flatCoordinates);
+    this.changed();
+  }
 };
 
 
 /**
- * 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.
+ * @inheritDoc
+ * @api
  */
-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);
+ol.geom.SimpleGeometry.prototype.scale = function(sx, opt_sy, opt_anchor) {
+  var sy = opt_sy;
+  if (sy === undefined) {
+    sy = sx;
+  }
+  var anchor = opt_anchor;
+  if (!anchor) {
+    anchor = ol.extent.getCenter(this.getExtent());
+  }
+  var flatCoordinates = this.getFlatCoordinates();
+  if (flatCoordinates) {
+    var stride = this.getStride();
+    ol.geom.flat.transform.scale(
+        flatCoordinates, 0, flatCoordinates.length,
+        stride, sx, sy, anchor, flatCoordinates);
+    this.changed();
+  }
 };
 
 
 /**
- * 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.
+ * @inheritDoc
+ * @api
  */
-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);
+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();
+  }
 };
 
 
 /**
- * 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.
+ * @param {ol.geom.SimpleGeometry} simpleGeometry Simple geometry.
+ * @param {ol.Transform} transform Transform.
+ * @param {Array.<number>=} opt_dest Destination.
+ * @return {Array.<number>} Transformed flat coordinates.
  */
-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);
+ol.geom.SimpleGeometry.transform2D = 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);
+  }
 };
 
-
-/**
- * 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);
-};
+goog.provide('ol.geom.flat.area');
 
 
 /**
- * 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.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} Area.
  */
-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
-  );
+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;
 };
 
 
 /**
- * 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.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @return {number} Area.
  */
-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;
+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;
   }
-
-  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
-  );
+  return area;
 };
 
 
 /**
- * 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.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} stride Stride.
+ * @return {number} Area.
  */
-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
-  );
+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');
 
-/**
- * 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;
-  }
+goog.require('ol.math');
 
-  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];
+/**
+ * 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] = ol.math.lerp(flatCoordinates[offset1 + i],
+            flatCoordinates[offset2 + i], t);
+      }
+      closestPoint.length = stride;
+      return;
+    } else {
+      offset = offset1;
     }
-    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);
+  for (i = 0; i < stride; ++i) {
+    closestPoint[i] = flatCoordinates[offset + i];
   }
-  return true;
+  closestPoint.length = stride;
 };
 
 
 /**
- * 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.
+ * 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.
  */
-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;
+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;
 };
 
 
 /**
- * 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.
+ * @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.
  */
-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]);
+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;
   }
-
-  // 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;
+  return maxSquaredDelta;
 };
 
 
 /**
- * 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.
+ * @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.
  */
-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]);
+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;
 };
 
 
 /**
- * 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.
+ * @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.
  */
-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]);
+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;
+    }
+  }
+  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;
 };
 
 
 /**
- * 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.
+ * @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.
  */
-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);
+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;
 };
 
 
 /**
- * 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.
+ * @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.
  */
-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;
+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;
 };
 
-
-/**
- * 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;
-};
+goog.provide('ol.geom.flat.deflate');
 
 
 /**
- * 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.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} stride Stride.
+ * @return {number} offset Offset.
  */
-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;
+ol.geom.flat.deflate.coordinate = function(flatCoordinates, offset, coordinate, stride) {
+  var i, ii;
+  for (i = 0, ii = coordinate.length; i < ii; ++i) {
+    flatCoordinates[offset++] = coordinate[i];
+  }
+  return offset;
 };
 
 
 /**
- * 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.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {number} stride Stride.
+ * @return {number} offset Offset.
  */
-goog.vec.Mat4.getTranslation = function(mat, translation) {
-  translation[0] = mat[12];
-  translation[1] = mat[13];
-  translation[2] = mat[14];
-  return translation;
+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];
+    var j;
+    for (j = 0; j < stride; ++j) {
+      flatCoordinates[offset++] = coordinate[j];
+    }
+  }
+  return offset;
 };
 
 
 /**
- * @type {!Array<!goog.vec.Vec3.Type>}
- * @private
+ * @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.
  */
-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
+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;
 };
 
 
 /**
- * Build an extent that includes all given coordinates.
- *
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @return {ol.Extent} Bounding extent.
- * @api stable
+ * @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.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]);
+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];
   }
-  return extent;
+  endss.length = i;
+  return endss;
 };
 
-
-/**
- * @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);
-};
+goog.provide('ol.geom.flat.inflate');
 
 
 /**
- * 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
+ * @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.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
-    ];
+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;
 };
 
 
 /**
- * Creates a clone of an extent.
- *
- * @param {ol.Extent} extent Extent to clone.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} The clone.
+ * @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.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();
+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 {ol.Extent} extent Extent.
- * @param {number} x X.
- * @param {number} y Y.
- * @return {number} Closest squared distance.
+ * @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.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;
+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];
   }
-  return dx * dx + dy * dy;
+  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.
 
-/**
- * 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]);
-};
-
+goog.provide('ol.geom.flat.simplify');
 
-/**
- * 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];
-};
+goog.require('ol.math');
 
 
 /**
- * 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
+ * @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.extent.containsXY = function(extent, x, y) {
-  return extent[0] <= x && x <= extent[2] && extent[1] <= y && y <= extent[3];
+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;
 };
 
 
 /**
- * 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).
+ * @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.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;
+ol.geom.flat.simplify.douglasPeucker = function(flatCoordinates, offset, end,
+    stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) {
+  var n = (end - offset) / stride;
+  if (n < 3) {
+    for (; offset < end; offset += stride) {
+      simplifiedFlatCoordinates[simplifiedOffset++] =
+          flatCoordinates[offset];
+      simplifiedFlatCoordinates[simplifiedOffset++] =
+          flatCoordinates[offset + 1];
+    }
+    return simplifiedOffset;
   }
-  if (y < minY) {
-    relationship = relationship | ol.extent.Relationship.BELOW;
-  } else if (y > maxY) {
-    relationship = relationship | ol.extent.Relationship.ABOVE;
+  /** @type {Array.<number>} */
+  var markers = new Array(n);
+  markers[0] = 1;
+  markers[n - 1] = 1;
+  /** @type {Array.<number>} */
+  var stack = [offset, end - stride];
+  var index = 0;
+  var i;
+  while (stack.length > 0) {
+    var last = stack.pop();
+    var first = stack.pop();
+    var maxSquaredDistance = 0;
+    var x1 = flatCoordinates[first];
+    var y1 = flatCoordinates[first + 1];
+    var x2 = flatCoordinates[last];
+    var y2 = flatCoordinates[last + 1];
+    for (i = first + stride; i < last; i += stride) {
+      var x = flatCoordinates[i];
+      var y = flatCoordinates[i + 1];
+      var squaredDistance = ol.math.squaredSegmentDistance(
+          x, y, x1, y1, x2, y2);
+      if (squaredDistance > maxSquaredDistance) {
+        index = i;
+        maxSquaredDistance = squaredDistance;
+      }
+    }
+    if (maxSquaredDistance > squaredTolerance) {
+      markers[(index - offset) / stride] = 1;
+      if (first + stride < index) {
+        stack.push(first, index);
+      }
+      if (index + stride < last) {
+        stack.push(index, last);
+      }
+    }
   }
-  if (relationship === ol.extent.Relationship.UNKNOWN) {
-    relationship = ol.extent.Relationship.INTERSECTING;
+  for (i = 0; i < n; ++i) {
+    if (markers[i]) {
+      simplifiedFlatCoordinates[simplifiedOffset++] =
+          flatCoordinates[offset + i * stride];
+      simplifiedFlatCoordinates[simplifiedOffset++] =
+          flatCoordinates[offset + i * stride + 1];
+    }
   }
-  return relationship;
+  return simplifiedOffset;
 };
 
 
 /**
- * Create an empty extent.
- * @return {ol.Extent} Empty extent.
- * @api stable
+ * @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.extent.createEmpty = function() {
-  return [Infinity, Infinity, -Infinity, -Infinity];
+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;
 };
 
 
 /**
- * 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.
+ * @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.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];
+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;
 };
 
 
 /**
- * Create a new empty extent or make the provided one empty.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
+ * @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.extent.createOrUpdateEmpty = function(opt_extent) {
-  return ol.extent.createOrUpdate(
-      Infinity, Infinity, -Infinity, -Infinity, opt_extent);
+ol.geom.flat.simplify.radialDistance = function(flatCoordinates, offset, end,
+    stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) {
+  if (end <= offset + stride) {
+    // zero or one point, no simplification possible, so copy and return
+    for (; offset < end; offset += stride) {
+      simplifiedFlatCoordinates[simplifiedOffset++] = flatCoordinates[offset];
+      simplifiedFlatCoordinates[simplifiedOffset++] =
+          flatCoordinates[offset + 1];
+    }
+    return simplifiedOffset;
+  }
+  var x1 = flatCoordinates[offset];
+  var y1 = flatCoordinates[offset + 1];
+  // copy first point
+  simplifiedFlatCoordinates[simplifiedOffset++] = x1;
+  simplifiedFlatCoordinates[simplifiedOffset++] = y1;
+  var x2 = x1;
+  var y2 = y1;
+  for (offset += stride; offset < end; offset += stride) {
+    x2 = flatCoordinates[offset];
+    y2 = flatCoordinates[offset + 1];
+    if (ol.math.squaredDistance(x1, y1, x2, y2) > squaredTolerance) {
+      // copy point at offset
+      simplifiedFlatCoordinates[simplifiedOffset++] = x2;
+      simplifiedFlatCoordinates[simplifiedOffset++] = y2;
+      x1 = x2;
+      y1 = y2;
+    }
+  }
+  if (x2 != x1 || y2 != y1) {
+    // copy last point
+    simplifiedFlatCoordinates[simplifiedOffset++] = x2;
+    simplifiedFlatCoordinates[simplifiedOffset++] = y2;
+  }
+  return simplifiedOffset;
 };
 
 
 /**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
+ * @param {number} value Value.
+ * @param {number} tolerance Tolerance.
+ * @return {number} Rounded value.
  */
-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);
+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 {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
+ * @param {number} tolerance Tolerance.
+ * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
+ *     coordinates.
+ * @param {number} simplifiedOffset Simplified offset.
+ * @return {number} Simplified offset.
  */
-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);
+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.<Array.<ol.Coordinate>>} rings Rings.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
+ * @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.extent.createOrUpdateFromRings = function(rings, opt_extent) {
-  var extent = ol.extent.createOrUpdateEmpty(opt_extent);
-  return ol.extent.extendRings(extent, rings);
+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;
 };
 
 
 /**
- * Empty an extent in place.
- * @param {ol.Extent} extent Extent.
- * @return {ol.Extent} Extent.
+ * @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.extent.empty = function(extent) {
-  extent[0] = extent[1] = Infinity;
-  extent[2] = extent[3] = -Infinity;
-  return extent;
+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');
+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');
+
 
 /**
- * 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
+ * @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
  */
-ol.extent.equals = function(extent1, extent2) {
-  return extent1[0] == extent2[0] && extent1[2] == extent2[2] &&
-      extent1[1] == extent2[1] && extent1[3] == extent2[3];
+ol.geom.LinearRing = function(coordinates, opt_layout) {
+
+  ol.geom.SimpleGeometry.call(this);
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDelta_ = -1;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDeltaRevision_ = -1;
+
+  this.setCoordinates(coordinates, opt_layout);
+
 };
+ol.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry);
 
 
 /**
- * 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
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.LinearRing} Clone.
+ * @override
+ * @api
  */
-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;
+ol.geom.LinearRing.prototype.clone = function() {
+  var linearRing = new ol.geom.LinearRing(null);
+  linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+  return linearRing;
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {ol.Coordinate} coordinate Coordinate.
+ * @inheritDoc
  */
-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];
+ol.geom.LinearRing.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
+  if (minSquaredDistance <
+      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
+    return minSquaredDistance;
   }
-  if (coordinate[1] > extent[3]) {
-    extent[3] = coordinate[1];
+  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);
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @return {ol.Extent} Extent.
+ * Return the area of the linear ring on projected plane.
+ * @return {number} Area (on projected plane).
+ * @api
  */
-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;
+ol.geom.LinearRing.prototype.getArea = function() {
+  return ol.geom.flat.area.linearRing(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
 };
 
 
 /**
- * @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.
+ * Return the coordinates of the linear ring.
+ * @return {Array.<ol.Coordinate>} Coordinates.
+ * @override
+ * @api
  */
-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;
+ol.geom.LinearRing.prototype.getCoordinates = function() {
+  return ol.geom.flat.inflate.coordinates(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {Array.<Array.<ol.Coordinate>>} rings Rings.
- * @return {ol.Extent} Extent.
+ * @inheritDoc
  */
-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;
+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;
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {number} x X.
- * @param {number} y Y.
+ * @inheritDoc
+ * @api
  */
-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);
+ol.geom.LinearRing.prototype.getType = function() {
+  return ol.geom.GeometryType.LINEAR_RING;
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-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;
-};
+ol.geom.LinearRing.prototype.intersectsExtent = function(extent) {};
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @return {number} Area.
+ * Set the coordinates of the linear ring.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @override
+ * @api
  */
-ol.extent.getArea = function(extent) {
-  var area = 0;
-  if (!ol.extent.isEmpty(extent)) {
-    area = ol.extent.getWidth(extent) * ol.extent.getHeight(extent);
+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();
   }
-  return area;
 };
 
 
 /**
- * Get the bottom left coordinate of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {ol.Coordinate} Bottom left coordinate.
- * @api stable
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
  */
-ol.extent.getBottomLeft = function(extent) {
-  return [extent[0], extent[1]];
+ol.geom.LinearRing.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
 };
 
+goog.provide('ol.geom.Point');
+
+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.deflate');
+goog.require('ol.math');
+
 
 /**
- * Get the bottom right coordinate of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {ol.Coordinate} Bottom right coordinate.
- * @api stable
+ * @classdesc
+ * Point geometry.
+ *
+ * @constructor
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {ol.Coordinate} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api
  */
-ol.extent.getBottomRight = function(extent) {
-  return [extent[2], extent[1]];
+ol.geom.Point = function(coordinates, opt_layout) {
+  ol.geom.SimpleGeometry.call(this);
+  this.setCoordinates(coordinates, opt_layout);
 };
+ol.inherits(ol.geom.Point, ol.geom.SimpleGeometry);
 
 
 /**
- * Get the center coordinate of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {ol.Coordinate} Center.
- * @api stable
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.Point} Clone.
+ * @override
+ * @api
  */
-ol.extent.getCenter = function(extent) {
-  return [(extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2];
+ol.geom.Point.prototype.clone = function() {
+  var point = new ol.geom.Point(null);
+  point.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+  return point;
 };
 
 
 /**
- * Get a corner coordinate of an extent.
- * @param {ol.Extent} extent Extent.
- * @param {ol.extent.Corner} corner Corner.
- * @return {ol.Coordinate} Corner coordinate.
+ * @inheritDoc
  */
-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);
+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 {
-    goog.asserts.fail('Invalid corner: %s', corner);
+    return minSquaredDistance;
   }
-  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.
+ * Return the coordinate of the point.
+ * @return {ol.Coordinate} Coordinates.
+ * @override
+ * @api
  */
-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);
+ol.geom.Point.prototype.getCoordinates = function() {
+  return !this.flatCoordinates ? [] : this.flatCoordinates.slice();
 };
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-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);
+ol.geom.Point.prototype.computeExtent = function(extent) {
+  return ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates, extent);
 };
 
 
 /**
- * Get the height of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {number} Height.
- * @api stable
+ * @inheritDoc
+ * @api
  */
-ol.extent.getHeight = function(extent) {
-  return extent[3] - extent[1];
+ol.geom.Point.prototype.getType = function() {
+  return ol.geom.GeometryType.POINT;
 };
 
 
 /**
- * @param {ol.Extent} extent1 Extent 1.
- * @param {ol.Extent} extent2 Extent 2.
- * @return {number} Intersection area.
+ * @inheritDoc
+ * @api
  */
-ol.extent.getIntersectionArea = function(extent1, extent2) {
-  var intersection = ol.extent.getIntersection(extent1, extent2);
-  return ol.extent.getArea(intersection);
+ol.geom.Point.prototype.intersectsExtent = function(extent) {
+  return ol.extent.containsXY(extent,
+      this.flatCoordinates[0], this.flatCoordinates[1]);
 };
 
 
 /**
- * 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
+ * @inheritDoc
+ * @api
  */
-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];
+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();
   }
-  return intersection;
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @return {number} Margin.
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
  */
-ol.extent.getMargin = function(extent) {
-  return ol.extent.getWidth(extent) + ol.extent.getHeight(extent);
+ol.geom.Point.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
 };
 
+goog.provide('ol.geom.flat.contains');
 
-/**
- * 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]];
-};
+goog.require('ol.extent');
 
 
 /**
- * Get the top left coordinate of an 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 {ol.Coordinate} Top left coordinate.
- * @api stable
+ * @return {boolean} Contains extent.
  */
-ol.extent.getTopLeft = function(extent) {
-  return [extent[0], extent[3]];
+ol.geom.flat.contains.linearRingContainsExtent = function(flatCoordinates, offset, end, stride, extent) {
+  var outside = ol.extent.forEachCorner(extent,
+      /**
+       * @param {ol.Coordinate} coordinate Coordinate.
+       * @return {boolean} Contains (x, y).
+       */
+      function(coordinate) {
+        return !ol.geom.flat.contains.linearRingContainsXY(flatCoordinates,
+            offset, end, stride, coordinate[0], coordinate[1]);
+      });
+  return !outside;
 };
 
 
 /**
- * Get the top right coordinate of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {ol.Coordinate} Top right coordinate.
- * @api stable
+ * @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.extent.getTopRight = function(extent) {
-  return [extent[2], extent[3]];
+ol.geom.flat.contains.linearRingContainsXY = function(flatCoordinates, offset, end, stride, x, y) {
+  // http://geomalgorithms.com/a03-_inclusion.html
+  // Copyright 2000 softSurfer, 2012 Dan Sunday
+  // This code may be freely used and modified for any purpose
+  // providing that this copyright notice is included with it.
+  // SoftSurfer makes no warranty for this code, and cannot be held
+  // liable for any real or imagined damage resulting from its use.
+  // Users of this code must verify correctness for their application.
+  var wn = 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];
+    if (y1 <= y) {
+      if (y2 > y && ((x2 - x1) * (y - y1)) - ((x - x1) * (y2 - y1)) > 0) {
+        wn++;
+      }
+    } else if (y2 <= y && ((x2 - x1) * (y - y1)) - ((x - x1) * (y2 - y1)) < 0) {
+      wn--;
+    }
+    x1 = x2;
+    y1 = y2;
+  }
+  return wn !== 0;
 };
 
 
 /**
- * Get the width of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {number} Width.
- * @api stable
+ * @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.extent.getWidth = function(extent) {
-  return extent[2] - extent[0];
+ol.geom.flat.contains.linearRingsContainsXY = function(flatCoordinates, offset, ends, stride, x, y) {
+  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;
 };
 
 
 /**
- * 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
+ * @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.extent.intersects = function(extent1, extent2) {
-  return extent1[0] <= extent2[2] &&
-      extent1[2] >= extent2[0] &&
-      extent1[1] <= extent2[3] &&
-      extent1[3] >= extent2[1];
+ol.geom.flat.contains.linearRingssContainsXY = function(flatCoordinates, offset, endss, stride, x, y) {
+  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');
 
-/**
- * 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];
-};
+goog.require('ol.array');
+goog.require('ol.geom.flat.contains');
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @return {boolean} Is infinite.
+ * 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.extent.isInfinite = function(extent) {
-  return extent[0] == -Infinity || extent[1] == -Infinity ||
-      extent[2] == Infinity || extent[3] == Infinity;
+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 {ol.Extent} extent Extent.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {ol.Coordinate} Coordinate.
+ * @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.extent.normalize = function(extent, coordinate) {
-  return [
-    (coordinate[0] - extent[0]) / (extent[2] - extent[0]),
-    (coordinate[1] - extent[1]) / (extent[3] - extent[1])
-  ];
+ol.geom.flat.interiorpoint.linearRingss = function(flatCoordinates, offset, endss, stride, flatCenters) {
+  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');
+
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
+ * 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.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;
+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('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.
- * @param {number} value Value.
+ * @return {boolean} True if the geometry and the extent intersect.
  */
-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;
+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);
+      });
 };
 
 
 /**
- * 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.
+ * @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.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;
+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 intersects;
+  return false;
 };
 
 
 /**
- * @param {ol.Extent} extent1 Extent 1.
- * @param {ol.Extent} extent2 Extent 2.
- * @return {boolean} Touches.
+ * @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.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]);
+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;
 };
 
 
 /**
- * Apply a transform function to the extent.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
  * @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
+ * @return {boolean} True if the geometry and the extent intersect.
  */
-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);
+ol.geom.flat.intersectsextent.linearRings = function(flatCoordinates, offset, ends, stride, extent) {
+  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;
 };
 
 
 /**
- * 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.
+ * @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.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;
+ol.geom.flat.intersectsextent.linearRingss = function(flatCoordinates, offset, endss, stride, extent) {
+  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;
 };
 
-// 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.
+goog.provide('ol.geom.flat.reverse');
+
 
 /**
- * @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)
+ * @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.provide('goog.functions');
+goog.require('ol.geom.flat.reverse');
 
 
 /**
- * Creates a function that always returns the same value.
- * @param {T} retValue The value to return.
- * @return {function():T} The new function.
- * @template T
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {boolean} Is clockwise.
  */
-goog.functions.constant = function(retValue) {
-  return function() {
-    return retValue;
-  };
+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;
 };
 
 
 /**
- * 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}
+ * 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.
  */
-goog.functions.NULL = goog.functions.constant(null);
+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;
+};
 
 
 /**
- * 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
+ * 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.
  */
-goog.functions.identity = function(opt_returnValue, var_args) {
-  return opt_returnValue;
+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;
 };
 
 
 /**
- * Creates a function that always throws an error with the given message.
- * @param {string} message The error message.
- * @return {!Function} The error-throwing function.
+ * Orient coordinates in a flat array of linear rings.  By default, rings
+ * are oriented following the left-hand rule (clockwise for exterior and
+ * counter-clockwise for interior rings).  To orient according to the
+ * right-hand rule, use the `opt_right` argument.
+ *
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @param {boolean=} opt_right Follow the right-hand rule for orientation.
+ * @return {number} End.
  */
-goog.functions.error = function(message) {
-  return function() {
-    throw Error(message);
-  };
+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;
 };
 
 
 /**
- * Creates a function that throws the given object.
- * @param {*} err An object to be thrown.
- * @return {!Function} The error-throwing function.
+ * Orient coordinates in a flat array of linear rings.  By default, rings
+ * are oriented following the left-hand rule (clockwise for exterior and
+ * counter-clockwise for interior rings).  To orient according to the
+ * right-hand rule, use the `opt_right` argument.
+ *
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Array of array of end indexes.
+ * @param {number} stride Stride.
+ * @param {boolean=} opt_right Follow the right-hand rule for orientation.
+ * @return {number} End.
  */
-goog.functions.fail = function(err) {
-  return function() {
-    throw err;
+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');
 
-/**
- * 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));
-  };
-};
+goog.require('ol');
+goog.require('ol.array');
+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');
+goog.require('ol.math');
 
 
 /**
- * Creates a function that returns its nth argument.
- * @param {number} n The position of the return argument.
- * @return {!Function} A new function.
+ * @classdesc
+ * Polygon geometry.
+ *
+ * @constructor
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {Array.<Array.<ol.Coordinate>>} coordinates Array of linear
+ *     rings that define the polygon. The first linear ring of the array
+ *     defines the outer-boundary or surface of the polygon. Each subsequent
+ *     linear ring defines a hole in the surface of the polygon. A linear ring
+ *     is an array of vertices' coordinates where the first coordinate and the
+ *     last are equivalent.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api
  */
-goog.functions.nth = function(n) {
-  return function() {
-    return arguments[n];
-  };
-};
+ol.geom.Polygon = function(coordinates, opt_layout) {
 
+  ol.geom.SimpleGeometry.call(this);
 
-/**
- * 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));
-};
+  /**
+   * @type {Array.<number>}
+   * @private
+   */
+  this.ends_ = [];
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.flatInteriorPointRevision_ = -1;
 
-/**
- * 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);
-  };
-};
+  /**
+   * @private
+   * @type {ol.Coordinate}
+   */
+  this.flatInteriorPoint_ = null;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDelta_ = -1;
 
-/**
- * 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);
-    }
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDeltaRevision_ = -1;
 
-    for (var i = length - 2; i >= 0; i--) {
-      result = functions[i].call(this, result);
-    }
-    return result;
-  };
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.orientedRevision_ = -1;
 
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.orientedFlatCoordinates_ = null;
+
+  this.setCoordinates(coordinates, opt_layout);
 
-/**
- * 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;
-  };
 };
+ol.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry);
 
 
 /**
- * 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.
+ * Append the passed linear ring to this polygon.
+ * @param {ol.geom.LinearRing} linearRing Linear ring.
+ * @api
  */
-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;
-  };
+ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) {
+  if (!this.flatCoordinates) {
+    this.flatCoordinates = linearRing.getFlatCoordinates().slice();
+  } else {
+    ol.array.extend(this.flatCoordinates, linearRing.getFlatCoordinates());
+  }
+  this.ends_.push(this.flatCoordinates.length);
+  this.changed();
 };
 
 
 /**
- * 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.
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.Polygon} Clone.
+ * @override
+ * @api
  */
-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;
-  };
+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;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.functions.not = function(f) {
-  return function() {
-    return !f.apply(this, arguments);
-  };
+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);
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-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;
+ol.geom.Polygon.prototype.containsXY = function(x, y) {
+  return ol.geom.flat.contains.linearRingsContainsXY(
+      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y);
 };
 
 
 /**
- * @define {boolean} Whether the return value cache should be used.
- *    This should only be used to disable caches when testing.
+ * Return the area of the polygon on projected plane.
+ * @return {number} Area (on projected plane).
+ * @api
  */
-goog.define('goog.functions.CACHE_RETURN_VALUE', true);
+ol.geom.Polygon.prototype.getArea = function() {
+  return ol.geom.flat.area.linearRings(
+      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride);
+};
 
 
 /**
- * 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.
+ * Get the coordinate array for this geometry.  This array has the structure
+ * of a GeoJSON coordinate array for polygons.
  *
- * @param {!function():T} fn A function to lazily evaluate.
- * @return {!function():T} A wrapped version the function.
- * @template T
+ * @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.
+ * @override
+ * @api
  */
-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;
+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);
 };
 
 
 /**
- * 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.
+ * @return {Array.<number>} Ends.
  */
-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();
-    }
-  };
+ol.geom.Polygon.prototype.getEnds = function() {
+  return this.ends_;
 };
 
 
 /**
- * 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
+ * @return {Array.<number>} Interior point.
  */
-goog.functions.debounce = function(f, interval, opt_scope) {
-  if (opt_scope) {
-    f = goog.bind(f, opt_scope);
+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();
   }
-  var timeout = null;
-  return function() {
-    goog.global.clearTimeout(timeout);
-    timeout = goog.global.setTimeout(f, interval);
-  };
+  return this.flatInteriorPoint_;
 };
 
 
 /**
- * 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
+ * Return an interior point of the polygon.
+ * @return {ol.geom.Point} Interior point.
+ * @api
  */
-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;
-    }
-  };
+ol.geom.Polygon.prototype.getInteriorPoint = function() {
+  return new ol.geom.Point(this.getFlatInteriorPoint());
 };
 
-/**
- * @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('goog.math');
-
-
 
 /**
- * @classdesc
- * Class to create objects that can be used with {@link
- * ol.geom.Polygon.circular}.
- *
- * For example to create a sphere whose radius is equal to the semi-major
- * axis of the WGS84 ellipsoid:
- *
- * ```js
- * var wgs84Sphere= new ol.Sphere(6378137);
- * ```
+ * Return the number of rings of the polygon,  this includes the exterior
+ * ring and any interior rings.
  *
- * @constructor
- * @param {number} radius Radius.
+ * @return {number} Number of rings.
  * @api
  */
-ol.Sphere = function(radius) {
-
-  /**
-   * @type {number}
-   */
-  this.radius = radius;
-
+ol.geom.Polygon.prototype.getLinearRingCount = function() {
+  return this.ends_.length;
 };
 
 
 /**
- * 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
+ * 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 {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.
+ * @param {number} index Index.
+ * @return {ol.geom.LinearRing} Linear ring.
  * @api
  */
-ol.Sphere.prototype.geodesicArea = function(coordinates) {
-  var area = 0, len = coordinates.length;
-  var x1 = coordinates[len - 1][0];
-  var y1 = coordinates[len - 1][1];
-  for (var i = 0; i < len; i++) {
-    var x2 = coordinates[i][0], y2 = coordinates[i][1];
-    area += goog.math.toRadians(x2 - x1) *
-        (2 + Math.sin(goog.math.toRadians(y1)) +
-        Math.sin(goog.math.toRadians(y2)));
-    x1 = x2;
-    y1 = y2;
+ol.geom.Polygon.prototype.getLinearRing = function(index) {
+  if (index < 0 || this.ends_.length <= index) {
+    return null;
   }
-  return area * this.radius * this.radius / 2.0;
+  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;
 };
 
 
 /**
- * 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.
+ * Return the linear rings of the polygon.
+ * @return {Array.<ol.geom.LinearRing>} Linear rings.
  * @api
  */
-ol.Sphere.prototype.haversineDistance = function(c1, c2) {
-  var lat1 = goog.math.toRadians(c1[1]);
-  var lat2 = goog.math.toRadians(c2[1]);
-  var deltaLatBy2 = (lat2 - lat1) / 2;
-  var deltaLonBy2 = goog.math.toRadians(c2[0] - c1[0]) / 2;
-  var a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) +
-      Math.sin(deltaLonBy2) * Math.sin(deltaLonBy2) *
-      Math.cos(lat1) * Math.cos(lat2);
-  return 2 * this.radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+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;
 };
 
 
 /**
- * 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.
+ * @return {Array.<number>} Oriented flat coordinates.
  */
-ol.Sphere.prototype.offset = function(c1, distance, bearing) {
-  var lat1 = goog.math.toRadians(c1[1]);
-  var lon1 = goog.math.toRadians(c1[0]);
-  var dByR = distance / this.radius;
-  var lat = Math.asin(
-      Math.sin(lat1) * Math.cos(dByR) +
-      Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing));
-  var lon = lon1 + Math.atan2(
-      Math.sin(bearing) * Math.sin(dByR) * Math.cos(lat1),
-      Math.cos(dByR) - Math.sin(lat1) * Math.sin(lat));
-  return [goog.math.toDegrees(lon), goog.math.toDegrees(lat)];
+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_;
 };
 
-goog.provide('ol.sphere.NORMAL');
-
-goog.require('ol.Sphere');
-
 
 /**
- * The normal sphere.
- * @const
- * @type {ol.Sphere}
+ * @inheritDoc
  */
-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');
+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;
+};
 
 
 /**
- * A projection as {@link ol.proj.Projection}, SRS identifier string or
- * undefined.
- * @typedef {ol.proj.Projection|string|undefined} ol.proj.ProjectionLike
- * @api stable
+ * @inheritDoc
+ * @api
  */
-ol.proj.ProjectionLike;
+ol.geom.Polygon.prototype.getType = function() {
+  return ol.geom.GeometryType.POLYGON;
+};
 
 
 /**
- * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, or `'us-ft'`.
- * @enum {string}
- * @api stable
+ * @inheritDoc
+ * @api
  */
-ol.proj.Units = {
-  DEGREES: 'degrees',
-  FEET: 'ft',
-  METERS: 'm',
-  PIXELS: 'pixels',
-  USFEET: 'us-ft'
+ol.geom.Polygon.prototype.intersectsExtent = function(extent) {
+  return ol.geom.flat.intersectsextent.linearRings(
+      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent);
 };
 
 
 /**
- * Meters per unit lookup table.
- * @const
- * @type {Object.<ol.proj.Units, number>}
- * @api stable
+ * Set the coordinates of the polygon.
+ * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @override
+ * @api
  */
-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;
+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) {
+  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
+ */
+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) {
+    ol.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 arrayLength = stride * (sides + 1);
+  var flatCoordinates = new Array(arrayLength);
+  for (var i = 0; i < arrayLength; i++) {
+    flatCoordinates[i] = 0;
+  }
+  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();
+  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 + (ol.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.require('ol');
+goog.require('ol.CenterConstraint');
+goog.require('ol.Object');
+goog.require('ol.ResolutionConstraint');
+goog.require('ol.RotationConstraint');
+goog.require('ol.ViewHint');
+goog.require('ol.ViewProperty');
+goog.require('ol.array');
+goog.require('ol.asserts');
+goog.require('ol.coordinate');
+goog.require('ol.easing');
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.Polygon');
+goog.require('ol.geom.SimpleGeometry');
+goog.require('ol.obj');
+goog.require('ol.proj');
+goog.require('ol.proj.Units');
 
 
 /**
  * @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.
+ * An ol.View object represents a simple 2D view of the map.
  *
- * You can use {@link ol.proj.get} to retrieve the object for a particular
- * projection.
+ * This is the object to act upon to change the center, resolution,
+ * and rotation of the map.
  *
- * 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
+ * ### The view states
  *
- * If you use proj4js, aliases can be added using `proj4.defs()`; see
- * [documentation](https://github.com/proj4js/proj4js).
+ * 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
- * @param {olx.ProjectionOptions} options Projection options.
- * @struct
- * @api stable
+ * @extends {ol.Object}
+ * @param {olx.ViewOptions=} opt_options View options.
+ * @api
  */
-ol.proj.Projection = function(options) {
+ol.View = function(opt_options) {
+  ol.Object.call(this);
+
+  var options = ol.obj.assign({}, opt_options);
 
   /**
    * @private
-   * @type {string}
+   * @type {Array.<number>}
    */
-  this.code_ = options.code;
+  this.hints_ = [0, 0];
 
   /**
    * @private
-   * @type {ol.proj.Units}
+   * @type {Array.<Array.<ol.ViewAnimation>>}
    */
-  this.units_ = /** @type {ol.proj.Units} */ (options.units);
+  this.animations_ = [];
 
   /**
    * @private
-   * @type {ol.Extent}
+   * @type {number|undefined}
    */
-  this.extent_ = options.extent !== undefined ? options.extent : null;
+  this.updateAnimationKey_;
+
+  this.updateAnimations_ = this.updateAnimations_.bind(this);
 
   /**
    * @private
-   * @type {ol.Extent}
+   * @const
+   * @type {ol.proj.Projection}
    */
-  this.worldExtent_ = options.worldExtent !== undefined ?
-      options.worldExtent : null;
+  this.projection_ = ol.proj.createProjection(options.projection, 'EPSG:3857');
+
+  this.applyOptions_(options);
+};
+ol.inherits(ol.View, ol.Object);
+
+
+/**
+ * Set up the view with the given options.
+ * @param {olx.ViewOptions} options View options.
+ */
+ol.View.prototype.applyOptions_ = function(options) {
+
+  /**
+   * @type {Object.<string, *>}
+   */
+  var properties = {};
+  properties[ol.ViewProperty.CENTER] = options.center !== undefined ?
+      options.center : null;
+
+  var resolutionConstraintInfo = ol.View.createResolutionConstraint_(
+      options);
 
   /**
    * @private
-   * @type {string}
+   * @type {number}
    */
-  this.axisOrientation_ = options.axisOrientation !== undefined ?
-      options.axisOrientation : 'enu';
+  this.maxResolution_ = resolutionConstraintInfo.maxResolution;
 
   /**
    * @private
-   * @type {boolean}
+   * @type {number}
    */
-  this.global_ = options.global !== undefined ? options.global : false;
+  this.minResolution_ = resolutionConstraintInfo.minResolution;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.zoomFactor_ = resolutionConstraintInfo.zoomFactor;
 
   /**
    * @private
-   * @type {boolean}
+   * @type {Array.<number>|undefined}
    */
-  this.canWrapX_ = !!(this.global_ && this.extent_);
+  this.resolutions_ = options.resolutions;
 
   /**
-  * @private
-  * @type {function(number, ol.Coordinate):number}
-  */
-  this.getPointResolutionFunc_ = options.getPointResolution !== undefined ?
-      options.getPointResolution : this.getPointResolution_;
+   * @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.tilegrid.TileGrid}
+   * @type {ol.Constraints}
    */
-  this.defaultTileGrid_ = null;
+  this.constraints_ = {
+    center: centerConstraint,
+    resolution: resolutionConstraint,
+    rotation: rotationConstraint
+  };
 
-  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);
-          }
-        }
-      }
-    }
+  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);
 
-};
+  /**
+   * @private
+   * @type {olx.ViewOptions}
+   */
+  this.options_ = options;
 
+};
 
 /**
- * @return {boolean} The projection is suitable for wrapping the x-axis
+ * Get an updated version of the view options used to construct the view.  The
+ * current resolution (or zoom), center, and rotation are applied to any stored
+ * options.  The provided options can be uesd to apply new min/max zoom or
+ * resolution limits.
+ * @param {olx.ViewOptions} newOptions New options to be applied.
+ * @return {olx.ViewOptions} New options updated with the current view state.
  */
-ol.proj.Projection.prototype.canWrapX = function() {
-  return this.canWrapX_;
-};
+ol.View.prototype.getUpdatedOptions_ = function(newOptions) {
+  var options = ol.obj.assign({}, this.options_);
+
+  // preserve resolution (or zoom)
+  if (options.resolution !== undefined) {
+    options.resolution = this.getResolution();
+  } else {
+    options.zoom = this.getZoom();
+  }
 
+  // preserve center
+  options.center = this.getCenter();
 
-/**
- * Get the code for this projection, e.g. 'EPSG:4326'.
- * @return {string} Code.
- * @api stable
- */
-ol.proj.Projection.prototype.getCode = function() {
-  return this.code_;
+  // preserve rotation
+  options.rotation = this.getRotation();
+
+  return ol.obj.assign({}, options, newOptions);
 };
 
 
 /**
- * Get the validity extent for this projection.
- * @return {ol.Extent} Extent.
- * @api stable
+ * Animate the view.  The view's center, zoom (or resolution), and rotation
+ * can be animated for smooth transitions between view states.  For example,
+ * to animate the view to a new zoom level:
+ *
+ *     view.animate({zoom: view.getZoom() + 1});
+ *
+ * By default, the animation lasts one second and uses in-and-out easing.  You
+ * can customize this behavior by including `duration` (in milliseconds) and
+ * `easing` options (see {@link ol.easing}).
+ *
+ * To chain together multiple animations, call the method with multiple
+ * animation objects.  For example, to first zoom and then pan:
+ *
+ *     view.animate({zoom: 10}, {center: [0, 0]});
+ *
+ * If you provide a function as the last argument to the animate method, it
+ * will get called at the end of an animation series.  The callback will be
+ * called with `true` if the animation series completed on its own or `false`
+ * if it was cancelled.
+ *
+ * Animations are cancelled by user interactions (e.g. dragging the map) or by
+ * calling `view.setCenter()`, `view.setResolution()`, or `view.setRotation()`
+ * (or another method that calls one of these).
+ *
+ * @param {...(olx.AnimationOptions|function(boolean))} var_args Animation
+ *     options.  Multiple animations can be run in series by passing multiple
+ *     options objects.  To run multiple animations in parallel, call the method
+ *     multiple times.  An optional callback can be provided as a final
+ *     argument.  The callback will be called with a boolean indicating whether
+ *     the animation completed without being cancelled.
+ * @api
  */
-ol.proj.Projection.prototype.getExtent = function() {
-  return this.extent_;
+ol.View.prototype.animate = function(var_args) {
+  var start = Date.now();
+  var center = this.getCenter().slice();
+  var resolution = this.getResolution();
+  var rotation = this.getRotation();
+  var animationCount = arguments.length;
+  var callback;
+  if (animationCount > 1 && typeof arguments[animationCount - 1] === 'function') {
+    callback = arguments[animationCount - 1];
+    --animationCount;
+  }
+  var series = [];
+  for (var i = 0; i < animationCount; ++i) {
+    var options = /** @type {olx.AnimationOptions} */ (arguments[i]);
+
+    var animation = /** @type {ol.ViewAnimation} */ ({
+      start: start,
+      complete: false,
+      anchor: options.anchor,
+      duration: options.duration !== undefined ? options.duration : 1000,
+      easing: options.easing || ol.easing.inAndOut
+    });
+
+    if (options.center) {
+      animation.sourceCenter = center;
+      animation.targetCenter = options.center;
+      center = animation.targetCenter;
+    }
+
+    if (options.zoom !== undefined) {
+      animation.sourceResolution = resolution;
+      animation.targetResolution = this.constrainResolution(
+            this.maxResolution_, options.zoom - this.minZoom_, 0);
+      resolution = animation.targetResolution;
+    } else if (options.resolution) {
+      animation.sourceResolution = resolution;
+      animation.targetResolution = options.resolution;
+      resolution = animation.targetResolution;
+    }
+
+    if (options.rotation !== undefined) {
+      animation.sourceRotation = rotation;
+      animation.targetRotation = options.rotation;
+      rotation = animation.targetRotation;
+    }
+
+    animation.callback = callback;
+    start += animation.duration;
+    series.push(animation);
+  }
+  this.animations_.push(series);
+  this.setHint(ol.ViewHint.ANIMATING, 1);
+  this.updateAnimations_();
 };
 
 
 /**
- * Get the units of this projection.
- * @return {ol.proj.Units} Units.
- * @api stable
+ * Determine if the view is being animated.
+ * @return {boolean} The view is being animated.
+ * @api
  */
-ol.proj.Projection.prototype.getUnits = function() {
-  return this.units_;
+ol.View.prototype.getAnimating = function() {
+  return this.getHints()[ol.ViewHint.ANIMATING] > 0;
 };
 
 
 /**
- * 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
+ * Determine if the user is interacting with the view, such as panning or zooming.
+ * @return {boolean} The view is being interacted with.
+ * @api
  */
-ol.proj.Projection.prototype.getMetersPerUnit = function() {
-  return ol.proj.METERS_PER_UNIT[this.units_];
+ol.View.prototype.getInteracting = function() {
+  return this.getHints()[ol.ViewHint.INTERACTING] > 0;
 };
 
 
 /**
- * Get the world extent for this projection.
- * @return {ol.Extent} Extent.
+ * Cancel any ongoing animations.
  * @api
  */
-ol.proj.Projection.prototype.getWorldExtent = function() {
-  return this.worldExtent_;
+ol.View.prototype.cancelAnimations = function() {
+  this.setHint(ol.ViewHint.ANIMATING, -this.getHints()[ol.ViewHint.ANIMATING]);
+  for (var i = 0, ii = this.animations_.length; i < ii; ++i) {
+    var series = this.animations_[i];
+    if (series[0].callback) {
+      series[0].callback(false);
+    }
+  }
+  this.animations_.length = 0;
 };
 
+/**
+ * Update all animations.
+ */
+ol.View.prototype.updateAnimations_ = function() {
+  if (this.updateAnimationKey_ !== undefined) {
+    cancelAnimationFrame(this.updateAnimationKey_);
+    this.updateAnimationKey_ = undefined;
+  }
+  if (!this.getAnimating()) {
+    return;
+  }
+  var now = Date.now();
+  var more = false;
+  for (var i = this.animations_.length - 1; i >= 0; --i) {
+    var series = this.animations_[i];
+    var seriesComplete = true;
+    for (var j = 0, jj = series.length; j < jj; ++j) {
+      var animation = series[j];
+      if (animation.complete) {
+        continue;
+      }
+      var elapsed = now - animation.start;
+      var fraction = animation.duration > 0 ? elapsed / animation.duration : 1;
+      if (fraction >= 1) {
+        animation.complete = true;
+        fraction = 1;
+      } else {
+        seriesComplete = false;
+      }
+      var progress = animation.easing(fraction);
+      if (animation.sourceCenter) {
+        var x0 = animation.sourceCenter[0];
+        var y0 = animation.sourceCenter[1];
+        var x1 = animation.targetCenter[0];
+        var y1 = animation.targetCenter[1];
+        var x = x0 + progress * (x1 - x0);
+        var y = y0 + progress * (y1 - y0);
+        this.set(ol.ViewProperty.CENTER, [x, y]);
+      }
+      if (animation.sourceResolution && animation.targetResolution) {
+        var resolution = progress === 1 ?
+            animation.targetResolution :
+            animation.sourceResolution + progress * (animation.targetResolution - animation.sourceResolution);
+        if (animation.anchor) {
+          this.set(ol.ViewProperty.CENTER,
+              this.calculateCenterZoom(resolution, animation.anchor));
+        }
+        this.set(ol.ViewProperty.RESOLUTION, resolution);
+      }
+      if (animation.sourceRotation !== undefined && animation.targetRotation !== undefined) {
+        var rotation = progress === 1 ?
+            animation.targetRotation :
+            animation.sourceRotation + progress * (animation.targetRotation - animation.sourceRotation);
+        if (animation.anchor) {
+          this.set(ol.ViewProperty.CENTER,
+              this.calculateCenterRotate(rotation, animation.anchor));
+        }
+        this.set(ol.ViewProperty.ROTATION, rotation);
+      }
+      more = true;
+      if (!animation.complete) {
+        break;
+      }
+    }
+    if (seriesComplete) {
+      this.animations_[i] = null;
+      this.setHint(ol.ViewHint.ANIMATING, -1);
+      var callback = series[0].callback;
+      if (callback) {
+        callback(true);
+      }
+    }
+  }
+  // prune completed series
+  this.animations_ = this.animations_.filter(Boolean);
+  if (more && this.updateAnimationKey_ === undefined) {
+    this.updateAnimationKey_ = requestAnimationFrame(this.updateAnimations_);
+  }
+};
 
 /**
- * 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.
+ * @param {number} rotation Target rotation.
+ * @param {ol.Coordinate} anchor Rotation anchor.
+ * @return {ol.Coordinate|undefined} Center for rotation and anchor.
  */
-ol.proj.Projection.prototype.getAxisOrientation = function() {
-  return this.axisOrientation_;
+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;
 };
 
 
 /**
- * Is this projection a global projection which spans the whole world?
- * @return {boolean} Whether the projection is global.
- * @api stable
+ * @param {number} resolution Target resolution.
+ * @param {ol.Coordinate} anchor Zoom anchor.
+ * @return {ol.Coordinate|undefined} Center for resolution and anchor.
  */
-ol.proj.Projection.prototype.isGlobal = function() {
-  return this.global_;
+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;
 };
 
 
 /**
-* 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_);
+ * @private
+ * @return {ol.Size} Viewport size or `[100, 100]` when no viewport is found.
+ */
+ol.View.prototype.getSizeFromViewport_ = function() {
+  var size = [100, 100];
+  var selector = '.ol-viewport[data-view="' + ol.getUid(this) + '"]';
+  var element = document.querySelector(selector);
+  if (element) {
+    var metrics = getComputedStyle(element);
+    size[0] = parseInt(metrics.width, 10);
+    size[1] = parseInt(metrics.height, 10);
+  }
+  return size;
 };
 
 
 /**
- * @return {ol.tilegrid.TileGrid} The default tile grid.
+ * Get the constrained center of this view.
+ * @param {ol.Coordinate|undefined} center Center.
+ * @return {ol.Coordinate|undefined} Constrained center.
+ * @api
  */
-ol.proj.Projection.prototype.getDefaultTileGrid = function() {
-  return this.defaultTileGrid_;
+ol.View.prototype.constrainCenter = function(center) {
+  return this.constraints_.center(center);
 };
 
 
 /**
- * @param {ol.tilegrid.TileGrid} tileGrid The default tile grid.
+ * 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.proj.Projection.prototype.setDefaultTileGrid = function(tileGrid) {
-  this.defaultTileGrid_ = tileGrid;
+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);
 };
 
 
 /**
- * Set the validity extent for this projection.
- * @param {ol.Extent} extent Extent.
- * @api stable
+ * 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.proj.Projection.prototype.setExtent = function(extent) {
-  this.extent_ = extent;
-  this.canWrapX_ = !!(this.global_ && extent);
+ol.View.prototype.constrainRotation = function(rotation, opt_delta) {
+  var delta = opt_delta || 0;
+  return this.constraints_.rotation(rotation, delta);
 };
 
 
 /**
- * Set the world extent for this projection.
- * @param {ol.Extent} worldExtent World extent
- *     [minlon, minlat, maxlon, maxlat].
+ * Get the view center.
+ * @return {ol.Coordinate|undefined} The center of the view.
+ * @observable
  * @api
  */
-ol.proj.Projection.prototype.setWorldExtent = function(worldExtent) {
-  this.worldExtent_ = worldExtent;
+ol.View.prototype.getCenter = function() {
+  return /** @type {ol.Coordinate|undefined} */ (
+      this.get(ol.ViewProperty.CENTER));
 };
 
 
 /**
-* 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;
+ * @return {ol.Constraints} Constraints.
+ */
+ol.View.prototype.getConstraints = function() {
+  return this.constraints_;
 };
 
 
 /**
-* 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;
+ * @param {Array.<number>=} opt_hints Destination array.
+ * @return {Array.<number>} Hint.
+ */
+ol.View.prototype.getHints = function(opt_hints) {
+  if (opt_hints !== undefined) {
+    opt_hints[0] = this.hints_[0];
+    opt_hints[1] = this.hints_[1];
+    return opt_hints;
   } 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;
+    return this.hints_.slice();
   }
 };
 
 
 /**
- * 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.
+ * 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=} opt_size Box pixel size. If not provided, the size of the
+ * first map that uses this view will be used.
+ * @return {ol.Extent} Extent.
  * @api
  */
-ol.proj.Projection.prototype.getPointResolution = function(resolution, point) {
-  return this.getPointResolutionFunc_(resolution, point);
+ol.View.prototype.calculateExtent = function(opt_size) {
+  var size = opt_size || this.getSizeFromViewport_();
+  var center = /** @type {!ol.Coordinate} */ (this.getCenter());
+  ol.asserts.assert(center, 1); // The view center is not defined
+  var resolution = /** @type {!number} */ (this.getResolution());
+  ol.asserts.assert(resolution !== undefined, 2); // The view resolution is not defined
+  var rotation = /** @type {!number} */ (this.getRotation());
+  ol.asserts.assert(rotation !== undefined, 3); // The view rotation is not defined
+
+  return ol.extent.getForViewAndSize(center, resolution, rotation, size);
 };
 
 
 /**
- * @private
- * @type {Object.<string, ol.proj.Projection>}
+ * Get the maximum resolution of the view.
+ * @return {number} The maximum resolution of the view.
+ * @api
  */
-ol.proj.projections_ = {};
+ol.View.prototype.getMaxResolution = function() {
+  return this.maxResolution_;
+};
 
 
 /**
- * @private
- * @type {Object.<string, Object.<string, ol.TransformFunction>>}
+ * Get the minimum resolution of the view.
+ * @return {number} The minimum resolution of the view.
+ * @api
  */
-ol.proj.transforms_ = {};
+ol.View.prototype.getMinResolution = function() {
+  return this.minResolution_;
+};
 
 
 /**
- * Registers transformation functions that don't alter coordinates. Those allow
- * to transform between projections with equal meaning.
- *
- * @param {Array.<ol.proj.Projection>} projections Projections.
+ * Get the maximum zoom level for the view.
+ * @return {number} The maximum zoom level.
  * @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);
-      }
-    });
-  });
+ol.View.prototype.getMaxZoom = function() {
+  return /** @type {number} */ (this.getZoomForResolution(this.minResolution_));
 };
 
 
 /**
- * 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..
+ * Set a new maximum zoom level for the view.
+ * @param {number} zoom The maximum zoom level.
+ * @api
  */
-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);
-    });
-  });
+ol.View.prototype.setMaxZoom = function(zoom) {
+  this.applyOptions_(this.getUpdatedOptions_({maxZoom: zoom}));
 };
 
 
 /**
- * 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
+ * Get the minimum zoom level for the view.
+ * @return {number} The minimum zoom level.
+ * @api
  */
-ol.proj.addProjection = function(projection) {
-  ol.proj.projections_[projection.getCode()] = projection;
-  ol.proj.addTransform(projection, projection, ol.proj.cloneTransform);
+ol.View.prototype.getMinZoom = function() {
+  return /** @type {number} */ (this.getZoomForResolution(this.maxResolution_));
 };
 
 
 /**
- * @param {Array.<ol.proj.Projection>} projections Projections.
+ * Set a new minimum zoom level for the view.
+ * @param {number} zoom The minimum zoom level.
+ * @api
  */
-ol.proj.addProjections = function(projections) {
-  var addedProjections = [];
-  projections.forEach(function(projection) {
-    addedProjections.push(ol.proj.addProjection(projection));
-  });
+ol.View.prototype.setMinZoom = function(zoom) {
+  this.applyOptions_(this.getUpdatedOptions_({minZoom: zoom}));
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * Get the view projection.
+ * @return {ol.proj.Projection} The projection of the view.
+ * @api
  */
-ol.proj.clearAllProjections = function() {
-  ol.proj.projections_ = {};
-  ol.proj.transforms_ = {};
+ol.View.prototype.getProjection = function() {
+  return this.projection_;
 };
 
 
 /**
- * @param {ol.proj.Projection|string|undefined} projection Projection.
- * @param {string} defaultCode Default code.
- * @return {ol.proj.Projection} Projection.
+ * Get the view resolution.
+ * @return {number|undefined} The resolution of the view.
+ * @observable
+ * @api
  */
-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;
-  }
+ol.View.prototype.getResolution = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.ViewProperty.RESOLUTION));
 };
 
 
 /**
- * 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.
+ * Get the resolutions for the view. This returns the array of resolutions
+ * passed to the constructor of the {ol.View}, or undefined if none were given.
+ * @return {Array.<number>|undefined} The resolutions of the view.
+ * @api
  */
-ol.proj.addTransform = function(source, destination, transformFn) {
-  var sourceCode = source.getCode();
-  var destinationCode = destination.getCode();
-  var transforms = ol.proj.transforms_;
-  if (!goog.object.containsKey(transforms, sourceCode)) {
-    transforms[sourceCode] = {};
-  }
-  transforms[sourceCode][destinationCode] = transformFn;
+ol.View.prototype.getResolutions = function() {
+  return this.resolutions_;
 };
 
 
 /**
- * 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
+ * Get the resolution for a provided extent (in map units) and size (in pixels).
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.Size=} opt_size Box pixel size.
+ * @return {number} The resolution at which the provided extent will render at
+ *     the given size.
+ * @api
  */
-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));
+ol.View.prototype.getResolutionForExtent = function(extent, opt_size) {
+  var size = opt_size || this.getSizeFromViewport_();
+  var xResolution = ol.extent.getWidth(extent) / size[0];
+  var yResolution = ol.extent.getHeight(extent) / size[1];
+  return Math.max(xResolution, yResolution);
 };
 
 
 /**
- * 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.
+ * 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.proj.createTransformFromCoordinateTransform = function(transform) {
+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 {Array.<number>} input Input.
-       * @param {Array.<number>=} opt_output Output.
-       * @param {number=} opt_dimension Dimension.
-       * @return {Array.<number>} Output.
+       * @param {number} value Value.
+       * @return {number} Resolution.
        */
-      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;
+      function(value) {
+        var resolution = maxResolution / Math.pow(power, value * max);
+        return resolution;
       });
 };
 
 
 /**
- * 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.
+ * Get the view rotation.
+ * @return {number} The rotation of the view in radians.
+ * @observable
+ * @api
  */
-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;
+ol.View.prototype.getRotation = function() {
+  return /** @type {number} */ (this.get(ol.ViewProperty.ROTATION));
 };
 
 
 /**
- * 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
+ * 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.proj.fromLonLat = function(coordinate, opt_projection) {
-  return ol.proj.transform(coordinate, 'EPSG:4326',
-      opt_projection !== undefined ? opt_projection : 'EPSG:3857');
+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;
+        return value;
+      });
 };
 
 
 /**
- * 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
+ * @return {olx.ViewState} View state.
  */
-ol.proj.toLonLat = function(coordinate, opt_projection) {
-  return ol.proj.transform(coordinate,
-      opt_projection !== undefined ? opt_projection : 'EPSG:3857', 'EPSG:4326');
+ol.View.prototype.getState = function() {
+  var center = /** @type {ol.Coordinate} */ (this.getCenter());
+  var projection = this.getProjection();
+  var resolution = /** @type {number} */ (this.getResolution());
+  var rotation = this.getRotation();
+  return /** @type {olx.ViewState} */ ({
+    center: center.slice(),
+    projection: projection !== undefined ? projection : null,
+    resolution: resolution,
+    rotation: rotation
+  });
 };
 
 
 /**
- * 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
+ * Get the current zoom level. Return undefined if the current
+ * resolution is undefined or not within the "resolution constraints".
+ * @return {number|undefined} Zoom.
+ * @api
  */
-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);
+ol.View.prototype.getZoom = function() {
+  var zoom;
+  var resolution = this.getResolution();
+  if (resolution !== undefined) {
+    zoom = this.getZoomForResolution(resolution);
+  }
+  return zoom;
+};
+
+
+/**
+ * Get the zoom level for a resolution.
+ * @param {number} resolution The resolution.
+ * @return {number|undefined} The zoom level for the provided resolution.
+ * @api
+ */
+ol.View.prototype.getZoomForResolution = function(resolution) {
+  var zoom;
+  if (resolution >= this.minResolution_ && resolution <= this.maxResolution_) {
+    var offset = this.minZoom_ || 0;
+    var max, zoomFactor;
+    if (this.resolutions_) {
+      var nearest = ol.array.linearFindNearest(this.resolutions_, resolution, 1);
+      offset += nearest;
+      if (nearest == this.resolutions_.length - 1) {
+        return offset;
+      }
+      max = this.resolutions_[nearest];
+      zoomFactor = max / this.resolutions_[nearest + 1];
+    } else {
+      max = this.maxResolution_;
+      zoomFactor = this.zoomFactor_;
     }
-  } else {
-    projection = null;
+    zoom = offset + Math.log(max / resolution) / Math.log(zoomFactor);
   }
-  return projection;
+  return zoom;
 };
 
 
 /**
- * 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.
+ * 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} geometryOrExtent The geometry or
+ *     extent to fit the view to.
+ * @param {olx.view.FitOptions=} opt_options Options.
+ * @api
  */
-ol.proj.equivalent = function(projection1, projection2) {
-  if (projection1 === projection2) {
-    return true;
-  } else if (projection1.getCode() === projection2.getCode()) {
-    return true;
-  } else if (projection1.getUnits() != projection2.getUnits()) {
-    return false;
+ol.View.prototype.fit = function(geometryOrExtent, opt_options) {
+  var options = opt_options || {};
+  var size = options.size;
+  if (!size) {
+    size = this.getSizeFromViewport_();
+  }
+  /** @type {ol.geom.SimpleGeometry} */
+  var geometry;
+  if (!(geometryOrExtent instanceof ol.geom.SimpleGeometry)) {
+    ol.asserts.assert(Array.isArray(geometryOrExtent),
+        24); // Invalid extent or geometry provided as `geometry`
+    ol.asserts.assert(!ol.extent.isEmpty(geometryOrExtent),
+        25); // Cannot fit empty extent provided as `geometry`
+    geometry = ol.geom.Polygon.fromExtent(geometryOrExtent);
+  } else if (geometryOrExtent.getType() === ol.geom.GeometryType.CIRCLE) {
+    geometryOrExtent = geometryOrExtent.getExtent();
+    geometry = ol.geom.Polygon.fromExtent(geometryOrExtent);
+    geometry.rotate(this.getRotation(), ol.extent.getCenter(geometryOrExtent));
   } else {
-    var transformFn = ol.proj.getTransformFromProjections(
-        projection1, projection2);
-    return transformFn === ol.proj.cloneTransform;
+    geometry = geometryOrExtent;
+  }
+
+  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();
+  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;
+  }
+
+  // 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;
+  var center = [centerX, centerY];
+  var callback = options.callback ? options.callback : ol.nullFunction;
+
+  if (options.duration !== undefined) {
+    this.animate({
+      resolution: resolution,
+      center: center,
+      duration: options.duration,
+      easing: options.easing
+    }, callback);
+  } else {
+    this.setResolution(resolution);
+    this.setCenter(center);
+    setTimeout(callback.bind(undefined, true), 0);
   }
 };
 
 
 /**
- * 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
+ * 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.proj.getTransform = function(source, destination) {
-  var sourceProjection = ol.proj.get(source);
-  var destinationProjection = ol.proj.get(destination);
-  return ol.proj.getTransformFromProjections(
-      sourceProjection, destinationProjection);
+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]);
 };
 
 
 /**
- * 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.
+ * @return {boolean} Is defined.
  */
-ol.proj.getTransformFromProjections =
-    function(sourceProjection, destinationProjection) {
-  var transforms = ol.proj.transforms_;
-  var sourceCode = sourceProjection.getCode();
-  var destinationCode = destinationProjection.getCode();
-  var transform;
-  if (goog.object.containsKey(transforms, sourceCode) &&
-      goog.object.containsKey(transforms[sourceCode], destinationCode)) {
-    transform = transforms[sourceCode][destinationCode];
-  }
-  if (transform === undefined) {
-    goog.asserts.assert(transform !== undefined, 'transform should be defined');
-    transform = ol.proj.identityTransform;
-  }
-  return transform;
+ol.View.prototype.isDef = function() {
+  return !!this.getCenter() && this.getResolution() !== undefined;
 };
 
 
 /**
- * @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).
+ * 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
  */
-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;
+ol.View.prototype.rotate = function(rotation, opt_anchor) {
+  if (opt_anchor !== undefined) {
+    var center = this.calculateCenterRotate(rotation, opt_anchor);
+    this.setCenter(center);
   }
-  return input;
+  this.setRotation(rotation);
 };
 
 
 /**
- * @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).
+ * Set the center of the current view.
+ * @param {ol.Coordinate|undefined} center The center of the view.
+ * @observable
+ * @api
  */
-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();
+ol.View.prototype.setCenter = function(center) {
+  this.set(ol.ViewProperty.CENTER, center);
+  if (this.getAnimating()) {
+    this.cancelAnimations();
   }
-  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
+ * @param {ol.ViewHint} hint Hint.
+ * @param {number} delta Delta.
+ * @return {number} New value.
  */
-ol.proj.transform = function(coordinate, source, destination) {
-  var transformFn = ol.proj.getTransform(source, destination);
-  return transformFn(coordinate, undefined, coordinate.length);
+ol.View.prototype.setHint = function(hint, delta) {
+  this.hints_[hint] += delta;
+  this.changed();
+  return this.hints_[hint];
 };
 
 
 /**
- * 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
+ * Set the resolution for this view.
+ * @param {number|undefined} resolution The resolution of the view.
+ * @observable
+ * @api
  */
-ol.proj.transformExtent = function(extent, source, destination) {
-  var transformFn = ol.proj.getTransform(source, destination);
-  return ol.extent.applyTransform(extent, transformFn);
+ol.View.prototype.setResolution = function(resolution) {
+  this.set(ol.ViewProperty.RESOLUTION, resolution);
+  if (this.getAnimating()) {
+    this.cancelAnimations();
+  }
 };
 
 
 /**
- * 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.
+ * Set the rotation for this view.
+ * @param {number} rotation The rotation of the view in radians.
+ * @observable
+ * @api
  */
-ol.proj.transformWithProjections =
-    function(point, sourceProjection, destinationProjection) {
-  var transformFn = ol.proj.getTransformFromProjections(
-      sourceProjection, destinationProjection);
-  return transformFn(point);
+ol.View.prototype.setRotation = function(rotation) {
+  this.set(ol.ViewProperty.ROTATION, rotation);
+  if (this.getAnimating()) {
+    this.cancelAnimations();
+  }
 };
 
-goog.provide('ol.geom.Geometry');
-goog.provide('ol.geom.GeometryLayout');
-goog.provide('ol.geom.GeometryType');
-
-goog.require('goog.functions');
-goog.require('ol.Object');
-goog.require('ol.extent');
-goog.require('ol.proj');
-
 
 /**
- * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`,
- * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`,
- * `'GeometryCollection'`, `'Circle'`.
- * @enum {string}
- * @api stable
+ * Zoom to a specific zoom level.
+ * @param {number} zoom Zoom level.
+ * @api
  */
-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'
+ol.View.prototype.setZoom = function(zoom) {
+  var resolution = this.constrainResolution(
+      this.maxResolution_, zoom - this.minZoom_, 0);
+  this.setResolution(resolution);
 };
 
 
 /**
- * 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
+ * @param {olx.ViewOptions} options View options.
+ * @private
+ * @return {ol.CenterConstraintType} The constraint.
  */
-ol.geom.GeometryLayout = {
-  XY: 'XY',
-  XYZ: 'XYZ',
-  XYM: 'XYM',
-  XYZM: 'XYZM'
+ol.View.createCenterConstraint_ = function(options) {
+  if (options.extent !== undefined) {
+    return ol.CenterConstraint.createExtent(options.extent);
+  } else {
+    return ol.CenterConstraint.none;
+  }
 };
 
 
-
 /**
- * @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
+ * @private
+ * @param {olx.ViewOptions} options View options.
+ * @return {{constraint: ol.ResolutionConstraintType, maxResolution: number,
+ *     minResolution: number, zoomFactor: number}} The constraint.
  */
-ol.geom.Geometry = function() {
-
-  goog.base(this);
+ol.View.createResolutionConstraint_ = function(options) {
+  var resolutionConstraint;
+  var maxResolution;
+  var minResolution;
 
-  /**
+  // TODO: move these to be ol constants
+  // see https://github.com/openlayers/openlayers/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] /
+            projection.getMetersPerUnit() :
+        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, zoomFactor: zoomFactor};
+};
+
+
+/**
+ * @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 (typeof constrainRotation === 'number') {
+      return ol.RotationConstraint.createSnapToN(constrainRotation);
+    } else {
+      return ol.RotationConstraint.none;
+    }
+  } else {
+    return ol.RotationConstraint.disable;
+  }
+};
+
+goog.provide('ol.Kinetic');
+
+
+/**
+ * @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 {ol.Extent}
+   * @type {number}
    */
-  this.extent_ = ol.extent.createEmpty();
+  this.decay_ = decay;
 
   /**
    * @private
    * @type {number}
    */
-  this.extentRevision_ = -1;
+  this.minVelocity_ = minVelocity;
 
   /**
-   * @protected
-   * @type {Object.<string, ol.geom.Geometry>}
+   * @private
+   * @type {number}
    */
-  this.simplifiedGeometryCache = {};
+  this.delay_ = delay;
 
   /**
-   * @protected
-   * @type {number}
+   * @private
+   * @type {Array.<number>}
    */
-  this.simplifiedGeometryMaxMinSquaredTolerance = 0;
+  this.points_ = [];
 
   /**
-   * @protected
+   * @private
    * @type {number}
    */
-  this.simplifiedGeometryRevision = 0;
+  this.angle_ = 0;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.initialVelocity_ = 0;
 };
-goog.inherits(ol.geom.Geometry, ol.Object);
 
 
 /**
- * Make a complete copy of the geometry.
- * @function
- * @return {!ol.geom.Geometry} Clone.
+ * FIXME empty description for jsdoc
  */
-ol.geom.Geometry.prototype.clone = goog.abstractMethod;
+ol.Kinetic.prototype.begin = function() {
+  this.points_.length = 0;
+  this.angle_ = 0;
+  this.initialVelocity_ = 0;
+};
 
 
 /**
  * @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;
+ol.Kinetic.prototype.update = function(x, y) {
+  this.points_.push(x, y, Date.now());
+};
 
 
 /**
- * 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
+ * @return {boolean} Whether we should do kinetic animation.
  */
-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;
+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];
+  // we don't want a duration of 0 (divide by zero)
+  // we also make sure the user panned for a duration of at least one frame
+  // (1/60s) to compute sane displacement values
+  if (duration < 1000 / 60) {
+    return false;
+  }
+
+  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} coordinate Coordinate.
- * @return {boolean} Contains coordinate.
+ * @return {number} Total distance travelled (pixels).
  */
-ol.geom.Geometry.prototype.containsCoordinate = function(coordinate) {
-  return this.containsXY(coordinate[0], coordinate[1]);
+ol.Kinetic.prototype.getDistance = function() {
+  return (this.minVelocity_ - this.initialVelocity_) / this.decay_;
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @protected
- * @return {ol.Extent} extent Extent.
+ * @return {number} Angle of the kinetic panning animation (radians).
  */
-ol.geom.Geometry.prototype.computeExtent = goog.abstractMethod;
+ol.Kinetic.prototype.getAngle = function() {
+  return this.angle_;
+};
 
+goog.provide('ol.interaction.Property');
 
 /**
- * @param {number} x X.
- * @param {number} y Y.
- * @return {boolean} Contains (x, y).
+ * @enum {string}
  */
-ol.geom.Geometry.prototype.containsXY = goog.functions.FALSE;
+ol.interaction.Property = {
+  ACTIVE: 'active'
+};
+
+// FIXME factor out key precondition (shift et. al)
+
+goog.provide('ol.interaction.Interaction');
+
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.easing');
+goog.require('ol.interaction.Property');
 
 
 /**
- * Get the extent of the geometry.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} extent Extent.
- * @api stable
+ * @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.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);
+ol.interaction.Interaction = function(options) {
+
+  ol.Object.call(this);
+
+  /**
+   * @private
+   * @type {ol.Map}
+   */
+  this.map_ = null;
+
+  this.setActive(true);
+
+  /**
+   * @type {function(ol.MapBrowserEvent):boolean}
+   */
+  this.handleEvent = options.handleEvent;
+
 };
+ol.inherits(ol.interaction.Interaction, ol.Object);
 
 
 /**
- * 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.
+ * Return whether the interaction is currently active.
+ * @return {boolean} `true` if the interaction is active, `false` otherwise.
+ * @observable
  * @api
  */
-ol.geom.Geometry.prototype.simplify = function(tolerance) {
-  return this.getSimplifiedGeometry(tolerance * tolerance);
+ol.interaction.Interaction.prototype.getActive = function() {
+  return /** @type {boolean} */ (
+      this.get(ol.interaction.Property.ACTIVE));
 };
 
 
 /**
- * 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.
+ * Get the map associated with this interaction.
+ * @return {ol.Map} Map.
+ * @api
  */
-ol.geom.Geometry.prototype.getSimplifiedGeometry = goog.abstractMethod;
+ol.interaction.Interaction.prototype.getMap = function() {
+  return this.map_;
+};
 
 
 /**
- * Get the type of this geometry.
- * @function
- * @return {ol.geom.GeometryType} Geometry type.
+ * Activate or deactivate the interaction.
+ * @param {boolean} active Active.
+ * @observable
+ * @api
  */
-ol.geom.Geometry.prototype.getType = goog.abstractMethod;
+ol.interaction.Interaction.prototype.setActive = function(active) {
+  this.set(ol.interaction.Property.ACTIVE, active);
+};
 
 
 /**
- * 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.
+ * 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.geom.Geometry.prototype.applyTransform = goog.abstractMethod;
+ol.interaction.Interaction.prototype.setMap = function(map) {
+  this.map_ = map;
+};
 
 
 /**
- * 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
+ * @param {ol.View} view View.
+ * @param {ol.Coordinate} delta Delta.
+ * @param {number=} opt_duration Duration.
  */
-ol.geom.Geometry.prototype.intersectsExtent = goog.abstractMethod;
+ol.interaction.Interaction.pan = function(view, delta, opt_duration) {
+  var currentCenter = view.getCenter();
+  if (currentCenter) {
+    var center = view.constrainCenter(
+        [currentCenter[0] + delta[0], currentCenter[1] + delta[1]]);
+    if (opt_duration) {
+      view.animate({
+        duration: opt_duration,
+        easing: ol.easing.linear,
+        center: center
+      });
+    } else {
+      view.setCenter(center);
+    }
+  }
+};
 
 
 /**
- * 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
+ * @param {ol.View} view View.
+ * @param {number|undefined} rotation Rotation.
+ * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
+ * @param {number=} opt_duration Duration.
  */
-ol.geom.Geometry.prototype.translate = goog.abstractMethod;
+ol.interaction.Interaction.rotate = function(view, rotation, opt_anchor, opt_duration) {
+  rotation = view.constrainRotation(rotation, 0);
+  ol.interaction.Interaction.rotateWithoutConstraints(
+      view, rotation, opt_anchor, opt_duration);
+};
 
 
 /**
- * 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
+ * @param {ol.View} view View.
+ * @param {number|undefined} rotation Rotation.
+ * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
+ * @param {number=} opt_duration Duration.
  */
-ol.geom.Geometry.prototype.transform = function(source, destination) {
-  this.applyTransform(ol.proj.getTransform(source, destination));
-  return this;
+ol.interaction.Interaction.rotateWithoutConstraints = function(view, rotation, opt_anchor, opt_duration) {
+  if (rotation !== undefined) {
+    var currentRotation = view.getRotation();
+    var currentCenter = view.getCenter();
+    if (currentRotation !== undefined && currentCenter && opt_duration > 0) {
+      view.animate({
+        rotation: rotation,
+        anchor: opt_anchor,
+        duration: opt_duration,
+        easing: ol.easing.easeOut
+      });
+    } else {
+      view.rotate(rotation, opt_anchor);
+    }
+  }
 };
 
-goog.provide('ol.geom.flat.transform');
 
-goog.require('goog.vec.Mat4');
+/**
+ * @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(view, resolution, opt_anchor, opt_duration, opt_direction) {
+  resolution = view.constrainResolution(resolution, 0, opt_direction);
+  ol.interaction.Interaction.zoomWithoutConstraints(
+      view, resolution, opt_anchor, opt_duration);
+};
 
 
 /**
- * @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.
+ * @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.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;
+ol.interaction.Interaction.zoomByDelta = function(view, delta, opt_anchor, opt_duration) {
+  var currentResolution = view.getResolution();
+  var resolution = view.constrainResolution(currentResolution, delta, 0);
+
+  // If we have a constraint on center, we need to change the anchor so that the
+  // new center is within the extent. We first calculate the new center, apply
+  // the constraint to it, and then calculate back the anchor
+  if (opt_anchor && resolution !== undefined && resolution !== currentResolution) {
+    var currentCenter = view.getCenter();
+    var center = view.calculateCenterZoom(resolution, opt_anchor);
+    center = view.constrainCenter(center);
+
+    opt_anchor = [
+      (resolution * currentCenter[0] - currentResolution * center[0]) /
+          (resolution - currentResolution),
+      (resolution * currentCenter[1] - currentResolution * center[1]) /
+          (resolution - currentResolution)
+    ];
   }
-  return dest;
+
+  ol.interaction.Interaction.zoomWithoutConstraints(
+      view, resolution, opt_anchor, opt_duration);
 };
 
 
 /**
- * @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.
+ * @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.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];
+ol.interaction.Interaction.zoomWithoutConstraints = function(view, resolution, opt_anchor, opt_duration) {
+  if (resolution) {
+    var currentResolution = view.getResolution();
+    var currentCenter = view.getCenter();
+    if (currentResolution !== undefined && currentCenter &&
+        resolution !== currentResolution && opt_duration) {
+      view.animate({
+        resolution: resolution,
+        anchor: opt_anchor,
+        duration: opt_duration,
+        easing: ol.easing.easeOut
+      });
+    } else {
+      if (opt_anchor) {
+        var center = view.calculateCenterZoom(resolution, opt_anchor);
+        view.setCenter(center);
+      }
+      view.setResolution(resolution);
     }
   }
-  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');
+goog.provide('ol.interaction.DoubleClickZoom');
 
+goog.require('ol');
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.interaction.Interaction');
 
 
 /**
  * @classdesc
- * Abstract base class; only used for creating subclasses; do not instantiate
- * in apps, as cannot be rendered.
+ * Allows the user to zoom by double-clicking on the map.
  *
  * @constructor
- * @extends {ol.geom.Geometry}
- * @api stable
+ * @extends {ol.interaction.Interaction}
+ * @param {olx.interaction.DoubleClickZoomOptions=} opt_options Options.
+ * @api
  */
-ol.geom.SimpleGeometry = function() {
-
-  goog.base(this);
+ol.interaction.DoubleClickZoom = function(opt_options) {
 
-  /**
-   * @protected
-   * @type {ol.geom.GeometryLayout}
-   */
-  this.layout = ol.geom.GeometryLayout.XY;
+  var options = opt_options ? opt_options : {};
 
   /**
-   * @protected
+   * @private
    * @type {number}
    */
-  this.stride = 2;
+  this.delta_ = options.delta ? options.delta : 1;
+
+  ol.interaction.Interaction.call(this, {
+    handleEvent: ol.interaction.DoubleClickZoom.handleEvent
+  });
 
   /**
-   * @protected
-   * @type {Array.<number>}
+   * @private
+   * @type {number}
    */
-  this.flatCoordinates = null;
+  this.duration_ = options.duration !== undefined ? options.duration : 250;
 
 };
-goog.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry);
+ol.inherits(ol.interaction.DoubleClickZoom, ol.interaction.Interaction);
 
 
 /**
- * @param {number} stride Stride.
- * @private
- * @return {ol.geom.GeometryLayout} layout Layout.
+ * 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.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);
+ol.interaction.DoubleClickZoom.handleEvent = function(mapBrowserEvent) {
+  var stopEvent = false;
+  var browserEvent = mapBrowserEvent.originalEvent;
+  if (mapBrowserEvent.type == ol.MapBrowserEventType.DBLCLICK) {
+    var map = mapBrowserEvent.map;
+    var anchor = mapBrowserEvent.coordinate;
+    var delta = browserEvent.shiftKey ? -this.delta_ : this.delta_;
+    var view = map.getView();
+    ol.interaction.Interaction.zoomByDelta(
+        view, delta, anchor, this.duration_);
+    mapBrowserEvent.preventDefault();
+    stopEvent = true;
   }
+  return !stopEvent;
 };
 
+goog.provide('ol.events.condition');
+
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.asserts');
+goog.require('ol.functions');
+goog.require('ol.has');
+
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @return {number} Stride.
+ * 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
  */
-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);
-  }
+ol.events.condition.altKeyOnly = function(mapBrowserEvent) {
+  var originalEvent = mapBrowserEvent.originalEvent;
+  return (
+      originalEvent.altKey &&
+      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
+      !originalEvent.shiftKey);
 };
 
 
 /**
- * @inheritDoc
+ * 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
  */
-ol.geom.SimpleGeometry.prototype.containsXY = goog.functions.FALSE;
+ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) {
+  var originalEvent = mapBrowserEvent.originalEvent;
+  return (
+      originalEvent.altKey &&
+      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
+      originalEvent.shiftKey);
+};
 
 
 /**
- * @inheritDoc
+ * Return always true.
+ *
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True.
+ * @function
+ * @api
  */
-ol.geom.SimpleGeometry.prototype.computeExtent = function(extent) {
-  return ol.extent.createOrUpdateFromFlatCoordinates(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      extent);
-};
+ol.events.condition.always = ol.functions.TRUE;
 
 
 /**
- * @return {Array} Coordinates.
+ * 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
  */
-ol.geom.SimpleGeometry.prototype.getCoordinates = goog.abstractMethod;
+ol.events.condition.click = function(mapBrowserEvent) {
+  return mapBrowserEvent.type == ol.MapBrowserEventType.CLICK;
+};
 
 
 /**
- * Return the first coordinate of the geometry.
- * @return {ol.Coordinate} First coordinate.
- * @api stable
+ * Return `true` if the event has an "action"-producing mouse button.
+ *
+ * By definition, this includes left-click on windows/linux, and left-click
+ * without the ctrl key on Macs.
+ *
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} The result.
  */
-ol.geom.SimpleGeometry.prototype.getFirstCoordinate = function() {
-  return this.flatCoordinates.slice(0, this.stride);
+ol.events.condition.mouseActionButton = function(mapBrowserEvent) {
+  var originalEvent = mapBrowserEvent.originalEvent;
+  return originalEvent.button == 0 &&
+      !(ol.has.WEBKIT && ol.has.MAC && originalEvent.ctrlKey);
 };
 
 
 /**
- * @return {Array.<number>} Flat coordinates.
+ * Return always false.
+ *
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} False.
+ * @function
+ * @api
  */
-ol.geom.SimpleGeometry.prototype.getFlatCoordinates = function() {
-  return this.flatCoordinates;
-};
+ol.events.condition.never = ol.functions.FALSE;
 
 
 /**
- * Return the last coordinate of the geometry.
- * @return {ol.Coordinate} Last point.
- * @api stable
+ * 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.geom.SimpleGeometry.prototype.getLastCoordinate = function() {
-  return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride);
+ol.events.condition.pointerMove = function(mapBrowserEvent) {
+  return mapBrowserEvent.type == 'pointermove';
 };
 
 
 /**
- * Return the {@link ol.geom.GeometryLayout layout} of the geometry.
- * @return {ol.geom.GeometryLayout} Layout.
- * @api stable
+ * 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
  */
-ol.geom.SimpleGeometry.prototype.getLayout = function() {
-  return this.layout;
+ol.events.condition.singleClick = function(mapBrowserEvent) {
+  return mapBrowserEvent.type == ol.MapBrowserEventType.SINGLECLICK;
 };
 
 
 /**
- * @inheritDoc
+ * 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
  */
-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;
-    }
-  }
+ol.events.condition.doubleClick = function(mapBrowserEvent) {
+  return mapBrowserEvent.type == ol.MapBrowserEventType.DBLCLICK;
 };
 
 
 /**
- * @param {number} squaredTolerance Squared tolerance.
- * @return {ol.geom.SimpleGeometry} Simplified geometry.
- * @protected
+ * 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
  */
-ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal =
-    function(squaredTolerance) {
-  return this;
+ol.events.condition.noModifierKeys = function(mapBrowserEvent) {
+  var originalEvent = mapBrowserEvent.originalEvent;
+  return (
+      !originalEvent.altKey &&
+      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
+      !originalEvent.shiftKey);
 };
 
 
 /**
- * @return {number} Stride.
+ * 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
  */
-ol.geom.SimpleGeometry.prototype.getStride = function() {
-  return this.stride;
+ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) {
+  var originalEvent = mapBrowserEvent.originalEvent;
+  return (
+      !originalEvent.altKey &&
+      (ol.has.MAC ? originalEvent.metaKey : originalEvent.ctrlKey) &&
+      !originalEvent.shiftKey);
 };
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @protected
+ * 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
  */
-ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal =
-    function(layout, flatCoordinates) {
-  this.stride = ol.geom.SimpleGeometry.getStrideForLayout(layout);
-  this.layout = layout;
-  this.flatCoordinates = flatCoordinates;
+ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) {
+  var originalEvent = mapBrowserEvent.originalEvent;
+  return (
+      !originalEvent.altKey &&
+      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
+      originalEvent.shiftKey);
 };
 
 
 /**
- * @param {Array} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * 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.geom.SimpleGeometry.prototype.setCoordinates = goog.abstractMethod;
+ol.events.condition.targetNotEditable = function(mapBrowserEvent) {
+  var target = mapBrowserEvent.originalEvent.target;
+  var tagName = target.tagName;
+  return (
+      tagName !== 'INPUT' &&
+      tagName !== 'SELECT' &&
+      tagName !== 'TEXTAREA');
+};
 
 
 /**
- * @param {ol.geom.GeometryLayout|undefined} layout Layout.
- * @param {Array} coordinates Coordinates.
- * @param {number} nesting Nesting.
- * @protected
+ * Return `true` if the event originates from a mouse device.
+ *
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True if the event originates from a mouse device.
+ * @api
  */
-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;
+ol.events.condition.mouseOnly = function(mapBrowserEvent) {
+  ol.asserts.assert(mapBrowserEvent.pointerEvent, 56); // mapBrowserEvent must originate from a pointer event
+  // see http://www.w3.org/TR/pointerevents/#widl-PointerEvent-pointerType
+  return /** @type {ol.MapBrowserEvent} */ (mapBrowserEvent).pointerEvent.pointerType == 'mouse';
 };
 
 
 /**
- * @inheritDoc
- * @api stable
+ * Return `true` if the event originates from a primary pointer in
+ * contact with the surface or if the left mouse button is pressed.
+ * @see http://www.w3.org/TR/pointerevents/#button-states
+ *
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True if the event originates from a primary pointer.
+ * @api
  */
-ol.geom.SimpleGeometry.prototype.applyTransform = function(transformFn) {
-  if (this.flatCoordinates) {
-    transformFn(this.flatCoordinates, this.flatCoordinates, this.stride);
-    this.changed();
-  }
+ol.events.condition.primaryAction = function(mapBrowserEvent) {
+  var pointerEvent = mapBrowserEvent.pointerEvent;
+  return pointerEvent.isPrimary && pointerEvent.button === 0;
 };
 
+goog.provide('ol.interaction.Pointer');
+
+goog.require('ol');
+goog.require('ol.functions');
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.MapBrowserPointerEvent');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.obj');
+
 
 /**
- * @inheritDoc
- * @api stable
+ * @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.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();
-  }
+ol.interaction.Pointer = function(opt_options) {
+
+  var options = opt_options ? opt_options : {};
+
+  var handleEvent = options.handleEvent ?
+      options.handleEvent : ol.interaction.Pointer.handleEvent;
+
+  ol.interaction.Interaction.call(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 = [];
+
 };
+ol.inherits(ol.interaction.Pointer, ol.interaction.Interaction);
 
 
 /**
- * @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.
+ * @param {Array.<ol.pointer.PointerEvent>} pointerEvents List of events.
+ * @return {ol.Pixel} Centroid pixel.
  */
-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);
+ol.interaction.Pointer.centroid = function(pointerEvents) {
+  var length = pointerEvents.length;
+  var clientX = 0;
+  var clientY = 0;
+  for (var i = 0; i < length; i++) {
+    clientX += pointerEvents[i].clientX;
+    clientY += pointerEvents[i].clientY;
   }
+  return [clientX / length, clientY / length];
 };
 
-goog.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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Whether the event is a pointerdown, pointerdrag
+ *     or pointerup event.
+ * @private
  */
-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;
+ol.interaction.Pointer.prototype.isPointerDraggingEvent_ = function(mapBrowserEvent) {
+  var type = mapBrowserEvent.type;
+  return (
+      type === ol.MapBrowserEventType.POINTERDOWN ||
+      type === ol.MapBrowserEventType.POINTERDRAG ||
+      type === ol.MapBrowserEventType.POINTERUP);
 };
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @return {number} Area.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @private
  */
-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;
+ol.interaction.Pointer.prototype.updateTrackedPointers_ = function(mapBrowserEvent) {
+  if (this.isPointerDraggingEvent_(mapBrowserEvent)) {
+    var event = mapBrowserEvent.pointerEvent;
+
+    if (mapBrowserEvent.type == ol.MapBrowserEventType.POINTERUP) {
+      delete this.trackedPointers_[event.pointerId];
+    } else if (mapBrowserEvent.type ==
+        ol.MapBrowserEventType.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 = ol.obj.getValues(this.trackedPointers_);
   }
-  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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.Pointer}
  */
-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;
-};
+ol.interaction.Pointer.handleDragEvent = ol.nullFunction;
 
-goog.provide('ol.geom.flat.closest');
 
-goog.require('goog.asserts');
-goog.require('goog.math');
-goog.require('ol.math');
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Capture dragging.
+ * @this {ol.interaction.Pointer}
+ */
+ol.interaction.Pointer.handleUpEvent = ol.functions.FALSE;
 
 
 /**
- * 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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Capture dragging.
+ * @this {ol.interaction.Pointer}
  */
-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;
-};
+ol.interaction.Pointer.handleDownEvent = ol.functions.FALSE;
 
 
 /**
- * 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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.Pointer}
  */
-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;
-};
+ol.interaction.Pointer.handleMoveEvent = ol.nullFunction;
 
 
 /**
- * @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.
+ * 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.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;
+ol.interaction.Pointer.handleEvent = function(mapBrowserEvent) {
+  if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
+    return true;
   }
-  return maxSquaredDelta;
+
+  var stopEvent = false;
+  this.updateTrackedPointers_(mapBrowserEvent);
+  if (this.handlingDownUpSequence) {
+    if (mapBrowserEvent.type == ol.MapBrowserEventType.POINTERDRAG) {
+      this.handleDragEvent_(mapBrowserEvent);
+    } else if (mapBrowserEvent.type == ol.MapBrowserEventType.POINTERUP) {
+      var handledUp = this.handleUpEvent_(mapBrowserEvent);
+      this.handlingDownUpSequence = handledUp && this.targetPointers.length > 0;
+    }
+  } else {
+    if (mapBrowserEvent.type == ol.MapBrowserEventType.POINTERDOWN) {
+      var handled = this.handleDownEvent_(mapBrowserEvent);
+      this.handlingDownUpSequence = handled;
+      stopEvent = this.shouldStopEvent(handled);
+    } else if (mapBrowserEvent.type == ol.MapBrowserEventType.POINTERMOVE) {
+      this.handleMoveEvent_(mapBrowserEvent);
+    }
+  }
+  return !stopEvent;
 };
 
 
 /**
- * @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.
+ * 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.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;
+ol.interaction.Pointer.prototype.shouldStopEvent = function(handled) {
+  return handled;
 };
 
+goog.provide('ol.interaction.DragPan');
 
-/**
- * @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;
-};
+goog.require('ol');
+goog.require('ol.ViewHint');
+goog.require('ol.coordinate');
+goog.require('ol.easing');
+goog.require('ol.events.condition');
+goog.require('ol.functions');
+goog.require('ol.interaction.Pointer');
 
 
 /**
- * @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.
+ * @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
  */
-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;
+ol.interaction.DragPan = function(opt_options) {
+
+  ol.interaction.Pointer.call(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;
+
+  /**
+   * @type {ol.Pixel}
+   */
+  this.lastCentroid = null;
+
+  /**
+   * @type {number}
+   */
+  this.lastPointersCount_;
+
+  /**
+   * @private
+   * @type {ol.EventsConditionType}
+   */
+  this.condition_ = options.condition ?
+      options.condition : ol.events.condition.noModifierKeys;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.noKinetic_ = false;
+
 };
+ol.inherits(ol.interaction.DragPan, ol.interaction.Pointer);
 
 
 /**
- * @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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.DragPan}
+ * @private
  */
-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];
+ol.interaction.DragPan.handleDragEvent_ = function(mapBrowserEvent) {
+  var targetPointers = this.targetPointers;
+  var centroid =
+      ol.interaction.Pointer.centroid(targetPointers);
+  if (targetPointers.length == this.lastPointersCount_) {
+    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);
+      view.setCenter(center);
+    }
+  } else if (this.kinetic_) {
+    // reset so we don't overestimate the kinetic energy after
+    // after one finger down, tiny drag, second finger down
+    this.kinetic_.begin();
   }
-  return minSquaredDistance;
+  this.lastCentroid = centroid;
+  this.lastPointersCount_ = targetPointers.length;
 };
 
-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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragPan}
+ * @private
  */
-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];
+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 = /** @type {!ol.Coordinate} */ (view.getCenter());
+      var centerpx = map.getPixelFromCoordinate(center);
+      var dest = map.getCoordinateFromPixel([
+        centerpx[0] - distance * Math.cos(angle),
+        centerpx[1] - distance * Math.sin(angle)
+      ]);
+      view.animate({
+        center: view.constrainCenter(dest),
+        duration: 500,
+        easing: ol.easing.easeOut
+      });
+    }
+    view.setHint(ol.ViewHint.INTERACTING, -1);
+    return false;
+  } else {
+    if (this.kinetic_) {
+      // reset so we don't overestimate the kinetic energy after
+      // after one finger up, tiny drag, second finger up
+      this.kinetic_.begin();
+    }
+    this.lastCentroid = null;
+    return true;
   }
-  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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragPan}
+ * @private
  */
-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];
+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);
+    }
+    // stop any current animation
+    if (view.getHints()[ol.ViewHint.ANIMATING]) {
+      view.setCenter(mapBrowserEvent.frameState.viewState.center);
+    }
+    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;
   }
-  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.
+ * @inheritDoc
  */
-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;
-};
+ol.interaction.DragPan.prototype.shouldStopEvent = ol.functions.FALSE;
+
+goog.provide('ol.interaction.DragRotate');
+
+goog.require('ol');
+goog.require('ol.RotationConstraint');
+goog.require('ol.ViewHint');
+goog.require('ol.events.condition');
+goog.require('ol.functions');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.interaction.Pointer');
 
 
 /**
- * @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.
+ * @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
  */
-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;
-};
+ol.interaction.DragRotate = function(opt_options) {
 
-goog.provide('ol.geom.flat.inflate');
+  var options = opt_options ? opt_options : {};
+
+  ol.interaction.Pointer.call(this, {
+    handleDownEvent: ol.interaction.DragRotate.handleDownEvent_,
+    handleDragEvent: ol.interaction.DragRotate.handleDragEvent_,
+    handleUpEvent: ol.interaction.DragRotate.handleUpEvent_
+  });
+
+  /**
+   * @private
+   * @type {ol.EventsConditionType}
+   */
+  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;
+};
+ol.inherits(ol.interaction.DragRotate, ol.interaction.Pointer);
 
 
 /**
- * @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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.DragRotate}
+ * @private
  */
-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);
+ol.interaction.DragRotate.handleDragEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return;
   }
-  coordinates.length = i;
-  return coordinates;
+
+  var map = mapBrowserEvent.map;
+  var view = map.getView();
+  if (view.getConstraints().rotation === ol.RotationConstraint.disable) {
+    return;
+  }
+  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 rotation = view.getRotation();
+    ol.interaction.Interaction.rotateWithoutConstraints(
+        view, rotation - delta);
+  }
+  this.lastAngle_ = theta;
 };
 
 
 /**
- * @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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragRotate}
+ * @private
  */
-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;
+ol.interaction.DragRotate.handleUpEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return true;
   }
-  coordinatess.length = i;
-  return coordinatess;
+
+  var map = mapBrowserEvent.map;
+  var view = map.getView();
+  view.setHint(ol.ViewHint.INTERACTING, -1);
+  var rotation = view.getRotation();
+  ol.interaction.Interaction.rotate(view, rotation,
+      undefined, this.duration_);
+  return false;
 };
 
 
 /**
- * @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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragRotate}
+ * @private
  */
-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];
+ol.interaction.DragRotate.handleDownEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return false;
+  }
+
+  if (ol.events.condition.mouseActionButton(mapBrowserEvent) &&
+      this.condition_(mapBrowserEvent)) {
+    var map = mapBrowserEvent.map;
+    map.getView().setHint(ol.ViewHint.INTERACTING, 1);
+    this.lastAngle_ = undefined;
+    return true;
+  } else {
+    return false;
   }
-  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');
+/**
+ * @inheritDoc
+ */
+ol.interaction.DragRotate.prototype.shouldStopEvent = ol.functions.FALSE;
 
-goog.require('ol.math');
+// FIXME add rotation
+
+goog.provide('ol.render.Box');
+
+goog.require('ol');
+goog.require('ol.Disposable');
+goog.require('ol.geom.Polygon');
 
 
 /**
- * @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.
+ * @constructor
+ * @extends {ol.Disposable}
+ * @param {string} className CSS class name.
  */
-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;
-};
+ol.render.Box = function(className) {
 
+  /**
+   * @type {ol.geom.Polygon}
+   * @private
+   */
+  this.geometry_ = null;
 
-/**
- * @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;
-};
+  /**
+   * @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;
 
-/**
- * @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;
-};
+  /**
+   * @private
+   * @type {ol.Pixel}
+   */
+  this.startPixel_ = null;
 
+  /**
+   * @private
+   * @type {ol.Pixel}
+   */
+  this.endPixel_ = null;
 
-/**
- * @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;
 };
+ol.inherits(ol.render.Box, ol.Disposable);
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-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;
+ol.render.Box.prototype.disposeInternal = function() {
+  this.setMap(null);
 };
 
 
 /**
- * @param {number} value Value.
- * @param {number} tolerance Tolerance.
- * @return {number} Rounded value.
+ * @private
  */
-ol.geom.flat.simplify.snap = function(value, tolerance) {
-  return tolerance * Math.round(value / tolerance);
+ol.render.Box.prototype.render_ = function() {
+  var startPixel = this.startPixel_;
+  var endPixel = this.endPixel_;
+  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;
 };
 
 
 /**
- * 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.
+ * @param {ol.Map} map Map.
  */
-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;
+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';
   }
-  // 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;
+  this.map_ = map;
+  if (this.map_) {
+    this.map_.getOverlayContainer().appendChild(this.element_);
   }
-  // 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.
+ * @param {ol.Pixel} startPixel Start pixel.
+ * @param {ol.Pixel} endPixel End pixel.
  */
-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;
+ol.render.Box.prototype.setPixels = function(startPixel, endPixel) {
+  this.startPixel_ = startPixel;
+  this.endPixel_ = endPixel;
+  this.createOrUpdateGeometry();
+  this.render_();
 };
 
 
 /**
- * @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.
+ * Creates or updates the cached geometry.
  */
-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];
+ol.render.Box.prototype.createOrUpdateGeometry = function() {
+  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 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');
+/**
+ * @return {ol.geom.Polygon} Geometry.
+ */
+ol.render.Box.prototype.getGeometry = function() {
+  return this.geometry_;
+};
+
+// FIXME draw drag box
+goog.provide('ol.interaction.DragBox');
 
+goog.require('ol.events.Event');
+goog.require('ol');
+goog.require('ol.events.condition');
+goog.require('ol.interaction.Pointer');
+goog.require('ol.render.Box');
 
 
 /**
  * @classdesc
- * Linear ring geometry. Only used as part of polygon; cannot be rendered
- * on its own.
+ * 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.geom.SimpleGeometry}
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * @extends {ol.interaction.Pointer}
+ * @fires ol.interaction.DragBox.Event
+ * @param {olx.interaction.DragBoxOptions=} opt_options Options.
+ * @api
  */
-ol.geom.LinearRing = function(coordinates, opt_layout) {
+ol.interaction.DragBox = function(opt_options) {
 
-  goog.base(this);
+  ol.interaction.Pointer.call(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 {number}
+   * @private
    */
-  this.maxDelta_ = -1;
+  this.minArea_ = options.minArea !== undefined ? options.minArea : 64;
 
   /**
+   * @type {ol.Pixel}
    * @private
-   * @type {number}
    */
-  this.maxDeltaRevision_ = -1;
+  this.startPixel_ = null;
 
-  this.setCoordinates(coordinates, opt_layout);
+  /**
+   * @private
+   * @type {ol.EventsConditionType}
+   */
+  this.condition_ = options.condition ?
+      options.condition : ol.events.condition.always;
 
+  /**
+   * @private
+   * @type {ol.DragBoxEndConditionType}
+   */
+  this.boxEndCondition_ = options.boxEndCondition ?
+      options.boxEndCondition : ol.interaction.DragBox.defaultBoxEndCondition;
 };
-goog.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry);
+ol.inherits(ol.interaction.DragBox, ol.interaction.Pointer);
 
 
 /**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.LinearRing} Clone.
- * @api stable
+ * The default condition for determining whether the boxend event
+ * should fire.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent The originating MapBrowserEvent
+ *     leading to the box end.
+ * @param {ol.Pixel} startPixel The starting pixel of the box.
+ * @param {ol.Pixel} endPixel The end pixel of the box.
+ * @return {boolean} Whether or not the boxend condition should be fired.
+ * @this {ol.interaction.DragBox}
  */
-ol.geom.LinearRing.prototype.clone = function() {
-  var linearRing = new ol.geom.LinearRing(null);
-  linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return linearRing;
+ol.interaction.DragBox.defaultBoxEndCondition = function(mapBrowserEvent, startPixel, endPixel) {
+  var width = endPixel[0] - startPixel[0];
+  var height = endPixel[1] - startPixel[1];
+  return width * width + height * height >= this.minArea_;
 };
 
 
 /**
- * @inheritDoc
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.DragBox}
+ * @private
  */
-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();
+ol.interaction.DragBox.handleDragEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return;
   }
-  return ol.geom.flat.closest.getClosestPoint(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
-};
 
+  this.box_.setPixels(this.startPixel_, mapBrowserEvent.pixel);
 
-/**
- * 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);
+  this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType_.BOXDRAG,
+    mapBrowserEvent.coordinate, mapBrowserEvent));
 };
 
 
 /**
- * Return the coordinates of the linear ring.
- * @return {Array.<ol.Coordinate>} Coordinates.
- * @api stable
+ * Returns geometry of last drawn box.
+ * @return {ol.geom.Polygon} Geometry.
+ * @api
  */
-ol.geom.LinearRing.prototype.getCoordinates = function() {
-  return ol.geom.flat.inflate.coordinates(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
+ol.interaction.DragBox.prototype.getGeometry = function() {
+  return this.box_.getGeometry();
 };
 
 
 /**
- * @inheritDoc
+ * To be overridden by child classes.
+ * FIXME: use constructor option instead of relying on overriding.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @protected
  */
-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;
-};
+ol.interaction.DragBox.prototype.onBoxEnd = ol.nullFunction;
 
 
 /**
- * @inheritDoc
- * @api stable
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragBox}
+ * @private
  */
-ol.geom.LinearRing.prototype.getType = function() {
-  return ol.geom.GeometryType.LINEAR_RING;
-};
+ol.interaction.DragBox.handleUpEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return true;
+  }
 
+  this.box_.setMap(null);
 
-/**
- * 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();
+  if (this.boxEndCondition_(mapBrowserEvent,
+      this.startPixel_, mapBrowserEvent.pixel)) {
+    this.onBoxEnd(mapBrowserEvent);
+    this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType_.BOXEND,
+        mapBrowserEvent.coordinate, mapBrowserEvent));
   }
+  return false;
 };
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragBox}
+ * @private
  */
-ol.geom.LinearRing.prototype.setFlatCoordinates =
-    function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.changed();
+ol.interaction.DragBox.handleDownEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return false;
+  }
+
+  if (ol.events.condition.mouseActionButton(mapBrowserEvent) &&
+      this.condition_(mapBrowserEvent)) {
+    this.startPixel_ = mapBrowserEvent.pixel;
+    this.box_.setMap(mapBrowserEvent.map);
+    this.box_.setPixels(this.startPixel_, this.startPixel_);
+    this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType_.BOXSTART,
+        mapBrowserEvent.coordinate, mapBrowserEvent));
+    return true;
+  } else {
+    return false;
+  }
 };
 
-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');
+/**
+ * @enum {string}
+ * @private
+ */
+ol.interaction.DragBox.EventType_ = {
+  /**
+   * Triggered upon drag box start.
+   * @event ol.interaction.DragBox.Event#boxstart
+   * @api
+   */
+  BOXSTART: 'boxstart',
+
+  /**
+   * Triggered on drag when box is active.
+   * @event ol.interaction.DragBox.Event#boxdrag
+   * @api
+   */
+  BOXDRAG: 'boxdrag',
 
+  /**
+   * Triggered upon drag box end.
+   * @event ol.interaction.DragBox.Event#boxend
+   * @api
+   */
+  BOXEND: 'boxend'
+};
 
 
 /**
  * @classdesc
- * Point geometry.
+ * 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.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Originating event.
+ * @extends {ol.events.Event}
  * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {ol.Coordinate} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * @implements {oli.DragBoxEvent}
  */
-ol.geom.Point = function(coordinates, opt_layout) {
-  goog.base(this);
-  this.setCoordinates(coordinates, opt_layout);
-};
-goog.inherits(ol.geom.Point, ol.geom.SimpleGeometry);
+ol.interaction.DragBox.Event = function(type, coordinate, mapBrowserEvent) {
+  ol.events.Event.call(this, type);
 
+  /**
+   * The coordinate of the drag event.
+   * @const
+   * @type {ol.Coordinate}
+   * @api
+   */
+  this.coordinate = coordinate;
+
+  /**
+   * @const
+   * @type {ol.MapBrowserEvent}
+   * @api
+   */
+  this.mapBrowserEvent = mapBrowserEvent;
 
-/**
- * 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;
 };
+ol.inherits(ol.interaction.DragBox.Event, ol.events.Event);
 
+goog.provide('ol.interaction.DragZoom');
 
-/**
- * @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;
-  }
-};
+goog.require('ol');
+goog.require('ol.easing');
+goog.require('ol.events.condition');
+goog.require('ol.extent');
+goog.require('ol.interaction.DragBox');
 
 
 /**
- * Return the coordinate of the point.
- * @return {ol.Coordinate} Coordinates.
- * @api stable
+ * @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
  */
-ol.geom.Point.prototype.getCoordinates = function() {
-  return !this.flatCoordinates ? [] : this.flatCoordinates.slice();
-};
+ol.interaction.DragZoom = function(opt_options) {
+  var options = opt_options ? opt_options : {};
 
+  var condition = options.condition ?
+      options.condition : ol.events.condition.shiftKeyOnly;
 
-/**
- * @inheritDoc
- */
-ol.geom.Point.prototype.computeExtent = function(extent) {
-  return ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates, extent);
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.duration_ = options.duration !== undefined ? options.duration : 200;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.out_ = options.out !== undefined ? options.out : false;
 
+  ol.interaction.DragBox.call(this, {
+    condition: condition,
+    className: options.className || 'ol-dragzoom'
+  });
 
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.Point.prototype.getType = function() {
-  return ol.geom.GeometryType.POINT;
 };
+ol.inherits(ol.interaction.DragZoom, ol.interaction.DragBox);
 
 
 /**
  * @inheritDoc
- * @api stable
  */
-ol.geom.Point.prototype.intersectsExtent = function(extent) {
-  return ol.extent.containsXY(extent,
-      this.flatCoordinates[0], this.flatCoordinates[1]);
-};
+ol.interaction.DragZoom.prototype.onBoxEnd = function() {
+  var map = this.getMap();
 
+  var view = /** @type {!ol.View} */ (map.getView());
 
-/**
- * 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();
-  }
-};
+  var size = /** @type {!ol.Size} */ (map.getSize());
 
+  var extent = this.getGeometry().getExtent();
 
-/**
- * @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();
-};
+  if (this.out_) {
+    var mapExtent = view.calculateExtent(size);
+    var boxPixelExtent = ol.extent.createOrUpdateFromCoordinates([
+      map.getPixelFromCoordinate(ol.extent.getBottomLeft(extent)),
+      map.getPixelFromCoordinate(ol.extent.getTopRight(extent))]);
+    var factor = view.getResolutionForExtent(boxPixelExtent, size);
 
-goog.provide('ol.geom.flat.contains');
+    ol.extent.scaleFromCenter(mapExtent, 1 / factor);
+    extent = mapExtent;
+  }
 
-goog.require('goog.asserts');
-goog.require('ol.extent');
+  var resolution = view.constrainResolution(
+      view.getResolutionForExtent(extent, size));
 
+  var center = ol.extent.getCenter(extent);
+  center = view.constrainCenter(center);
+
+  view.animate({
+    resolution: resolution,
+    center: center,
+    duration: this.duration_,
+    easing: ol.easing.easeOut
+  });
 
-/**
- * @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;
 };
 
+goog.provide('ol.events.KeyCode');
 
 /**
- * @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).
+ * @enum {number}
+ * @const
  */
-ol.geom.flat.contains.linearRingContainsXY =
-    function(flatCoordinates, offset, end, stride, x, y) {
-  // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
-  var contains = false;
-  var x1 = flatCoordinates[end - stride];
-  var y1 = flatCoordinates[end - stride + 1];
-  for (; offset < end; offset += stride) {
-    var x2 = flatCoordinates[offset];
-    var y2 = flatCoordinates[offset + 1];
-    var intersect = ((y1 > y) != (y2 > y)) &&
-        (x < (x2 - x1) * (y - y1) / (y2 - y1) + x1);
-    if (intersect) {
-      contains = !contains;
-    }
-    x1 = x2;
-    y1 = y2;
-  }
-  return contains;
+ol.events.KeyCode = {
+  LEFT: 37,
+  UP: 38,
+  RIGHT: 39,
+  DOWN: 40
 };
 
+goog.provide('ol.interaction.KeyboardPan');
 
-/**
- * @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;
-};
+goog.require('ol');
+goog.require('ol.coordinate');
+goog.require('ol.events.EventType');
+goog.require('ol.events.KeyCode');
+goog.require('ol.events.condition');
+goog.require('ol.interaction.Interaction');
 
 
 /**
- * @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).
+ * @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
  */
-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;
-};
+ol.interaction.KeyboardPan = function(opt_options) {
 
-goog.provide('ol.geom.flat.interiorpoint');
+  ol.interaction.Interaction.call(this, {
+    handleEvent: ol.interaction.KeyboardPan.handleEvent
+  });
 
-goog.require('goog.asserts');
-goog.require('ol.geom.flat.contains');
+  var options = opt_options || {};
+
+  /**
+   * @private
+   * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event.
+   * @return {boolean} Combined condition result.
+   */
+  this.defaultCondition_ = function(mapBrowserEvent) {
+    return ol.events.condition.noModifierKeys(mapBrowserEvent) &&
+      ol.events.condition.targetNotEditable(mapBrowserEvent);
+  };
+
+  /**
+   * @private
+   * @type {ol.EventsConditionType}
+   */
+  this.condition_ = options.condition !== undefined ?
+      options.condition : this.defaultCondition_;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.duration_ = options.duration !== undefined ? options.duration : 100;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.pixelDelta_ = options.pixelDelta !== undefined ?
+      options.pixelDelta : 128;
 
+};
+ol.inherits(ol.interaction.KeyboardPan, ol.interaction.Interaction);
 
 /**
- * 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.
+ * 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.geom.flat.interiorpoint.linearRings = function(flatCoordinates, offset,
-    ends, stride, flatCenters, flatCentersOffset, opt_dest) {
-  var i, ii, x, x1, x2, y1, y2;
-  var y = flatCenters[flatCentersOffset + 1];
-  /** @type {Array.<number>} */
-  var intersections = [];
-  // Calculate intersections with the horizontal line
-  var end = ends[0];
-  x1 = flatCoordinates[end - stride];
-  y1 = flatCoordinates[end - stride + 1];
-  for (i = offset; i < end; i += stride) {
-    x2 = flatCoordinates[i];
-    y2 = flatCoordinates[i + 1];
-    if ((y <= y1 && y2 <= y) || (y1 <= y && y <= y2)) {
-      x = (y - y1) / (y2 - y1) * (x2 - x1) + x1;
-      intersections.push(x);
-    }
-    x1 = x2;
-    y1 = y2;
-  }
-  // Find the longest segment of the horizontal line that has its center point
-  // inside the linear ring.
-  var pointX = NaN;
-  var maxSegmentLength = -Infinity;
-  intersections.sort();
-  x1 = intersections[0];
-  for (i = 1, ii = intersections.length; i < ii; ++i) {
-    x2 = intersections[i];
-    var segmentLength = Math.abs(x2 - x1);
-    if (segmentLength > maxSegmentLength) {
-      x = (x1 + x2) / 2;
-      if (ol.geom.flat.contains.linearRingsContainsXY(
-          flatCoordinates, offset, ends, stride, x, y)) {
-        pointX = x;
-        maxSegmentLength = segmentLength;
+ol.interaction.KeyboardPan.handleEvent = function(mapBrowserEvent) {
+  var stopEvent = false;
+  if (mapBrowserEvent.type == ol.events.EventType.KEYDOWN) {
+    var keyEvent = mapBrowserEvent.originalEvent;
+    var keyCode = keyEvent.keyCode;
+    if (this.condition_(mapBrowserEvent) &&
+        (keyCode == ol.events.KeyCode.DOWN ||
+        keyCode == ol.events.KeyCode.LEFT ||
+        keyCode == ol.events.KeyCode.RIGHT ||
+        keyCode == ol.events.KeyCode.UP)) {
+      var map = mapBrowserEvent.map;
+      var view = map.getView();
+      var mapUnitsDelta = view.getResolution() * this.pixelDelta_;
+      var deltaX = 0, deltaY = 0;
+      if (keyCode == ol.events.KeyCode.DOWN) {
+        deltaY = -mapUnitsDelta;
+      } else if (keyCode == ol.events.KeyCode.LEFT) {
+        deltaX = -mapUnitsDelta;
+      } else if (keyCode == ol.events.KeyCode.RIGHT) {
+        deltaX = mapUnitsDelta;
+      } else {
+        deltaY = mapUnitsDelta;
       }
+      var delta = [deltaX, deltaY];
+      ol.coordinate.rotate(delta, view.getRotation());
+      ol.interaction.Interaction.pan(view, delta, this.duration_);
+      mapBrowserEvent.preventDefault();
+      stopEvent = true;
     }
-    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];
   }
+  return !stopEvent;
 };
 
+goog.provide('ol.interaction.KeyboardZoom');
+
+goog.require('ol');
+goog.require('ol.events.EventType');
+goog.require('ol.events.condition');
+goog.require('ol.interaction.Interaction');
+
 
 /**
- * @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.
+ * @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
  */
-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;
-};
+ol.interaction.KeyboardZoom = function(opt_options) {
 
-goog.provide('ol.geom.flat.segments');
+  ol.interaction.Interaction.call(this, {
+    handleEvent: ol.interaction.KeyboardZoom.handleEvent
+  });
+
+  var options = opt_options ? opt_options : {};
+
+  /**
+   * @private
+   * @type {ol.EventsConditionType}
+   */
+  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;
+
+};
+ol.inherits(ol.interaction.KeyboardZoom, ol.interaction.Interaction);
 
 
 /**
- * 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
+ * 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.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;
+ol.interaction.KeyboardZoom.handleEvent = function(mapBrowserEvent) {
+  var stopEvent = false;
+  if (mapBrowserEvent.type == ol.events.EventType.KEYDOWN ||
+      mapBrowserEvent.type == ol.events.EventType.KEYPRESS) {
+    var keyEvent = mapBrowserEvent.originalEvent;
+    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_;
+      var view = map.getView();
+      ol.interaction.Interaction.zoomByDelta(
+          view, delta, undefined, this.duration_);
+      mapBrowserEvent.preventDefault();
+      stopEvent = true;
     }
-    point1[0] = point2[0];
-    point1[1] = point2[1];
   }
-  return false;
+  return !stopEvent;
 };
 
-goog.provide('ol.geom.flat.intersectsextent');
+goog.provide('ol.interaction.MouseWheelZoom');
 
-goog.require('goog.asserts');
-goog.require('ol.extent');
-goog.require('ol.geom.flat.contains');
-goog.require('ol.geom.flat.segments');
+goog.require('ol');
+goog.require('ol.ViewHint');
+goog.require('ol.easing');
+goog.require('ol.events.EventType');
+goog.require('ol.has');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.math');
 
 
 /**
- * @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.
+ * @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
  */
-ol.geom.flat.intersectsextent.lineString =
-    function(flatCoordinates, offset, end, stride, extent) {
-  var coordinatesExtent = ol.extent.extendFlatCoordinates(
-      ol.extent.createEmpty(), flatCoordinates, offset, end, stride);
-  if (!ol.extent.intersects(extent, coordinatesExtent)) {
-    return false;
-  }
-  if (ol.extent.containsExtent(extent, coordinatesExtent)) {
-    return true;
-  }
-  if (coordinatesExtent[0] >= extent[0] &&
-      coordinatesExtent[2] <= extent[2]) {
-    return true;
-  }
-  if (coordinatesExtent[1] >= extent[1] &&
-      coordinatesExtent[3] <= extent[3]) {
-    return true;
-  }
-  return ol.geom.flat.segments.forEach(flatCoordinates, offset, end, stride,
-      /**
-       * @param {ol.Coordinate} point1 Start point.
-       * @param {ol.Coordinate} point2 End point.
-       * @return {boolean} `true` if the segment and the extent intersect,
-       *     `false` otherwise.
-       */
-      function(point1, point2) {
-        return ol.extent.intersectsSegment(extent, point1, point2);
-      });
-};
+ol.interaction.MouseWheelZoom = function(opt_options) {
 
+  ol.interaction.Interaction.call(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 {number}
+   */
+  this.timeout_ = options.timeout !== undefined ? options.timeout : 80;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.useAnchor_ = options.useAnchor !== undefined ? options.useAnchor : true;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.constrainResolution_ = options.constrainResolution || false;
+
+  /**
+   * @private
+   * @type {?ol.Coordinate}
+   */
+  this.lastAnchor_ = null;
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.startTime_ = undefined;
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.timeoutId_ = undefined;
+
+  /**
+   * @private
+   * @type {ol.interaction.MouseWheelZoom.Mode_|undefined}
+   */
+  this.mode_ = undefined;
+
+  /**
+   * Trackpad events separated by this delay will be considered separate
+   * interactions.
+   * @type {number}
+   */
+  this.trackpadEventGap_ = 400;
+
+  /**
+   * @type {number|undefined}
+   */
+  this.trackpadTimeoutId_ = undefined;
+
+  /**
+   * The number of delta values per zoom level
+   * @private
+   * @type {number}
+   */
+  this.trackpadDeltaPerZoom_ = 300;
+
+  /**
+   * The zoom factor by which scroll zooming is allowed to exceed the limits.
+   * @private
+   * @type {number}
+   */
+  this.trackpadZoomBuffer_ = 1.5;
 
-/**
- * @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;
 };
+ol.inherits(ol.interaction.MouseWheelZoom, ol.interaction.Interaction);
 
 
 /**
- * @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.
+ * 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} Allow event propagation.
+ * @this {ol.interaction.MouseWheelZoom}
+ * @api
  */
-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])) {
+ol.interaction.MouseWheelZoom.handleEvent = function(mapBrowserEvent) {
+  var type = mapBrowserEvent.type;
+  if (type !== ol.events.EventType.WHEEL && type !== ol.events.EventType.MOUSEWHEEL) {
     return true;
   }
-  return false;
-};
 
+  mapBrowserEvent.preventDefault();
 
-/**
- * @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 map = mapBrowserEvent.map;
+  var wheelEvent = /** @type {WheelEvent} */ (mapBrowserEvent.originalEvent);
+
+  if (this.useAnchor_) {
+    this.lastAnchor_ = mapBrowserEvent.coordinate;
   }
-  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;
+
+  // Delta normalisation inspired by
+  // https://github.com/mapbox/mapbox-gl-js/blob/001c7b9/js/ui/handler/scroll_zoom.js
+  var delta;
+  if (mapBrowserEvent.type == ol.events.EventType.WHEEL) {
+    delta = wheelEvent.deltaY;
+    if (ol.has.FIREFOX &&
+        wheelEvent.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
+      delta /= ol.has.DEVICE_PIXEL_RATIO;
+    }
+    if (wheelEvent.deltaMode === WheelEvent.DOM_DELTA_LINE) {
+      delta *= 40;
+    }
+  } else if (mapBrowserEvent.type == ol.events.EventType.MOUSEWHEEL) {
+    delta = -wheelEvent.wheelDeltaY;
+    if (ol.has.SAFARI) {
+      delta /= 3;
     }
   }
-  return true;
-};
 
+  if (delta === 0) {
+    return false;
+  }
 
-/**
- * @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];
+  var now = Date.now();
+
+  if (this.startTime_ === undefined) {
+    this.startTime_ = now;
   }
-  return false;
-};
 
-goog.provide('ol.geom.flat.reverse');
+  if (!this.mode_ || now - this.startTime_ > this.trackpadEventGap_) {
+    this.mode_ = Math.abs(delta) < 4 ?
+        ol.interaction.MouseWheelZoom.Mode_.TRACKPAD :
+        ol.interaction.MouseWheelZoom.Mode_.WHEEL;
+  }
 
+  if (this.mode_ === ol.interaction.MouseWheelZoom.Mode_.TRACKPAD) {
+    var view = map.getView();
+    if (this.trackpadTimeoutId_) {
+      clearTimeout(this.trackpadTimeoutId_);
+    } else {
+      view.setHint(ol.ViewHint.INTERACTING, 1);
+    }
+    this.trackpadTimeoutId_ = setTimeout(this.decrementInteractingHint_.bind(this), this.trackpadEventGap_);
+    var resolution = view.getResolution() * Math.pow(2, delta / this.trackpadDeltaPerZoom_);
+    var minResolution = view.getMinResolution();
+    var maxResolution = view.getMaxResolution();
+    var rebound = 0;
+    if (resolution < minResolution) {
+      resolution = Math.max(resolution, minResolution / this.trackpadZoomBuffer_);
+      rebound = 1;
+    } else if (resolution > maxResolution) {
+      resolution = Math.min(resolution, maxResolution * this.trackpadZoomBuffer_);
+      rebound = -1;
+    }
+    if (this.lastAnchor_) {
+      var center = view.calculateCenterZoom(resolution, this.lastAnchor_);
+      view.setCenter(view.constrainCenter(center));
+    }
+    view.setResolution(resolution);
 
-/**
- * @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;
+    if (rebound === 0 && this.constrainResolution_) {
+      view.animate({
+        resolution: view.constrainResolution(resolution, delta > 0 ? -1 : 1),
+        easing: ol.easing.easeOut,
+        anchor: this.lastAnchor_,
+        duration: this.duration_
+      });
     }
-    offset += stride;
-    end -= stride;
+
+    if (rebound > 0) {
+      view.animate({
+        resolution: minResolution,
+        easing: ol.easing.easeOut,
+        anchor: this.lastAnchor_,
+        duration: 500
+      });
+    } else if (rebound < 0) {
+      view.animate({
+        resolution: maxResolution,
+        easing: ol.easing.easeOut,
+        anchor: this.lastAnchor_,
+        duration: 500
+      });
+    }
+    this.startTime_ = now;
+    return false;
   }
-};
 
-goog.provide('ol.geom.flat.orient');
+  this.delta_ += delta;
 
-goog.require('ol');
-goog.require('ol.geom.flat.reverse');
+  var timeLeft = Math.max(this.timeout_ - (now - this.startTime_), 0);
 
+  clearTimeout(this.timeoutId_);
+  this.timeoutId_ = setTimeout(this.handleWheelZoom_.bind(this, map), timeLeft);
 
-/**
- * @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;
+  return false;
 };
 
 
 /**
- * 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.
+ * @private
  */
-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;
+ol.interaction.MouseWheelZoom.prototype.decrementInteractingHint_ = function() {
+  this.trackpadTimeoutId_ = undefined;
+  var view = this.getMap().getView();
+  view.setHint(ol.ViewHint.INTERACTING, -1);
 };
 
 
 /**
- * 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.
+ * @private
+ * @param {ol.Map} map Map.
  */
-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;
-    }
+ol.interaction.MouseWheelZoom.prototype.handleWheelZoom_ = function(map) {
+  var view = map.getView();
+  if (view.getAnimating()) {
+    view.cancelAnimations();
   }
-  return true;
+  var maxDelta = ol.MOUSEWHEELZOOM_MAXDELTA;
+  var delta = ol.math.clamp(this.delta_, -maxDelta, maxDelta);
+  ol.interaction.Interaction.zoomByDelta(view, -delta, this.lastAnchor_,
+      this.duration_);
+  this.mode_ = undefined;
+  this.delta_ = 0;
+  this.lastAnchor_ = null;
+  this.startTime_ = undefined;
+  this.timeoutId_ = undefined;
 };
 
 
 /**
- * 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.
+ * 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.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;
+ol.interaction.MouseWheelZoom.prototype.setMouseAnchor = function(useAnchor) {
+  this.useAnchor_ = useAnchor;
+  if (!useAnchor) {
+    this.lastAnchor_ = null;
   }
-  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.
+ * @enum {string}
+ * @private
  */
-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;
+ol.interaction.MouseWheelZoom.Mode_ = {
+  TRACKPAD: 'trackpad',
+  WHEEL: 'wheel'
 };
 
-goog.provide('ol.geom.Polygon');
+goog.provide('ol.interaction.PinchRotate');
 
-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');
-
+goog.require('ol.ViewHint');
+goog.require('ol.functions');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.interaction.Pointer');
+goog.require('ol.RotationConstraint');
 
 
 /**
  * @classdesc
- * Polygon geometry.
+ * Allows the user to rotate the map by twisting with two fingers
+ * on a touch screen.
  *
  * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * @extends {ol.interaction.Pointer}
+ * @param {olx.interaction.PinchRotateOptions=} opt_options Options.
+ * @api
  */
-ol.geom.Polygon = function(coordinates, opt_layout) {
+ol.interaction.PinchRotate = function(opt_options) {
 
-  goog.base(this);
+  ol.interaction.Pointer.call(this, {
+    handleDownEvent: ol.interaction.PinchRotate.handleDownEvent_,
+    handleDragEvent: ol.interaction.PinchRotate.handleDragEvent_,
+    handleUpEvent: ol.interaction.PinchRotate.handleUpEvent_
+  });
 
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.ends_ = [];
+  var options = opt_options || {};
 
   /**
    * @private
-   * @type {number}
+   * @type {ol.Coordinate}
    */
-  this.flatInteriorPointRevision_ = -1;
+  this.anchor_ = null;
 
   /**
    * @private
-   * @type {ol.Coordinate}
+   * @type {number|undefined}
    */
-  this.flatInteriorPoint_ = null;
+  this.lastAngle_ = undefined;
 
   /**
    * @private
-   * @type {number}
+   * @type {boolean}
    */
-  this.maxDelta_ = -1;
+  this.rotating_ = false;
 
   /**
    * @private
    * @type {number}
    */
-  this.maxDeltaRevision_ = -1;
+  this.rotationDelta_ = 0.0;
 
   /**
    * @private
    * @type {number}
    */
-  this.orientedRevision_ = -1;
+  this.threshold_ = options.threshold !== undefined ? options.threshold : 0.3;
 
   /**
    * @private
-   * @type {Array.<number>}
+   * @type {number}
    */
-  this.orientedFlatCoordinates_ = null;
-
-  this.setCoordinates(coordinates, opt_layout);
+  this.duration_ = options.duration !== undefined ? options.duration : 250;
 
 };
-goog.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry);
+ol.inherits(ol.interaction.PinchRotate, ol.interaction.Pointer);
 
 
 /**
- * Append the passed linear ring to this polygon.
- * @param {ol.geom.LinearRing} linearRing Linear ring.
- * @api stable
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.PinchRotate}
+ * @private
  */
-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();
-};
-
+ol.interaction.PinchRotate.handleDragEvent_ = function(mapBrowserEvent) {
+  var rotationDelta = 0.0;
 
-/**
- * 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;
-};
+  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);
 
-/**
- * @inheritDoc
- */
-ol.geom.Polygon.prototype.closestPointXY =
-    function(x, y, closestPoint, minSquaredDistance) {
-  if (minSquaredDistance <
-      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
-    return minSquaredDistance;
+  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;
   }
-  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();
+  this.lastAngle_ = angle;
+
+  var map = mapBrowserEvent.map;
+  var view = map.getView();
+  if (view.getConstraints().rotation === ol.RotationConstraint.disable) {
+    return;
   }
-  return ol.geom.flat.closest.getsClosestPoint(
-      this.flatCoordinates, 0, this.ends_, this.stride,
-      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
-};
 
+  // rotate anchor point.
+  // FIXME: should be the intersection point between the lines:
+  //     touch0,touch1 and previousTouch0,previousTouch1
+  var viewportPosition = map.getViewport().getBoundingClientRect();
+  var centroid = ol.interaction.Pointer.centroid(this.targetPointers);
+  centroid[0] -= viewportPosition.left;
+  centroid[1] -= viewportPosition.top;
+  this.anchor_ = map.getCoordinateFromPixel(centroid);
 
-/**
- * @inheritDoc
- */
-ol.geom.Polygon.prototype.containsXY = function(x, y) {
-  return ol.geom.flat.contains.linearRingsContainsXY(
-      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y);
+  // rotate
+  if (this.rotating_) {
+    var rotation = view.getRotation();
+    map.render();
+    ol.interaction.Interaction.rotateWithoutConstraints(view,
+        rotation + rotationDelta, this.anchor_);
+  }
 };
 
 
 /**
- * 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
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.PinchRotate}
+ * @private
  */
-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);
+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(
+          view, rotation, this.anchor_, this.duration_);
+    }
+    return false;
   } else {
-    flatCoordinates = this.flatCoordinates;
+    return true;
   }
-
-  return ol.geom.flat.inflate.coordinatess(
-      flatCoordinates, 0, this.ends_, this.stride);
 };
 
 
 /**
- * @return {Array.<number>} Ends.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.PinchRotate}
+ * @private
  */
-ol.geom.Polygon.prototype.getEnds = function() {
-  return this.ends_;
+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);
+    }
+    return true;
+  } else {
+    return false;
+  }
 };
 
 
 /**
- * @return {Array.<number>} Interior point.
+ * @inheritDoc
  */
-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_;
-};
+ol.interaction.PinchRotate.prototype.shouldStopEvent = ol.functions.FALSE;
 
+goog.provide('ol.interaction.PinchZoom');
 
-/**
- * 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());
-};
+goog.require('ol');
+goog.require('ol.ViewHint');
+goog.require('ol.functions');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.interaction.Pointer');
 
 
 /**
- * Return the number of rings of the polygon,  this includes the exterior
- * ring and any interior rings.
+ * @classdesc
+ * Allows the user to zoom the map by pinching with two fingers
+ * on a touch screen.
  *
- * @return {number} Number of rings.
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @param {olx.interaction.PinchZoomOptions=} opt_options Options.
  * @api
  */
-ol.geom.Polygon.prototype.getLinearRingCount = function() {
-  return this.ends_.length;
+ol.interaction.PinchZoom = function(opt_options) {
+
+  ol.interaction.Pointer.call(this, {
+    handleDownEvent: ol.interaction.PinchZoom.handleDownEvent_,
+    handleDragEvent: ol.interaction.PinchZoom.handleDragEvent_,
+    handleUpEvent: ol.interaction.PinchZoom.handleUpEvent_
+  });
+
+  var options = opt_options ? opt_options : {};
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.constrainResolution_ = options.constrainResolution || false;
+
+  /**
+   * @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;
+
 };
+ol.inherits(ol.interaction.PinchZoom, ol.interaction.Pointer);
 
 
 /**
- * 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
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.PinchZoom}
+ * @private
  */
-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;
+ol.interaction.PinchZoom.handleDragEvent_ = function(mapBrowserEvent) {
+  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;
   }
-  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;
+  this.lastDistance_ = distance;
+
+
+  var map = mapBrowserEvent.map;
+  var view = map.getView();
+  var resolution = view.getResolution();
+  var maxResolution = view.getMaxResolution();
+  var minResolution = view.getMinResolution();
+  var newResolution = resolution * scaleDelta;
+  if (newResolution > maxResolution) {
+    scaleDelta = maxResolution / resolution;
+    newResolution = maxResolution;
+  } else if (newResolution < minResolution) {
+    scaleDelta = minResolution / resolution;
+    newResolution = minResolution;
+  }
+
+  if (scaleDelta != 1.0) {
+    this.lastScaleDelta_ = scaleDelta;
+  }
+
+  // scale anchor point.
+  var viewportPosition = map.getViewport().getBoundingClientRect();
+  var centroid = ol.interaction.Pointer.centroid(this.targetPointers);
+  centroid[0] -= viewportPosition.left;
+  centroid[1] -= viewportPosition.top;
+  this.anchor_ = map.getCoordinateFromPixel(centroid);
+
+  // scale, bypass the resolution constraint
+  map.render();
+  ol.interaction.Interaction.zoomWithoutConstraints(view, newResolution, this.anchor_);
 };
 
 
 /**
- * Return the linear rings of the polygon.
- * @return {Array.<ol.geom.LinearRing>} Linear rings.
- * @api stable
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.PinchZoom}
+ * @private
  */
-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;
+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();
+    if (this.constrainResolution_ ||
+        resolution < view.getMinResolution() ||
+        resolution > view.getMaxResolution()) {
+      // 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(view, resolution,
+          this.anchor_, this.duration_, direction);
+    }
+    return false;
+  } else {
+    return true;
   }
-  return linearRings;
 };
 
 
 /**
- * @return {Array.<number>} Oriented flat coordinates.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.PinchZoom}
+ * @private
  */
-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);
+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);
     }
-    this.orientedRevision_ = this.getRevision();
+    return true;
+  } else {
+    return false;
   }
-  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;
-};
+ol.interaction.PinchZoom.prototype.shouldStopEvent = ol.functions.FALSE;
+
+goog.provide('ol.interaction');
+
+goog.require('ol.Collection');
+goog.require('ol.Kinetic');
+goog.require('ol.interaction.DoubleClickZoom');
+goog.require('ol.interaction.DragPan');
+goog.require('ol.interaction.DragRotate');
+goog.require('ol.interaction.DragZoom');
+goog.require('ol.interaction.KeyboardPan');
+goog.require('ol.interaction.KeyboardZoom');
+goog.require('ol.interaction.MouseWheelZoom');
+goog.require('ol.interaction.PinchRotate');
+goog.require('ol.interaction.PinchZoom');
 
 
 /**
- * @inheritDoc
- * @api stable
+ * 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
  */
-ol.geom.Polygon.prototype.getType = function() {
-  return ol.geom.GeometryType.POLYGON;
+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({
+      constrainResolution: options.constrainResolution,
+      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({
+      constrainResolution: options.constrainResolution,
+      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.Property');
 
 /**
- * @inheritDoc
- * @api stable
+ * @enum {string}
  */
-ol.geom.Polygon.prototype.intersectsExtent = function(extent) {
-  return ol.geom.flat.intersectsextent.linearRings(
-      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent);
+ol.layer.Property = {
+  OPACITY: 'opacity',
+  VISIBLE: 'visible',
+  EXTENT: 'extent',
+  Z_INDEX: 'zIndex',
+  MAX_RESOLUTION: 'maxResolution',
+  MIN_RESOLUTION: 'minResolution',
+  SOURCE: 'source'
 };
 
+goog.provide('ol.layer.Base');
+
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.layer.Property');
+goog.require('ol.math');
+goog.require('ol.obj');
+
 
 /**
- * Set the coordinates of the polygon.
- * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * @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
+ * @abstract
+ * @extends {ol.Object}
+ * @param {olx.layer.BaseOptions} options Layer options.
+ * @api
  */
-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();
-  }
+ol.layer.Base = function(options) {
+
+  ol.Object.call(this);
+
+  /**
+   * @type {Object.<string, *>}
+   */
+  var properties = ol.obj.assign({}, options);
+  properties[ol.layer.Property.OPACITY] =
+      options.opacity !== undefined ? options.opacity : 1;
+  properties[ol.layer.Property.VISIBLE] =
+      options.visible !== undefined ? options.visible : true;
+  properties[ol.layer.Property.Z_INDEX] =
+      options.zIndex !== undefined ? options.zIndex : 0;
+  properties[ol.layer.Property.MAX_RESOLUTION] =
+      options.maxResolution !== undefined ? options.maxResolution : Infinity;
+  properties[ol.layer.Property.MIN_RESOLUTION] =
+      options.minResolution !== undefined ? options.minResolution : 0;
+
+  this.setProperties(properties);
+
+  /**
+   * @type {ol.LayerState}
+   * @private
+   */
+  this.state_ = /** @type {ol.LayerState} */ ({
+    layer: /** @type {ol.layer.Layer} */ (this),
+    managed: true
+  });
+
 };
+ol.inherits(ol.layer.Base, ol.Object);
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {Array.<number>} ends Ends.
+ * Create a renderer for this layer.
+ * @abstract
+ * @param {ol.renderer.Map} mapRenderer The map renderer.
+ * @return {ol.renderer.Layer} A layer renderer.
  */
-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();
-};
+ol.layer.Base.prototype.createRenderer = function(mapRenderer) {};
 
 
 /**
- * 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
+ * @return {ol.LayerState} Layer state.
  */
-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;
+ol.layer.Base.prototype.getLayerState = function() {
+  this.state_.opacity = ol.math.clamp(this.getOpacity(), 0, 1);
+  this.state_.sourceState = this.getSourceState();
+  this.state_.visible = this.getVisible();
+  this.state_.extent = this.getExtent();
+  this.state_.zIndex = this.getZIndex();
+  this.state_.maxResolution = this.getMaxResolution();
+  this.state_.minResolution = Math.max(this.getMinResolution(), 0);
+
+  return this.state_;
 };
 
 
 /**
- * Create a polygon from an extent. The layout used is `XY`.
- * @param {ol.Extent} extent The extent.
- * @return {ol.geom.Polygon} The polygon.
- * @api
+ * @abstract
+ * @param {Array.<ol.layer.Layer>=} opt_array Array of layers (to be
+ *     modified in place).
+ * @return {Array.<ol.layer.Layer>} Array of layers.
  */
-ol.geom.Polygon.fromExtent = function(extent) {
-  var minX = extent[0];
-  var minY = extent[1];
-  var maxX = extent[2];
-  var maxY = extent[3];
-  var flatCoordinates =
-      [minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY];
-  var polygon = new ol.geom.Polygon(null);
-  polygon.setFlatCoordinates(
-      ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]);
-  return polygon;
-};
+ol.layer.Base.prototype.getLayersArray = function(opt_array) {};
 
 
 /**
- * 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.
+ * @abstract
+ * @param {Array.<ol.LayerState>=} opt_states Optional list of layer
+ *     states (to be modified in place).
+ * @return {Array.<ol.LayerState>} List of layer states.
+ */
+ol.layer.Base.prototype.getLayerStatesArray = function(opt_states) {};
+
+
+/**
+ * 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
  */
-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;
+ol.layer.Base.prototype.getExtent = function() {
+  return /** @type {ol.Extent|undefined} */ (
+      this.get(ol.layer.Property.EXTENT));
 };
 
 
 /**
- * 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.
+ * Return the maximum resolution of the layer.
+ * @return {number} The maximum resolution of the layer.
+ * @observable
+ * @api
  */
-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);
+ol.layer.Base.prototype.getMaxResolution = function() {
+  return /** @type {number} */ (
+      this.get(ol.layer.Property.MAX_RESOLUTION));
 };
 
-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}
+ * Return the minimum resolution of the layer.
+ * @return {number} The minimum resolution of the layer.
+ * @observable
+ * @api
  */
-ol.ViewProperty = {
-  CENTER: 'center',
-  RESOLUTION: 'resolution',
-  ROTATION: 'rotation'
+ol.layer.Base.prototype.getMinResolution = function() {
+  return /** @type {number} */ (
+      this.get(ol.layer.Property.MIN_RESOLUTION));
 };
 
 
 /**
- * @enum {number}
+ * Return the opacity of the layer (between 0 and 1).
+ * @return {number} The opacity of the layer.
+ * @observable
+ * @api
  */
-ol.ViewHint = {
-  ANIMATING: 0,
-  INTERACTING: 1
+ol.layer.Base.prototype.getOpacity = function() {
+  return /** @type {number} */ (this.get(ol.layer.Property.OPACITY));
 };
 
 
-
 /**
- * @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
+ * @abstract
+ * @return {ol.source.State} Source state.
  */
-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;
+ol.layer.Base.prototype.getSourceState = function() {};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.minZoom_ = resolutionConstraintInfo.minZoom;
 
-  var centerConstraint = ol.View.createCenterConstraint_(options);
-  var resolutionConstraint = resolutionConstraintInfo.constraint;
-  var rotationConstraint = ol.View.createRotationConstraint_(options);
+/**
+ * Return the visibility of the layer (`true` or `false`).
+ * @return {boolean} The visibility of the layer.
+ * @observable
+ * @api
+ */
+ol.layer.Base.prototype.getVisible = function() {
+  return /** @type {boolean} */ (this.get(ol.layer.Property.VISIBLE));
+};
 
-  /**
-   * @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);
+/**
+ * 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.Property.Z_INDEX));
 };
-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.
+ * 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
  */
-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;
+ol.layer.Base.prototype.setExtent = function(extent) {
+  this.set(ol.layer.Property.EXTENT, extent);
 };
 
 
 /**
- * @param {number} resolution Target resolution.
- * @param {ol.Coordinate} anchor Zoom anchor.
- * @return {ol.Coordinate|undefined} Center for resolution and anchor.
+ * Set the maximum resolution at which the layer is visible.
+ * @param {number} maxResolution The maximum resolution of the layer.
+ * @observable
+ * @api
  */
-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;
+ol.layer.Base.prototype.setMaxResolution = function(maxResolution) {
+  this.set(ol.layer.Property.MAX_RESOLUTION, maxResolution);
 };
 
 
 /**
- * Get the constrained center of this view.
- * @param {ol.Coordinate|undefined} center Center.
- * @return {ol.Coordinate|undefined} Constrained center.
+ * Set the minimum resolution at which the layer is visible.
+ * @param {number} minResolution The minimum resolution of the layer.
+ * @observable
  * @api
  */
-ol.View.prototype.constrainCenter = function(center) {
-  return this.constraints_.center(center);
+ol.layer.Base.prototype.setMinResolution = function(minResolution) {
+  this.set(ol.layer.Property.MIN_RESOLUTION, minResolution);
 };
 
 
 /**
- * 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.
+ * Set the opacity of the layer, allowed values range from 0 to 1.
+ * @param {number} opacity The opacity of the layer.
+ * @observable
  * @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);
+ol.layer.Base.prototype.setOpacity = function(opacity) {
+  this.set(ol.layer.Property.OPACITY, opacity);
 };
 
 
 /**
- * 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.
+ * Set the visibility of the layer (`true` or `false`).
+ * @param {boolean} visible The visibility of the layer.
+ * @observable
  * @api
  */
-ol.View.prototype.constrainRotation = function(rotation, opt_delta) {
-  var delta = opt_delta || 0;
-  return this.constraints_.rotation(rotation, delta);
+ol.layer.Base.prototype.setVisible = function(visible) {
+  this.set(ol.layer.Property.VISIBLE, visible);
 };
 
 
 /**
- * Get the view center.
- * @return {ol.Coordinate|undefined} The center of the view.
+ * 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 stable
+ * @api
  */
-ol.View.prototype.getCenter = function() {
-  return /** @type {ol.Coordinate|undefined} */ (
-      this.get(ol.ViewProperty.CENTER));
+ol.layer.Base.prototype.setZIndex = function(zindex) {
+  this.set(ol.layer.Property.Z_INDEX, zindex);
 };
 
+goog.provide('ol.source.State');
+
 
 /**
- * @return {Array.<number>} Hint.
+ * State of the source, one of 'undefined', 'loading', 'ready' or 'error'.
+ * @enum {string}
  */
-ol.View.prototype.getHints = function() {
-  return this.hints_.slice();
+ol.source.State = {
+  UNDEFINED: 'undefined',
+  LOADING: 'loading',
+  READY: 'ready',
+  ERROR: 'error'
 };
 
 
+goog.provide('ol.layer.Group');
+
+goog.require('ol');
+goog.require('ol.Collection');
+goog.require('ol.CollectionEventType');
+goog.require('ol.Object');
+goog.require('ol.ObjectEventType');
+goog.require('ol.asserts');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
+goog.require('ol.layer.Base');
+goog.require('ol.obj');
+goog.require('ol.source.State');
+
+
 /**
- * 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
+ * @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
  */
-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');
+ol.layer.Group = function(opt_options) {
+
+  var options = opt_options || {};
+  var baseOptions = /** @type {olx.layer.GroupOptions} */
+      (ol.obj.assign({}, options));
+  delete baseOptions.layers;
+
+  var layers = options.layers;
+
+  ol.layer.Base.call(this, baseOptions);
+
+  /**
+   * @private
+   * @type {Array.<ol.EventsKey>}
+   */
+  this.layersListenerKeys_ = [];
+
+  /**
+   * @private
+   * @type {Object.<string, Array.<ol.EventsKey>>}
+   */
+  this.listenerKeys_ = {};
+
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.layer.Group.Property_.LAYERS),
+      this.handleLayersChanged_, this);
+
+  if (layers) {
+    if (Array.isArray(layers)) {
+      layers = new ol.Collection(layers.slice(), {unique: true});
+    } else {
+      ol.asserts.assert(layers instanceof ol.Collection,
+          43); // Expected `layers` to be an array or an `ol.Collection`
+      layers = layers;
+    }
+  } else {
+    layers = new ol.Collection(undefined, {unique: true});
+  }
+
+  this.setLayers(layers);
 
-  return ol.extent.getForViewAndSize(center, resolution, rotation, size);
 };
+ol.inherits(ol.layer.Group, ol.layer.Base);
 
 
 /**
- * Get the view projection.
- * @return {ol.proj.Projection} The projection of the view.
- * @api stable
+ * @inheritDoc
  */
-ol.View.prototype.getProjection = function() {
-  return this.projection_;
+ol.layer.Group.prototype.createRenderer = function(mapRenderer) {};
+
+
+/**
+ * @private
+ */
+ol.layer.Group.prototype.handleLayerChange_ = function() {
+  if (this.getVisible()) {
+    this.changed();
+  }
 };
 
 
 /**
- * Get the view resolution.
- * @return {number|undefined} The resolution of the view.
- * @observable
- * @api stable
+ * @param {ol.events.Event} event Event.
+ * @private
  */
-ol.View.prototype.getResolution = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.ViewProperty.RESOLUTION));
+ol.layer.Group.prototype.handleLayersChanged_ = function(event) {
+  this.layersListenerKeys_.forEach(ol.events.unlistenByKey);
+  this.layersListenerKeys_.length = 0;
+
+  var layers = this.getLayers();
+  this.layersListenerKeys_.push(
+      ol.events.listen(layers, ol.CollectionEventType.ADD,
+          this.handleLayersAdd_, this),
+      ol.events.listen(layers, ol.CollectionEventType.REMOVE,
+          this.handleLayersRemove_, this));
+
+  for (var id in this.listenerKeys_) {
+    this.listenerKeys_[id].forEach(ol.events.unlistenByKey);
+  }
+  ol.obj.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_[ol.getUid(layer).toString()] = [
+      ol.events.listen(layer, ol.ObjectEventType.PROPERTYCHANGE,
+          this.handleLayerChange_, this),
+      ol.events.listen(layer, ol.events.EventType.CHANGE,
+          this.handleLayerChange_, this)
+    ];
+  }
+
+  this.changed();
 };
 
 
 /**
- * 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.
+ * @param {ol.Collection.Event} collectionEvent Collection event.
+ * @private
  */
-ol.View.prototype.getResolutionForExtent = function(extent, size) {
-  var xResolution = ol.extent.getWidth(extent) / size[0];
-  var yResolution = ol.extent.getHeight(extent) / size[1];
-  return Math.max(xResolution, yResolution);
+ol.layer.Group.prototype.handleLayersAdd_ = function(collectionEvent) {
+  var layer = /** @type {ol.layer.Base} */ (collectionEvent.element);
+  var key = ol.getUid(layer).toString();
+  this.listenerKeys_[key] = [
+    ol.events.listen(layer, ol.ObjectEventType.PROPERTYCHANGE,
+        this.handleLayerChange_, this),
+    ol.events.listen(layer, ol.events.EventType.CHANGE,
+        this.handleLayerChange_, this)
+  ];
+  this.changed();
 };
 
 
 /**
- * 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.
+ * @param {ol.Collection.Event} collectionEvent Collection event.
+ * @private
  */
-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;
-      });
+ol.layer.Group.prototype.handleLayersRemove_ = function(collectionEvent) {
+  var layer = /** @type {ol.layer.Base} */ (collectionEvent.element);
+  var key = ol.getUid(layer).toString();
+  this.listenerKeys_[key].forEach(ol.events.unlistenByKey);
+  delete this.listenerKeys_[key];
+  this.changed();
 };
 
 
 /**
- * Get the view rotation.
- * @return {number} The rotation of the view in radians.
+ * 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
+ * @api
  */
-ol.View.prototype.getRotation = function() {
-  return /** @type {number} */ (this.get(ol.ViewProperty.ROTATION));
+ol.layer.Group.prototype.getLayers = function() {
+  return /** @type {!ol.Collection.<ol.layer.Base>} */ (this.get(
+      ol.layer.Group.Property_.LAYERS));
 };
 
 
 /**
- * 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.
+ * 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
  */
-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;
-      });
+ol.layer.Group.prototype.setLayers = function(layers) {
+  this.set(ol.layer.Group.Property_.LAYERS, layers);
 };
 
 
 /**
- * @return {olx.ViewState} View state.
+ * @inheritDoc
  */
-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
+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;
 };
 
 
 /**
- * Get the current zoom level. Return undefined if the current
- * resolution is undefined or not a "constrained resolution".
- * @return {number|undefined} Zoom.
- * @api stable
+ * @inheritDoc
  */
-ol.View.prototype.getZoom = function() {
-  var offset;
-  var resolution = this.getResolution();
+ol.layer.Group.prototype.getLayerStatesArray = function(opt_states) {
+  var states = opt_states !== undefined ? opt_states : [];
 
-  if (resolution !== undefined) {
-    var res, z = 0;
-    do {
-      res = this.constrainResolution(this.maxResolution_, z);
-      if (res == resolution) {
-        offset = z;
-        break;
+  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;
       }
-      ++z;
-    } while (res > this.minResolution_);
+    }
   }
 
-  return offset !== undefined ? this.minZoom_ + offset : offset;
+  return states;
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-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 || {};
+ol.layer.Group.prototype.getSourceState = function() {
+  return ol.source.State.READY;
+};
 
-  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();
+/**
+ * @enum {string}
+ * @private
+ */
+ol.layer.Group.Property_ = {
+  LAYERS: 'layers'
+};
 
-  // 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);
-  }
+goog.provide('ol.render.EventType');
 
-  // 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);
+/**
+ * @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'
+};
 
-  // 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;
+goog.provide('ol.layer.Layer');
 
-  this.setCenter([centerX, centerY]);
-};
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.layer.Base');
+goog.require('ol.layer.Property');
+goog.require('ol.obj');
+goog.require('ol.render.EventType');
+goog.require('ol.source.State');
 
 
 /**
- * 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.
+ * @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
+ * @abstract
+ * @extends {ol.layer.Base}
+ * @fires ol.render.Event
+ * @param {olx.layer.LayerOptions} options Layer options.
  * @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;
+ol.layer.Layer = function(options) {
 
-  // go back to original angle
-  sinAngle = -sinAngle; // go back to original rotation
-  var centerX = rotX * cosAngle - rotY * sinAngle;
-  var centerY = rotY * cosAngle + rotX * sinAngle;
+  var baseOptions = ol.obj.assign({}, options);
+  delete baseOptions.source;
 
-  this.setCenter([centerX, centerY]);
-};
+  ol.layer.Base.call(this, /** @type {olx.layer.BaseOptions} */ (baseOptions));
 
+  /**
+   * @private
+   * @type {?ol.EventsKey}
+   */
+  this.mapPrecomposeKey_ = null;
 
-/**
- * @return {boolean} Is defined.
- */
-ol.View.prototype.isDef = function() {
-  return !!this.getCenter() && this.getResolution() !== undefined;
-};
+  /**
+   * @private
+   * @type {?ol.EventsKey}
+   */
+  this.mapRenderKey_ = null;
 
+  /**
+   * @private
+   * @type {?ol.EventsKey}
+   */
+  this.sourceChangeKey_ = null;
 
-/**
- * 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);
+  if (options.map) {
+    this.setMap(options.map);
   }
-  this.setRotation(rotation);
+
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.layer.Property.SOURCE),
+      this.handleSourcePropertyChange_, this);
+
+  var source = options.source ? options.source : null;
+  this.setSource(source);
 };
+ol.inherits(ol.layer.Layer, ol.layer.Base);
 
 
 /**
- * Set the center of the current view.
- * @param {ol.Coordinate|undefined} center The center of the view.
- * @observable
- * @api stable
+ * 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.LayerState} layerState Layer state.
+ * @param {number} resolution Resolution.
+ * @return {boolean} The layer is visible at the given resolution.
  */
-ol.View.prototype.setCenter = function(center) {
-  this.set(ol.ViewProperty.CENTER, center);
+ol.layer.Layer.visibleAtResolution = function(layerState, resolution) {
+  return layerState.visible && resolution >= layerState.minResolution &&
+      resolution < layerState.maxResolution;
 };
 
 
 /**
- * @param {ol.ViewHint} hint Hint.
- * @param {number} delta Delta.
- * @return {number} New value.
+ * @inheritDoc
  */
-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];
+ol.layer.Layer.prototype.getLayersArray = function(opt_array) {
+  var array = opt_array ? opt_array : [];
+  array.push(this);
+  return array;
 };
 
 
 /**
- * Set the resolution for this view.
- * @param {number|undefined} resolution The resolution of the view.
- * @observable
- * @api stable
+ * @inheritDoc
  */
-ol.View.prototype.setResolution = function(resolution) {
-  this.set(ol.ViewProperty.RESOLUTION, resolution);
+ol.layer.Layer.prototype.getLayerStatesArray = function(opt_states) {
+  var states = opt_states ? opt_states : [];
+  states.push(this.getLayerState());
+  return states;
 };
 
 
 /**
- * Set the rotation for this view.
- * @param {number} rotation The rotation of the view in radians.
+ * Get the layer source.
+ * @return {ol.source.Source} The layer source (or `null` if not yet set).
  * @observable
- * @api stable
+ * @api
  */
-ol.View.prototype.setRotation = function(rotation) {
-  this.set(ol.ViewProperty.ROTATION, rotation);
+ol.layer.Layer.prototype.getSource = function() {
+  var source = this.get(ol.layer.Property.SOURCE);
+  return /** @type {ol.source.Source} */ (source) || null;
 };
 
 
 /**
- * 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);
+  * @inheritDoc
+  */
+ol.layer.Layer.prototype.getSourceState = function() {
+  var source = this.getSource();
+  return !source ? ol.source.State.UNDEFINED : source.getState();
 };
 
 
 /**
- * @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;
-  }
+ol.layer.Layer.prototype.handleSourceChange_ = function() {
+  this.changed();
 };
 
 
 /**
  * @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;
+ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() {
+  if (this.sourceChangeKey_) {
+    ol.events.unlistenByKey(this.sourceChangeKey_);
+    this.sourceChangeKey_ = null;
+  }
+  var source = this.getSource();
+  if (source) {
+    this.sourceChangeKey_ = ol.events.listen(source,
+        ol.events.EventType.CHANGE, this.handleSourceChange_, this);
+  }
+  this.changed();
+};
 
-  // 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;
+/**
+ * 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) {
+  if (this.mapPrecomposeKey_) {
+    ol.events.unlistenByKey(this.mapPrecomposeKey_);
+    this.mapPrecomposeKey_ = null;
+  }
+  if (!map) {
+    this.changed();
+  }
+  if (this.mapRenderKey_) {
+    ol.events.unlistenByKey(this.mapRenderKey_);
+    this.mapRenderKey_ = null;
+  }
+  if (map) {
+    this.mapPrecomposeKey_ = ol.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[ol.getUid(this)] = layerState;
+        }, this);
+    this.mapRenderKey_ = ol.events.listen(
+        this, ol.events.EventType.CHANGE, map.render, map);
+    this.changed();
+  }
+};
 
-  var maxZoom = options.maxZoom !== undefined ?
-      options.maxZoom : defaultMaxZoom;
 
-  var zoomFactor = options.zoomFactor !== undefined ?
-      options.zoomFactor : defaultZoomFactor;
+/**
+ * Set the layer source.
+ * @param {ol.source.Source} source The layer source.
+ * @observable
+ * @api
+ */
+ol.layer.Layer.prototype.setSource = function(source) {
+  this.set(ol.layer.Property.SOURCE, source);
+};
 
-  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));
+goog.provide('ol.style.IconImageCache');
 
-    var defaultMaxResolution = size / ol.DEFAULT_TILE_SIZE / Math.pow(
-        defaultZoomFactor, ol.DEFAULT_MIN_ZOOM);
+goog.require('ol.color');
 
-    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);
-    }
+/**
+ * @constructor
+ */
+ol.style.IconImageCache = function() {
 
-    // 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;
-      }
-    }
+  /**
+   * @type {Object.<string, ol.style.IconImage>}
+   * @private
+   */
+  this.cache_ = {};
 
-    // 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);
+  /**
+   * @type {number}
+   * @private
+   */
+  this.cacheSize_ = 0;
 
-    resolutionConstraint = ol.ResolutionConstraint.createSnapToPower(
-        zoomFactor, maxResolution, maxZoom - minZoom);
-  }
-  return {constraint: resolutionConstraint, maxResolution: maxResolution,
-    minResolution: minResolution, minZoom: minZoom};
+  /**
+   * @const
+   * @type {number}
+   * @private
+   */
+  this.maxCacheSize_ = 32;
 };
 
 
 /**
- * @private
- * @param {olx.ViewOptions} options View options.
- * @return {ol.RotationConstraintType} Rotation constraint.
+ * @param {string} src Src.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.Color} color Color.
+ * @return {string} Cache key.
  */
-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;
-  }
+ol.style.IconImageCache.getKey = function(src, crossOrigin, color) {
+  var colorString = color ? ol.color.asString(color) : 'null';
+  return crossOrigin + ':' + src + ':' + colorString;
 };
 
-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
+ * FIXME empty description for jsdoc
  */
-ol.easing.easeIn = function(t) {
-  return Math.pow(t, 3);
+ol.style.IconImageCache.prototype.clear = function() {
+  this.cache_ = {};
+  this.cacheSize_ = 0;
 };
 
 
 /**
- * Start fast and slow down.
- * @param {number} t Input between 0 and 1.
- * @return {number} Output between 0 and 1.
- * @api
+ * FIXME empty description for jsdoc
  */
-ol.easing.easeOut = function(t) {
-  return 1 - ol.easing.easeIn(1 - t);
+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 && !iconImage.hasListener()) {
+        delete this.cache_[key];
+        --this.cacheSize_;
+      }
+    }
+  }
 };
 
 
 /**
- * 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
+ * @param {string} src Src.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.Color} color Color.
+ * @return {ol.style.IconImage} Icon image.
  */
-ol.easing.inAndOut = function(t) {
-  return 3 * t * t - 2 * t * t * t;
+ol.style.IconImageCache.prototype.get = function(src, crossOrigin, color) {
+  var key = ol.style.IconImageCache.getKey(src, crossOrigin, color);
+  return key in this.cache_ ? this.cache_[key] : null;
 };
 
 
 /**
- * Maintain a constant speed over time.
- * @param {number} t Input between 0 and 1.
- * @return {number} Output between 0 and 1.
- * @api
+ * @param {string} src Src.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.Color} color Color.
+ * @param {ol.style.IconImage} iconImage Icon image.
  */
-ol.easing.linear = function(t) {
-  return t;
+ol.style.IconImageCache.prototype.set = function(src, crossOrigin, color, iconImage) {
+  var key = ol.style.IconImageCache.getKey(src, crossOrigin, color);
+  this.cache_[key] = iconImage;
+  ++this.cacheSize_;
 };
 
+goog.provide('ol.style');
+
+goog.require('ol.style.IconImageCache');
+
+ol.style.iconImageCache = new ol.style.IconImageCache();
+
+goog.provide('ol.transform');
+
+goog.require('ol.asserts');
+
 
 /**
- * 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
+ * Collection of affine 2d transformation functions. The functions work on an
+ * array of 6 elements. The element order is compatible with the [SVGMatrix
+ * interface](https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix) and is
+ * a subset (elements a to f) of a 3x3 martrix:
+ * ```
+ * [ a c e ]
+ * [ b d f ]
+ * [ 0 0 1 ]
+ * ```
  */
-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');
+/**
+ * @private
+ * @type {ol.Transform}
+ */
+ol.transform.tmp_ = new Array(6);
 
 
 /**
- * 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
+ * Create an identity transform.
+ * @return {!ol.Transform} Identity transform.
  */
-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;
-        }
-      });
+ol.transform.create = function() {
+  return [1, 0, 0, 1, 0, 0];
 };
 
 
 /**
- * Generate an animated transition while updating the view center.
- * @param {olx.animation.PanOptions} options Pan options.
- * @return {ol.PreRenderFunction} Pre-render function.
- * @api
+ * Resets the given transform to an identity transform.
+ * @param {!ol.Transform} transform Transform.
+ * @return {!ol.Transform} Transform.
  */
-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;
-        }
-      });
+ol.transform.reset = function(transform) {
+  return ol.transform.set(transform, 1, 0, 0, 1, 0, 0);
 };
 
 
 /**
- * Generate an animated transition while updating the view rotation.
- * @param {olx.animation.RotateOptions} options Rotate options.
- * @return {ol.PreRenderFunction} Pre-render function.
- * @api
+ * Multiply the underlying matrices of two transforms and return the result in
+ * the first transform.
+ * @param {!ol.Transform} transform1 Transform parameters of matrix 1.
+ * @param {!ol.Transform} transform2 Transform parameters of matrix 2.
+ * @return {!ol.Transform} transform1 multiplied with transform2.
  */
-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;
+ol.transform.multiply = function(transform1, transform2) {
+  var a1 = transform1[0];
+  var b1 = transform1[1];
+  var c1 = transform1[2];
+  var d1 = transform1[3];
+  var e1 = transform1[4];
+  var f1 = transform1[5];
+  var a2 = transform2[0];
+  var b2 = transform2[1];
+  var c2 = transform2[2];
+  var d2 = transform2[3];
+  var e2 = transform2[4];
+  var f2 = transform2[5];
 
-  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;
-        }
-      });
-};
+  transform1[0] = a1 * a2 + c1 * b2;
+  transform1[1] = b1 * a2 + d1 * b2;
+  transform1[2] = a1 * c2 + c1 * d2;
+  transform1[3] = b1 * c2 + d1 * d2;
+  transform1[4] = a1 * e2 + c1 * f2 + e1;
+  transform1[5] = b1 * e2 + d1 * f2 + f1;
 
+  return transform1;
+};
 
 /**
- * Generate an animated transition while updating the view resolution.
- * @param {olx.animation.ZoomOptions} options Zoom options.
- * @return {ol.PreRenderFunction} Pre-render function.
- * @api
+ * Set the transform components a-f on a given transform.
+ * @param {!ol.Transform} transform Transform.
+ * @param {number} a The a component of the transform.
+ * @param {number} b The b component of the transform.
+ * @param {number} c The c component of the transform.
+ * @param {number} d The d component of the transform.
+ * @param {number} e The e component of the transform.
+ * @param {number} f The f component of the transform.
+ * @return {!ol.Transform} Matrix with transform applied.
  */
-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;
-        }
-      });
+ol.transform.set = function(transform, a, b, c, d, e, f) {
+  transform[0] = a;
+  transform[1] = b;
+  transform[2] = c;
+  transform[3] = d;
+  transform[4] = e;
+  transform[5] = f;
+  return transform;
 };
 
-goog.provide('ol.TileCoord');
-goog.provide('ol.tilecoord');
 
-goog.require('goog.asserts');
-goog.require('ol.extent');
+/**
+ * Set transform on one matrix from another matrix.
+ * @param {!ol.Transform} transform1 Matrix to set transform to.
+ * @param {!ol.Transform} transform2 Matrix to set transform from.
+ * @return {!ol.Transform} transform1 with transform from transform2 applied.
+ */
+ol.transform.setFromArray = function(transform1, transform2) {
+  transform1[0] = transform2[0];
+  transform1[1] = transform2[1];
+  transform1[2] = transform2[2];
+  transform1[3] = transform2[3];
+  transform1[4] = transform2[4];
+  transform1[5] = transform2[5];
+  return transform1;
+};
 
 
 /**
- * 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
+ * Transforms the given coordinate with the given transform returning the
+ * resulting, transformed coordinate. The coordinate will be modified in-place.
+ *
+ * @param {ol.Transform} transform The transformation.
+ * @param {ol.Coordinate|ol.Pixel} coordinate The coordinate to transform.
+ * @return {ol.Coordinate|ol.Pixel} return coordinate so that operations can be
+ *     chained together.
  */
-ol.TileCoord;
+ol.transform.apply = function(transform, coordinate) {
+  var x = coordinate[0], y = coordinate[1];
+  coordinate[0] = transform[0] * x + transform[2] * y + transform[4];
+  coordinate[1] = transform[1] * x + transform[3] * y + transform[5];
+  return coordinate;
+};
 
 
 /**
- * @enum {number}
+ * Applies rotation to the given transform.
+ * @param {!ol.Transform} transform Transform.
+ * @param {number} angle Angle in radians.
+ * @return {!ol.Transform} The rotated transform.
  */
-ol.QuadKeyCharCode = {
-  ZERO: '0'.charCodeAt(0),
-  ONE: '1'.charCodeAt(0),
-  TWO: '2'.charCodeAt(0),
-  THREE: '3'.charCodeAt(0)
+ol.transform.rotate = function(transform, angle) {
+  var cos = Math.cos(angle);
+  var sin = Math.sin(angle);
+  return ol.transform.multiply(transform,
+      ol.transform.set(ol.transform.tmp_, cos, sin, -sin, cos, 0, 0));
 };
 
 
 /**
- * @param {string} str String that follows pattern “z/x/y” where x, y and z are
- *   numbers.
- * @return {ol.TileCoord} Tile coord.
+ * Applies scale to a given transform.
+ * @param {!ol.Transform} transform Transform.
+ * @param {number} x Scale factor x.
+ * @param {number} y Scale factor y.
+ * @return {!ol.Transform} The scaled transform.
  */
-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);
-  });
+ol.transform.scale = function(transform, x, y) {
+  return ol.transform.multiply(transform,
+      ol.transform.set(ol.transform.tmp_, x, 0, 0, y, 0, 0));
 };
 
 
 /**
- * @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.
+ * Applies translation to the given transform.
+ * @param {!ol.Transform} transform Transform.
+ * @param {number} dx Translation x.
+ * @param {number} dy Translation y.
+ * @return {!ol.Transform} The translated transform.
  */
-ol.tilecoord.getKeyZXY = function(z, x, y) {
-  return z + '/' + x + '/' + y;
+ol.transform.translate = function(transform, dx, dy) {
+  return ol.transform.multiply(transform,
+      ol.transform.set(ol.transform.tmp_, 1, 0, 0, 1, dx, dy));
 };
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coord.
- * @return {number} Hash.
+ * Creates a composite transform given an initial translation, scale, rotation, and
+ * final translation (in that order only, not commutative).
+ * @param {!ol.Transform} transform The transform (will be modified in place).
+ * @param {number} dx1 Initial translation x.
+ * @param {number} dy1 Initial translation y.
+ * @param {number} sx Scale factor x.
+ * @param {number} sy Scale factor y.
+ * @param {number} angle Rotation (in counter-clockwise radians).
+ * @param {number} dx2 Final translation x.
+ * @param {number} dy2 Final translation y.
+ * @return {!ol.Transform} The composite transform.
  */
-ol.tilecoord.hash = function(tileCoord) {
-  return (tileCoord[1] << tileCoord[0]) + tileCoord[2];
+ol.transform.compose = function(transform, dx1, dy1, sx, sy, angle, dx2, dy2) {
+  var sin = Math.sin(angle);
+  var cos = Math.cos(angle);
+  transform[0] = sx * cos;
+  transform[1] = sy * sin;
+  transform[2] = -sx * sin;
+  transform[3] = sy * cos;
+  transform[4] = dx2 * sx * cos - dy2 * sx * sin + dx1;
+  transform[5] = dx2 * sy * sin + dy2 * sy * cos + dy1;
+  return transform;
 };
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coord.
- * @return {string} Quad key.
+ * Invert the given transform.
+ * @param {!ol.Transform} transform Transform.
+ * @return {!ol.Transform} Inverse of the transform.
  */
-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('');
-};
-
+ol.transform.invert = function(transform) {
+  var det = ol.transform.determinant(transform);
+  ol.asserts.assert(det !== 0, 32); // Transformation matrix cannot be inverted
 
-/**
- * @param {ol.TileCoord} tileCoord Tile coord.
- * @return {string} String.
- */
-ol.tilecoord.toString = function(tileCoord) {
-  return ol.tilecoord.getKeyZXY(tileCoord[0], tileCoord[1], tileCoord[2]);
-};
+  var a = transform[0];
+  var b = transform[1];
+  var c = transform[2];
+  var d = transform[3];
+  var e = transform[4];
+  var f = transform[5];
 
+  transform[0] = d / det;
+  transform[1] = -b / det;
+  transform[2] = -c / det;
+  transform[3] = a / det;
+  transform[4] = (c * f - d * e) / det;
+  transform[5] = -(a * f - b * e) / det;
 
-/**
- * @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;
-  }
+  return transform;
 };
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid.
- * @return {boolean} Tile coordinate is within extent and zoom level range.
+ * Returns the determinant of the given matrix.
+ * @param {!ol.Transform} mat Matrix.
+ * @return {number} Determinant.
  */
-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);
-  }
+ol.transform.determinant = function(mat) {
+  return mat[0] * mat[3] - mat[1] * mat[2];
 };
 
-goog.provide('ol.TileRange');
-
-goog.require('goog.asserts');
-goog.require('ol.Size');
-goog.require('ol.TileCoord');
+goog.provide('ol.renderer.Map');
 
+goog.require('ol');
+goog.require('ol.Disposable');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
+goog.require('ol.functions');
+goog.require('ol.layer.Layer');
+goog.require('ol.style');
+goog.require('ol.transform');
 
 
 /**
- * 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.
+ * @abstract
+ * @extends {ol.Disposable}
+ * @param {Element} container Container.
+ * @param {ol.Map} map Map.
  * @struct
  */
-ol.TileRange = function(minX, maxX, minY, maxY) {
+ol.renderer.Map = function(container, map) {
+
+  ol.Disposable.call(this);
 
-  /**
-   * @type {number}
-   */
-  this.minX = minX;
 
   /**
-   * @type {number}
+   * @private
+   * @type {ol.Map}
    */
-  this.maxX = maxX;
+  this.map_ = map;
 
   /**
-   * @type {number}
+   * @private
+   * @type {Object.<string, ol.renderer.Layer>}
    */
-  this.minY = minY;
+  this.layerRenderers_ = {};
 
   /**
-   * @type {number}
+   * @private
+   * @type {Object.<string, ol.EventsKey>}
    */
-  this.maxY = maxY;
+  this.layerRendererListeners_ = {};
 
 };
+ol.inherits(ol.renderer.Map, ol.Disposable);
 
 
 /**
- * @param {...ol.TileCoord} var_args Tile coordinates.
- * @return {!ol.TileRange} Bounding tile box.
+ * @param {olx.FrameState} frameState FrameState.
+ * @protected
  */
-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;
+ol.renderer.Map.prototype.calculateMatrices2D = function(frameState) {
+  var viewState = frameState.viewState;
+  var coordinateToPixelTransform = frameState.coordinateToPixelTransform;
+  var pixelToCoordinateTransform = frameState.pixelToCoordinateTransform;
+
+  ol.transform.compose(coordinateToPixelTransform,
+      frameState.size[0] / 2, frameState.size[1] / 2,
+      1 / viewState.resolution, -1 / viewState.resolution,
+      -viewState.rotation,
+      -viewState.center[0], -viewState.center[1]);
+
+  ol.transform.invert(
+      ol.transform.setFromArray(pixelToCoordinateTransform, coordinateToPixelTransform));
 };
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-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);
+ol.renderer.Map.prototype.disposeInternal = function() {
+  for (var id in this.layerRenderers_) {
+    this.layerRenderers_[id].dispose();
   }
 };
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @return {boolean} Contains tile coordinate.
+ * @param {ol.Map} map Map.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
-ol.TileRange.prototype.contains = function(tileCoord) {
-  return this.containsXY(tileCoord[1], tileCoord[2]);
+ol.renderer.Map.expireIconCache_ = function(map, frameState) {
+  var cache = ol.style.iconImageCache;
+  cache.expire();
 };
 
 
 /**
- * @param {ol.TileRange} tileRange Tile range.
- * @return {boolean} Contains.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState FrameState.
+ * @param {number} hitTolerance Hit tolerance in pixels.
+ * @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.TileRange.prototype.containsTileRange = function(tileRange) {
-  return this.minX <= tileRange.minX && tileRange.maxX <= this.maxX &&
-      this.minY <= tileRange.minY && tileRange.maxY <= this.maxY;
+ol.renderer.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg,
+        layerFilter, thisArg2) {
+  var result;
+  var viewState = frameState.viewState;
+  var viewResolution = viewState.resolution;
+
+  /**
+   * @param {ol.Feature|ol.render.Feature} feature Feature.
+   * @param {ol.layer.Layer} layer Layer.
+   * @return {?} Callback result.
+   */
+  function forEachFeatureAtCoordinate(feature, layer) {
+    var key = ol.getUid(feature).toString();
+    var managed = frameState.layerStates[ol.getUid(layer)].managed;
+    if (!(key in frameState.skippedFeatureUids && !managed)) {
+      return callback.call(thisArg, feature, managed ? layer : 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, hitTolerance, forEachFeatureAtCoordinate, thisArg);
+      }
+      if (result) {
+        return result;
+      }
+    }
+  }
+  return undefined;
 };
 
 
 /**
- * @param {number} x Tile coordinate x.
- * @param {number} y Tile coordinate y.
- * @return {boolean} Contains coordinate.
+ * @abstract
+ * @param {ol.Pixel} pixel Pixel.
+ * @param {olx.FrameState} frameState FrameState.
+ * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): 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.TileRange.prototype.containsXY = function(x, y) {
-  return this.minX <= x && x <= this.maxX && this.minY <= y && y <= this.maxY;
-};
+ol.renderer.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg,
+        layerFilter, thisArg2) {};
 
 
 /**
- * @param {ol.TileRange} tileRange Tile range.
- * @return {boolean} Equals.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState FrameState.
+ * @param {number} hitTolerance Hit tolerance in pixels.
+ * @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.TileRange.prototype.equals = function(tileRange) {
-  return this.minX == tileRange.minX && this.minY == tileRange.minY &&
-      this.maxX == tileRange.maxX && this.maxY == tileRange.maxY;
+ol.renderer.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) {
+  var hasFeature = this.forEachFeatureAtCoordinate(
+      coordinate, frameState, hitTolerance, ol.functions.TRUE, this, layerFilter, thisArg);
+
+  return hasFeature !== undefined;
 };
 
 
 /**
- * @param {ol.TileRange} tileRange Tile range.
+ * @param {ol.layer.Layer} layer Layer.
+ * @protected
+ * @return {ol.renderer.Layer} Layer renderer.
  */
-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;
+ol.renderer.Map.prototype.getLayerRenderer = function(layer) {
+  var layerKey = ol.getUid(layer).toString();
+  if (layerKey in this.layerRenderers_) {
+    return this.layerRenderers_[layerKey];
+  } else {
+    var layerRenderer = layer.createRenderer(this);
+    this.layerRenderers_[layerKey] = layerRenderer;
+    this.layerRendererListeners_[layerKey] = ol.events.listen(layerRenderer,
+        ol.events.EventType.CHANGE, this.handleLayerRendererChange_, this);
+
+    return layerRenderer;
   }
 };
 
 
 /**
- * @return {number} Height.
+ * @param {string} layerKey Layer key.
+ * @protected
+ * @return {ol.renderer.Layer} Layer renderer.
  */
-ol.TileRange.prototype.getHeight = function() {
-  return this.maxY - this.minY + 1;
+ol.renderer.Map.prototype.getLayerRendererByKey = function(layerKey) {
+  return this.layerRenderers_[layerKey];
 };
 
 
 /**
- * @return {ol.Size} Size.
+ * @protected
+ * @return {Object.<string, ol.renderer.Layer>} Layer renderers.
  */
-ol.TileRange.prototype.getSize = function() {
-  return [this.getWidth(), this.getHeight()];
+ol.renderer.Map.prototype.getLayerRenderers = function() {
+  return this.layerRenderers_;
 };
 
 
 /**
- * @return {number} Width.
+ * @return {ol.Map} Map.
  */
-ol.TileRange.prototype.getWidth = function() {
-  return this.maxX - this.minX + 1;
+ol.renderer.Map.prototype.getMap = function() {
+  return this.map_;
 };
 
 
 /**
- * @param {ol.TileRange} tileRange Tile range.
- * @return {boolean} Intersects.
+ * @abstract
+ * @return {string} Type
  */
-ol.TileRange.prototype.intersects = function(tileRange) {
-  return this.minX <= tileRange.maxX &&
-      this.maxX >= tileRange.minX &&
-      this.minY <= tileRange.maxY &&
-      this.maxY >= tileRange.minY;
-};
+ol.renderer.Map.prototype.getType = function() {};
 
-goog.provide('ol.Attribution');
-
-goog.require('goog.math');
-goog.require('ol.TileRange');
 
+/**
+ * Handle changes in a layer renderer.
+ * @private
+ */
+ol.renderer.Map.prototype.handleLayerRendererChange_ = function() {
+  this.map_.render();
+};
 
 
 /**
- * @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
+ * @param {string} layerKey Layer key.
+ * @return {ol.renderer.Layer} Layer renderer.
+ * @private
  */
-ol.Attribution = function(options) {
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.html_ = options.html;
+ol.renderer.Map.prototype.removeLayerRendererByKey_ = function(layerKey) {
+  var layerRenderer = this.layerRenderers_[layerKey];
+  delete this.layerRenderers_[layerKey];
 
-  /**
-   * @private
-   * @type {Object.<string, Array.<ol.TileRange>>}
-   */
-  this.tileRanges_ = options.tileRanges ? options.tileRanges : null;
+  ol.events.unlistenByKey(this.layerRendererListeners_[layerKey]);
+  delete this.layerRendererListeners_[layerKey];
 
+  return layerRenderer;
 };
 
 
 /**
- * Get the attribution markup.
- * @return {string} The attribution HTML.
- * @api stable
+ * Render.
+ * @param {?olx.FrameState} frameState Frame state.
  */
-ol.Attribution.prototype.getHTML = function() {
-  return this.html_;
-};
+ol.renderer.Map.prototype.renderFrame = ol.nullFunction;
 
 
 /**
- * @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.
+ * @param {ol.Map} map Map.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
-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;
-        }
-      }
+ol.renderer.Map.prototype.removeUnusedLayerRenderers_ = function(map, frameState) {
+  var layerKey;
+  for (layerKey in this.layerRenderers_) {
+    if (!frameState || !(layerKey in frameState.layerStates)) {
+      this.removeLayerRendererByKey_(layerKey).dispose();
     }
   }
-  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
+ * @param {olx.FrameState} frameState Frame state.
+ * @protected
  */
-ol.CanvasFunctionType;
+ol.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) {
+  frameState.postRenderFunctions.push(
+    /** @type {ol.PostRenderFunction} */ (ol.renderer.Map.expireIconCache_)
+  );
+};
+
 
 /**
- * An implementation of Google Maps' MVCArray.
- * @see https://developers.google.com/maps/documentation/javascript/reference
+ * @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(
+        /** @type {ol.PostRenderFunction} */ (this.removeUnusedLayerRenderers_.bind(this))
+      );
+      return;
+    }
+  }
+};
 
-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');
+/**
+ * @param {ol.LayerState} state1 First layer state.
+ * @param {ol.LayerState} state2 Second layer state.
+ * @return {number} The zIndex difference.
+ */
+ol.renderer.Map.sortByZIndex = function(state1, state2) {
+  return state1.zIndex - state2.zIndex;
+};
+
+goog.provide('ol.renderer.Type');
 
 
 /**
+ * Available renderers: `'canvas'` or `'webgl'`.
  * @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'
+ol.renderer.Type = {
+  CANVAS: 'canvas',
+  WEBGL: 'webgl'
 };
 
+goog.provide('ol.render.Event');
+
+goog.require('ol');
+goog.require('ol.events.Event');
 
 
 /**
- * @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.
+ * @extends {ol.events.Event}
+ * @implements {oli.render.Event}
+ * @param {ol.render.EventType} type Type.
+ * @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.CollectionEvent = function(type, opt_element, opt_target) {
+ol.render.Event = function(
+    type, opt_vectorContext, opt_frameState, opt_context,
+    opt_glContext) {
 
-  goog.base(this, type, opt_target);
+  ol.events.Event.call(this, type);
 
   /**
-   * The element that is added to or removed from the collection.
-   * @type {*}
-   * @api stable
+   * For canvas, this is an instance of {@link ol.render.canvas.Immediate}.
+   * @type {ol.render.VectorContext|undefined}
+   * @api
    */
-  this.element = opt_element;
+  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.CollectionEvent, goog.events.Event);
+ol.inherits(ol.render.Event, ol.events.Event);
+
+goog.provide('ol.render.canvas');
 
 
 /**
- * @enum {string}
+ * @const
+ * @type {string}
  */
-ol.CollectionProperty = {
-  LENGTH: 'length'
-};
+ol.render.canvas.defaultFont = '10px sans-serif';
+
 
+/**
+ * @const
+ * @type {ol.Color}
+ */
+ol.render.canvas.defaultFillStyle = [0, 0, 0, 1];
 
 
 /**
- * @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
+ * @const
+ * @type {string}
  */
-ol.Collection.prototype.clear = function() {
-  while (this.getLength() > 0) {
-    this.pop();
-  }
-};
+ol.render.canvas.defaultLineCap = 'round';
 
 
 /**
- * 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
+ * @const
+ * @type {Array.<number>}
  */
-ol.Collection.prototype.extend = function(arr) {
-  var i, ii;
-  for (i = 0, ii = arr.length; i < ii; ++i) {
-    this.push(arr[i]);
-  }
-  return this;
-};
+ol.render.canvas.defaultLineDash = [];
 
 
 /**
- * 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
+ * @const
+ * @type {number}
  */
-ol.Collection.prototype.forEach = function(f, opt_this) {
-  this.array_.forEach(f, opt_this);
-};
+ol.render.canvas.defaultLineDashOffset = 0;
 
 
 /**
- * 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
+ * @const
+ * @type {string}
  */
-ol.Collection.prototype.getArray = function() {
-  return this.array_;
-};
+ol.render.canvas.defaultLineJoin = 'round';
 
 
 /**
- * Get the element at the provided index.
- * @param {number} index Index.
- * @return {T} Element.
- * @api stable
+ * @const
+ * @type {number}
  */
-ol.Collection.prototype.item = function(index) {
-  return this.array_[index];
-};
+ol.render.canvas.defaultMiterLimit = 10;
 
 
 /**
- * Get the length of this collection.
- * @return {number} The length of the array.
- * @observable
- * @api stable
+ * @const
+ * @type {ol.Color}
  */
-ol.Collection.prototype.getLength = function() {
-  return /** @type {number} */ (this.get(ol.CollectionProperty.LENGTH));
-};
+ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1];
 
 
 /**
- * Insert an element at the provided index.
- * @param {number} index Index.
- * @param {T} elem Element.
- * @api stable
+ * @const
+ * @type {string}
  */
-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));
-};
+ol.render.canvas.defaultTextAlign = 'center';
 
 
 /**
- * Remove the last element of the collection and return it.
- * Return `undefined` if the collection is empty.
- * @return {T|undefined} Element.
- * @api stable
+ * @const
+ * @type {string}
  */
-ol.Collection.prototype.pop = function() {
-  return this.removeAt(this.getLength() - 1);
-};
+ol.render.canvas.defaultTextBaseline = 'middle';
 
 
 /**
- * Insert the provided element at the end of the collection.
- * @param {T} elem Element.
- * @return {number} Length.
- * @api stable
+ * @const
+ * @type {number}
  */
-ol.Collection.prototype.push = function(elem) {
-  var n = this.array_.length;
-  this.insertAt(n, elem);
-  return n;
-};
+ol.render.canvas.defaultLineWidth = 1;
 
 
 /**
- * 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
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} rotation Rotation.
+ * @param {number} offsetX X offset.
+ * @param {number} offsetY Y offset.
  */
-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);
-    }
+ol.render.canvas.rotateAtOffset = function(context, rotation, offsetX, offsetY) {
+  if (rotation !== 0) {
+    context.translate(offsetX, offsetY);
+    context.rotate(rotation);
+    context.translate(-offsetX, -offsetY);
   }
-  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;
-};
+goog.provide('ol.render.VectorContext');
 
 
 /**
- * Set the element at the provided index.
- * @param {number} index Index.
- * @param {T} elem Element.
- * @api stable
+ * Context for drawing geometries.  A vector context is available on render
+ * events and does not need to be constructed directly.
+ * @constructor
+ * @abstract
+ * @struct
+ * @api
  */
-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);
-  }
+ol.render.VectorContext = function() {
 };
 
 
 /**
- * @private
+ * Render a geometry.
+ *
+ * @param {ol.geom.Geometry} geometry The geometry to render.
  */
-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'
-};
+ol.render.VectorContext.prototype.drawGeometry = function(geometry) {};
 
-// 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.
+ * Set the rendering style.
+ *
+ * @param {ol.style.Style} style The rendering style.
  */
-
-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');
+ol.render.VectorContext.prototype.setStyle = function(style) {};
 
 
 /**
- * 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>}
+ * @param {ol.geom.Circle} circleGeometry Circle geometry.
+ * @param {ol.Feature} feature Feature.
  */
-goog.color.Rgb;
+ol.render.VectorContext.prototype.drawCircle = function(circleGeometry, feature) {};
 
 
 /**
- * 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>}
+ * @param {ol.Feature} feature Feature.
+ * @param {ol.style.Style} style Style.
  */
-goog.color.Hsv;
+ol.render.VectorContext.prototype.drawFeature = function(feature, style) {};
 
 
 /**
- * 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>}
+ * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry
+ *     collection.
+ * @param {ol.Feature} feature Feature.
  */
-goog.color.Hsl;
+ol.render.VectorContext.prototype.drawGeometryCollection = function(geometryCollectionGeometry, feature) {};
 
 
 /**
- * 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').
+ * @param {ol.geom.LineString|ol.render.Feature} lineStringGeometry Line
+ *     string geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
  */
-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');
-};
+ol.render.VectorContext.prototype.drawLineString = function(lineStringGeometry, feature) {};
 
 
 /**
- * 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.
+ * @param {ol.geom.MultiLineString|ol.render.Feature} multiLineStringGeometry
+ *     MultiLineString geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
  */
-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()]);
-};
+ol.render.VectorContext.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {};
 
 
 /**
- * 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.
+ * @param {ol.geom.MultiPoint|ol.render.Feature} multiPointGeometry MultiPoint
+ *     geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
  */
-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;
-};
+ol.render.VectorContext.prototype.drawMultiPoint = function(multiPointGeometry, feature) {};
 
 
 /**
- * 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.
+ * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
  */
-goog.color.hexToRgbStyle = function(hexColor) {
-  return goog.color.rgbStyle_(goog.color.hexToRgb(hexColor));
-};
+ol.render.VectorContext.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {};
 
 
 /**
- * Regular expression for extracting the digits in a hex color triplet.
- * @type {RegExp}
- * @private
+ * @param {ol.geom.Point|ol.render.Feature} pointGeometry Point geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
  */
-goog.color.hexTripletRe_ = /#(.)(.)(.)/;
+ol.render.VectorContext.prototype.drawPoint = function(pointGeometry, feature) {};
 
 
 /**
- * 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.
+ * @param {ol.geom.Polygon|ol.render.Feature} polygonGeometry Polygon
+ *     geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
  */
-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();
-};
+ol.render.VectorContext.prototype.drawPolygon = function(polygonGeometry, feature) {};
 
 
 /**
- * Converts a hex representation of a color to RGB.
- * @param {string} hexColor Color to convert.
- * @return {!goog.color.Rgb} rgb representation of the color.
+ * @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.
  */
-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];
-};
+ol.render.VectorContext.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) {};
 
 
 /**
- * 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.
+ * @param {ol.style.Fill} fillStyle Fill style.
+ * @param {ol.style.Stroke} strokeStyle Stroke style.
  */
-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;
-};
+ol.render.VectorContext.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {};
 
 
 /**
- * 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.
+ * @param {ol.style.Image} imageStyle Image style.
  */
-goog.color.rgbArrayToHex = function(rgb) {
-  return goog.color.rgbToHex(rgb[0], rgb[1], rgb[2]);
-};
+ol.render.VectorContext.prototype.setImageStyle = function(imageStyle) {};
 
 
 /**
- * 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.
+ * @param {ol.style.Text} textStyle Text style.
  */
-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);
+ol.render.VectorContext.prototype.setTextStyle = function(textStyle) {};
 
-  // 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;
-    }
+// FIXME test, especially polygons with holes and multipolygons
+// FIXME need to handle large thick features (where pixel size matters)
+// FIXME add offset and end to ol.geom.flat.transform.transform2D?
 
-    if (0 < l && l <= 0.5) {
-      s = (max - min) / (2 * l);
-    } else {
-      s = (max - min) / (2 - 2 * l);
-    }
-  }
+goog.provide('ol.render.canvas.Immediate');
 
-  // Make sure the hue falls between 0 and 360.
-  return [Math.round(h + 360) % 360, s, l];
-};
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.colorlike');
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.SimpleGeometry');
+goog.require('ol.geom.flat.transform');
+goog.require('ol.has');
+goog.require('ol.render.VectorContext');
+goog.require('ol.render.canvas');
+goog.require('ol.transform');
 
 
 /**
- * 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.
+ * @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 {ol.Transform} transform Transform.
+ * @param {number} viewRotation View rotation.
+ * @struct
  */
-goog.color.rgbArrayToHsl = function(rgb) {
-  return goog.color.rgbToHsl(rgb[0], rgb[1], rgb[2]);
-};
+ol.render.canvas.Immediate = function(context, pixelRatio, extent, transform, viewRotation) {
+  ol.render.VectorContext.call(this);
 
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context_ = context;
 
-/**
- * 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;
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.pixelRatio_ = pixelRatio;
 
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.extent_ = extent;
 
-/**
- * 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]
+  /**
+   * @private
+   * @type {ol.Transform}
+   */
+  this.transform_ = transform;
 
-  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));
-  }
+  /**
+   * @private
+   * @type {number}
+   */
+  this.viewRotation_ = viewRotation;
 
-  return [Math.round(r), Math.round(g), Math.round(b)];
-};
+  /**
+   * @private
+   * @type {?ol.CanvasFillState}
+   */
+  this.contextFillState_ = null;
 
+  /**
+   * @private
+   * @type {?ol.CanvasStrokeState}
+   */
+  this.contextStrokeState_ = null;
 
-/**
- * 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]);
-};
+  /**
+   * @private
+   * @type {?ol.CanvasTextState}
+   */
+  this.contextTextState_ = null;
 
+  /**
+   * @private
+   * @type {?ol.CanvasFillState}
+   */
+  this.fillState_ = null;
 
-/**
- * Helper for isValidHexColor_.
- * @type {RegExp}
- * @private
- */
-goog.color.validHexColorRe_ = /^#(?:[0-9a-f]{3}){1,2}$/i;
+  /**
+   * @private
+   * @type {?ol.CanvasStrokeState}
+   */
+  this.strokeState_ = null;
 
+  /**
+   * @private
+   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
+   */
+  this.image_ = null;
 
-/**
- * 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);
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageAnchorX_ = 0;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageAnchorY_ = 0;
 
-/**
- * Helper for isNormalizedHexColor_.
- * @type {RegExp}
- * @private
- */
-goog.color.normalizedHexColorRe_ = /^#[0-9a-f]{6}$/;
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageHeight_ = 0;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageOpacity_ = 0;
 
-/**
- * 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);
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageOriginX_ = 0;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageOriginY_ = 0;
 
-/**
- * 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;
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.imageRotateWithView_ = false;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageRotation_ = 0;
 
-/**
- * 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 [];
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageScale_ = 0;
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.imageSnapToPixel_ = false;
 
-/**
- * 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;
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageWidth_ = 0;
 
+  /**
+   * @private
+   * @type {string}
+   */
+  this.text_ = '';
 
-/**
- * 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;
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textOffsetX_ = 0;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textOffsetY_ = 0;
 
-/**
- * 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(',') + ')';
-};
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.textRotateWithView_ = false;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textRotation_ = 0;
 
-/**
- * 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;
-    }
-  }
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textScale_ = 0;
 
-  return [Math.floor(red), Math.floor(green), Math.floor(blue)];
-};
+  /**
+   * @private
+   * @type {?ol.CanvasFillState}
+   */
+  this.textFillState_ = null;
 
+  /**
+   * @private
+   * @type {?ol.CanvasStrokeState}
+   */
+  this.textStrokeState_ = null;
 
-/**
- * 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) {
+  /**
+   * @private
+   * @type {?ol.CanvasTextState}
+   */
+  this.textState_ = null;
 
-  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;
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.pixelCoordinates_ = [];
 
-    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;
-    }
-  }
+  /**
+   * @private
+   * @type {ol.Transform}
+   */
+  this.tmpLocalTransform_ = ol.transform.create();
 
-  return [hue, saturation, value];
 };
+ol.inherits(ol.render.canvas.Immediate, ol.render.VectorContext);
 
 
 /**
- * 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.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @private
  */
-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]);
+ol.render.canvas.Immediate.prototype.drawImages_ = function(flatCoordinates, offset, end, stride) {
+  if (!this.image_) {
+    return;
+  }
+  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 = Math.round(x);
+      y = Math.round(y);
+    }
+    if (rotation !== 0 || this.imageScale_ != 1) {
+      var centerX = x + this.imageAnchorX_;
+      var centerY = y + this.imageAnchorY_;
+      ol.transform.compose(localTransform,
+          centerX, centerY,
+          this.imageScale_, this.imageScale_,
+          rotation,
+          -centerX, -centerY);
+      context.setTransform.apply(context, localTransform);
+    }
+    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;
+  }
 };
 
 
 /**
- * Converts a hex representation of a color to HSL.
- * @param {string} hex Color to convert.
- * @return {!goog.color.Hsv} hsv representation of the color.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @private
  */
-goog.color.hexToHsl = function(hex) {
-  var rgb = goog.color.hexToRgb(hex);
-  return goog.color.rgbToHsl(rgb[0], rgb[1], rgb[2]);
+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_);
+  var pixelCoordinates = ol.geom.flat.transform.transform2D(
+      flatCoordinates, offset, end, stride, this.transform_,
+      this.pixelCoordinates_);
+  var context = this.context_;
+  var rotation = this.textRotation_;
+  if (this.textRotateWithView_) {
+    rotation += this.viewRotation_;
+  }
+  for (; offset < end; offset += stride) {
+    var x = pixelCoordinates[offset] + this.textOffsetX_;
+    var y = pixelCoordinates[offset + 1] + this.textOffsetY_;
+    if (rotation !== 0 || this.textScale_ != 1) {
+      var localTransform = ol.transform.compose(this.tmpLocalTransform_,
+          x, y,
+          this.textScale_, this.textScale_,
+          rotation,
+          -x, -y);
+      context.setTransform.apply(context, localTransform);
+    }
+    if (this.textStrokeState_) {
+      context.strokeText(this.text_, x, y);
+    }
+    if (this.textFillState_) {
+      context.fillText(this.text_, x, y);
+    }
+  }
+  if (rotation !== 0 || this.textScale_ != 1) {
+    context.setTransform(1, 0, 0, 1, 0, 0);
+  }
 };
 
 
 /**
- * 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.
+ * @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.
  */
-goog.color.hslToHex = function(h, s, l) {
-  return goog.color.rgbArrayToHex(goog.color.hslToRgb(h, s, l));
+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 length = pixelCoordinates.length;
+  if (close) {
+    length -= 2;
+  }
+  for (var i = 2; i < length; i += 2) {
+    context.lineTo(pixelCoordinates[i], pixelCoordinates[i + 1]);
+  }
+  if (close) {
+    context.closePath();
+  }
+  return end;
 };
 
 
 /**
- * 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.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @private
+ * @return {number} End.
  */
-goog.color.hslArrayToHex = function(hsl) {
-  return goog.color.rgbArrayToHex(goog.color.hslToRgb(hsl[0], hsl[1], hsl[2]));
+ol.render.canvas.Immediate.prototype.drawRings_ = function(flatCoordinates, offset, ends, stride) {
+  var i, ii;
+  for (i = 0, ii = ends.length; i < ii; ++i) {
+    offset = this.moveToLineTo_(
+        flatCoordinates, offset, ends[i], stride, true);
+  }
+  return offset;
 };
 
 
 /**
- * Converts a hex representation of a color to HSV
- * @param {string} hex Color to convert.
- * @return {!goog.color.Hsv} hsv representation of the color.
+ * Render a circle geometry into the canvas.  Rendering is immediate and uses
+ * the current fill and stroke styles.
+ *
+ * @param {ol.geom.Circle} geometry Circle geometry.
+ * @override
+ * @api
  */
-goog.color.hexToHsv = function(hex) {
-  return goog.color.rgbArrayToHsv(goog.color.hexToRgb(hex));
+ol.render.canvas.Immediate.prototype.drawCircle = function(geometry) {
+  if (!ol.extent.intersects(this.extent_, geometry.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.SimpleGeometry.transform2D(
+        geometry, 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_(geometry.getCenter(), 0, 2, 2);
+  }
 };
 
 
 /**
- * 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.
+ * Set the rendering style.  Note that since this is an immediate rendering API,
+ * any `zIndex` on the provided style will be ignored.
+ *
+ * @param {ol.style.Style} style The rendering style.
+ * @override
+ * @api
  */
-goog.color.hsvToHex = function(h, s, v) {
-  return goog.color.rgbArrayToHex(goog.color.hsvToRgb(h, s, v));
+ol.render.canvas.Immediate.prototype.setStyle = function(style) {
+  this.setFillStrokeStyle(style.getFill(), style.getStroke());
+  this.setImageStyle(style.getImage());
+  this.setTextStyle(style.getText());
 };
 
 
 /**
- * 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.
+ * Render a geometry into the canvas.  Call
+ * {@link ol.render.canvas.Immediate#setStyle} first to set the rendering style.
+ *
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render.
+ * @override
+ * @api
  */
-goog.color.hsvArrayToHex = function(hsv) {
-  return goog.color.hsvToHex(hsv[0], hsv[1], hsv[2]);
+ol.render.canvas.Immediate.prototype.drawGeometry = function(geometry) {
+  var type = geometry.getType();
+  switch (type) {
+    case ol.geom.GeometryType.POINT:
+      this.drawPoint(/** @type {ol.geom.Point} */ (geometry));
+      break;
+    case ol.geom.GeometryType.LINE_STRING:
+      this.drawLineString(/** @type {ol.geom.LineString} */ (geometry));
+      break;
+    case ol.geom.GeometryType.POLYGON:
+      this.drawPolygon(/** @type {ol.geom.Polygon} */ (geometry));
+      break;
+    case ol.geom.GeometryType.MULTI_POINT:
+      this.drawMultiPoint(/** @type {ol.geom.MultiPoint} */ (geometry));
+      break;
+    case ol.geom.GeometryType.MULTI_LINE_STRING:
+      this.drawMultiLineString(/** @type {ol.geom.MultiLineString} */ (geometry));
+      break;
+    case ol.geom.GeometryType.MULTI_POLYGON:
+      this.drawMultiPolygon(/** @type {ol.geom.MultiPolygon} */ (geometry));
+      break;
+    case ol.geom.GeometryType.GEOMETRY_COLLECTION:
+      this.drawGeometryCollection(/** @type {ol.geom.GeometryCollection} */ (geometry));
+      break;
+    case ol.geom.GeometryType.CIRCLE:
+      this.drawCircle(/** @type {ol.geom.Circle} */ (geometry));
+      break;
+    default:
+  }
 };
 
 
 /**
- * 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].
+ * Render a feature into the canvas.  Note that any `zIndex` on the provided
+ * style will be ignored - features are rendered immediately in the order that
+ * this method is called.  If you need `zIndex` support, you should be using an
+ * {@link ol.layer.Vector} instead.
+ *
+ * @param {ol.Feature} feature Feature.
+ * @param {ol.style.Style} style Style.
+ * @override
+ * @api
  */
-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]);
+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 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);
+  this.setStyle(style);
+  this.drawGeometry(geometry);
 };
 
 
 /**
- * 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.
+ * 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} geometry Geometry collection.
+ * @override
  */
-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])
-  ];
+ol.render.canvas.Immediate.prototype.drawGeometryCollection = function(geometry) {
+  var geometries = geometry.getGeometriesArray();
+  var i, ii;
+  for (i = 0, ii = geometries.length; i < ii; ++i) {
+    this.drawGeometry(geometries[i]);
+  }
 };
 
 
 /**
- * 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.
+ * Render a Point geometry into the canvas.  Rendering is immediate and uses
+ * the current style.
+ *
+ * @param {ol.geom.Point|ol.render.Feature} geometry Point geometry.
+ * @override
  */
-goog.color.darken = function(rgb, factor) {
-  var black = [0, 0, 0];
-  return goog.color.blend(black, rgb, factor);
+ol.render.canvas.Immediate.prototype.drawPoint = function(geometry) {
+  var flatCoordinates = geometry.getFlatCoordinates();
+  var stride = geometry.getStride();
+  if (this.image_) {
+    this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride);
+  }
+  if (this.text_ !== '') {
+    this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride);
+  }
 };
 
 
 /**
- * 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.
+ * Render a MultiPoint geometry  into the canvas.  Rendering is immediate and
+ * uses the current style.
+ *
+ * @param {ol.geom.MultiPoint|ol.render.Feature} geometry MultiPoint geometry.
+ * @override
  */
-goog.color.lighten = function(rgb, factor) {
-  var white = [255, 255, 255];
-  return goog.color.blend(white, rgb, factor);
+ol.render.canvas.Immediate.prototype.drawMultiPoint = function(geometry) {
+  var flatCoordinates = geometry.getFlatCoordinates();
+  var stride = geometry.getStride();
+  if (this.image_) {
+    this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride);
+  }
+  if (this.text_ !== '') {
+    this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride);
+  }
 };
 
 
 /**
- * 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..
+ * Render a LineString into the canvas.  Rendering is immediate and uses
+ * the current style.
+ *
+ * @param {ol.geom.LineString|ol.render.Feature} geometry LineString geometry.
+ * @override
  */
-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)
-    });
+ol.render.canvas.Immediate.prototype.drawLineString = function(geometry) {
+  if (!ol.extent.intersects(this.extent_, geometry.getExtent())) {
+    return;
+  }
+  if (this.strokeState_) {
+    this.setContextStrokeState_(this.strokeState_);
+    var context = this.context_;
+    var flatCoordinates = geometry.getFlatCoordinates();
+    context.beginPath();
+    this.moveToLineTo_(flatCoordinates, 0, flatCoordinates.length,
+        geometry.getStride(), false);
+    context.stroke();
+  }
+  if (this.text_ !== '') {
+    var flatMidpoint = geometry.getFlatMidpoint();
+    this.drawText_(flatMidpoint, 0, 2, 2);
   }
-  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
+ * Render a MultiLineString geometry into the canvas.  Rendering is immediate
+ * and uses the current style.
+ *
+ * @param {ol.geom.MultiLineString|ol.render.Feature} geometry MultiLineString
+ *     geometry.
+ * @override
  */
-goog.color.yiqBrightness_ = function(rgb) {
-  return Math.round((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000);
+ol.render.canvas.Immediate.prototype.drawMultiLineString = function(geometry) {
+  var geometryExtent = geometry.getExtent();
+  if (!ol.extent.intersects(this.extent_, geometryExtent)) {
+    return;
+  }
+  if (this.strokeState_) {
+    this.setContextStrokeState_(this.strokeState_);
+    var context = this.context_;
+    var flatCoordinates = geometry.getFlatCoordinates();
+    var offset = 0;
+    var ends = geometry.getEnds();
+    var stride = geometry.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 = geometry.getFlatMidpoints();
+    this.drawText_(flatMidpoints, 0, flatMidpoints.length, 2);
+  }
 };
 
 
 /**
- * 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
+ * Render a Polygon geometry into the canvas.  Rendering is immediate and uses
+ * the current style.
+ *
+ * @param {ol.geom.Polygon|ol.render.Feature} geometry Polygon geometry.
+ * @override
  */
-goog.color.yiqBrightnessDiff_ = function(rgb1, rgb2) {
-  return Math.abs(goog.color.yiqBrightness_(rgb1) -
-                  goog.color.yiqBrightness_(rgb2));
+ol.render.canvas.Immediate.prototype.drawPolygon = function(geometry) {
+  if (!ol.extent.intersects(this.extent_, geometry.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_(geometry.getOrientedFlatCoordinates(),
+        0, geometry.getEnds(), geometry.getStride());
+    if (this.fillState_) {
+      context.fill();
+    }
+    if (this.strokeState_) {
+      context.stroke();
+    }
+  }
+  if (this.text_ !== '') {
+    var flatInteriorPoint = geometry.getFlatInteriorPoint();
+    this.drawText_(flatInteriorPoint, 0, 2, 2);
+  }
 };
 
 
 /**
- * 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
+ * Render MultiPolygon geometry into the canvas.  Rendering is immediate and
+ * uses the current style.
+ * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry.
+ * @override
  */
-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]);
+ol.render.canvas.Immediate.prototype.drawMultiPolygon = function(geometry) {
+  if (!ol.extent.intersects(this.extent_, geometry.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 = geometry.getOrientedFlatCoordinates();
+    var offset = 0;
+    var endss = geometry.getEndss();
+    var stride = geometry.getStride();
+    var i, ii;
+    context.beginPath();
+    for (i = 0, ii = endss.length; i < ii; ++i) {
+      var ends = endss[i];
+      offset = this.drawRings_(flatCoordinates, offset, ends, stride);
+    }
+    if (this.fillState_) {
+      context.fill();
+    }
+    if (this.strokeState_) {
+      context.stroke();
+    }
+  }
+  if (this.text_ !== '') {
+    var flatInteriorPoints = geometry.getFlatInteriorPoints();
+    this.drawText_(flatInteriorPoints, 0, flatInteriorPoints.length, 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}
+ * @param {ol.CanvasFillState} fillState Fill state.
  * @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;
+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;
+    }
+  }
+};
 
 
 /**
- * @see goog.color.alpha.rgbaColorRe_
- * @const
- * @type {RegExp}
+ * @param {ol.CanvasStrokeState} strokeState Stroke state.
  * @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;
+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 {
-    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;
+    if (contextStrokeState.lineCap != strokeState.lineCap) {
+      contextStrokeState.lineCap = context.lineCap = strokeState.lineCap;
+    }
+    if (ol.has.CANVAS_LINE_DASH) {
+      if (!ol.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;
     }
   }
-  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
+ * @param {ol.CanvasTextState} textState Text state.
+ * @private
  */
-ol.color.asArray = function(color) {
-  if (goog.isArray(color)) {
-    return color;
+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 {
-    goog.asserts.assert(goog.isString(color), 'Color should be a string');
-    return ol.color.fromString(color);
+    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;
+    }
   }
 };
 
 
 /**
- * Return the color as an rgba string.
- * @param {ol.Color|string} color Color.
- * @return {string} Rgba string.
- * @api
+ * 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.
+ * @override
  */
-ol.color.asString = function(color) {
-  if (goog.isString(color)) {
-    return color;
+ol.render.canvas.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
+  if (!fillStyle) {
+    this.fillState_ = null;
   } else {
-    goog.asserts.assert(goog.isArray(color), 'Color should be an array');
-    return ol.color.toString(color);
+    var fillStyleColor = fillStyle.getColor();
+    this.fillState_ = {
+      fillStyle: ol.colorlike.asColorLike(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 strokeStyleLineDashOffset = strokeStyle.getLineDashOffset();
+    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,
+      lineDashOffset: strokeStyleLineDashOffset ?
+          strokeStyleLineDashOffset : ol.render.canvas.defaultLineDashOffset,
+      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.colorlike.asColorLike(strokeStyleColor ?
+          strokeStyleColor : ol.render.canvas.defaultStrokeStyle)
+    };
   }
 };
 
 
 /**
- * @param {ol.Color} color1 Color1.
- * @param {ol.Color} color2 Color2.
- * @return {boolean} Equals.
+ * Set the image style for subsequent draw operations.  Pass null to remove
+ * the image style.
+ *
+ * @param {ol.style.Image} imageStyle Image style.
+ * @override
  */
-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]);
+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();
+    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];
+  }
 };
 
 
 /**
- * @param {string} s String.
- * @return {ol.Color} Color.
+ * Set the text style for subsequent draw operations.  Pass null to
+ * remove the text style.
+ *
+ * @param {ol.style.Text} textStyle Text style.
+ * @override
  */
-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 = {};
+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.colorlike.asColorLike(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 textStrokeStyleLineDashOffset = textStrokeStyle.getLineDashOffset();
+      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,
+        lineDashOffset: textStrokeStyleLineDashOffset ?
+            textStrokeStyleLineDashOffset : ol.render.canvas.defaultLineDashOffset,
+        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.colorlike.asColorLike(textStrokeStyleColor ?
+            textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle)
+      };
+    }
+    var textFont = textStyle.getFont();
+    var textOffsetX = textStyle.getOffsetX();
+    var textOffsetY = textStyle.getOffsetY();
+    var textRotateWithView = textStyle.getRotateWithView();
+    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.textRotateWithView_ = textRotateWithView !== undefined ? textRotateWithView : false;
+    this.textRotation_ = textRotation !== undefined ? textRotation : 0;
+    this.textScale_ = this.pixelRatio_ * (textScale !== undefined ?
+        textScale : 1);
+  }
+};
 
-      /**
-       * @type {number}
-       */
-      var cacheSize = 0;
+// FIXME offset panning
 
-      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;
-          });
+goog.provide('ol.renderer.canvas.Map');
 
-    })();
+goog.require('ol.transform');
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.css');
+goog.require('ol.dom');
+goog.require('ol.layer.Layer');
+goog.require('ol.render.Event');
+goog.require('ol.render.EventType');
+goog.require('ol.render.canvas');
+goog.require('ol.render.canvas.Immediate');
+goog.require('ol.renderer.Map');
+goog.require('ol.renderer.Type');
+goog.require('ol.source.State');
 
 
 /**
- * @param {string} s String.
- * @private
- * @return {ol.Color} Color.
+ * @constructor
+ * @extends {ol.renderer.Map}
+ * @param {Element} container Container.
+ * @param {ol.Map} map Map.
  */
-ol.color.fromStringInternal_ = function(s) {
+ol.renderer.canvas.Map = function(container, map) {
 
-  var isHex = false;
-  if (ol.ENABLE_NAMED_COLORS && goog.color.names.hasOwnProperty(s)) {
-    // goog.color.names does not have a type declaration, so add a typecast
-    s = /** @type {string} */ (goog.color.names[s]);
-    isHex = true;
-  }
+  ol.renderer.Map.call(this, container, map);
 
-  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');
-  }
+  /**
+   * @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_.style.display = 'block';
+  this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
+  container.insertBefore(this.canvas_, container.childNodes[0] || null);
 
-/**
- * @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;
-};
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.renderedVisible_ = true;
 
+  /**
+   * @private
+   * @type {ol.Transform}
+   */
+  this.transform_ = ol.transform.create();
 
-/**
- * @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;
 };
+ol.inherits(ol.renderer.canvas.Map, ol.renderer.Map);
 
 
 /**
- * @param {ol.Color} color Color.
- * @return {string} String.
+ * @param {ol.render.EventType} type Event type.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
-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;
+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, vectorContext,
+        frameState, context, null);
+    map.dispatchEvent(composeEvent);
   }
-  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.
+ * @param {olx.FrameState} frameState Frame state.
+ * @protected
+ * @return {!ol.Transform} Transform.
  */
-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);
+ol.renderer.canvas.Map.prototype.getTransform = function(frameState) {
+  var viewState = frameState.viewState;
+  var dx1 = this.canvas_.width / 2;
+  var dy1 = this.canvas_.height / 2;
+  var sx = frameState.pixelRatio / viewState.resolution;
+  var sy = -sx;
+  var angle = -viewState.rotation;
+  var dx2 = -viewState.center[0];
+  var dy2 = -viewState.center[1];
+  return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2);
 };
 
 
 /**
- * @param {ol.Color|string} color1 Color2.
- * @param {ol.Color|string} color2 Color2.
- * @return {boolean} Equals.
+ * @inheritDoc
  */
-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);
+ol.renderer.canvas.Map.prototype.getType = function() {
+  return ol.renderer.Type.CANVAS;
 };
 
-// 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.
- *
+ * @inheritDoc
  */
+ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
 
+  if (!frameState) {
+    if (this.renderedVisible_) {
+      this.canvas_.style.display = 'none';
+      this.renderedVisible_ = false;
+    }
+    return;
+  }
 
-goog.provide('goog.dom.BrowserFeature');
+  var context = this.context_;
+  var pixelRatio = frameState.pixelRatio;
+  var width = Math.round(frameState.size[0] * pixelRatio);
+  var height = Math.round(frameState.size[1] * pixelRatio);
+  if (this.canvas_.width != width || this.canvas_.height != height) {
+    this.canvas_.width = width;
+    this.canvas_.height = height;
+  } else {
+    context.clearRect(0, 0, width, height);
+  }
 
-goog.require('goog.userAgent');
+  var rotation = frameState.viewState.rotation;
 
+  this.calculateMatrices2D(frameState);
 
-/**
- * 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),
+  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
 
-  /**
-   * Whether we can use element.children to access an element's Element
-   * children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment
-   * nodes in the collection.)
-   */
-  CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE ||
-      goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9) ||
-      goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'),
+  var layerStatesArray = frameState.layerStatesArray;
+  ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);
 
-  /**
-   * Opera, Safari 3, and Internet Explorer 9 all support innerText but they
-   * include text nodes in script and style tags. Not document-mode-dependent.
-   */
-  CAN_USE_INNER_TEXT: (
-      goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')),
+  if (rotation) {
+    context.save();
+    ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2);
+  }
 
-  /**
-   * 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,
+  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.canvas.Layer} */ (this.getLayerRenderer(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);
+    }
+  }
 
-  /**
-   * 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,
+  if (rotation) {
+    context.restore();
+  }
 
-  /**
-   * Whether we use legacy IE range API.
-   */
-  LEGACY_IE_RANGES: goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)
-};
+  this.dispatchComposeEvent_(
+      ol.render.EventType.POSTCOMPOSE, frameState);
 
-// 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.
+  if (!this.renderedVisible_) {
+    this.canvas_.style.display = '';
+    this.renderedVisible_ = true;
+  }
 
-/**
- * @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');
+  this.scheduleRemoveUnusedLayerRenderers(frameState);
+  this.scheduleExpireIconCache(frameState);
+};
 
 
 /**
- * Enum of all html tag names specified by the W3C HTML4.01 and HTML5
- * specifications.
- * @enum {string}
+ * @inheritDoc
  */
-goog.dom.TagName = {
-  A: 'A',
-  ABBR: 'ABBR',
-  ACRONYM: 'ACRONYM',
-  ADDRESS: 'ADDRESS',
-  APPLET: 'APPLET',
-  AREA: 'AREA',
-  ARTICLE: 'ARTICLE',
-  ASIDE: 'ASIDE',
-  AUDIO: 'AUDIO',
-  B: 'B',
-  BASE: 'BASE',
-  BASEFONT: 'BASEFONT',
-  BDI: 'BDI',
-  BDO: 'BDO',
-  BIG: 'BIG',
-  BLOCKQUOTE: 'BLOCKQUOTE',
-  BODY: 'BODY',
-  BR: 'BR',
-  BUTTON: 'BUTTON',
-  CANVAS: 'CANVAS',
-  CAPTION: 'CAPTION',
-  CENTER: 'CENTER',
-  CITE: 'CITE',
-  CODE: 'CODE',
-  COL: 'COL',
-  COLGROUP: 'COLGROUP',
-  COMMAND: 'COMMAND',
-  DATA: 'DATA',
-  DATALIST: 'DATALIST',
-  DD: 'DD',
-  DEL: 'DEL',
-  DETAILS: 'DETAILS',
-  DFN: 'DFN',
-  DIALOG: 'DIALOG',
-  DIR: 'DIR',
-  DIV: 'DIV',
-  DL: 'DL',
-  DT: 'DT',
-  EM: 'EM',
-  EMBED: 'EMBED',
-  FIELDSET: 'FIELDSET',
-  FIGCAPTION: 'FIGCAPTION',
-  FIGURE: 'FIGURE',
-  FONT: 'FONT',
-  FOOTER: 'FOOTER',
-  FORM: 'FORM',
-  FRAME: 'FRAME',
-  FRAMESET: 'FRAMESET',
-  H1: 'H1',
-  H2: 'H2',
-  H3: 'H3',
-  H4: 'H4',
-  H5: 'H5',
-  H6: 'H6',
-  HEAD: 'HEAD',
-  HEADER: 'HEADER',
-  HGROUP: 'HGROUP',
-  HR: 'HR',
-  HTML: 'HTML',
-  I: 'I',
-  IFRAME: 'IFRAME',
-  IMG: 'IMG',
-  INPUT: 'INPUT',
-  INS: 'INS',
-  ISINDEX: 'ISINDEX',
-  KBD: 'KBD',
-  KEYGEN: 'KEYGEN',
-  LABEL: 'LABEL',
-  LEGEND: 'LEGEND',
-  LI: 'LI',
-  LINK: 'LINK',
-  MAP: 'MAP',
-  MARK: 'MARK',
-  MATH: 'MATH',
-  MENU: 'MENU',
-  META: 'META',
-  METER: 'METER',
-  NAV: 'NAV',
-  NOFRAMES: 'NOFRAMES',
-  NOSCRIPT: 'NOSCRIPT',
-  OBJECT: 'OBJECT',
-  OL: 'OL',
-  OPTGROUP: 'OPTGROUP',
-  OPTION: 'OPTION',
-  OUTPUT: 'OUTPUT',
-  P: 'P',
-  PARAM: 'PARAM',
-  PRE: 'PRE',
-  PROGRESS: 'PROGRESS',
-  Q: 'Q',
-  RP: 'RP',
-  RT: 'RT',
-  RUBY: 'RUBY',
-  S: 'S',
-  SAMP: 'SAMP',
-  SCRIPT: 'SCRIPT',
-  SECTION: 'SECTION',
-  SELECT: 'SELECT',
-  SMALL: 'SMALL',
-  SOURCE: 'SOURCE',
-  SPAN: 'SPAN',
-  STRIKE: 'STRIKE',
-  STRONG: 'STRONG',
-  STYLE: 'STYLE',
-  SUB: 'SUB',
-  SUMMARY: 'SUMMARY',
-  SUP: 'SUP',
-  SVG: 'SVG',
-  TABLE: 'TABLE',
-  TBODY: 'TBODY',
-  TD: 'TD',
-  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.
+ol.renderer.canvas.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg,
+        layerFilter, thisArg2) {
+  var result;
+  var viewState = frameState.viewState;
+  var viewResolution = viewState.resolution;
 
-/**
- * @fileoverview Utilities for HTML element tag names.
- */
-goog.provide('goog.dom.tags');
+  var layerStates = frameState.layerStatesArray;
+  var numLayers = layerStates.length;
 
-goog.require('goog.object');
+  var coordinate = ol.transform.apply(
+      frameState.pixelToCoordinateTransform, pixel.slice());
 
+  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 = /** @type {ol.renderer.canvas.Layer} */ (this.getLayerRenderer(layer));
+      result = layerRenderer.forEachLayerAtCoordinate(
+          coordinate, frameState, callback, thisArg);
+      if (result) {
+        return result;
+      }
+    }
+  }
+  return undefined;
+};
 
-/**
- * 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');
+goog.provide('ol.render.ReplayType');
 
 
 /**
- * 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}
+ * @enum {string}
  */
-goog.dom.tags.isVoidTag = function(tagName) {
-  return goog.dom.tags.VOID_TAGS_[tagName] === true;
+ol.render.ReplayType = {
+  CIRCLE: 'Circle',
+  IMAGE: 'Image',
+  LINE_STRING: 'LineString',
+  POLYGON: 'Polygon',
+  TEXT: 'Text'
 };
 
-// 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('ol.render.replay');
 
-goog.provide('goog.string.TypedString');
+goog.require('ol.render.ReplayType');
+
+
+/**
+ * @const
+ * @type {Array.<ol.render.ReplayType>}
+ */
+ol.render.replay.ORDER = [
+  ol.render.ReplayType.POLYGON,
+  ol.render.ReplayType.CIRCLE,
+  ol.render.ReplayType.LINE_STRING,
+  ol.render.ReplayType.IMAGE,
+  ol.render.ReplayType.TEXT
+];
 
+goog.provide('ol.render.ReplayGroup');
 
 
 /**
- * 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
+ * Base class for replay groups.
+ * @constructor
+ * @abstract
  */
-goog.string.TypedString = function() {};
+ol.render.ReplayGroup = 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}
+ * @abstract
+ * @param {number|undefined} zIndex Z index.
+ * @param {ol.render.ReplayType} replayType Replay type.
+ * @return {ol.render.VectorContext} Replay.
  */
-goog.string.TypedString.prototype.implementsGoogStringTypedString;
+ol.render.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {};
 
 
 /**
- * Retrieves this wrapped string's value.
- * @return {!string} The wrapped string's value.
+ * @abstract
+ * @return {boolean} Is empty.
  */
-goog.string.TypedString.prototype.getTypedStringValue;
+ol.render.ReplayGroup.prototype.isEmpty = function() {};
 
-// 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('ol.webgl.Shader');
 
-goog.provide('goog.string.Const');
+goog.require('ol');
+goog.require('ol.functions');
 
-goog.require('goog.asserts');
-goog.require('goog.string.TypedString');
 
+if (ol.ENABLE_WEBGL) {
+
+  /**
+   * @constructor
+   * @abstract
+   * @param {string} source Source.
+   * @struct
+   */
+  ol.webgl.Shader = function(source) {
+
+    /**
+     * @private
+     * @type {string}
+     */
+    this.source_ = source;
+
+  };
 
 
-/**
- * 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}
+   * @abstract
+   * @return {number} Type.
    */
-  this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ = '';
+  ol.webgl.Shader.prototype.getType = function() {};
+
 
   /**
-   * A type marker used to implement additional run-time type checking.
-   * @see goog.string.Const#unwrap
-   * @const
-   * @private
+   * @return {string} Source.
    */
-  this.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ =
-      goog.string.Const.TYPE_MARKER_;
-};
+  ol.webgl.Shader.prototype.getSource = function() {
+    return this.source_;
+  };
 
 
-/**
- * @override
- * @const
- */
-goog.string.Const.prototype.implementsGoogStringTypedString = true;
+  /**
+   * @return {boolean} Is animated?
+   */
+  ol.webgl.Shader.prototype.isAnimated = ol.functions.FALSE;
 
+}
 
-/**
- * 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_;
-};
+goog.provide('ol.webgl.Fragment');
 
+goog.require('ol');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Shader');
 
-/**
- * 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';
-  }
-};
 
+if (ol.ENABLE_WEBGL) {
 
-/**
- * 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);
-};
+  /**
+   * @constructor
+   * @extends {ol.webgl.Shader}
+   * @param {string} source Source.
+   * @struct
+   */
+  ol.webgl.Fragment = function(source) {
+    ol.webgl.Shader.call(this, source);
+  };
+  ol.inherits(ol.webgl.Fragment, ol.webgl.Shader);
 
 
-/**
- * Type marker for the Const type, used to implement additional run-time
- * type checking.
- * @const {!Object}
- * @private
- */
-goog.string.Const.TYPE_MARKER_ = {};
+  /**
+   * @inheritDoc
+   */
+  ol.webgl.Fragment.prototype.getType = function() {
+    return ol.webgl.FRAGMENT_SHADER;
+  };
 
+}
 
-/**
- * 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;
-};
+goog.provide('ol.webgl.Vertex');
 
-// Copyright 2014 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+goog.require('ol');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Shader');
+
+
+if (ol.ENABLE_WEBGL) {
 
-/**
- * @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}
+   * @constructor
+   * @extends {ol.webgl.Shader}
+   * @param {string} source Source.
+   * @struct
    */
-  this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = '';
+  ol.webgl.Vertex = function(source) {
+    ol.webgl.Shader.call(this, source);
+  };
+  ol.inherits(ol.webgl.Vertex, ol.webgl.Shader);
+
 
   /**
-   * A type marker used to implement additional run-time type checking.
-   * @see goog.html.SafeStyle#unwrap
-   * @const
-   * @private
+   * @inheritDoc
    */
-  this.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
-      goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
-};
+  ol.webgl.Vertex.prototype.getType = function() {
+    return ol.webgl.VERTEX_SHADER;
+  };
 
+}
 
-/**
- * @override
- * @const
- */
-goog.html.SafeStyle.prototype.implementsGoogStringTypedString = true;
+// This file is automatically generated, do not edit
+/* eslint openlayers-internal/no-missing-requires: 0 */
+goog.provide('ol.render.webgl.circlereplay.defaultshader');
 
+goog.require('ol');
+goog.require('ol.webgl.Fragment');
+goog.require('ol.webgl.Vertex');
 
-/**
- * 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_ = {};
+if (ol.ENABLE_WEBGL) {
 
+  /**
+   * @constructor
+   * @extends {ol.webgl.Fragment}
+   * @struct
+   */
+  ol.render.webgl.circlereplay.defaultshader.Fragment = function() {
+    ol.webgl.Fragment.call(this, ol.render.webgl.circlereplay.defaultshader.Fragment.SOURCE);
+  };
+  ol.inherits(ol.render.webgl.circlereplay.defaultshader.Fragment, ol.webgl.Fragment);
 
-/**
- * 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);
-};
+
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.circlereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_center;\nvarying vec2 v_offset;\nvarying float v_halfWidth;\nvarying float v_pixelRatio;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_fillColor;\nuniform vec4 u_strokeColor;\nuniform vec2 u_size;\n\nvoid main(void) {\n  vec2 windowCenter = vec2((v_center.x + 1.0) / 2.0 * u_size.x * v_pixelRatio,\n      (v_center.y + 1.0) / 2.0 * u_size.y * v_pixelRatio);\n  vec2 windowOffset = vec2((v_offset.x + 1.0) / 2.0 * u_size.x * v_pixelRatio,\n      (v_offset.y + 1.0) / 2.0 * u_size.y * v_pixelRatio);\n  float radius = length(windowCenter - windowOffset);\n  float dist = length(windowCenter - gl_FragCoord.xy);\n  if (dist > radius + v_halfWidth) {\n    if (u_strokeColor.a == 0.0) {\n      gl_FragColor = u_fillColor;\n    } else {\n      gl_FragColor = u_strokeColor;\n    }\n    gl_FragColor.a = gl_FragColor.a - (dist - (radius + v_halfWidth));\n  } else if (u_fillColor.a == 0.0) {\n    // Hooray, no fill, just stroke. We can use real antialiasing.\n    gl_FragColor = u_strokeColor;\n    if (dist < radius - v_halfWidth) {\n      gl_FragColor.a = gl_FragColor.a - (radius - v_halfWidth - dist);\n    }\n  } else {\n    gl_FragColor = u_fillColor;\n    float strokeDist = radius - v_halfWidth;\n    float antialias = 2.0 * v_pixelRatio;\n    if (dist > strokeDist) {\n      gl_FragColor = u_strokeColor;\n    } else if (dist >= strokeDist - antialias) {\n      float step = smoothstep(strokeDist - antialias, strokeDist, dist);\n      gl_FragColor = mix(u_fillColor, u_strokeColor, step);\n    }\n  }\n  gl_FragColor.a = gl_FragColor.a * u_opacity;\n  if (gl_FragColor.a <= 0.0) {\n    discard;\n  }\n}\n';
 
 
-/**
- * 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);
-};
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.circlereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying vec2 b;varying float c;varying float d;uniform float m;uniform vec4 n;uniform vec4 o;uniform vec2 p;void main(void){vec2 windowCenter=vec2((a.x+1.0)/2.0*p.x*d,(a.y+1.0)/2.0*p.y*d);vec2 windowOffset=vec2((b.x+1.0)/2.0*p.x*d,(b.y+1.0)/2.0*p.y*d);float radius=length(windowCenter-windowOffset);float dist=length(windowCenter-gl_FragCoord.xy);if(dist>radius+c){if(o.a==0.0){gl_FragColor=n;}else{gl_FragColor=o;}gl_FragColor.a=gl_FragColor.a-(dist-(radius+c));}else if(n.a==0.0){gl_FragColor=o;if(dist<radius-c){gl_FragColor.a=gl_FragColor.a-(radius-c-dist);}} else{gl_FragColor=n;float strokeDist=radius-c;float antialias=2.0*d;if(dist>strokeDist){gl_FragColor=o;}else if(dist>=strokeDist-antialias){float step=smoothstep(strokeDist-antialias,strokeDist,dist);gl_FragColor=mix(n,o,step);}} gl_FragColor.a=gl_FragColor.a*m;if(gl_FragColor.a<=0.0){discard;}}';
 
 
-/**
- * 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_;
-};
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.circlereplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ?
+      ol.render.webgl.circlereplay.defaultshader.Fragment.DEBUG_SOURCE :
+      ol.render.webgl.circlereplay.defaultshader.Fragment.OPTIMIZED_SOURCE;
+
+
+  ol.render.webgl.circlereplay.defaultshader.fragment = new ol.render.webgl.circlereplay.defaultshader.Fragment();
 
 
-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
+   * @constructor
+   * @extends {ol.webgl.Vertex}
+   * @struct
    */
-  goog.html.SafeStyle.prototype.toString = function() {
-    return 'SafeStyle{' +
-        this.privateDoNotAccessOrElseSafeStyleWrappedValue_ + '}';
+  ol.render.webgl.circlereplay.defaultshader.Vertex = function() {
+    ol.webgl.Vertex.call(this, ol.render.webgl.circlereplay.defaultshader.Vertex.SOURCE);
   };
-}
+  ol.inherits(ol.render.webgl.circlereplay.defaultshader.Vertex, ol.webgl.Vertex);
 
 
-/**
- * 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';
-  }
-};
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.circlereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_center;\nvarying vec2 v_offset;\nvarying float v_halfWidth;\nvarying float v_pixelRatio;\n\n\nattribute vec2 a_position;\nattribute float a_instruction;\nattribute float a_radius;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\nuniform float u_lineWidth;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n  mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n  v_center = vec4(u_projectionMatrix * vec4(a_position, 0.0, 1.0)).xy;\n  v_pixelRatio = u_pixelRatio;\n  float lineWidth = u_lineWidth * u_pixelRatio;\n  v_halfWidth = lineWidth / 2.0;\n  if (lineWidth == 0.0) {\n    lineWidth = 2.0 * u_pixelRatio;\n  }\n  vec2 offset;\n  // Radius with anitaliasing (roughly).\n  float radius = a_radius + 3.0 * u_pixelRatio;\n  // Until we get gl_VertexID in WebGL, we store an instruction.\n  if (a_instruction == 0.0) {\n    // Offsetting the edges of the triangle by lineWidth / 2 is necessary, however\n    // we should also leave some space for the antialiasing, thus we offset by lineWidth.\n    offset = vec2(-1.0, 1.0);\n  } else if (a_instruction == 1.0) {\n    offset = vec2(-1.0, -1.0);\n  } else if (a_instruction == 2.0) {\n    offset = vec2(1.0, -1.0);\n  } else {\n    offset = vec2(1.0, 1.0);\n  }\n\n  gl_Position = u_projectionMatrix * vec4(a_position + offset * radius, 0.0, 1.0) +\n      offsetMatrix * vec4(offset * lineWidth, 0.0, 0.0);\n  v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y,\n      0.0, 1.0)).xy;\n\n  if (distance(v_center, v_offset) > 20000.0) {\n    gl_Position = vec4(v_center, 0.0, 1.0);\n  }\n}\n\n\n';
 
 
-/**
- * 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);
-};
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.circlereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying vec2 b;varying float c;varying float d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;void main(void){mat4 offsetMatrix=i*j;a=vec4(h*vec4(e,0.0,1.0)).xy;d=l;float lineWidth=k*l;c=lineWidth/2.0;if(lineWidth==0.0){lineWidth=2.0*l;}vec2 offset;float radius=g+3.0*l;if(f==0.0){offset=vec2(-1.0,1.0);}else if(f==1.0){offset=vec2(-1.0,-1.0);}else if(f==2.0){offset=vec2(1.0,-1.0);}else{offset=vec2(1.0,1.0);}gl_Position=h*vec4(e+offset*radius,0.0,1.0)+offsetMatrix*vec4(offset*lineWidth,0.0,0.0);b=vec4(h*vec4(e.x+g,e.y,0.0,1.0)).xy;if(distance(a,b)>20000.0){gl_Position=vec4(a,0.0,1.0);}}';
 
 
-/**
- * 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;
-};
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.circlereplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ?
+      ol.render.webgl.circlereplay.defaultshader.Vertex.DEBUG_SOURCE :
+      ol.render.webgl.circlereplay.defaultshader.Vertex.OPTIMIZED_SOURCE;
 
 
-/**
- * A SafeStyle instance corresponding to the empty string.
- * @const {!goog.html.SafeStyle}
- */
-goog.html.SafeStyle.EMPTY =
-    goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse('');
+  ol.render.webgl.circlereplay.defaultshader.vertex = new ol.render.webgl.circlereplay.defaultshader.Vertex();
 
 
-/**
- * The innocuous string generated by goog.html.SafeUrl.create when passed
- * an unsafe value.
- * @const {string}
- */
-goog.html.SafeStyle.INNOCUOUS_STRING = 'zClosurez';
+  /**
+   * @constructor
+   * @param {WebGLRenderingContext} gl GL.
+   * @param {WebGLProgram} program Program.
+   * @struct
+   */
+  ol.render.webgl.circlereplay.defaultshader.Locations = function(gl, program) {
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_fillColor = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_fillColor' : 'n');
 
-/**
- * Mapping of property names to their values.
- * @typedef {!Object<string, goog.string.Const|string>}
- */
-goog.html.SafeStyle.PropertyMap;
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_lineWidth = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_lineWidth' : 'k');
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_offsetRotateMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j');
 
-/**
- * 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);
-};
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_offsetScaleMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i');
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_opacity = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_opacity' : 'm');
 
-/**
- * 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;
-};
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_pixelRatio = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_pixelRatio' : 'l');
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_projectionMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h');
 
-// 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]+$/;
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_size = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_size' : 'p');
+
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_strokeColor = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_strokeColor' : 'o');
 
+    /**
+     * @type {number}
+     */
+    this.a_instruction = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_instruction' : 'f');
 
-/**
- * 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 = '';
+    /**
+     * @type {number}
+     */
+    this.a_position = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_position' : 'e');
 
-  /**
-   * @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);
-    }
+    /**
+     * @type {number}
+     */
+    this.a_radius = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_radius' : 'g');
   };
 
-  goog.array.forEach(arguments, addArgument);
-  if (!style) {
-    return goog.html.SafeStyle.EMPTY;
-  }
-  return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
-      style);
-};
+}
+
+goog.provide('ol.vec.Mat4');
 
-// 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.
+ * @return {Array.<number>} 4x4 matrix representing a 3D identity transform.
  */
+ol.vec.Mat4.create = function() {
+  return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
+};
 
-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');
+/**
+ * @param {Array.<number>} mat4 Flattened 4x4 matrix receiving the result.
+ * @param {ol.Transform} transform Transformation matrix.
+ * @return {Array.<number>} 2D transformation matrix as flattened 4x4 matrix.
+ */
+ol.vec.Mat4.fromTransform = function(mat4, transform) {
+  mat4[0] = transform[0];
+  mat4[1] = transform[1];
+  mat4[4] = transform[2];
+  mat4[5] = transform[3];
+  mat4[12] = transform[4];
+  mat4[13] = transform[5];
+  return mat4;
+};
 
+goog.provide('ol.render.webgl.Replay');
 
+goog.require('ol');
+goog.require('ol.extent');
+goog.require('ol.render.VectorContext');
+goog.require('ol.transform');
+goog.require('ol.vec.Mat4');
+goog.require('ol.webgl');
 
-/**
- * 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_ = '';
+if (ol.ENABLE_WEBGL) {
 
   /**
-   * A type marker used to implement additional run-time type checking.
-   * @see goog.html.SafeStyleSheet#unwrap
-   * @const
-   * @private
+   * @constructor
+   * @abstract
+   * @extends {ol.render.VectorContext}
+   * @param {number} tolerance Tolerance.
+   * @param {ol.Extent} maxExtent Max extent.
+   * @struct
    */
-  this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
-      goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
-};
+  ol.render.webgl.Replay = function(tolerance, maxExtent) {
+    ol.render.VectorContext.call(this);
 
+    /**
+     * @protected
+     * @type {number}
+     */
+    this.tolerance = tolerance;
 
-/**
- * @override
- * @const
- */
-goog.html.SafeStyleSheet.prototype.implementsGoogStringTypedString = true;
+    /**
+     * @protected
+     * @const
+     * @type {ol.Extent}
+     */
+    this.maxExtent = maxExtent;
 
+    /**
+     * 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.
+     * @protected
+     * @type {ol.Coordinate}
+     */
+    this.origin = ol.extent.getCenter(maxExtent);
 
-/**
- * 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_ = {};
+    /**
+     * @private
+     * @type {ol.Transform}
+     */
+    this.projectionMatrix_ = ol.transform.create();
 
+    /**
+     * @private
+     * @type {ol.Transform}
+     */
+    this.offsetRotateMatrix_ = ol.transform.create();
 
-/**
- * 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 = '';
+    /**
+     * @private
+     * @type {ol.Transform}
+     */
+    this.offsetScaleMatrix_ = ol.transform.create();
 
-  /**
-   * @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);
-    }
-  };
+    /**
+     * @private
+     * @type {Array.<number>}
+     */
+    this.tmpMat4_ = ol.vec.Mat4.create();
 
-  goog.array.forEach(arguments, addArgument);
-  return goog.html.SafeStyleSheet
-      .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(result);
-};
+    /**
+     * @protected
+     * @type {Array.<number>}
+     */
+    this.indices = [];
 
+    /**
+     * @protected
+     * @type {?ol.webgl.Buffer}
+     */
+    this.indicesBuffer = null;
 
-/**
- * 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);
-};
+    /**
+     * Start index per feature (the index).
+     * @protected
+     * @type {Array.<number>}
+     */
+    this.startIndices = [];
 
+    /**
+     * Start index per feature (the feature).
+     * @protected
+     * @type {Array.<ol.Feature|ol.render.Feature>}
+     */
+    this.startIndicesFeature = [];
 
-/**
- * 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_;
-};
+    /**
+     * @protected
+     * @type {Array.<number>}
+     */
+    this.vertices = [];
+
+    /**
+     * @protected
+     * @type {?ol.webgl.Buffer}
+     */
+    this.verticesBuffer = null;
+
+    /**
+     * Optional parameter for PolygonReplay instances.
+     * @protected
+     * @type {ol.render.webgl.LineStringReplay|undefined}
+     */
+    this.lineStringReplay = undefined;
+
+  };
+  ol.inherits(ol.render.webgl.Replay, ol.render.VectorContext);
 
 
-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
+   * @abstract
+   * @param {ol.webgl.Context} context WebGL context.
+   * @return {function()} Delete resources function.
    */
-  goog.html.SafeStyleSheet.prototype.toString = function() {
-    return 'SafeStyleSheet{' +
-        this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ + '}';
-  };
-}
+  ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = function(context) {};
 
 
-/**
- * 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';
-  }
-};
+  /**
+   * @abstract
+   * @param {ol.webgl.Context} context Context.
+   */
+  ol.render.webgl.Replay.prototype.finish = function(context) {};
 
 
-/**
- * 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);
-};
+  /**
+   * @abstract
+   * @protected
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {ol.webgl.Context} context Context.
+   * @param {ol.Size} size Size.
+   * @param {number} pixelRatio Pixel ratio.
+   * @return {ol.render.webgl.circlereplay.defaultshader.Locations|
+              ol.render.webgl.imagereplay.defaultshader.Locations|
+              ol.render.webgl.linestringreplay.defaultshader.Locations|
+              ol.render.webgl.polygonreplay.defaultshader.Locations} Locations.
+   */
+  ol.render.webgl.Replay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {};
 
 
-/**
- * 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;
-};
+  /**
+   * @abstract
+   * @protected
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {ol.render.webgl.circlereplay.defaultshader.Locations|
+             ol.render.webgl.imagereplay.defaultshader.Locations|
+             ol.render.webgl.linestringreplay.defaultshader.Locations|
+             ol.render.webgl.polygonreplay.defaultshader.Locations} locations Locations.
+   */
+  ol.render.webgl.Replay.prototype.shutDownProgram = function(gl, locations) {};
 
 
-/**
- * A SafeStyleSheet instance corresponding to the empty string.
- * @const {!goog.html.SafeStyleSheet}
- */
-goog.html.SafeStyleSheet.EMPTY =
-    goog.html.SafeStyleSheet.
-        createSafeStyleSheetSecurityPrivateDoNotAccessOrElse('');
+  /**
+   * @abstract
+   * @protected
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {ol.webgl.Context} context Context.
+   * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+   *  to skip.
+   * @param {boolean} hitDetection Hit detection mode.
+   */
+  ol.render.webgl.Replay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) {};
 
-// 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.
- */
+  /**
+   * @abstract
+   * @protected
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {ol.webgl.Context} context Context.
+   * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+   *  to skip.
+   * @param {function((ol.Feature|ol.render.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.Replay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, featureCallback, opt_hitExtent) {};
 
-goog.provide('goog.fs.url');
 
+  /**
+   * @protected
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {ol.webgl.Context} context Context.
+   * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+   *  to skip.
+   * @param {function((ol.Feature|ol.render.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.Replay.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);
+    }
+  };
 
-/**
- * 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);
-};
 
+  /**
+   * @protected
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {ol.webgl.Context} context Context.
+   * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+   *  to skip.
+   * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback.
+   * @return {T|undefined} Callback result.
+   * @template T
+   */
+  ol.render.webgl.Replay.prototype.drawHitDetectionReplayAll = function(gl, context, skippedFeaturesHash,
+      featureCallback) {
+    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+    this.drawReplay(gl, context, skippedFeaturesHash, true);
+
+    var result = featureCallback(null);
+    if (result) {
+      return result;
+    } else {
+      return undefined;
+    }
+  };
 
-/**
- * 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);
-};
 
+  /**
+   * @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|ol.render.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.Replay.prototype.replay = function(context,
+      center, resolution, rotation, size, pixelRatio,
+      opacity, skippedFeaturesHash,
+      featureCallback, oneByOne, opt_hitExtent) {
+    var gl = context.getGL();
+    var tmpStencil, tmpStencilFunc, tmpStencilMaskVal, tmpStencilRef, tmpStencilMask,
+        tmpStencilOpFail, tmpStencilOpPass, tmpStencilOpZFail;
+
+    if (this.lineStringReplay) {
+      tmpStencil = gl.isEnabled(gl.STENCIL_TEST);
+      tmpStencilFunc = gl.getParameter(gl.STENCIL_FUNC);
+      tmpStencilMaskVal = gl.getParameter(gl.STENCIL_VALUE_MASK);
+      tmpStencilRef = gl.getParameter(gl.STENCIL_REF);
+      tmpStencilMask = gl.getParameter(gl.STENCIL_WRITEMASK);
+      tmpStencilOpFail = gl.getParameter(gl.STENCIL_FAIL);
+      tmpStencilOpPass = gl.getParameter(gl.STENCIL_PASS_DEPTH_PASS);
+      tmpStencilOpZFail = gl.getParameter(gl.STENCIL_PASS_DEPTH_FAIL);
+
+      gl.enable(gl.STENCIL_TEST);
+      gl.clear(gl.STENCIL_BUFFER_BIT);
+      gl.stencilMask(255);
+      gl.stencilFunc(gl.ALWAYS, 1, 255);
+      gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
+
+      this.lineStringReplay.replay(context,
+          center, resolution, rotation, size, pixelRatio,
+          opacity, skippedFeaturesHash,
+          featureCallback, oneByOne, opt_hitExtent);
 
-/**
- * @typedef {{createObjectURL: (function(!Blob): string),
- *            revokeObjectURL: function(string): void}}
- */
-goog.fs.url.UrlObject_;
+      gl.stencilMask(0);
+      gl.stencilFunc(gl.NOTEQUAL, 1, 255);
+    }
 
+    context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer);
 
-/**
- * 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');
-  }
-};
+    context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
 
+    var locations = this.setUpProgram(gl, context, size, pixelRatio);
 
-/**
- * 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;
-  }
-};
+    // set the "uniform" values
+    var projectionMatrix = ol.transform.reset(this.projectionMatrix_);
+    ol.transform.scale(projectionMatrix, 2 / (resolution * size[0]), 2 / (resolution * size[1]));
+    ol.transform.rotate(projectionMatrix, -rotation);
+    ol.transform.translate(projectionMatrix, -(center[0] - this.origin[0]), -(center[1] - this.origin[1]));
 
+    var offsetScaleMatrix = ol.transform.reset(this.offsetScaleMatrix_);
+    ol.transform.scale(offsetScaleMatrix, 2 / size[0], 2 / size[1]);
 
-/**
- * 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;
-};
+    var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_);
+    if (rotation !== 0) {
+      ol.transform.rotate(offsetRotateMatrix, -rotation);
+    }
 
-// 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.
+    gl.uniformMatrix4fv(locations.u_projectionMatrix, false,
+       ol.vec.Mat4.fromTransform(this.tmpMat4_, projectionMatrix));
+    gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false,
+       ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetScaleMatrix));
+    gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false,
+       ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetRotateMatrix));
+    gl.uniform1f(locations.u_opacity, opacity);
 
-/**
- * @fileoverview Utility functions for supporting Bidi issues.
- */
+    // draw!
+    var result;
+    if (featureCallback === undefined) {
+      this.drawReplay(gl, context, skippedFeaturesHash, false);
+    } else {
+      // draw feature by feature for the hit-detection
+      result = this.drawHitDetectionReplay(gl, context, skippedFeaturesHash,
+          featureCallback, oneByOne, opt_hitExtent);
+    }
 
+    // disable the vertex attrib arrays
+    this.shutDownProgram(gl, locations);
 
-/**
- * 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');
+    if (this.lineStringReplay) {
+      if (!tmpStencil) {
+        gl.disable(gl.STENCIL_TEST);
+      }
+      gl.clear(gl.STENCIL_BUFFER_BIT);
+      gl.stencilFunc(/** @type {number} */ (tmpStencilFunc),
+         /** @type {number} */ (tmpStencilRef), /** @type {number} */ (tmpStencilMaskVal));
+      gl.stencilMask(/** @type {number} */ (tmpStencilMask));
+      gl.stencilOp(/** @type {number} */ (tmpStencilOpFail),
+         /** @type {number} */ (tmpStencilOpZFail), /** @type {number} */ (tmpStencilOpPass));
+    }
 
+    return result;
+  };
 
-/**
- * @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);
+  /**
+   * @protected
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {ol.webgl.Context} context Context.
+   * @param {number} start Start index.
+   * @param {number} end End index.
+   */
+  ol.render.webgl.Replay.prototype.drawElements = function(
+      gl, context, start, end) {
+    var elementType = context.hasOESElementIndexUint ?
+        ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT;
+    var elementSize = context.hasOESElementIndexUint ? 4 : 2;
+
+    var numItems = end - start;
+    var offsetInBytes = start * elementSize;
+    gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, offsetInBytes);
+  };
 
+}
 
-/**
- * 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) == '_')
-    );
+goog.provide('ol.render.webgl');
 
+goog.require('ol');
 
-/**
- * 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'
-};
 
+if (ol.ENABLE_WEBGL) {
 
-/**
- * Directionality enum.
- * @enum {number}
- */
-goog.i18n.bidi.Dir = {
   /**
-   * Left-to-right.
+   * @const
+   * @type {ol.Color}
    */
-  LTR: 1,
+  ol.render.webgl.defaultFillStyle = [0.0, 0.0, 0.0, 1.0];
 
   /**
-   * Right-to-left.
+   * @const
+   * @type {string}
    */
-  RTL: -1,
+  ol.render.webgl.defaultLineCap = 'round';
+
 
   /**
-   * Neither left-to-right nor right-to-left.
+   * @const
+   * @type {Array.<number>}
    */
-  NEUTRAL: 0
-};
-
+  ol.render.webgl.defaultLineDash = [];
 
-/**
- * 'right' string constant.
- * @type {string}
- */
-goog.i18n.bidi.RIGHT = 'right';
 
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.render.webgl.defaultLineDashOffset = 0;
 
-/**
- * 'left' string constant.
- * @type {string}
- */
-goog.i18n.bidi.LEFT = 'left';
 
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.defaultLineJoin = 'round';
 
-/**
- * '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;
 
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.render.webgl.defaultMiterLimit = 10;
 
-/**
- * '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;
-  }
-};
+  /**
+   * @const
+   * @type {ol.Color}
+   */
+  ol.render.webgl.defaultStrokeStyle = [0.0, 0.0, 0.0, 1.0];
 
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.render.webgl.defaultLineWidth = 1;
 
-/**
- * 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';
+  /**
+   * Calculates the orientation of a triangle based on the determinant method.
+   * @param {number} x1 First X coordinate.
+   * @param {number} y1 First Y coordinate.
+   * @param {number} x2 Second X coordinate.
+   * @param {number} y2 Second Y coordinate.
+   * @param {number} x3 Third X coordinate.
+   * @param {number} y3 Third Y coordinate.
+   * @return {boolean|undefined} Triangle is clockwise.
+   */
+  ol.render.webgl.triangleIsCounterClockwise = function(x1, y1, x2, y2, x3, y3) {
+    var area = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1);
+    return (area <= ol.render.webgl.EPSILON && area >= -ol.render.webgl.EPSILON) ?
+        undefined : area > 0;
+  };
 
+  /**
+   * @const
+   * @type {number}
+   */
+  ol.render.webgl.EPSILON = Number.EPSILON || 2.220446049250313e-16;
 
-/**
- * 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';
+}
 
+goog.provide('ol.webgl.Buffer');
 
-/**
- * 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;
+goog.require('ol');
+goog.require('ol.webgl');
 
 
-/**
- * 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;
-};
+if (ol.ENABLE_WEBGL) {
 
+  /**
+   * @constructor
+   * @param {Array.<number>=} opt_arr Array.
+   * @param {number=} opt_usage Usage.
+   * @struct
+   */
+  ol.webgl.Buffer = function(opt_arr, opt_usage) {
 
-/**
- * Regular expression to check for RTL characters.
- * @type {RegExp}
- * @private
- */
-goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']');
+    /**
+     * @private
+     * @type {Array.<number>}
+     */
+    this.arr_ = opt_arr !== undefined ? opt_arr : [];
 
+    /**
+     * @private
+     * @type {number}
+     */
+    this.usage_ = opt_usage !== undefined ?
+        opt_usage : ol.webgl.Buffer.Usage_.STATIC_DRAW;
 
-/**
- * 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));
-};
+  /**
+   * @return {Array.<number>} Array.
+   */
+  ol.webgl.Buffer.prototype.getArray = function() {
+    return this.arr_;
+  };
 
 
-/**
- * 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;
+  /**
+   * @return {number} Usage.
+   */
+  ol.webgl.Buffer.prototype.getUsage = function() {
+    return this.usage_;
+  };
 
 
-/**
- * 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));
-};
+  /**
+   * @enum {number}
+   * @private
+   */
+  ol.webgl.Buffer.Usage_ = {
+    STATIC_DRAW: ol.webgl.STATIC_DRAW,
+    STREAM_DRAW: ol.webgl.STREAM_DRAW,
+    DYNAMIC_DRAW: ol.webgl.DYNAMIC_DRAW
+  };
 
+}
 
-/**
- * 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_ + ']');
+goog.provide('ol.render.webgl.CircleReplay');
 
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.color');
+goog.require('ol.extent');
+goog.require('ol.obj');
+goog.require('ol.geom.flat.transform');
+goog.require('ol.render.webgl.circlereplay.defaultshader');
+goog.require('ol.render.webgl.Replay');
+goog.require('ol.render.webgl');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Buffer');
 
-/**
- * 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_ + ']');
 
+if (ol.ENABLE_WEBGL) {
 
-/**
- * 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);
-};
+  /**
+   * @constructor
+   * @extends {ol.render.webgl.Replay}
+   * @param {number} tolerance Tolerance.
+   * @param {ol.Extent} maxExtent Max extent.
+   * @struct
+   */
+  ol.render.webgl.CircleReplay = function(tolerance, maxExtent) {
+    ol.render.webgl.Replay.call(this, tolerance, maxExtent);
 
+    /**
+     * @private
+     * @type {ol.render.webgl.circlereplay.defaultshader.Locations}
+     */
+    this.defaultLocations_ = null;
 
-/**
- * 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);
-};
-
+    /**
+     * @private
+     * @type {Array.<Array.<Array.<number>|number>>}
+     */
+    this.styles_ = [];
 
-/**
- * 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);
-};
+    /**
+     * @private
+     * @type {Array.<number>}
+     */
+    this.styleIndices_ = [];
 
+    /**
+     * @private
+     * @type {number}
+     */
+    this.radius_ = 0;
 
-/**
- * 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_ + ']');
+    /**
+     * @private
+     * @type {{fillColor: (Array.<number>|null),
+     *         strokeColor: (Array.<number>|null),
+     *         lineDash: Array.<number>,
+     *         lineDashOffset: (number|undefined),
+     *         lineWidth: (number|undefined),
+     *         changed: boolean}|null}
+     */
+    this.state_ = {
+      fillColor: null,
+      strokeColor: null,
+      lineDash: null,
+      lineDashOffset: undefined,
+      lineWidth: undefined,
+      changed: false
+    };
 
+  };
+  ol.inherits(ol.render.webgl.CircleReplay, ol.render.webgl.Replay);
 
-/**
- * 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_ + ']');
 
+  /**
+   * @private
+   * @param {Array.<number>} flatCoordinates Flat coordinates.
+   * @param {number} offset Offset.
+   * @param {number} end End.
+   * @param {number} stride Stride.
+   */
+  ol.render.webgl.CircleReplay.prototype.drawCoordinates_ = function(
+      flatCoordinates, offset, end, stride) {
+    var numVertices = this.vertices.length;
+    var numIndices = this.indices.length;
+    var n = numVertices / 4;
+    var i, ii;
+    for (i = offset, ii = end; i < ii; i += stride) {
+      this.vertices[numVertices++] = flatCoordinates[i];
+      this.vertices[numVertices++] = flatCoordinates[i + 1];
+      this.vertices[numVertices++] = 0;
+      this.vertices[numVertices++] = this.radius_;
 
-/**
- * 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));
-};
+      this.vertices[numVertices++] = flatCoordinates[i];
+      this.vertices[numVertices++] = flatCoordinates[i + 1];
+      this.vertices[numVertices++] = 1;
+      this.vertices[numVertices++] = this.radius_;
 
+      this.vertices[numVertices++] = flatCoordinates[i];
+      this.vertices[numVertices++] = flatCoordinates[i + 1];
+      this.vertices[numVertices++] = 2;
+      this.vertices[numVertices++] = this.radius_;
 
-/**
- * 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;
+      this.vertices[numVertices++] = flatCoordinates[i];
+      this.vertices[numVertices++] = flatCoordinates[i + 1];
+      this.vertices[numVertices++] = 3;
+      this.vertices[numVertices++] = this.radius_;
 
+      this.indices[numIndices++] = n;
+      this.indices[numIndices++] = n + 1;
+      this.indices[numIndices++] = n + 2;
 
-/**
- * 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));
-};
+      this.indices[numIndices++] = n + 2;
+      this.indices[numIndices++] = n + 3;
+      this.indices[numIndices++] = n;
 
+      n += 4;
+    }
+  };
 
-/**
- * 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;
 
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.CircleReplay.prototype.drawCircle = function(circleGeometry, feature) {
+    var radius = circleGeometry.getRadius();
+    var stride = circleGeometry.getStride();
+    if (radius) {
+      this.startIndices.push(this.indices.length);
+      this.startIndicesFeature.push(feature);
+      if (this.state_.changed) {
+        this.styleIndices_.push(this.indices.length);
+        this.state_.changed = false;
+      }
 
-/**
- * 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:\/\/.*/;
+      this.radius_ = radius;
+      var flatCoordinates = circleGeometry.getFlatCoordinates();
+      flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, 2,
+          stride, -this.origin[0], -this.origin[1]);
+      this.drawCoordinates_(flatCoordinates, 0, 2, stride);
+    } else {
+      if (this.state_.changed) {
+        this.styles_.pop();
+        if (this.styles_.length) {
+          var lastState = this.styles_[this.styles_.length - 1];
+          this.state_.fillColor =  /** @type {Array.<number>} */ (lastState[0]);
+          this.state_.strokeColor = /** @type {Array.<number>} */ (lastState[1]);
+          this.state_.lineWidth = /** @type {number} */ (lastState[2]);
+          this.state_.changed = false;
+        }
+      }
+    }
+  };
 
 
-/**
- * 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);
-};
+  /**
+   * @inheritDoc
+   **/
+  ol.render.webgl.CircleReplay.prototype.finish = function(context) {
+    // create, bind, and populate the vertices buffer
+    this.verticesBuffer = new ol.webgl.Buffer(this.vertices);
 
+    // create, bind, and populate the indices buffer
+    this.indicesBuffer = new ol.webgl.Buffer(this.indices);
 
-/**
- * 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_ + ']*$');
+    this.startIndices.push(this.indices.length);
 
+    //Clean up, if there is nothing to draw
+    if (this.styleIndices_.length === 0 && this.styles_.length > 0) {
+      this.styles_ = [];
+    }
 
-/**
- * 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_ + ']*$');
+    this.vertices = null;
+    this.indices = null;
+  };
 
 
-/**
- * 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));
-};
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.CircleReplay.prototype.getDeleteResourcesFunction = function(context) {
+    // We only delete our stuff here. The shaders and the program may
+    // be used by other CircleReplay instances (for other layers). And
+    // they will be deleted when disposing of the ol.webgl.Context
+    // object.
+    var verticesBuffer = this.verticesBuffer;
+    var indicesBuffer = this.indicesBuffer;
+    return function() {
+      context.deleteBuffer(verticesBuffer);
+      context.deleteBuffer(indicesBuffer);
+    };
+  };
 
 
-/**
- * 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;
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.CircleReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {
+    // get the program
+    var fragmentShader, vertexShader;
+    fragmentShader = ol.render.webgl.circlereplay.defaultshader.fragment;
+    vertexShader = ol.render.webgl.circlereplay.defaultshader.vertex;
+    var program = context.getProgram(fragmentShader, vertexShader);
+
+    // get the locations
+    var locations;
+    if (!this.defaultLocations_) {
+      // eslint-disable-next-line openlayers-internal/no-missing-requires
+      locations = new ol.render.webgl.circlereplay.defaultshader.Locations(gl, program);
+      this.defaultLocations_ = locations;
+    } else {
+      locations = this.defaultLocations_;
+    }
 
+    context.useProgram(program);
 
-/**
- * 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));
-};
+    // enable the vertex attrib arrays
+    gl.enableVertexAttribArray(locations.a_position);
+    gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT,
+        false, 16, 0);
 
+    gl.enableVertexAttribArray(locations.a_instruction);
+    gl.vertexAttribPointer(locations.a_instruction, 1, ol.webgl.FLOAT,
+        false, 16, 8);
 
-/**
- * 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;
+    gl.enableVertexAttribArray(locations.a_radius);
+    gl.vertexAttribPointer(locations.a_radius, 1, ol.webgl.FLOAT,
+        false, 16, 12);
 
+    // Enable renderer specific uniforms.
+    gl.uniform2fv(locations.u_size, size);
+    gl.uniform1f(locations.u_pixelRatio, pixelRatio);
 
-/**
- * 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');
+    return locations;
+  };
 
 
-/**
- * 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);
-};
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.CircleReplay.prototype.shutDownProgram = function(gl, locations) {
+    gl.disableVertexAttribArray(locations.a_position);
+    gl.disableVertexAttribArray(locations.a_instruction);
+    gl.disableVertexAttribArray(locations.a_radius);
+  };
 
 
-/**
- * Regular expression for bracket guard replacement in html.
- * @type {RegExp}
- * @private
- */
-goog.i18n.bidi.bracketGuardHtmlRe_ =
-    /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(&lt;.*?(&gt;)+)/g;
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.CircleReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) {
+    if (!ol.obj.isEmpty(skippedFeaturesHash)) {
+      this.drawReplaySkipping_(gl, context, skippedFeaturesHash);
+    } else {
+      //Draw by style groups to minimize drawElements() calls.
+      var i, start, end, nextStyle;
+      end = this.startIndices[this.startIndices.length - 1];
+      for (i = this.styleIndices_.length - 1; i >= 0; --i) {
+        start = this.styleIndices_[i];
+        nextStyle = this.styles_[i];
+        this.setFillStyle_(gl, /** @type {Array.<number>} */ (nextStyle[0]));
+        this.setStrokeStyle_(gl, /** @type {Array.<number>} */ (nextStyle[1]),
+            /** @type {number} */ (nextStyle[2]));
+        this.drawElements(gl, context, start, end);
+        end = start;
+      }
+    }
+  };
 
 
-/**
- * Regular expression for bracket guard replacement in text.
- * @type {RegExp}
- * @private
- */
-goog.i18n.bidi.bracketGuardTextRe_ =
-    /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g;
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.CircleReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash,
+      featureCallback, opt_hitExtent) {
+    var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex;
+    featureIndex = this.startIndices.length - 2;
+    end = this.startIndices[featureIndex + 1];
+    for (i = this.styleIndices_.length - 1; i >= 0; --i) {
+      nextStyle = this.styles_[i];
+      this.setFillStyle_(gl, /** @type {Array.<number>} */ (nextStyle[0]));
+      this.setStrokeStyle_(gl, /** @type {Array.<number>} */ (nextStyle[1]),
+          /** @type {number} */ (nextStyle[2]));
+      groupStart = this.styleIndices_[i];
+
+      while (featureIndex >= 0 &&
+          this.startIndices[featureIndex] >= groupStart) {
+        start = this.startIndices[featureIndex];
+        feature = this.startIndicesFeature[featureIndex];
+        featureUid = ol.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, context, start, end);
 
+          var result = featureCallback(feature);
 
-/**
- * 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>');
-};
+          if (result) {
+            return result;
+          }
 
+        }
+        featureIndex--;
+        end = start;
+      }
+    }
+    return undefined;
+  };
 
-/**
- * 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);
-};
 
+  /**
+   * @private
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {ol.webgl.Context} context Context.
+   * @param {Object} skippedFeaturesHash Ids of features to skip.
+   */
+  ol.render.webgl.CircleReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) {
+    var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart;
+    featureIndex = this.startIndices.length - 2;
+    end = start = this.startIndices[featureIndex + 1];
+    for (i = this.styleIndices_.length - 1; i >= 0; --i) {
+      nextStyle = this.styles_[i];
+      this.setFillStyle_(gl, /** @type {Array.<number>} */ (nextStyle[0]));
+      this.setStrokeStyle_(gl, /** @type {Array.<number>} */ (nextStyle[1]),
+          /** @type {number} */ (nextStyle[2]));
+      groupStart = this.styleIndices_[i];
 
-/**
- * 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>';
-};
+      while (featureIndex >= 0 &&
+          this.startIndices[featureIndex] >= groupStart) {
+        featureStart = this.startIndices[featureIndex];
+        feature = this.startIndicesFeature[featureIndex];
+        featureUid = ol.getUid(feature).toString();
 
+        if (skippedFeaturesHash[featureUid]) {
+          if (start !== end) {
+            this.drawElements(gl, context, start, end);
+          }
+          end = featureStart;
+        }
+        featureIndex--;
+        start = featureStart;
+      }
+      if (start !== end) {
+        this.drawElements(gl, context, start, end);
+      }
+      start = end = groupStart;
+    }
+  };
 
-/**
- * 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;
-};
 
+  /**
+   * @private
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {Array.<number>} color Color.
+   */
+  ol.render.webgl.CircleReplay.prototype.setFillStyle_ = function(gl, color) {
+    gl.uniform4fv(this.defaultLocations_.u_fillColor, color);
+  };
 
-/**
- * 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>';
-};
 
+  /**
+   * @private
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {Array.<number>} color Color.
+   * @param {number} lineWidth Line width.
+   */
+  ol.render.webgl.CircleReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth) {
+    gl.uniform4fv(this.defaultLocations_.u_strokeColor, color);
+    gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth);
+  };
 
-/**
- * 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;
-};
 
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.CircleReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
+    var strokeStyleColor, strokeStyleWidth;
+    if (strokeStyle) {
+      var strokeStyleLineDash = strokeStyle.getLineDash();
+      this.state_.lineDash = strokeStyleLineDash ?
+          strokeStyleLineDash : ol.render.webgl.defaultLineDash;
+      var strokeStyleLineDashOffset = strokeStyle.getLineDashOffset();
+      this.state_.lineDashOffset = strokeStyleLineDashOffset ?
+          strokeStyleLineDashOffset : ol.render.webgl.defaultLineDashOffset;
+      strokeStyleColor = strokeStyle.getColor();
+      if (!(strokeStyleColor instanceof CanvasGradient) &&
+          !(strokeStyleColor instanceof CanvasPattern)) {
+        strokeStyleColor = ol.color.asArray(strokeStyleColor).map(function(c, i) {
+          return i != 3 ? c / 255 : c;
+        }) || ol.render.webgl.defaultStrokeStyle;
+      } else {
+        strokeStyleColor = ol.render.webgl.defaultStrokeStyle;
+      }
+      strokeStyleWidth = strokeStyle.getWidth();
+      strokeStyleWidth = strokeStyleWidth !== undefined ?
+          strokeStyleWidth : ol.render.webgl.defaultLineWidth;
+    } else {
+      strokeStyleColor = [0, 0, 0, 0];
+      strokeStyleWidth = 0;
+    }
+    var fillStyleColor = fillStyle ? fillStyle.getColor() : [0, 0, 0, 0];
+    if (!(fillStyleColor instanceof CanvasGradient) &&
+        !(fillStyleColor instanceof CanvasPattern)) {
+      fillStyleColor = ol.color.asArray(fillStyleColor).map(function(c, i) {
+        return i != 3 ? c / 255 : c;
+      }) || ol.render.webgl.defaultFillStyle;
+    } else {
+      fillStyleColor = ol.render.webgl.defaultFillStyle;
+    }
+    if (!this.state_.strokeColor || !ol.array.equals(this.state_.strokeColor, strokeStyleColor) ||
+        !this.state_.fillColor || !ol.array.equals(this.state_.fillColor, fillStyleColor) ||
+        this.state_.lineWidth !== strokeStyleWidth) {
+      this.state_.changed = true;
+      this.state_.fillColor = fillStyleColor;
+      this.state_.strokeColor = strokeStyleColor;
+      this.state_.lineWidth = strokeStyleWidth;
+      this.styles_.push([fillStyleColor, strokeStyleColor, strokeStyleWidth]);
+    }
+  };
 
-/**
- * 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;
+}
 
+// This file is automatically generated, do not edit
+/* eslint openlayers-internal/no-missing-requires: 0 */
+goog.provide('ol.render.webgl.imagereplay.defaultshader');
 
-/**
- * Regular expression for left.
- * @type {RegExp}
- * @private
- */
-goog.i18n.bidi.leftRe_ = /left/gi;
+goog.require('ol');
+goog.require('ol.webgl.Fragment');
+goog.require('ol.webgl.Vertex');
 
+if (ol.ENABLE_WEBGL) {
 
-/**
- * Regular expression for right.
- * @type {RegExp}
- * @private
- */
-goog.i18n.bidi.rightRe_ = /right/gi;
+  /**
+   * @constructor
+   * @extends {ol.webgl.Fragment}
+   * @struct
+   */
+  ol.render.webgl.imagereplay.defaultshader.Fragment = function() {
+    ol.webgl.Fragment.call(this, ol.render.webgl.imagereplay.defaultshader.Fragment.SOURCE);
+  };
+  ol.inherits(ol.render.webgl.imagereplay.defaultshader.Fragment, ol.webgl.Fragment);
 
 
-/**
- * Placeholder regular expression for swapping.
- * @type {RegExp}
- * @private
- */
-goog.i18n.bidi.tempRe_ = /%%%%/g;
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.imagereplay.defaultshader.Fragment.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';
 
 
-/**
- * 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);
-};
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.imagereplay.defaultshader.Fragment.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;}';
 
 
-/**
- * Regular expression for hebrew double quote substitution, finding quote
- * directly after hebrew characters.
- * @type {RegExp}
- * @private
- */
-goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g;
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.imagereplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ?
+      ol.render.webgl.imagereplay.defaultshader.Fragment.DEBUG_SOURCE :
+      ol.render.webgl.imagereplay.defaultshader.Fragment.OPTIMIZED_SOURCE;
 
 
-/**
- * Regular expression for hebrew single quote substitution, finding quote
- * directly after hebrew characters.
- * @type {RegExp}
- * @private
- */
-goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g;
+  ol.render.webgl.imagereplay.defaultshader.fragment = new ol.render.webgl.imagereplay.defaultshader.Fragment();
 
 
-/**
- * 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');
-};
+  /**
+   * @constructor
+   * @extends {ol.webgl.Vertex}
+   * @struct
+   */
+  ol.render.webgl.imagereplay.defaultshader.Vertex = function() {
+    ol.webgl.Vertex.call(this, ol.render.webgl.imagereplay.defaultshader.Vertex.SOURCE);
+  };
+  ol.inherits(ol.render.webgl.imagereplay.defaultshader.Vertex, ol.webgl.Vertex);
 
 
-/**
- * Regular expression to split a string into "words" for directionality
- * estimation based on relative word counts.
- * @type {RegExp}
- * @private
- */
-goog.i18n.bidi.wordSeparatorRe_ = /\s+/;
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.imagereplay.defaultshader.Vertex.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, 0.0);\n  gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;\n  v_texCoord = a_texCoord;\n  v_opacity = a_opacity;\n}\n\n\n';
 
 
-/**
- * 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]/;
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.imagereplay.defaultshader.Vertex.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,0.0);gl_Position=h*vec4(c,0.0,1.0)+offsets;a=d;b=f;}';
 
 
-/**
- * This constant controls threshold of RTL directionality.
- * @type {number}
- * @private
- */
-goog.i18n.bidi.rtlDetectionThreshold_ = 0.40;
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.imagereplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ?
+      ol.render.webgl.imagereplay.defaultshader.Vertex.DEBUG_SOURCE :
+      ol.render.webgl.imagereplay.defaultshader.Vertex.OPTIMIZED_SOURCE;
 
 
-/**
- * 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;
-    }
-  }
+  ol.render.webgl.imagereplay.defaultshader.vertex = new ol.render.webgl.imagereplay.defaultshader.Vertex();
 
-  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);
-};
 
+  /**
+   * @constructor
+   * @param {WebGLRenderingContext} gl GL.
+   * @param {WebGLProgram} program Program.
+   * @struct
+   */
+  ol.render.webgl.imagereplay.defaultshader.Locations = function(gl, program) {
 
-/**
- * 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;
-};
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_image = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_image' : 'l');
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_offsetRotateMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j');
 
-/**
- * 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';
-    }
-  }
-};
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_offsetScaleMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i');
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_opacity = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_opacity' : 'k');
 
-/**
- * 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');
-  }
-};
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_projectionMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h');
 
+    /**
+     * @type {number}
+     */
+    this.a_offsets = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_offsets' : 'e');
 
+    /**
+     * @type {number}
+     */
+    this.a_opacity = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_opacity' : 'f');
 
-/**
- * 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() {};
+    /**
+     * @type {number}
+     */
+    this.a_position = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_position' : 'c');
 
+    /**
+     * @type {number}
+     */
+    this.a_rotateWithView = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_rotateWithView' : 'g');
 
-/**
- * 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;
+    /**
+     * @type {number}
+     */
+    this.a_texCoord = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'd');
+  };
 
+}
 
-/**
- * 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;
+goog.provide('ol.webgl.ContextEventType');
 
-// 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.
+ * @enum {string}
  */
+ol.webgl.ContextEventType = {
+  LOST: 'webglcontextlost',
+  RESTORED: 'webglcontextrestored'
+};
 
-goog.provide('goog.html.SafeUrl');
+goog.provide('ol.webgl.Context');
 
-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');
+goog.require('ol');
+goog.require('ol.Disposable');
+goog.require('ol.array');
+goog.require('ol.events');
+goog.require('ol.obj');
+goog.require('ol.webgl');
+goog.require('ol.webgl.ContextEventType');
 
 
+if (ol.ENABLE_WEBGL) {
 
-/**
- * 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}
+   * @classdesc
+   * A WebGL context for accessing low-level WebGL capabilities.
+   *
+   * @constructor
+   * @extends {ol.Disposable}
+   * @param {HTMLCanvasElement} canvas Canvas.
+   * @param {WebGLRenderingContext} gl GL.
    */
-  this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
+  ol.webgl.Context = function(canvas, gl) {
 
-  /**
-   * 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_;
-};
+    /**
+     * @private
+     * @type {HTMLCanvasElement}
+     */
+    this.canvas_ = canvas;
 
+    /**
+     * @private
+     * @type {WebGLRenderingContext}
+     */
+    this.gl_ = gl;
 
-/**
- * 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';
+    /**
+     * @private
+     * @type {Object.<string, ol.WebglBufferCacheEntry>}
+     */
+    this.bufferCache_ = {};
 
+    /**
+     * @private
+     * @type {Object.<string, WebGLShader>}
+     */
+    this.shaderCache_ = {};
 
-/**
- * @override
- * @const
- */
-goog.html.SafeUrl.prototype.implementsGoogStringTypedString = true;
+    /**
+     * @private
+     * @type {Object.<string, WebGLProgram>}
+     */
+    this.programCache_ = {};
 
+    /**
+     * @private
+     * @type {WebGLProgram}
+     */
+    this.currentProgram_ = null;
 
-/**
- * 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_;
-};
+    /**
+     * @private
+     * @type {WebGLFramebuffer}
+     */
+    this.hitDetectionFramebuffer_ = null;
+
+    /**
+     * @private
+     * @type {WebGLTexture}
+     */
+    this.hitDetectionTexture_ = null;
 
+    /**
+     * @private
+     * @type {WebGLRenderbuffer}
+     */
+    this.hitDetectionRenderbuffer_ = null;
 
-/**
- * @override
- * @const
- */
-goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString = true;
+    /**
+     * @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) {
+      gl.getExtension('OES_element_index_uint');
+    }
 
-/**
- * Returns this URLs directionality, which is always {@code LTR}.
- * @override
- */
-goog.html.SafeUrl.prototype.getDirection = function() {
-  return goog.i18n.bidi.Dir.LTR;
-};
+    ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST,
+        this.handleWebGLContextLost, this);
+    ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED,
+        this.handleWebGLContextRestored, this);
+
+  };
+  ol.inherits(ol.webgl.Context, ol.Disposable);
 
 
-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
+   * 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.
    */
-  goog.html.SafeUrl.prototype.toString = function() {
-    return 'SafeUrl{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
-        '}';
+  ol.webgl.Context.prototype.bindBuffer = function(target, buf) {
+    var gl = this.getGL();
+    var arr = buf.getArray();
+    var bufferKey = String(ol.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);
+      var /** @type {ArrayBufferView} */ arrayBuffer;
+      if (target == ol.webgl.ARRAY_BUFFER) {
+        arrayBuffer = new Float32Array(arr);
+      } else if (target == ol.webgl.ELEMENT_ARRAY_BUFFER) {
+        arrayBuffer = this.hasOESElementIndexUint ?
+            new Uint32Array(arr) : new Uint16Array(arr);
+      }
+      gl.bufferData(target, arrayBuffer, buf.getUsage());
+      this.bufferCache_[bufferKey] = {
+        buf: buf,
+        buffer: buffer
+      };
+    }
   };
-}
 
 
-/**
- * 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';
+  /**
+   * @param {ol.webgl.Buffer} buf Buffer.
+   */
+  ol.webgl.Context.prototype.deleteBuffer = function(buf) {
+    var gl = this.getGL();
+    var bufferKey = String(ol.getUid(buf));
+    var bufferCacheEntry = this.bufferCache_[bufferKey];
+    if (!gl.isContextLost()) {
+      gl.deleteBuffer(bufferCacheEntry.buffer);
+    }
+    delete this.bufferCache_[bufferKey];
+  };
 
-  }
-};
 
+  /**
+   * @inheritDoc
+   */
+  ol.webgl.Context.prototype.disposeInternal = function() {
+    ol.events.unlistenAll(this.canvas_);
+    var gl = this.getGL();
+    if (!gl.isContextLost()) {
+      var key;
+      for (key in this.bufferCache_) {
+        gl.deleteBuffer(this.bufferCache_[key].buffer);
+      }
+      for (key in this.programCache_) {
+        gl.deleteProgram(this.programCache_[key]);
+      }
+      for (key in this.shaderCache_) {
+        gl.deleteShader(this.shaderCache_[key]);
+      }
+      // delete objects for hit-detection
+      gl.deleteFramebuffer(this.hitDetectionFramebuffer_);
+      gl.deleteRenderbuffer(this.hitDetectionRenderbuffer_);
+      gl.deleteTexture(this.hitDetectionTexture_);
+    }
+  };
 
-/**
- * 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));
-};
 
+  /**
+   * @return {HTMLCanvasElement} Canvas.
+   */
+  ol.webgl.Context.prototype.getCanvas = function() {
+    return this.canvas_;
+  };
 
-/**
- * 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;
 
+  /**
+   * Get the WebGL rendering context
+   * @return {WebGLRenderingContext} The rendering context.
+   * @api
+   */
+  ol.webgl.Context.prototype.getGL = function() {
+    return this.gl_;
+  };
 
-/**
- * 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);
-};
 
+  /**
+   * 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_;
+  };
 
-/**
- * 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;
 
+  /**
+   * 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 = String(ol.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);
+      this.shaderCache_[shaderKey] = shader;
+      return shader;
+    }
+  };
 
-/**
- * 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);
-};
 
+  /**
+   * 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.Fragment} fragmentShaderObject Fragment shader.
+   * @param {ol.webgl.Vertex} vertexShaderObject Vertex shader.
+   * @return {WebGLProgram} Program.
+   */
+  ol.webgl.Context.prototype.getProgram = function(
+      fragmentShaderObject, vertexShaderObject) {
+    var programKey =
+        ol.getUid(fragmentShaderObject) + '/' + ol.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);
+      this.programCache_[programKey] = program;
+      return program;
+    }
+  };
 
-/**
- * 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;
 
+  /**
+   * FIXME empy description for jsdoc
+   */
+  ol.webgl.Context.prototype.handleWebGLContextLost = function() {
+    ol.obj.clear(this.bufferCache_);
+    ol.obj.clear(this.shaderCache_);
+    ol.obj.clear(this.programCache_);
+    this.currentProgram_ = null;
+    this.hitDetectionFramebuffer_ = null;
+    this.hitDetectionTexture_ = null;
+    this.hitDetectionRenderbuffer_ = null;
+  };
 
-/**
- * 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);
-};
 
+  /**
+   * FIXME empy description for jsdoc
+   */
+  ol.webgl.Context.prototype.handleWebGLContextRestored = function() {
+  };
 
-/**
- * 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_ = {};
 
+  /**
+   * 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;
+  };
 
-/**
- * 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.
+  /**
+   * 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;
+    }
+  };
 
-/**
- * @fileoverview The TrustedResourceUrl type and its builders.
- *
- * TODO(xtof): Link to document stating type contract.
- */
 
-goog.provide('goog.html.TrustedResourceUrl');
+  /**
+   * @param {WebGLRenderingContext} gl WebGL rendering context.
+   * @param {number=} opt_wrapS wrapS.
+   * @param {number=} opt_wrapT wrapT.
+   * @return {WebGLTexture} The texture.
+   * @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);
 
-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');
+    if (opt_wrapS !== undefined) {
+      gl.texParameteri(
+          ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S, opt_wrapS);
+    }
+    if (opt_wrapT !== undefined) {
+      gl.texParameteri(
+          ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T, opt_wrapT);
+    }
 
+    return texture;
+  };
 
 
-/**
- * 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}
+   * @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} The texture.
    */
-  this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = '';
+  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;
+  };
+
 
   /**
-   * A type marker used to implement additional run-time type checking.
-   * @see goog.html.TrustedResourceUrl#unwrap
-   * @const
-   * @private
+   * @param {WebGLRenderingContext} gl WebGL rendering context.
+   * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image.
+   * @param {number=} opt_wrapS wrapS.
+   * @param {number=} opt_wrapT wrapT.
+   * @return {WebGLTexture} The texture.
    */
-  this.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
-      goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
-};
+  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;
+  };
 
-/**
- * @override
- * @const
- */
-goog.html.TrustedResourceUrl.prototype.implementsGoogStringTypedString = true;
+}
 
+goog.provide('ol.render.webgl.ImageReplay');
 
-/**
- * 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_;
-};
+goog.require('ol');
+goog.require('ol.extent');
+goog.require('ol.obj');
+goog.require('ol.render.webgl.imagereplay.defaultshader');
+goog.require('ol.render.webgl.Replay');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Buffer');
+goog.require('ol.webgl.Context');
 
 
-/**
- * @override
- * @const
- */
-goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString =
-    true;
+if (ol.ENABLE_WEBGL) {
 
+  /**
+   * @constructor
+   * @extends {ol.render.webgl.Replay}
+   * @param {number} tolerance Tolerance.
+   * @param {ol.Extent} maxExtent Max extent.
+   * @struct
+   */
+  ol.render.webgl.ImageReplay = function(tolerance, maxExtent) {
+    ol.render.webgl.Replay.call(this, tolerance, maxExtent);
 
-/**
- * Returns this URLs directionality, which is always {@code LTR}.
- * @override
- */
-goog.html.TrustedResourceUrl.prototype.getDirection = function() {
-  return goog.i18n.bidi.Dir.LTR;
-};
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    this.anchorX_ = undefined;
 
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    this.anchorY_ = undefined;
 
-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_ + '}';
-  };
-}
+    /**
+     * @type {Array.<number>}
+     * @private
+     */
+    this.groupIndices_ = [];
 
+    /**
+     * @type {Array.<number>}
+     * @private
+     */
+    this.hitDetectionGroupIndices_ = [];
 
-/**
- * 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';
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    this.height_ = undefined;
 
-  }
-};
+    /**
+     * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
+     * @private
+     */
+    this.images_ = [];
 
+    /**
+     * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
+     * @private
+     */
+    this.hitDetectionImages_ = [];
 
-/**
- * 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 {number|undefined}
+     * @private
+     */
+    this.imageHeight_ = undefined;
 
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    this.imageWidth_ = undefined;
 
-/**
- * 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_ = {};
+    /**
+     * @private
+     * @type {ol.render.webgl.imagereplay.defaultshader.Locations}
+     */
+    this.defaultLocations_ = null;
 
+    /**
+     * @private
+     * @type {number|undefined}
+     */
+    this.opacity_ = undefined;
 
-/**
- * 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;
-};
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    this.originX_ = undefined;
 
-// 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.
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    this.originY_ = undefined;
+
+    /**
+     * @private
+     * @type {boolean|undefined}
+     */
+    this.rotateWithView_ = undefined;
 
+    /**
+     * @private
+     * @type {number|undefined}
+     */
+    this.rotation_ = undefined;
 
-/**
- * @fileoverview The SafeHtml type and its builders.
- *
- * TODO(xtof): Link to document stating type contract.
- */
+    /**
+     * @private
+     * @type {number|undefined}
+     */
+    this.scale_ = undefined;
 
-goog.provide('goog.html.SafeHtml');
+    /**
+     * @type {Array.<WebGLTexture>}
+     * @private
+     */
+    this.textures_ = [];
 
-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');
+    /**
+     * @type {Array.<WebGLTexture>}
+     * @private
+     */
+    this.hitDetectionTextures_ = [];
 
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    this.width_ = undefined;
+  };
+  ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.Replay);
 
 
-/**
- * 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}
+   * @inheritDoc
    */
-  this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
+  ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = function(context) {
+    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);
+    };
+  };
+
 
   /**
-   * 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_;
+   * @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) {
+    var anchorX = /** @type {number} */ (this.anchorX_);
+    var anchorY = /** @type {number} */ (this.anchorY_);
+    var height = /** @type {number} */ (this.height_);
+    var imageHeight = /** @type {number} */ (this.imageHeight_);
+    var imageWidth = /** @type {number} */ (this.imageWidth_);
+    var opacity = /** @type {number} */ (this.opacity_);
+    var originX = /** @type {number} */ (this.originX_);
+    var originY = /** @type {number} */ (this.originY_);
+    var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0;
+    // this.rotation_ is anti-clockwise, but rotation is clockwise
+    var rotation = /** @type {number} */ (-this.rotation_);
+    var scale = /** @type {number} */ (this.scale_);
+    var width = /** @type {number} */ (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;
+  };
+
 
   /**
-   * This SafeHtml's directionality, or null if unknown.
-   * @private {?goog.i18n.bidi.Dir}
+   * @inheritDoc
    */
-  this.dir_ = null;
-};
-
+  ol.render.webgl.ImageReplay.prototype.drawMultiPoint = 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);
+  };
 
-/**
- * @override
- * @const
- */
-goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true;
 
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.ImageReplay.prototype.drawPoint = 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);
+  };
 
-/** @override */
-goog.html.SafeHtml.prototype.getDirection = function() {
-  return this.dir_;
-};
 
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.ImageReplay.prototype.finish = function(context) {
+    var gl = context.getGL();
+
+    this.groupIndices_.push(this.indices.length);
+    this.hitDetectionGroupIndices_.push(this.indices.length);
+
+    // create, bind, and populate the vertices buffer
+    this.verticesBuffer = new ol.webgl.Buffer(this.vertices);
+
+    var indices = this.indices;
+
+    // create, bind, and populate the indices buffer
+    this.indicesBuffer = new ol.webgl.Buffer(indices);
+
+    // create textures
+    /** @type {Object.<string, WebGLTexture>} */
+    var texturePerImage = {};
+
+    this.createTextures_(this.textures_, this.images_, texturePerImage, gl);
+
+    this.createTextures_(this.hitDetectionTextures_, this.hitDetectionImages_,
+        texturePerImage, gl);
+
+    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;
+  };
 
-/**
- * @override
- * @const
- */
-goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true;
 
+  /**
+   * @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) {
+    var texture, image, uid, i;
+    var ii = images.length;
+    for (i = 0; i < ii; ++i) {
+      image = images[i];
 
-/**
- * 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_;
-};
+      uid = ol.getUid(image).toString();
+      if (uid in texturePerImage) {
+        texture = texturePerImage[uid];
+      } else {
+        texture = ol.webgl.Context.createTexture(
+            gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE);
+        texturePerImage[uid] = texture;
+      }
+      textures[i] = texture;
+    }
+  };
 
 
-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
+   * @inheritDoc
    */
-  goog.html.SafeHtml.prototype.toString = function() {
-    return 'SafeHtml{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
-        '}';
-  };
-}
+  ol.render.webgl.ImageReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {
+    // get the program
+    var fragmentShader = ol.render.webgl.imagereplay.defaultshader.fragment;
+    var vertexShader = ol.render.webgl.imagereplay.defaultshader.vertex;
+    var program = context.getProgram(fragmentShader, vertexShader);
+
+    // get the locations
+    var locations;
+    if (!this.defaultLocations_) {
+      // eslint-disable-next-line openlayers-internal/no-missing-requires
+      locations = new ol.render.webgl.imagereplay.defaultshader.Locations(gl, program);
+      this.defaultLocations_ = locations;
+    } else {
+      locations = this.defaultLocations_;
+    }
 
+    // use the program (FIXME: use the return value)
+    context.useProgram(program);
 
-/**
- * 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';
-  }
-};
+    // enable the vertex attrib arrays
+    gl.enableVertexAttribArray(locations.a_position);
+    gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT,
+        false, 32, 0);
 
+    gl.enableVertexAttribArray(locations.a_offsets);
+    gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT,
+        false, 32, 8);
 
-/**
- * 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_;
+    gl.enableVertexAttribArray(locations.a_texCoord);
+    gl.vertexAttribPointer(locations.a_texCoord, 2, ol.webgl.FLOAT,
+        false, 32, 16);
 
+    gl.enableVertexAttribArray(locations.a_opacity);
+    gl.vertexAttribPointer(locations.a_opacity, 1, ol.webgl.FLOAT,
+        false, 32, 24);
 
-/**
- * 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);
-};
+    gl.enableVertexAttribArray(locations.a_rotateWithView);
+    gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT,
+        false, 32, 28);
 
+    return locations;
+  };
 
-/**
- * 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());
-};
 
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.ImageReplay.prototype.shutDownProgram = function(gl, locations) {
+    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);
+  };
 
-/**
- * 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());
-};
 
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.ImageReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) {
+    var textures = hitDetection ? this.hitDetectionTextures_ : this.textures_;
+    var groupIndices = hitDetection ? this.hitDetectionGroupIndices_ : this.groupIndices_;
 
-/**
- * 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;
+    if (!ol.obj.isEmpty(skippedFeaturesHash)) {
+      this.drawReplaySkipping_(
+          gl, context, skippedFeaturesHash, textures, groupIndices);
+    } else {
+      var i, ii, start;
+      for (i = 0, ii = textures.length, start = 0; i < ii; ++i) {
+        gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]);
+        var end = groupIndices[i];
+        this.drawElements(gl, context, start, end);
+        start = end;
+      }
+    }
+  };
 
 
-/**
- * @const
- * @private
- */
-goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/;
+  /**
+   * 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 {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.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash, textures,
+      groupIndices) {
+    var featureIndex = 0;
 
+    var i, ii;
+    for (i = 0, ii = textures.length; i < ii; ++i) {
+      gl.bindTexture(ol.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 = ol.getUid(feature).toString();
+        if (skippedFeaturesHash[featureUid] !== undefined) {
+          // feature should be skipped
+          if (start !== end) {
+            // draw the features so far
+            this.drawElements(gl, context, start, end);
+          }
+          // 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++;
+      }
 
-/**
- * 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');
+      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, context, start, end);
+      }
+    }
+  };
 
 
-/**
- * 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);
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash,
+      featureCallback, opt_hitExtent) {
+    var i, groupStart, start, end, feature, featureUid;
+    var featureIndex = this.startIndices.length - 1;
+    for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) {
+      gl.bindTexture(ol.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 = ol.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, context, start, end);
 
+          var result = featureCallback(feature);
+          if (result) {
+            return result;
+          }
+        }
 
-/**
- * @typedef {string|number|goog.string.TypedString|
- *     goog.html.SafeStyle.PropertyMap}
- * @private
- */
-goog.html.SafeHtml.AttributeValue_;
+        end = start;
+        featureIndex--;
+      }
+    }
+    return undefined;
+  };
 
 
-/**
- * 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();
+  /**
+   * @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 opacity = imageStyle.getOpacity();
+    var origin = imageStyle.getOrigin();
+    var rotateWithView = imageStyle.getRotateWithView();
+    var rotation = imageStyle.getRotation();
+    var size = imageStyle.getSize();
+    var scale = imageStyle.getScale();
+
+    var currentImage;
+    if (this.images_.length === 0) {
+      this.images_.push(image);
     } else {
-      throw Error('Attribute "' + name + '" on tag "' + tagName +
-          '" requires goog.html.SafeUrl, goog.string.Const, or string,' +
-          ' value "' + value + '" given.');
+      currentImage = this.images_[this.images_.length - 1];
+      if (ol.getUid(currentImage) != ol.getUid(image)) {
+        this.groupIndices_.push(this.indices.length);
+        this.images_.push(image);
+      }
     }
-  }
 
-  // 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();
-  }
+    if (this.hitDetectionImages_.length === 0) {
+      this.hitDetectionImages_.push(hitDetectionImage);
+    } else {
+      currentImage =
+          this.hitDetectionImages_[this.hitDetectionImages_.length - 1];
+      if (ol.getUid(currentImage) != ol.getUid(hitDetectionImage)) {
+        this.hitDetectionGroupIndices_.push(this.indices.length);
+        this.hitDetectionImages_.push(hitDetectionImage);
+      }
+    }
 
-  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)) + '"';
-};
+    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];
+  };
 
+}
 
-/**
- * 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);
-};
+goog.provide('ol.geom.flat.topology');
 
+goog.require('ol.geom.flat.area');
 
 /**
- * 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.
+ * Check if the linestring is a boundary.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {boolean} The linestring is a boundary.
  */
-goog.html.SafeHtml.createWithDir = function(dir, tagName, opt_attributes,
-    opt_content) {
-  var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content);
-  html.dir_ = dir;
-  return html;
+ol.geom.flat.topology.lineStringIsClosed = function(flatCoordinates, offset, end, stride) {
+  var lastCoord = end - stride;
+  if (flatCoordinates[offset] === flatCoordinates[lastCoord] &&
+      flatCoordinates[offset + 1] === flatCoordinates[lastCoord + 1] && (end - offset) / stride > 3) {
+    return !!ol.geom.flat.area.linearRing(flatCoordinates, offset, end, stride);
+  }
+  return false;
 };
 
+// This file is automatically generated, do not edit
+/* eslint openlayers-internal/no-missing-requires: 0 */
+goog.provide('ol.render.webgl.linestringreplay.defaultshader');
 
-/**
- * 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 = '';
+goog.require('ol');
+goog.require('ol.webgl.Fragment');
+goog.require('ol.webgl.Vertex');
+
+if (ol.ENABLE_WEBGL) {
 
   /**
-   * @param {!goog.html.SafeHtml.TextOrHtml_|
-   *     !Array<!goog.html.SafeHtml.TextOrHtml_>} argument
+   * @constructor
+   * @extends {ol.webgl.Fragment}
+   * @struct
    */
-  var addArgument = function(argument) {
-    if (goog.isArray(argument)) {
-      goog.array.forEach(argument, addArgument);
-    } else {
-      var html = goog.html.SafeHtml.htmlEscape(argument);
-      content += goog.html.SafeHtml.unwrap(html);
-      var htmlDir = html.getDirection();
-      if (dir == goog.i18n.bidi.Dir.NEUTRAL) {
-        dir = htmlDir;
-      } else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) {
-        dir = null;
-      }
-    }
+  ol.render.webgl.linestringreplay.defaultshader.Fragment = function() {
+    ol.webgl.Fragment.call(this, ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE);
   };
-
-  goog.array.forEach(arguments, addArgument);
-  return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
-      content, dir);
-};
+  ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Fragment, ol.webgl.Fragment);
 
 
-/**
- * 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;
-};
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\nuniform vec2 u_size;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n  if (v_round > 0.0) {\n    vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n        (v_roundVertex.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n    if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth * u_pixelRatio) {\n      discard;\n    }\n  }\n  gl_FragColor = u_color;\n  float alpha = u_color.a * u_opacity;\n  if (alpha == 0.0) {\n    discard;\n  }\n  gl_FragColor.a = alpha;\n}\n';
 
 
-/**
- * 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_ = {};
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec2 b;varying float c;uniform float m;uniform vec4 n;uniform vec2 o;uniform float p;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*o.x*p,(b.y+1.0)/2.0*o.y*p);if(length(windowCoords-gl_FragCoord.xy)>c*p){discard;}} gl_FragColor=n;float alpha=n.a*m;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}';
 
 
-/**
- * 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);
-};
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ?
+      ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE :
+      ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE;
 
 
-/**
- * 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;
-};
+  ol.render.webgl.linestringreplay.defaultshader.fragment = new ol.render.webgl.linestringreplay.defaultshader.Fragment();
 
 
-/**
- * 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;
+  /**
+   * @constructor
+   * @extends {ol.webgl.Vertex}
+   * @struct
+   */
+  ol.render.webgl.linestringreplay.defaultshader.Vertex = function() {
+    ol.webgl.Vertex.call(this, ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE);
+  };
+  ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Vertex, ol.webgl.Vertex);
 
-  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];
-  }
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nbool nearlyEquals(in float value, in float ref) {\n  float epsilon = 0.000000000001;\n  return value >= ref - epsilon && value <= ref + epsilon;\n}\n\nvoid alongNormal(out vec2 offset, in vec2 nextP, in float turnDir, in float direction) {\n  vec2 dirVect = nextP - a_position;\n  vec2 normal = normalize(vec2(-turnDir * dirVect.y, turnDir * dirVect.x));\n  offset = u_lineWidth / 2.0 * normal * direction;\n}\n\nvoid miterUp(out vec2 offset, out float round, in bool isRound, in float direction) {\n  float halfWidth = u_lineWidth / 2.0;\n  vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n  vec2 normal = vec2(-tangent.y, tangent.x);\n  vec2 dirVect = a_nextPos - a_position;\n  vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n  float miterLength = abs(halfWidth / dot(normal, tmpNormal));\n  offset = normal * direction * miterLength;\n  round = 0.0;\n  if (isRound) {\n    round = 1.0;\n  } else if (miterLength > u_miterLimit + u_lineWidth) {\n    offset = halfWidth * tmpNormal * direction;\n  }\n}\n\nbool miterDown(out vec2 offset, in vec4 projPos, in mat4 offsetMatrix, in float direction) {\n  bool degenerate = false;\n  vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n  vec2 normal = vec2(-tangent.y, tangent.x);\n  vec2 dirVect = a_lastPos - a_position;\n  vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n  vec2 longOffset, shortOffset, longVertex;\n  vec4 shortProjVertex;\n  float halfWidth = u_lineWidth / 2.0;\n  if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n    longOffset = tmpNormal * direction * halfWidth;\n    shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * halfWidth;\n    longVertex = a_nextPos;\n    shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0.0, 1.0);\n  } else {\n    shortOffset = tmpNormal * direction * halfWidth;\n    longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * halfWidth;\n    longVertex = a_lastPos;\n    shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0.0, 1.0);\n  }\n  //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n  vec4 p1 = u_projectionMatrix * vec4(longVertex, 0.0, 1.0) + offsetMatrix * vec4(longOffset, 0.0, 0.0);\n  vec4 p2 = projPos + offsetMatrix * vec4(longOffset, 0.0, 0.0);\n  vec4 p3 = shortProjVertex + offsetMatrix * vec4(-shortOffset, 0.0, 0.0);\n  vec4 p4 = shortProjVertex + offsetMatrix * vec4(shortOffset, 0.0, 0.0);\n  float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n  float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n  float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n  float epsilon = 0.000000000001;\n  if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n    shortProjVertex.x = p1.x + firstU * (p2.x - p1.x);\n    shortProjVertex.y = p1.y + firstU * (p2.y - p1.y);\n    offset = shortProjVertex.xy;\n    degenerate = true;\n  } else {\n    float miterLength = abs(halfWidth / dot(normal, tmpNormal));\n    offset = normal * direction * miterLength;\n  }\n  return degenerate;\n}\n\nvoid squareCap(out vec2 offset, out float round, in bool isRound, in vec2 nextP,\n    in float turnDir, in float direction) {\n  round = 0.0;\n  vec2 dirVect = a_position - nextP;\n  vec2 firstNormal = normalize(dirVect);\n  vec2 secondNormal = vec2(turnDir * firstNormal.y * direction, -turnDir * firstNormal.x * direction);\n  vec2 hypotenuse = normalize(firstNormal - secondNormal);\n  vec2 normal = vec2(turnDir * hypotenuse.y * direction, -turnDir * hypotenuse.x * direction);\n  float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n  offset = normal * length;\n  if (isRound) {\n    round = 1.0;\n  }\n}\n\nvoid main(void) {\n  bool degenerate = false;\n  float direction = float(sign(a_direction));\n  mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n  vec2 offset;\n  vec4 projPos = u_projectionMatrix * vec4(a_position, 0.0, 1.0);\n  bool round = nearlyEquals(mod(a_direction, 2.0), 0.0);\n\n  v_round = 0.0;\n  v_halfWidth = u_lineWidth / 2.0;\n  v_roundVertex = projPos.xy;\n\n  if (nearlyEquals(mod(a_direction, 3.0), 0.0) || nearlyEquals(mod(a_direction, 17.0), 0.0)) {\n    alongNormal(offset, a_nextPos, 1.0, direction);\n  } else if (nearlyEquals(mod(a_direction, 5.0), 0.0) || nearlyEquals(mod(a_direction, 13.0), 0.0)) {\n    alongNormal(offset, a_lastPos, -1.0, direction);\n  } else if (nearlyEquals(mod(a_direction, 23.0), 0.0)) {\n    miterUp(offset, v_round, round, direction);\n  } else if (nearlyEquals(mod(a_direction, 19.0), 0.0)) {\n    degenerate = miterDown(offset, projPos, offsetMatrix, direction);\n  } else if (nearlyEquals(mod(a_direction, 7.0), 0.0)) {\n    squareCap(offset, v_round, round, a_nextPos, 1.0, direction);\n  } else if (nearlyEquals(mod(a_direction, 11.0), 0.0)) {\n    squareCap(offset, v_round, round, a_lastPos, -1.0, direction);\n  }\n  if (!degenerate) {\n    vec4 offsets = offsetMatrix * vec4(offset, 0.0, 0.0);\n    gl_Position = projPos + offsets;\n  } else {\n    gl_Position = vec4(offset, 0.0, 1.0);\n  }\n}\n\n\n';
 
-  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;
-    }
-  }
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;bool nearlyEquals(in float value,in float ref){float epsilon=0.000000000001;return value>=ref-epsilon&&value<=ref+epsilon;}void alongNormal(out vec2 offset,in vec2 nextP,in float turnDir,in float direction){vec2 dirVect=nextP-e;vec2 normal=normalize(vec2(-turnDir*dirVect.y,turnDir*dirVect.x));offset=k/2.0*normal*direction;}void miterUp(out vec2 offset,out float round,in bool isRound,in float direction){float halfWidth=k/2.0;vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));float miterLength=abs(halfWidth/dot(normal,tmpNormal));offset=normal*direction*miterLength;round=0.0;if(isRound){round=1.0;}else if(miterLength>l+k){offset=halfWidth*tmpNormal*direction;}} bool miterDown(out vec2 offset,in vec4 projPos,in mat4 offsetMatrix,in float direction){bool degenerate=false;vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);vec2 dirVect=d-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;float halfWidth=k/2.0;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*halfWidth;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*halfWidth;longVertex=f;shortProjVertex=h*vec4(d,0.0,1.0);}else{shortOffset=tmpNormal*direction*halfWidth;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*halfWidth;longVertex=d;shortProjVertex=h*vec4(f,0.0,1.0);}vec4 p1=h*vec4(longVertex,0.0,1.0)+offsetMatrix*vec4(longOffset,0.0,0.0);vec4 p2=projPos+offsetMatrix*vec4(longOffset,0.0,0.0);vec4 p3=shortProjVertex+offsetMatrix*vec4(-shortOffset,0.0,0.0);vec4 p4=shortProjVertex+offsetMatrix*vec4(shortOffset,0.0,0.0);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;float epsilon=0.000000000001;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){shortProjVertex.x=p1.x+firstU*(p2.x-p1.x);shortProjVertex.y=p1.y+firstU*(p2.y-p1.y);offset=shortProjVertex.xy;degenerate=true;}else{float miterLength=abs(halfWidth/dot(normal,tmpNormal));offset=normal*direction*miterLength;}return degenerate;}void squareCap(out vec2 offset,out float round,in bool isRound,in vec2 nextP,in float turnDir,in float direction){round=0.0;vec2 dirVect=e-nextP;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(turnDir*firstNormal.y*direction,-turnDir*firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);vec2 normal=vec2(turnDir*hypotenuse.y*direction,-turnDir*hypotenuse.x*direction);float length=sqrt(c*c*2.0);offset=normal*length;if(isRound){round=1.0;}} void main(void){bool degenerate=false;float direction=float(sign(g));mat4 offsetMatrix=i*j;vec2 offset;vec4 projPos=h*vec4(e,0.0,1.0);bool round=nearlyEquals(mod(g,2.0),0.0);a=0.0;c=k/2.0;b=projPos.xy;if(nearlyEquals(mod(g,3.0),0.0)||nearlyEquals(mod(g,17.0),0.0)){alongNormal(offset,f,1.0,direction);}else if(nearlyEquals(mod(g,5.0),0.0)||nearlyEquals(mod(g,13.0),0.0)){alongNormal(offset,d,-1.0,direction);}else if(nearlyEquals(mod(g,23.0),0.0)){miterUp(offset,a,round,direction);}else if(nearlyEquals(mod(g,19.0),0.0)){degenerate=miterDown(offset,projPos,offsetMatrix,direction);}else if(nearlyEquals(mod(g,7.0),0.0)){squareCap(offset,a,round,f,1.0,direction);}else if(nearlyEquals(mod(g,11.0),0.0)){squareCap(offset,a,round,d,-1.0,direction);}if(!degenerate){vec4 offsets=offsetMatrix*vec4(offset,0.0,0.0);gl_Position=projPos+offsets;}else{gl_Position=vec4(offset,0.0,1.0);}}';
 
-  return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
-      result, dir);
-};
 
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ?
+      ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE :
+      ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE;
 
-/**
- * @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];
-  }
+  ol.render.webgl.linestringreplay.defaultshader.vertex = new ol.render.webgl.linestringreplay.defaultshader.Vertex();
 
-  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;
-};
+  /**
+   * @constructor
+   * @param {WebGLRenderingContext} gl GL.
+   * @param {WebGLProgram} program Program.
+   * @struct
+   */
+  ol.render.webgl.linestringreplay.defaultshader.Locations = function(gl, program) {
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_color = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_color' : 'n');
 
-/**
- * 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);
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_lineWidth = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_lineWidth' : 'k');
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_miterLimit = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_miterLimit' : 'l');
 
-/**
- * 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);
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_offsetRotateMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j');
 
-// 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.
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_offsetScaleMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i');
 
-/**
- * @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.
- */
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_opacity = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_opacity' : 'm');
 
-goog.provide('goog.dom.safe');
-goog.provide('goog.dom.safe.InsertAdjacentHtmlPosition');
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_pixelRatio = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_pixelRatio' : 'p');
 
-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');
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_projectionMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h');
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_size = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_size' : 'o');
 
-/** @enum {string} */
-goog.dom.safe.InsertAdjacentHtmlPosition = {
-  AFTERBEGIN: 'afterbegin',
-  AFTEREND: 'afterend',
-  BEFOREBEGIN: 'beforebegin',
-  BEFOREEND: 'beforeend'
-};
+    /**
+     * @type {number}
+     */
+    this.a_direction = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_direction' : 'g');
 
+    /**
+     * @type {number}
+     */
+    this.a_lastPos = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_lastPos' : 'd');
 
-/**
- * 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));
-};
-
+    /**
+     * @type {number}
+     */
+    this.a_nextPos = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_nextPos' : 'f');
 
-/**
- * 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);
-};
+    /**
+     * @type {number}
+     */
+    this.a_position = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_position' : 'e');
+  };
 
+}
 
-/**
- * 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);
-};
+goog.provide('ol.render.webgl.LineStringReplay');
 
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.color');
+goog.require('ol.extent');
+goog.require('ol.geom.flat.orient');
+goog.require('ol.geom.flat.transform');
+goog.require('ol.geom.flat.topology');
+goog.require('ol.obj');
+goog.require('ol.render.webgl');
+goog.require('ol.render.webgl.Replay');
+goog.require('ol.render.webgl.linestringreplay.defaultshader');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Buffer');
 
-/**
- * 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));
-};
 
+if (ol.ENABLE_WEBGL) {
 
-/**
- * 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);
-};
+  /**
+   * @constructor
+   * @extends {ol.render.webgl.Replay}
+   * @param {number} tolerance Tolerance.
+   * @param {ol.Extent} maxExtent Max extent.
+   * @struct
+   */
+  ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) {
+    ol.render.webgl.Replay.call(this, tolerance, maxExtent);
 
+    /**
+     * @private
+     * @type {ol.render.webgl.linestringreplay.defaultshader.Locations}
+     */
+    this.defaultLocations_ = null;
 
-/**
- * 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);
-};
+    /**
+     * @private
+     * @type {Array.<Array.<?>>}
+     */
+    this.styles_ = [];
 
+    /**
+     * @private
+     * @type {Array.<number>}
+     */
+    this.styleIndices_ = [];
 
-/**
- * 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);
-};
+    /**
+     * @private
+     * @type {{strokeColor: (Array.<number>|null),
+     *         lineCap: (string|undefined),
+     *         lineDash: Array.<number>,
+     *         lineDashOffset: (number|undefined),
+     *         lineJoin: (string|undefined),
+     *         lineWidth: (number|undefined),
+     *         miterLimit: (number|undefined),
+     *         changed: boolean}|null}
+     */
+    this.state_ = {
+      strokeColor: null,
+      lineCap: undefined,
+      lineDash: null,
+      lineDashOffset: undefined,
+      lineJoin: undefined,
+      lineWidth: undefined,
+      miterLimit: undefined,
+      changed: false
+    };
 
+  };
+  ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay);
 
-/**
- * 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);
-};
 
+  /**
+   * Draw one segment.
+   * @private
+   * @param {Array.<number>} flatCoordinates Flat coordinates.
+   * @param {number} offset Offset.
+   * @param {number} end End.
+   * @param {number} stride Stride.
+   */
+  ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) {
 
-/**
- * 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();
-  }
-};
+    var i, ii;
+    var numVertices = this.vertices.length;
+    var numIndices = this.indices.length;
+    //To save a vertex, the direction of a point is a product of the sign (1 or -1), a prime from
+    //ol.render.webgl.LineStringReplay.Instruction_, and a rounding factor (1 or 2). If the product is even,
+    //we round it. If it is odd, we don't.
+    var lineJoin = this.state_.lineJoin === 'bevel' ? 0 :
+        this.state_.lineJoin === 'miter' ? 1 : 2;
+    var lineCap = this.state_.lineCap === 'butt' ? 0 :
+        this.state_.lineCap === 'square' ? 1 : 2;
+    var closed = ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, offset, end, stride);
+    var startCoords, sign, n;
+    var lastIndex = numIndices;
+    var lastSign = 1;
+    //We need the adjacent vertices to define normals in joins. p0 = last, p1 = current, p2 = next.
+    var p0, p1, p2;
+
+    for (i = offset, ii = end; i < ii; i += stride) {
+
+      n = numVertices / 7;
+
+      p0 = p1;
+      p1 = p2 || [flatCoordinates[i], flatCoordinates[i + 1]];
+      //First vertex.
+      if (i === offset) {
+        p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]];
+        if (end - offset === stride * 2 && ol.array.equals(p1, p2)) {
+          break;
+        }
+        if (closed) {
+          //A closed line! Complete the circle.
+          p0 = [flatCoordinates[end - stride * 2],
+            flatCoordinates[end - stride * 2 + 1]];
 
+          startCoords = p2;
+        } else {
+          //Add the first two/four vertices.
 
-/**
- * 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);
-};
+          if (lineCap) {
+            numVertices = this.addVertices_([0, 0], p1, p2,
+                lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE_CAP * lineCap, numVertices);
 
+            numVertices = this.addVertices_([0, 0], p1, p2,
+                -lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE_CAP * lineCap, numVertices);
 
-/**
- * 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);
-};
+            this.indices[numIndices++] = n + 2;
+            this.indices[numIndices++] = n;
+            this.indices[numIndices++] = n + 1;
 
+            this.indices[numIndices++] = n + 1;
+            this.indices[numIndices++] = n + 3;
+            this.indices[numIndices++] = n + 2;
 
-/**
- * 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);
-};
+          }
 
+          numVertices = this.addVertices_([0, 0], p1, p2,
+              lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE * (lineCap || 1), numVertices);
 
-/**
- * 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);
-};
+          numVertices = this.addVertices_([0, 0], p1, p2,
+              -lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE * (lineCap || 1), numVertices);
 
-// 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.
+          lastIndex = numVertices / 7 - 1;
 
-/**
- * @fileoverview A utility class for representing two-dimensional positions.
- */
+          continue;
+        }
+      } else if (i === end - stride) {
+        //Last vertex.
+        if (closed) {
+          //Same as the first vertex.
+          p2 = startCoords;
+          break;
+        } else {
+          p0 = p0 || [0, 0];
 
+          numVertices = this.addVertices_(p0, p1, [0, 0],
+              lastSign * ol.render.webgl.LineStringReplay.Instruction_.END_LINE * (lineCap || 1), numVertices);
 
-goog.provide('goog.math.Coordinate');
+          numVertices = this.addVertices_(p0, p1, [0, 0],
+              -lastSign * ol.render.webgl.LineStringReplay.Instruction_.END_LINE * (lineCap || 1), numVertices);
 
-goog.require('goog.math');
+          this.indices[numIndices++] = n;
+          this.indices[numIndices++] = lastIndex - 1;
+          this.indices[numIndices++] = lastIndex;
 
+          this.indices[numIndices++] = lastIndex;
+          this.indices[numIndices++] = n + 1;
+          this.indices[numIndices++] = n;
 
+          if (lineCap) {
+            numVertices = this.addVertices_(p0, p1, [0, 0],
+                lastSign * ol.render.webgl.LineStringReplay.Instruction_.END_LINE_CAP * lineCap, numVertices);
 
-/**
- * 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;
+            numVertices = this.addVertices_(p0, p1, [0, 0],
+                -lastSign * ol.render.webgl.LineStringReplay.Instruction_.END_LINE_CAP * lineCap, numVertices);
 
-  /**
-   * Y-value
-   * @type {number}
-   */
-  this.y = goog.isDef(opt_y) ? opt_y : 0;
-};
+            this.indices[numIndices++] = n + 2;
+            this.indices[numIndices++] = n;
+            this.indices[numIndices++] = n + 1;
 
+            this.indices[numIndices++] = n + 1;
+            this.indices[numIndices++] = n + 3;
+            this.indices[numIndices++] = n + 2;
 
-/**
- * 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);
-};
+          }
 
+          break;
+        }
+      } else {
+        p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]];
+      }
 
-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 + ')';
-  };
-}
+      // We group CW and straight lines, thus the not so inituitive CCW checking function.
+      sign = ol.render.webgl.triangleIsCounterClockwise(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1])
+          ? -1 : 1;
 
+      numVertices = this.addVertices_(p0, p1, p2,
+          sign * ol.render.webgl.LineStringReplay.Instruction_.BEVEL_FIRST * (lineJoin || 1), numVertices);
 
-/**
- * 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;
-};
+      numVertices = this.addVertices_(p0, p1, p2,
+          sign * ol.render.webgl.LineStringReplay.Instruction_.BEVEL_SECOND * (lineJoin || 1), numVertices);
 
+      numVertices = this.addVertices_(p0, p1, p2,
+          -sign * ol.render.webgl.LineStringReplay.Instruction_.MITER_BOTTOM * (lineJoin || 1), numVertices);
 
-/**
- * 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);
-};
+      if (i > offset) {
+        this.indices[numIndices++] = n;
+        this.indices[numIndices++] = lastIndex - 1;
+        this.indices[numIndices++] = lastIndex;
 
+        this.indices[numIndices++] = n + 2;
+        this.indices[numIndices++] = n;
+        this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1;
+      }
 
-/**
- * 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);
-};
+      this.indices[numIndices++] = n;
+      this.indices[numIndices++] = n + 2;
+      this.indices[numIndices++] = n + 1;
 
+      lastIndex = n + 2;
+      lastSign = sign;
 
-/**
- * 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);
-};
+      //Add miter
+      if (lineJoin) {
+        numVertices = this.addVertices_(p0, p1, p2,
+            sign * ol.render.webgl.LineStringReplay.Instruction_.MITER_TOP * lineJoin, numVertices);
 
+        this.indices[numIndices++] = n + 1;
+        this.indices[numIndices++] = n + 3;
+        this.indices[numIndices++] = n;
+      }
+    }
 
-/**
- * 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;
-};
+    if (closed) {
+      n = n || numVertices / 7;
+      sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2)
+          ? 1 : -1;
 
+      numVertices = this.addVertices_(p0, p1, p2,
+          sign * ol.render.webgl.LineStringReplay.Instruction_.BEVEL_FIRST * (lineJoin || 1), numVertices);
 
-/**
- * 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);
-};
+      numVertices = this.addVertices_(p0, p1, p2,
+          -sign * ol.render.webgl.LineStringReplay.Instruction_.MITER_BOTTOM * (lineJoin || 1), numVertices);
 
+      this.indices[numIndices++] = n;
+      this.indices[numIndices++] = lastIndex - 1;
+      this.indices[numIndices++] = lastIndex;
 
-/**
- * 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);
-};
+      this.indices[numIndices++] = n + 1;
+      this.indices[numIndices++] = n;
+      this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1;
+    }
+  };
 
+  /**
+   * @param {Array.<number>} p0 Last coordinates.
+   * @param {Array.<number>} p1 Current coordinates.
+   * @param {Array.<number>} p2 Next coordinates.
+   * @param {number} product Sign, instruction, and rounding product.
+   * @param {number} numVertices Vertex counter.
+   * @return {number} Vertex counter.
+   * @private
+   */
+  ol.render.webgl.LineStringReplay.prototype.addVertices_ = function(p0, p1, p2, product, numVertices) {
+    this.vertices[numVertices++] = p0[0];
+    this.vertices[numVertices++] = p0[1];
+    this.vertices[numVertices++] = p1[0];
+    this.vertices[numVertices++] = p1[1];
+    this.vertices[numVertices++] = p2[0];
+    this.vertices[numVertices++] = p2[1];
+    this.vertices[numVertices++] = product;
 
-/**
- * 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;
-};
+    return numVertices;
+  };
 
+  /**
+   * Check if the linestring can be drawn (i. e. valid).
+   * @param {Array.<number>} flatCoordinates Flat coordinates.
+   * @param {number} offset Offset.
+   * @param {number} end End.
+   * @param {number} stride Stride.
+   * @return {boolean} The linestring can be drawn.
+   * @private
+   */
+  ol.render.webgl.LineStringReplay.prototype.isValid_ = function(flatCoordinates, offset, end, stride) {
+    var range = end - offset;
+    if (range < stride * 2) {
+      return false;
+    } else if (range === stride * 2) {
+      var firstP = [flatCoordinates[offset], flatCoordinates[offset + 1]];
+      var lastP = [flatCoordinates[offset + stride], flatCoordinates[offset + stride + 1]];
+      return !ol.array.equals(firstP, lastP);
+    }
 
-/**
- * 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;
-};
+    return true;
+  };
 
 
-/**
- * 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;
-};
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) {
+    var flatCoordinates = lineStringGeometry.getFlatCoordinates();
+    var stride = lineStringGeometry.getStride();
+    if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) {
+      flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length,
+          stride, -this.origin[0], -this.origin[1]);
+      if (this.state_.changed) {
+        this.styleIndices_.push(this.indices.length);
+        this.state_.changed = false;
+      }
+      this.startIndices.push(this.indices.length);
+      this.startIndicesFeature.push(feature);
+      this.drawCoordinates_(
+          flatCoordinates, 0, flatCoordinates.length, stride);
+    }
+  };
 
 
-/**
- * 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;
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {
+    var indexCount = this.indices.length;
+    var ends = multiLineStringGeometry.getEnds();
+    ends.unshift(0);
+    var flatCoordinates = multiLineStringGeometry.getFlatCoordinates();
+    var stride = multiLineStringGeometry.getStride();
+    var i, ii;
+    if (ends.length > 1) {
+      for (i = 1, ii = ends.length; i < ii; ++i) {
+        if (this.isValid_(flatCoordinates, ends[i - 1], ends[i], stride)) {
+          var lineString = ol.geom.flat.transform.translate(flatCoordinates, ends[i - 1], ends[i],
+              stride, -this.origin[0], -this.origin[1]);
+          this.drawCoordinates_(
+              lineString, 0, lineString.length, stride);
+        }
+      }
     }
-  }
-  return this;
-};
-
+    if (this.indices.length > indexCount) {
+      this.startIndices.push(indexCount);
+      this.startIndicesFeature.push(feature);
+      if (this.state_.changed) {
+        this.styleIndices_.push(indexCount);
+        this.state_.changed = false;
+      }
+    }
+  };
 
-/**
- * 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;
-};
 
+  /**
+   * @param {Array.<number>} flatCoordinates Flat coordinates.
+   * @param {Array.<Array.<number>>} holeFlatCoordinates Hole flat coordinates.
+   * @param {number} stride Stride.
+   */
+  ol.render.webgl.LineStringReplay.prototype.drawPolygonCoordinates = function(
+      flatCoordinates, holeFlatCoordinates, stride) {
+    if (!ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, 0,
+        flatCoordinates.length, stride)) {
+      flatCoordinates.push(flatCoordinates[0]);
+      flatCoordinates.push(flatCoordinates[1]);
+    }
+    this.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride);
+    if (holeFlatCoordinates.length) {
+      var i, ii;
+      for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) {
+        if (!ol.geom.flat.topology.lineStringIsClosed(holeFlatCoordinates[i], 0,
+            holeFlatCoordinates[i].length, stride)) {
+          holeFlatCoordinates[i].push(holeFlatCoordinates[i][0]);
+          holeFlatCoordinates[i].push(holeFlatCoordinates[i][1]);
+        }
+        this.drawCoordinates_(holeFlatCoordinates[i], 0,
+            holeFlatCoordinates[i].length, stride);
+      }
+    }
+  };
 
-/**
- * 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);
+  /**
+   * @param {ol.Feature|ol.render.Feature} feature Feature.
+   * @param {number=} opt_index Index count.
+   */
+  ol.render.webgl.LineStringReplay.prototype.setPolygonStyle = function(feature, opt_index) {
+    var index = opt_index === undefined ? this.indices.length : opt_index;
+    this.startIndices.push(index);
+    this.startIndicesFeature.push(feature);
+    if (this.state_.changed) {
+      this.styleIndices_.push(index);
+      this.state_.changed = false;
+    }
+  };
 
-  this.x = (x - center.x) * cos - (y - center.y) * sin + center.x;
-  this.y = (x - center.x) * sin + (y - center.y) * cos + center.y;
-};
 
+  /**
+   * @return {number} Current index.
+   */
+  ol.render.webgl.LineStringReplay.prototype.getCurrentIndex = function() {
+    return this.indices.length;
+  };
 
-/**
- * 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.
+  /**
+   * @inheritDoc
+   **/
+  ol.render.webgl.LineStringReplay.prototype.finish = function(context) {
+    // create, bind, and populate the vertices buffer
+    this.verticesBuffer = new ol.webgl.Buffer(this.vertices);
 
-/**
- * @fileoverview A utility class for representing two-dimensional sizes.
- * @author brenneman@google.com (Shawn Brenneman)
- */
+    // create, bind, and populate the indices buffer
+    this.indicesBuffer = new ol.webgl.Buffer(this.indices);
 
+    this.startIndices.push(this.indices.length);
 
-goog.provide('goog.math.Size');
+    //Clean up, if there is nothing to draw
+    if (this.styleIndices_.length === 0 && this.styles_.length > 0) {
+      this.styles_ = [];
+    }
 
+    this.vertices = null;
+    this.indices = null;
+  };
 
 
-/**
- * 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}
+   * @inheritDoc
    */
-  this.width = width;
+  ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function(context) {
+    var verticesBuffer = this.verticesBuffer;
+    var indicesBuffer = this.indicesBuffer;
+    return function() {
+      context.deleteBuffer(verticesBuffer);
+      context.deleteBuffer(indicesBuffer);
+    };
+  };
+
 
   /**
-   * Height
-   * @type {number}
+   * @inheritDoc
    */
-  this.height = height;
-};
+  ol.render.webgl.LineStringReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {
+    // get the program
+    var fragmentShader, vertexShader;
+    fragmentShader = ol.render.webgl.linestringreplay.defaultshader.fragment;
+    vertexShader = ol.render.webgl.linestringreplay.defaultshader.vertex;
+    var program = context.getProgram(fragmentShader, vertexShader);
+
+    // get the locations
+    var locations;
+    if (!this.defaultLocations_) {
+      // eslint-disable-next-line openlayers-internal/no-missing-requires
+      locations = new ol.render.webgl.linestringreplay.defaultshader.Locations(gl, program);
+      this.defaultLocations_ = locations;
+    } else {
+      locations = this.defaultLocations_;
+    }
+
+    context.useProgram(program);
 
+    // enable the vertex attrib arrays
+    gl.enableVertexAttribArray(locations.a_lastPos);
+    gl.vertexAttribPointer(locations.a_lastPos, 2, ol.webgl.FLOAT,
+        false, 28, 0);
 
-/**
- * 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;
-};
+    gl.enableVertexAttribArray(locations.a_position);
+    gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT,
+        false, 28, 8);
 
+    gl.enableVertexAttribArray(locations.a_nextPos);
+    gl.vertexAttribPointer(locations.a_nextPos, 2, ol.webgl.FLOAT,
+        false, 28, 16);
 
-/**
- * @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);
-};
+    gl.enableVertexAttribArray(locations.a_direction);
+    gl.vertexAttribPointer(locations.a_direction, 1, ol.webgl.FLOAT,
+        false, 28, 24);
+
+    // Enable renderer specific uniforms.
+    gl.uniform2fv(locations.u_size, size);
+    gl.uniform1f(locations.u_pixelRatio, pixelRatio);
+
+    return locations;
+  };
 
 
-if (goog.DEBUG) {
   /**
-   * Returns a nice string representing size.
-   * @return {string} In the form (50 x 73).
-   * @override
+   * @inheritDoc
    */
-  goog.math.Size.prototype.toString = function() {
-    return '(' + this.width + ' x ' + this.height + ')';
+  ol.render.webgl.LineStringReplay.prototype.shutDownProgram = function(gl, locations) {
+    gl.disableVertexAttribArray(locations.a_lastPos);
+    gl.disableVertexAttribArray(locations.a_position);
+    gl.disableVertexAttribArray(locations.a_nextPos);
+    gl.disableVertexAttribArray(locations.a_direction);
   };
-}
 
 
-/**
- * @return {number} The longer of the two dimensions in the size.
- */
-goog.math.Size.prototype.getLongest = function() {
-  return Math.max(this.width, this.height);
-};
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.LineStringReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) {
+    //Save GL parameters.
+    var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC));
+    var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK));
 
+    if (!hitDetection) {
+      gl.enable(gl.DEPTH_TEST);
+      gl.depthMask(true);
+      gl.depthFunc(gl.NOTEQUAL);
+    }
 
-/**
- * @return {number} The shorter of the two dimensions in the size.
- */
-goog.math.Size.prototype.getShortest = function() {
-  return Math.min(this.width, this.height);
-};
+    if (!ol.obj.isEmpty(skippedFeaturesHash)) {
+      this.drawReplaySkipping_(gl, context, skippedFeaturesHash);
+    } else {
+      //Draw by style groups to minimize drawElements() calls.
+      var i, start, end, nextStyle;
+      end = this.startIndices[this.startIndices.length - 1];
+      for (i = this.styleIndices_.length - 1; i >= 0; --i) {
+        start = this.styleIndices_[i];
+        nextStyle = this.styles_[i];
+        this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]);
+        this.drawElements(gl, context, start, end);
+        gl.clear(gl.DEPTH_BUFFER_BIT);
+        end = start;
+      }
+    }
+    if (!hitDetection) {
+      gl.disable(gl.DEPTH_TEST);
+      gl.clear(gl.DEPTH_BUFFER_BIT);
+      //Restore GL parameters.
+      gl.depthMask(tmpDepthMask);
+      gl.depthFunc(tmpDepthFunc);
+    }
+  };
 
 
-/**
- * @return {number} The area of the size (width * height).
- */
-goog.math.Size.prototype.area = function() {
-  return this.width * this.height;
-};
+  /**
+   * @private
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {ol.webgl.Context} context Context.
+   * @param {Object} skippedFeaturesHash Ids of features to skip.
+   */
+  ol.render.webgl.LineStringReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) {
+    var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart;
+    featureIndex = this.startIndices.length - 2;
+    end = start = this.startIndices[featureIndex + 1];
+    for (i = this.styleIndices_.length - 1; i >= 0; --i) {
+      nextStyle = this.styles_[i];
+      this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]);
+      groupStart = this.styleIndices_[i];
 
+      while (featureIndex >= 0 &&
+          this.startIndices[featureIndex] >= groupStart) {
+        featureStart = this.startIndices[featureIndex];
+        feature = this.startIndicesFeature[featureIndex];
+        featureUid = ol.getUid(feature).toString();
 
-/**
- * @return {number} The perimeter of the size (width + height) * 2.
- */
-goog.math.Size.prototype.perimeter = function() {
-  return (this.width + this.height) * 2;
-};
+        if (skippedFeaturesHash[featureUid]) {
+          if (start !== end) {
+            this.drawElements(gl, context, start, end);
+            gl.clear(gl.DEPTH_BUFFER_BIT);
+          }
+          end = featureStart;
+        }
+        featureIndex--;
+        start = featureStart;
+      }
+      if (start !== end) {
+        this.drawElements(gl, context, start, end);
+        gl.clear(gl.DEPTH_BUFFER_BIT);
+      }
+      start = end = groupStart;
+    }
+  };
 
 
-/**
- * @return {number} The ratio of the size's width to its height.
- */
-goog.math.Size.prototype.aspectRatio = function() {
-  return this.width / this.height;
-};
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.LineStringReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash,
+      featureCallback, opt_hitExtent) {
+    var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex;
+    featureIndex = this.startIndices.length - 2;
+    end = this.startIndices[featureIndex + 1];
+    for (i = this.styleIndices_.length - 1; i >= 0; --i) {
+      nextStyle = this.styles_[i];
+      this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]);
+      groupStart = this.styleIndices_[i];
+
+      while (featureIndex >= 0 &&
+          this.startIndices[featureIndex] >= groupStart) {
+        start = this.startIndices[featureIndex];
+        feature = this.startIndicesFeature[featureIndex];
+        featureUid = ol.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, context, start, end);
+
+          var result = featureCallback(feature);
 
+          if (result) {
+            return result;
+          }
 
-/**
- * @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();
-};
+        }
+        featureIndex--;
+        end = start;
+      }
+    }
+    return undefined;
+  };
 
 
-/**
- * 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;
-};
+  /**
+   * @private
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {Array.<number>} color Color.
+   * @param {number} lineWidth Line width.
+   * @param {number} miterLimit Miter limit.
+   */
+  ol.render.webgl.LineStringReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth, miterLimit) {
+    gl.uniform4fv(this.defaultLocations_.u_color, color);
+    gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth);
+    gl.uniform1f(this.defaultLocations_.u_miterLimit, miterLimit);
+  };
 
 
-/**
- * @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;
-};
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
+    var strokeStyleLineCap = strokeStyle.getLineCap();
+    this.state_.lineCap = strokeStyleLineCap !== undefined ?
+        strokeStyleLineCap : ol.render.webgl.defaultLineCap;
+    var strokeStyleLineDash = strokeStyle.getLineDash();
+    this.state_.lineDash = strokeStyleLineDash ?
+        strokeStyleLineDash : ol.render.webgl.defaultLineDash;
+    var strokeStyleLineDashOffset = strokeStyle.getLineDashOffset();
+    this.state_.lineDashOffset = strokeStyleLineDashOffset ?
+        strokeStyleLineDashOffset : ol.render.webgl.defaultLineDashOffset;
+    var strokeStyleLineJoin = strokeStyle.getLineJoin();
+    this.state_.lineJoin = strokeStyleLineJoin !== undefined ?
+        strokeStyleLineJoin : ol.render.webgl.defaultLineJoin;
+    var strokeStyleColor = strokeStyle.getColor();
+    if (!(strokeStyleColor instanceof CanvasGradient) &&
+        !(strokeStyleColor instanceof CanvasPattern)) {
+      strokeStyleColor = ol.color.asArray(strokeStyleColor).map(function(c, i) {
+        return i != 3 ? c / 255 : c;
+      }) || ol.render.webgl.defaultStrokeStyle;
+    } else {
+      strokeStyleColor = ol.render.webgl.defaultStrokeStyle;
+    }
+    var strokeStyleWidth = strokeStyle.getWidth();
+    strokeStyleWidth = strokeStyleWidth !== undefined ?
+        strokeStyleWidth : ol.render.webgl.defaultLineWidth;
+    var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
+    strokeStyleMiterLimit = strokeStyleMiterLimit !== undefined ?
+        strokeStyleMiterLimit : ol.render.webgl.defaultMiterLimit;
+    if (!this.state_.strokeColor || !ol.array.equals(this.state_.strokeColor, strokeStyleColor) ||
+        this.state_.lineWidth !== strokeStyleWidth || this.state_.miterLimit !== strokeStyleMiterLimit) {
+      this.state_.changed = true;
+      this.state_.strokeColor = strokeStyleColor;
+      this.state_.lineWidth = strokeStyleWidth;
+      this.state_.miterLimit = strokeStyleMiterLimit;
+      this.styles_.push([strokeStyleColor, strokeStyleWidth, strokeStyleMiterLimit]);
+    }
+  };
 
+  /**
+   * @enum {number}
+   * @private
+   */
+  ol.render.webgl.LineStringReplay.Instruction_ = {
+    ROUND: 2,
+    BEGIN_LINE: 3,
+    END_LINE: 5,
+    BEGIN_LINE_CAP: 7,
+    END_LINE_CAP: 11,
+    BEVEL_FIRST: 13,
+    BEVEL_SECOND: 17,
+    MITER_BOTTOM: 19,
+    MITER_TOP: 23
+  };
 
-/**
- * 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;
-};
+}
 
+// This file is automatically generated, do not edit
+/* eslint openlayers-internal/no-missing-requires: 0 */
+goog.provide('ol.render.webgl.polygonreplay.defaultshader');
 
-/**
- * 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;
-};
+goog.require('ol');
+goog.require('ol.webgl.Fragment');
+goog.require('ol.webgl.Vertex');
 
+if (ol.ENABLE_WEBGL) {
 
-/**
- * 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;
-};
+  /**
+   * @constructor
+   * @extends {ol.webgl.Fragment}
+   * @struct
+   */
+  ol.render.webgl.polygonreplay.defaultshader.Fragment = function() {
+    ol.webgl.Fragment.call(this, ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE);
+  };
+  ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Fragment, ol.webgl.Fragment);
 
 
-/**
- * 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;
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nuniform vec4 u_color;\nuniform float u_opacity;\n\nvoid main(void) {\n  gl_FragColor = u_color;\n  float alpha = u_color.a * u_opacity;\n  if (alpha == 0.0) {\n    discard;\n  }\n  gl_FragColor.a = alpha;\n}\n';
 
-  return this.scale(s);
-};
 
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform vec4 e;uniform float f;void main(void){gl_FragColor=e;float alpha=e.a*f;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}';
 
-/**
- * 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);
-};
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ?
+      ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE :
+      ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE;
 
-// 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)
- */
+  ol.render.webgl.polygonreplay.defaultshader.fragment = new ol.render.webgl.polygonreplay.defaultshader.Fragment();
 
 
-// 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.
+  /**
+   * @constructor
+   * @extends {ol.webgl.Vertex}
+   * @struct
+   */
+  ol.render.webgl.polygonreplay.defaultshader.Vertex = function() {
+    ol.webgl.Vertex.call(this, ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE);
+  };
+  ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Vertex, ol.webgl.Vertex);
 
 
-goog.provide('goog.dom');
-goog.provide('goog.dom.Appendable');
-goog.provide('goog.dom.DomHelper');
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n  gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0);\n}\n\n\n';
 
-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');
 
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){gl_Position=b*vec4(a,0.0,1.0);}';
 
-/**
- * @define {boolean} Whether we know at compile time that the browser is in
- * quirks mode.
- */
-goog.define('goog.dom.ASSUME_QUIRKS_MODE', false);
 
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ?
+      ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE :
+      ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE;
 
-/**
- * @define {boolean} Whether we know at compile time that the browser is in
- * standards compliance mode.
- */
-goog.define('goog.dom.ASSUME_STANDARDS_MODE', false);
 
+  ol.render.webgl.polygonreplay.defaultshader.vertex = new ol.render.webgl.polygonreplay.defaultshader.Vertex();
 
-/**
- * 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;
 
+  /**
+   * @constructor
+   * @param {WebGLRenderingContext} gl GL.
+   * @param {WebGLProgram} program Program.
+   * @struct
+   */
+  ol.render.webgl.polygonreplay.defaultshader.Locations = function(gl, program) {
 
-/**
- * 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()));
-};
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_color = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_color' : 'e');
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_offsetRotateMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'd');
 
-/**
- * Cached default DOM helper.
- * @type {goog.dom.DomHelper}
- * @private
- */
-goog.dom.defaultDomHelper_;
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_offsetScaleMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'c');
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_opacity = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_opacity' : 'f');
 
-/**
- * Gets the document object being used by the dom library.
- * @return {!Document} Document object.
- */
-goog.dom.getDocument = function() {
-  return document;
-};
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_projectionMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'b');
 
+    /**
+     * @type {number}
+     */
+    this.a_position = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_position' : 'a');
+  };
 
-/**
- * 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);
-};
+}
 
+goog.provide('ol.style.Stroke');
 
-/**
- * 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;
-};
+goog.require('ol');
 
 
 /**
- * Gets an element by id, asserting that the element is found.
- *
- * This is used when an element is expected to exist, and should fail with
- * an assertion error if it does not (if assertions are enabled).
+ * @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.
  *
- * @param {string} id Element ID.
- * @return {!Element} The element with the given ID, if it exists.
+ * @constructor
+ * @param {olx.style.StrokeOptions=} opt_options Options.
+ * @api
  */
-goog.dom.getRequiredElement = function(id) {
-  return goog.dom.getRequiredElementHelper_(document, id);
-};
-
+ol.style.Stroke = function(opt_options) {
 
-/**
- * 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;
-};
+  var options = opt_options || {};
 
+  /**
+   * @private
+   * @type {ol.Color|ol.ColorLike}
+   */
+  this.color_ = options.color !== undefined ? options.color : null;
 
-/**
- * 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;
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.lineCap_ = options.lineCap;
 
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.lineDash_ = options.lineDash !== undefined ? options.lineDash : null;
 
-/**
- * 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);
-};
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.lineDashOffset_ = options.lineDashOffset;
 
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.lineJoin_ = options.lineJoin;
 
-/**
- * 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);
-};
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.miterLimit_ = options.miterLimit;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.width_ = options.width;
 
-/**
- * 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;
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.checksum_ = undefined;
 };
 
 
 /**
- * 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.
+ * Clones the style.
+ * @return {ol.style.Stroke} The cloned style.
+ * @api
  */
-goog.dom.getRequiredElementByClass = function(className, opt_root) {
-  var retValue = goog.dom.getElementByClass(className, opt_root);
-  return goog.asserts.assert(retValue,
-      'No element found with className: ' + className);
+ol.style.Stroke.prototype.clone = function() {
+  var color = this.getColor();
+  return new ol.style.Stroke({
+    color: (color && color.slice) ? color.slice() : color || undefined,
+    lineCap: this.getLineCap(),
+    lineDash: this.getLineDash() ? this.getLineDash().slice() : undefined,
+    lineDashOffset: this.getLineDashOffset(),
+    lineJoin: this.getLineJoin(),
+    miterLimit: this.getMiterLimit(),
+    width: this.getWidth()
+  });
 };
 
 
 /**
- * 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
+ * Get the stroke color.
+ * @return {ol.Color|ol.ColorLike} Color.
+ * @api
  */
-goog.dom.canUseQuerySelector_ = function(parent) {
-  return !!(parent.querySelectorAll && parent.querySelector);
+ol.style.Stroke.prototype.getColor = function() {
+  return this.color_;
 };
 
 
 /**
- * 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
+ * Get the line cap type for the stroke.
+ * @return {string|undefined} Line cap.
+ * @api
  */
-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;
-  }
+ol.style.Stroke.prototype.getLineCap = function() {
+  return this.lineCap_;
 };
 
 
 /**
- * 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.
+ * Get the line dash style for the stroke.
+ * @return {Array.<number>} Line dash.
+ * @api
  */
-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;
-    }
-  });
+ol.style.Stroke.prototype.getLineDash = function() {
+  return this.lineDash_;
 };
 
 
 /**
- * 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
+ * Get the line dash offset for the stroke.
+ * @return {number|undefined} Line dash offset.
+ * @api
  */
-goog.dom.DIRECT_ATTRIBUTE_MAP_ = {
-  'cellpadding': 'cellPadding',
-  'cellspacing': 'cellSpacing',
-  'colspan': 'colSpan',
-  'frameborder': 'frameBorder',
-  'height': 'height',
-  'maxlength': 'maxLength',
-  'role': 'role',
-  'rowspan': 'rowSpan',
-  'type': 'type',
-  'usemap': 'useMap',
-  'valign': 'vAlign',
-  'width': 'width'
+ol.style.Stroke.prototype.getLineDashOffset = function() {
+  return this.lineDashOffset_;
 };
 
 
 /**
- * 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'.
+ * Get the line join type for the stroke.
+ * @return {string|undefined} Line join.
+ * @api
  */
-goog.dom.getViewportSize = function(opt_window) {
-  // TODO(arv): This should not take an argument
-  return goog.dom.getViewportSize_(opt_window || window);
+ol.style.Stroke.prototype.getLineJoin = function() {
+  return this.lineJoin_;
 };
 
 
 /**
- * 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
+ * Get the miter limit for the stroke.
+ * @return {number|undefined} Miter limit.
+ * @api
  */
-goog.dom.getViewportSize_ = function(win) {
-  var doc = win.document;
-  var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body;
-  return new goog.math.Size(el.clientWidth, el.clientHeight);
+ol.style.Stroke.prototype.getMiterLimit = function() {
+  return this.miterLimit_;
 };
 
 
 /**
- * Calculates the height of the document.
- *
- * @return {number} The height of the current document.
+ * Get the stroke width.
+ * @return {number|undefined} Width.
+ * @api
  */
-goog.dom.getDocumentHeight = function() {
-  return goog.dom.getDocumentHeight_(window);
+ol.style.Stroke.prototype.getWidth = function() {
+  return this.width_;
 };
 
 
 /**
- * Calculates the height of the document of the given window.
- *
- * Function code copied from the opensocial gadget api:
- *   gadgets.window.adjustHeight(opt_height)
+ * Set the color.
  *
- * @private
- * @param {!Window} win The window whose document height to retrieve.
- * @return {number} The height of the document of the given window.
+ * @param {ol.Color|ol.ColorLike} color Color.
+ * @api
  */
-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;
+ol.style.Stroke.prototype.setColor = function(color) {
+  this.color_ = color;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * Gets the page scroll distance as a coordinate object.
+ * Set the line cap.
  *
- * @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.
+ * @param {string|undefined} lineCap Line cap.
+ * @api
  */
-goog.dom.getPageScroll = function(opt_window) {
-  var win = opt_window || goog.global || window;
-  return goog.dom.getDomHelper(win.document).getDocumentScroll();
+ol.style.Stroke.prototype.setLineCap = function(lineCap) {
+  this.lineCap_ = lineCap;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * Gets the document scroll distance as a coordinate object.
+ * Set the line dash.
  *
- * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
- */
-goog.dom.getDocumentScroll = function() {
-  return goog.dom.getDocumentScroll_(document);
-};
-
-
-/**
- * Helper for {@code getDocumentScroll}.
+ * Please note that Internet Explorer 10 and lower [do not support][mdn] the
+ * `setLineDash` method on the `CanvasRenderingContext2D` and therefore this
+ * property will have no visual effect in these browsers.
  *
- * @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.
+ * [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility
+ *
+ * @param {Array.<number>} lineDash Line dash.
+ * @api
  */
-goog.dom.getDocumentScrollElement = function() {
-  return goog.dom.getDocumentScrollElement_(document);
+ol.style.Stroke.prototype.setLineDash = function(lineDash) {
+  this.lineDash_ = lineDash;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * Helper for {@code getDocumentScrollElement}.
- * @param {!Document} doc The document to get the scroll element for.
- * @return {!Element} Scrolling element.
- * @private
+ * Set the line dash offset.
+ *
+ * @param {number|undefined} lineDashOffset Line dash offset.
+ * @api
  */
-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;
+ol.style.Stroke.prototype.setLineDashOffset = function(lineDashOffset) {
+  this.lineDashOffset_ = lineDashOffset;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * Gets the window object associated with the given document.
+ * Set the line join.
  *
- * @param {Document=} opt_doc  Document object to get window for.
- * @return {!Window} The window associated with the given document.
+ * @param {string|undefined} lineJoin Line join.
+ * @api
  */
-goog.dom.getWindow = function(opt_doc) {
-  // TODO(arv): This should not take an argument.
-  return opt_doc ? goog.dom.getWindow_(opt_doc) : window;
+ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
+  this.lineJoin_ = lineJoin;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * Helper for {@code getWindow}.
+ * Set the miter limit.
  *
- * @param {!Document} doc  Document object to get window for.
- * @return {!Window} The window associated with the given document.
- * @private
+ * @param {number|undefined} miterLimit Miter limit.
+ * @api
  */
-goog.dom.getWindow_ = function(doc) {
-  return doc.parentWindow || doc.defaultView;
+ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
+  this.miterLimit_ = miterLimit;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * 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
+ * Set the width.
  *
- * @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.
+ * @param {number|undefined} width Width.
+ * @api
  */
-goog.dom.createDom = function(tagName, opt_attributes, var_args) {
-  return goog.dom.createDom_(document, arguments);
+ol.style.Stroke.prototype.setWidth = function(width) {
+  this.width_ = width;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * 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
+ * @return {string} The checksum.
  */
-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(' ');
+ol.style.Stroke.prototype.getChecksum = function() {
+  if (this.checksum_ === undefined) {
+    this.checksum_ = 's';
+    if (this.color_) {
+      if (typeof this.color_ === 'string') {
+        this.checksum_ += this.color_;
+      } else {
+        this.checksum_ += ol.getUid(this.color_).toString();
+      }
     } else {
-      goog.dom.setProperties(element, attributes);
+      this.checksum_ += '-';
     }
+    this.checksum_ += ',' +
+        (this.lineCap_ !== undefined ?
+            this.lineCap_.toString() : '-') + ',' +
+        (this.lineDash_ ?
+            this.lineDash_.toString() : '-') + ',' +
+        (this.lineDashOffset_ !== undefined ?
+            this.lineDashOffset_ : '-') + ',' +
+        (this.lineJoin_ !== undefined ?
+            this.lineJoin_ : '-') + ',' +
+        (this.miterLimit_ !== undefined ?
+            this.miterLimit_.toString() : '-') + ',' +
+        (this.width_ !== undefined ?
+            this.width_.toString() : '-');
   }
 
-  if (args.length > 2) {
-    goog.dom.append_(doc, element, args, 2);
-  }
-
-  return element;
+  return this.checksum_;
 };
 
+goog.provide('ol.structs.LinkedList');
 
 /**
- * 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
+ * Creates an empty linked list structure.
+ *
+ * @constructor
+ * @struct
+ * @param {boolean=} opt_circular The last item is connected to the first one,
+ * and the first item to the last one. Default is true.
  */
-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);
-    }
-  }
+ol.structs.LinkedList = function(opt_circular) {
 
-  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);
-    }
-  }
-};
+  /**
+   * @private
+   * @type {ol.LinkedListItem|undefined}
+   */
+  this.first_ = undefined;
 
+  /**
+   * @private
+   * @type {ol.LinkedListItem|undefined}
+   */
+  this.last_ = undefined;
 
-/**
- * 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;
+  /**
+   * @private
+   * @type {ol.LinkedListItem|undefined}
+   */
+  this.head_ = undefined;
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.circular_ = opt_circular === undefined ? true : opt_circular;
 
-/**
- * Creates a new element.
- * @param {string} name Tag name.
- * @return {!Element} The new element.
- */
-goog.dom.createElement = function(name) {
-  return document.createElement(name);
+  /**
+   * @private
+   * @type {number}
+   */
+  this.length_ = 0;
 };
 
-
 /**
- * Creates a new text node.
- * @param {number|string} content Content.
- * @return {!Text} The new text node.
+ * Inserts an item into the linked list right after the current one.
+ *
+ * @param {?} data Item data.
  */
-goog.dom.createTextNode = function(content) {
-  return document.createTextNode(String(content));
-};
+ol.structs.LinkedList.prototype.insertItem = function(data) {
 
+  /** @type {ol.LinkedListItem} */
+  var item = {
+    prev: undefined,
+    next: undefined,
+    data: data
+  };
 
-/**
- * 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);
-};
+  var head = this.head_;
 
+  //Initialize the list.
+  if (!head) {
+    this.first_ = item;
+    this.last_ = item;
+    if (this.circular_) {
+      item.next = item;
+      item.prev = item;
+    }
+  } else {
+    //Link the new item to the adjacent ones.
+    var next = head.next;
+    item.prev = head;
+    item.next = next;
+    head.next = item;
+    if (next) {
+      next.prev = item;
+    }
 
-/**
- * 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);
+    if (head === this.last_) {
+      this.last_ = item;
     }
-    tbody.appendChild(tr);
   }
-  return table;
+  this.head_ = item;
+  this.length_++;
 };
 
-
 /**
- * Converts HTML markup into a node.
- * @param {!goog.html.SafeHtml} html The HTML markup to convert.
- * @return {!Node} The resulting node.
+ * Removes the current item from the list. Sets the cursor to the next item,
+ * if possible.
  */
-goog.dom.safeHtmlToNode = function(html) {
-  return goog.dom.safeHtmlToNode_(document, html);
-};
-
+ol.structs.LinkedList.prototype.removeItem = function() {
+  var head = this.head_;
+  if (head) {
+    var next = head.next;
+    var prev = head.prev;
+    if (next) {
+      next.prev = prev;
+    }
+    if (prev) {
+      prev.next = next;
+    }
+    this.head_ = next || prev;
 
-/**
- * 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);
+    if (this.first_ === this.last_) {
+      this.head_ = undefined;
+      this.first_ = undefined;
+      this.last_ = undefined;
+    } else if (this.first_ === head) {
+      this.first_ = this.head_;
+    } else if (this.last_ === head) {
+      this.last_ = prev ? this.head_.prev : this.head_;
+    }
+    this.length_--;
   }
-  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}.
+ * Sets the cursor to the first item, and returns the associated data.
  *
- * @param {!Document} doc The document.
- * @param {string} htmlString The HTML string to convert.
- * @return {!Node} The resulting document fragment.
- * @private
+ * @return {?} Item data.
  */
-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;
+ol.structs.LinkedList.prototype.firstItem = function() {
+  this.head_ = this.first_;
+  if (this.head_) {
+    return this.head_.data;
   }
-  return goog.dom.childrenToNode_(doc, tempDiv);
+  return undefined;
 };
 
-
 /**
- * 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;
+* Sets the cursor to the last item, and returns the associated data.
+*
+* @return {?} Item data.
+*/
+ol.structs.LinkedList.prototype.lastItem = function() {
+  this.head_ = this.last_;
+  if (this.head_) {
+    return this.head_.data;
   }
+  return undefined;
 };
 
-
-/**
- * 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
+ * Sets the cursor to the next item, and returns the associated data.
+ *
+ * @return {?} Item data.
  */
-goog.dom.isCss1CompatMode_ = function(doc) {
-  if (goog.dom.COMPAT_MODE_KNOWN_) {
-    return goog.dom.ASSUME_STANDARDS_MODE;
+ol.structs.LinkedList.prototype.nextItem = function() {
+  if (this.head_ && this.head_.next) {
+    this.head_ = this.head_.next;
+    return this.head_.data;
   }
-
-  return doc.compatMode == 'CSS1Compat';
+  return undefined;
 };
 
-
 /**
- * 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() ?
+ * Returns the next item's data without moving the cursor.
  *
- * @param {Node} node The node to check.
- * @return {boolean} Whether the node can contain children.
+ * @return {?} Item data.
  */
-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;
+ol.structs.LinkedList.prototype.getNextItem = function() {
+  if (this.head_ && this.head_.next) {
+    return this.head_.next.data;
   }
-  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);
+  return undefined;
 };
 
-
 /**
- * Removes all the child nodes on a DOM node.
- * @param {Node} node Node to remove children from.
+ * Sets the cursor to the previous item, and returns the associated data.
+ *
+ * @return {?} Item data.
  */
-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);
+ol.structs.LinkedList.prototype.prevItem = function() {
+  if (this.head_ && this.head_.prev) {
+    this.head_ = this.head_.prev;
+    return this.head_.data;
   }
+  return undefined;
 };
 
-
 /**
- * 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.
+ * Returns the previous item's data without moving the cursor.
+ *
+ * @return {?} Item data.
  */
-goog.dom.insertSiblingBefore = function(newNode, refNode) {
-  if (refNode.parentNode) {
-    refNode.parentNode.insertBefore(newNode, refNode);
+ol.structs.LinkedList.prototype.getPrevItem = function() {
+  if (this.head_ && this.head_.prev) {
+    return this.head_.prev.data;
   }
+  return undefined;
 };
 
-
 /**
- * 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.
+ * Returns the current item's data.
+ *
+ * @return {?} Item data.
  */
-goog.dom.insertSiblingAfter = function(newNode, refNode) {
-  if (refNode.parentNode) {
-    refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
+ol.structs.LinkedList.prototype.getCurrItem = function() {
+  if (this.head_) {
+    return this.head_.data;
   }
+  return undefined;
 };
 
-
-/**
- * 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.
+ * Sets the first item of the list. This only works for circular lists, and sets
+ * the last item accordingly.
  */
-goog.dom.replaceNode = function(newNode, oldNode) {
-  var parent = oldNode.parentNode;
-  if (parent) {
-    parent.replaceChild(newNode, oldNode);
+ol.structs.LinkedList.prototype.setFirstItem = function() {
+  if (this.circular_ && this.head_) {
+    this.first_ = this.head_;
+    this.last_ = this.head_.prev;
   }
 };
 
-
 /**
- * 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.
+ * Concatenates two lists.
+ * @param {ol.structs.LinkedList} list List to merge into the current list.
  */
-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));
+ol.structs.LinkedList.prototype.concat = function(list) {
+  if (list.head_) {
+    if (this.head_) {
+      var end = this.head_.next;
+      this.head_.next = list.first_;
+      list.first_.prev = this.head_;
+      end.prev = list.last_;
+      list.last_.next = end;
+      this.length_ += list.length_;
     } 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));
+      this.head_ = list.head_;
+      this.first_ = list.first_;
+      this.last_ = list.last_;
+      this.length_ = list.length_;
     }
+    list.head_ = undefined;
+    list.first_ = undefined;
+    list.last_ = undefined;
+    list.length_ = 0;
   }
 };
 
-
-/**
- * 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.
+ * Returns the current length of the list.
+ *
+ * @return {number} Length.
  */
-goog.dom.isNodeLike = function(obj) {
-  return goog.isObject(obj) && obj.nodeType > 0;
+ol.structs.LinkedList.prototype.getLength = function() {
+  return this.length_;
 };
 
 
 /**
- * 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.
+ * @fileoverview
+ * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, unusedLocalVariables, uselessCode, visibility}
  */
-goog.dom.isElement = function(obj) {
-  return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT;
-};
-
+goog.provide('ol.ext.rbush');
 
-/**
- * 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;
-};
+/** @typedef {function(*)} */
+ol.ext.rbush = function() {};
 
+(function() {(function (exports) {
+'use strict';
 
-/**
- * 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;
-      }
+var index$2 = partialSort;
+function partialSort(arr, k, left, right, compare) {
+    left = left || 0;
+    right = right || (arr.length - 1);
+    compare = compare || defaultCompare;
+    while (right > left) {
+        if (right - left > 600) {
+            var n = right - left + 1;
+            var m = k - left + 1;
+            var z = Math.log(n);
+            var s = 0.5 * Math.exp(2 * z / 3);
+            var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
+            var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
+            var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
+            partialSort(arr, k, newLeft, newRight, compare);
+        }
+        var t = arr[k];
+        var i = left;
+        var 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;
     }
-  }
-  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;
-  }
+}
+function swap(arr, i, j) {
+    var tmp = arr[i];
+    arr[i] = arr[j];
+    arr[j] = tmp;
+}
+function defaultCompare(a, b) {
+    return a < b ? -1 : a > b ? 1 : 0;
+}
 
-  // 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;
+var index = rbush;
+function rbush(maxEntries, format) {
+    if (!(this instanceof rbush)) return new rbush(maxEntries, format);
+    this._maxEntries = Math.max(4, maxEntries || 9);
+    this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
+    if (format) {
+        this._initFormat(format);
     }
-  }
-
-  // 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);
+    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)) 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;
+                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)) 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;
+                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;
+        }
+        var node = this._build(data.slice(), 0, data.length - 1, 0);
+        if (!this.data.children.length) {
+            this.data = node;
+        } else if (this.data.height === node.height) {
+            this._splitRoot(this.data, node);
+        } else {
+            if (this.data.height < node.height) {
+                var tmpNode = this.data;
+                this.data = node;
+                node = tmpNode;
+            }
+            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 = createNode([]);
+        return this;
+    },
+    remove: function (item, equalsFn) {
+        if (!item) return this;
+        var node = this.data,
+            bbox = this.toBBox(item),
+            path = [],
+            indexes = [],
+            i, parent, index, goingUp;
+        while (node || path.length) {
+            if (!node) {
+                node = path.pop();
+                parent = path[path.length - 1];
+                i = indexes.pop();
+                goingUp = true;
+            }
+            if (node.leaf) {
+                index = findItem(item, node.children, equalsFn);
+                if (index !== -1) {
+                    node.children.splice(index, 1);
+                    path.push(node);
+                    this._condense(path);
+                    return this;
+                }
+            }
+            if (!goingUp && !node.leaf && contains(node, bbox)) {
+                path.push(node);
+                indexes.push(i);
+                i = 0;
+                parent = node;
+                node = node.children[0];
+            } else if (parent) {
+                i++;
+                node = parent.children[i];
+                goingUp = false;
+            } else node = null;
+        }
+        return this;
+    },
+    toBBox: function (item) { return item; },
+    compareMinX: compareNodeMinX,
+    compareMinY: compareNodeMinY,
+    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) {
+            node = createNode(items.slice(left, right + 1));
+            calcBBox(node, this.toBBox);
+            return node;
+        }
+        if (!height) {
+            height = Math.ceil(Math.log(N) / Math.log(M));
+            M = Math.ceil(N / Math.pow(M, height - 1));
+        }
+        node = createNode([]);
+        node.leaf = false;
+        node.height = height;
+        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);
+                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);
+                enlargement = enlargedArea(bbox, child) - area;
+                if (enlargement < minEnlargement) {
+                    minEnlargement = enlargement;
+                    minArea = area < minArea ? area : minArea;
+                    targetNode = child;
+                } else if (enlargement === minEnlargement) {
+                    if (area < minArea) {
+                        minArea = area;
+                        targetNode = child;
+                    }
+                }
+            }
+            node = targetNode || node.children[0];
+        }
+        return node;
+    },
+    _insert: function (item, level, isNode) {
+        var toBBox = this.toBBox,
+            bbox = isNode ? item : toBBox(item),
+            insertPath = [];
+        var node = this._chooseSubtree(bbox, this.data, level, insertPath);
+        node.children.push(item);
+        extend(node, bbox);
+        while (level >= 0) {
+            if (insertPath[level].children.length > this._maxEntries) {
+                this._split(insertPath, level);
+                level--;
+            } else break;
+        }
+        this._adjustParentBBoxes(bbox, insertPath, level);
+    },
+    _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 = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
+        newNode.height = node.height;
+        newNode.leaf = node.leaf;
+        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) {
+        this.data = createNode([node, newNode]);
+        this.data.height = node.height + 1;
+        this.data.leaf = false;
+        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);
+            if (overlap < minOverlap) {
+                minOverlap = overlap;
+                index = i;
+                minArea = area < minArea ? area : minArea;
+            } else if (overlap === minOverlap) {
+                if (area < minArea) {
+                    minArea = area;
+                    index = i;
+                }
+            }
+        }
+        return index;
+    },
+    _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 (xMargin < yMargin) node.children.sort(compareMinX);
+    },
+    _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);
+            margin += bboxMargin(leftBBox);
+        }
+        for (i = M - m - 1; i >= m; i--) {
+            child = node.children[i];
+            extend(rightBBox, node.leaf ? toBBox(child) : child);
+            margin += bboxMargin(rightBBox);
+        }
+        return margin;
+    },
+    _adjustParentBBoxes: function (bbox, path, level) {
+        for (var i = level; i >= 0; i--) {
+            extend(path[i], bbox);
+        }
+    },
+    _condense: function (path) {
+        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) {
+        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 {minX: a' + format[0] +
+            ', minY: a' + format[1] +
+            ', maxX: a' + format[2] +
+            ', maxY: a' + format[3] + '};');
     }
-  }
+};
+function findItem(item, items, equalsFn) {
+    if (!equalsFn) return items.indexOf(item);
+    for (var i = 0; i < items.length; i++) {
+        if (equalsFn(item, items[i])) return i;
+    }
+    return -1;
+}
+function calcBBox(node, toBBox) {
+    distBBox(node, 0, node.children.length, toBBox, node);
+}
+function distBBox(node, k, p, toBBox, destNode) {
+    if (!destNode) destNode = createNode(null);
+    destNode.minX = Infinity;
+    destNode.minY = Infinity;
+    destNode.maxX = -Infinity;
+    destNode.maxY = -Infinity;
+    for (var i = k, child; i < p; i++) {
+        child = node.children[i];
+        extend(destNode, node.leaf ? toBBox(child) : child);
+    }
+    return destNode;
+}
+function extend(a, b) {
+    a.minX = Math.min(a.minX, b.minX);
+    a.minY = Math.min(a.minY, b.minY);
+    a.maxX = Math.max(a.maxX, b.maxX);
+    a.maxY = Math.max(a.maxY, b.maxY);
+    return a;
+}
+function compareNodeMinX(a, b) { return a.minX - b.minX; }
+function compareNodeMinY(a, b) { return a.minY - b.minY; }
+function bboxArea(a)   { return (a.maxX - a.minX) * (a.maxY - a.minY); }
+function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
+function enlargedArea(a, b) {
+    return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
+           (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
+}
+function intersectionArea(a, b) {
+    var minX = Math.max(a.minX, b.minX),
+        minY = Math.max(a.minY, b.minY),
+        maxX = Math.min(a.maxX, b.maxX),
+        maxY = Math.min(a.maxY, b.maxY);
+    return Math.max(0, maxX - minX) *
+           Math.max(0, maxY - minY);
+}
+function contains(a, b) {
+    return a.minX <= b.minX &&
+           a.minY <= b.minY &&
+           b.maxX <= a.maxX &&
+           b.maxY <= a.maxY;
+}
+function intersects(a, b) {
+    return b.minX <= a.maxX &&
+           b.minY <= a.maxY &&
+           b.maxX >= a.minX &&
+           b.maxY >= a.minY;
+}
+function createNode(children) {
+    return {
+        children: children,
+        height: 1,
+        leaf: true,
+        minX: Infinity,
+        minY: Infinity,
+        maxX: -Infinity,
+        maxY: -Infinity
+    };
+}
+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;
+        index$2(arr, mid, left, right, compare);
+        stack.push(left, mid, mid, right);
+    }
+}
 
-  // For Safari, we compare ranges.
-  var doc = goog.dom.getOwnerDocument(node1);
+exports['default'] = index;
 
-  var range1, range2;
-  range1 = doc.createRange();
-  range1.selectNode(node1);
-  range1.collapse(true);
+}((this.rbush = this.rbush || {})));}).call(ol.ext);
+ol.ext.rbush = ol.ext.rbush.default;
 
-  range2 = doc.createRange();
-  range2.selectNode(node2);
-  range2.collapse(true);
+goog.provide('ol.structs.RBush');
 
-  return range1.compareBoundaryPoints(goog.global['Range'].START_TO_END,
-      range2);
-};
+goog.require('ol');
+goog.require('ol.ext.rbush');
+goog.require('ol.extent');
+goog.require('ol.obj');
 
 
 /**
- * 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
+ * Wrapper around the RBush by Vladimir Agafonkin.
+ *
+ * @constructor
+ * @param {number=} opt_maxEntries Max entries.
+ * @see https://github.com/mourner/rbush
+ * @struct
+ * @template T
  */
-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);
-};
+ol.structs.RBush = function(opt_maxEntries) {
 
+  /**
+   * @private
+   */
+  this.rbush_ = ol.ext.rbush(opt_maxEntries);
 
-/**
- * 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;
-    }
-  }
+  /**
+   * 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, ol.RBushEntry>}
+   */
+  this.items_ = {};
 
-  // 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.
+ * Insert a value into the RBush.
+ * @param {ol.Extent} extent Extent.
+ * @param {T} value Value.
  */
-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;
+ol.structs.RBush.prototype.insert = function(extent, value) {
+  /** @type {ol.RBushEntry} */
+  var item = {
+    minX: extent[0],
+    minY: extent[1],
+    maxX: extent[2],
+    maxY: extent[3],
+    value: value
+  };
+
+  this.rbush_.insert(item);
+  this.items_[ol.getUid(value)] = item;
 };
 
 
 /**
- * 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.
+ * Bulk-insert values into the RBush.
+ * @param {Array.<ol.Extent>} extents Extents.
+ * @param {Array.<T>} values Values.
  */
-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);
+ol.structs.RBush.prototype.load = function(extents, values) {
+  var items = new Array(values.length);
+  for (var i = 0, l = values.length; i < l; i++) {
+    var extent = extents[i];
+    var value = values[i];
+
+    /** @type {ol.RBushEntry} */
+    var item = {
+      minX: extent[0],
+      minY: extent[1],
+      maxX: extent[2],
+      maxY: extent[3],
+      value: value
+    };
+    items[i] = item;
+    this.items_[ol.getUid(value)] = item;
+  }
+  this.rbush_.load(items);
 };
 
 
 /**
- * Cross-browser function for getting the document element of a frame or iframe.
- * @param {Element} frame Frame element.
- * @return {!Document} The frame content document.
+ * Remove a value from the RBush.
+ * @param {T} value Value.
+ * @return {boolean} Removed.
  */
-goog.dom.getFrameContentDocument = function(frame) {
-  var doc = frame.contentDocument || frame.contentWindow.document;
-  return doc;
+ol.structs.RBush.prototype.remove = function(value) {
+  var uid = ol.getUid(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;
 };
 
 
 /**
- * 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.
+ * Update the extent of a value in the RBush.
+ * @param {ol.Extent} extent Extent.
+ * @param {T} value Value.
  */
-goog.dom.getFrameContentWindow = function(frame) {
-  return frame.contentWindow ||
-      goog.dom.getWindow(goog.dom.getFrameContentDocument(frame));
+ol.structs.RBush.prototype.update = function(extent, value) {
+  var item = this.items_[ol.getUid(value)];
+  var bbox = [item.minX, item.minY, item.maxX, item.maxY];
+  if (!ol.extent.equals(bbox, extent)) {
+    this.remove(value);
+    this.insert(extent, value);
+  }
 };
 
 
 /**
- * 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.
+ * Return all values in the RBush.
+ * @return {Array.<T>} All.
  */
-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)));
-  }
+ol.structs.RBush.prototype.getAll = function() {
+  var items = this.rbush_.all();
+  return items.map(function(item) {
+    return item.value;
+  });
 };
 
 
 /**
- * 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.
+ * Return all values in the given extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {Array.<T>} All in extent.
  */
-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;
-  }
+ol.structs.RBush.prototype.getInExtent = function(extent) {
+  /** @type {ol.RBushEntry} */
+  var bbox = {
+    minX: extent[0],
+    minY: extent[1],
+    maxX: extent[2],
+    maxY: extent[3]
+  };
+  var items = this.rbush_.search(bbox);
+  return items.map(function(item) {
+    return item.value;
+  });
 };
 
 
 /**
- * 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.
+ * Calls a callback function with each value in the tree.
+ * If the callback returns a truthy value, this value is returned without
+ * checking the rest of the tree.
+ * @param {function(this: S, T): *} callback Callback.
+ * @param {S=} opt_this The object to use as `this` in `callback`.
+ * @return {*} Callback return value.
+ * @template S
  */
-goog.dom.findNode = function(root, p) {
-  var rv = [];
-  var found = goog.dom.findNodes_(root, p, rv, true);
-  return found ? rv[0] : undefined;
+ol.structs.RBush.prototype.forEach = function(callback, opt_this) {
+  return this.forEach_(this.getAll(), callback, opt_this);
 };
 
 
 /**
- * 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.
+ * Calls a callback function with each value in the provided extent.
+ * @param {ol.Extent} extent Extent.
+ * @param {function(this: S, T): *} callback Callback.
+ * @param {S=} opt_this The object to use as `this` in `callback`.
+ * @return {*} Callback return value.
+ * @template S
  */
-goog.dom.findNodes = function(root, p) {
-  var rv = [];
-  goog.dom.findNodes_(root, p, rv, false);
-  return rv;
+ol.structs.RBush.prototype.forEachInExtent = function(extent, callback, opt_this) {
+  return this.forEach_(this.getInExtent(extent), callback, opt_this);
 };
 
 
 /**
- * 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.
+ * @param {Array.<T>} values Values.
+ * @param {function(this: S, T): *} callback Callback.
+ * @param {S=} opt_this The object to use as `this` in `callback`.
  * @private
+ * @return {*} Callback return value.
+ * @template S
  */
-goog.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;
+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 false;
+  return result;
 };
 
 
 /**
- * Map of tags whose content to ignore when calculating text length.
- * @private {!Object<string, number>}
- * @const
+ * @return {boolean} Is empty.
  */
-goog.dom.TAGS_TO_IGNORE_ = {
-  'SCRIPT': 1,
-  'STYLE': 1,
-  'HEAD': 1,
-  'IFRAME': 1,
-  'OBJECT': 1
+ol.structs.RBush.prototype.isEmpty = function() {
+  return ol.obj.isEmpty(this.items_);
 };
 
 
 /**
- * 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/
+ * Remove all values from the RBush.
  */
-goog.dom.isFocusableTabIndex = function(element) {
-  return goog.dom.hasSpecifiedTabIndex_(element) &&
-         goog.dom.isTabIndexFocusable_(element);
+ol.structs.RBush.prototype.clear = function() {
+  this.rbush_.clear();
+  this.items_ = {};
 };
 
 
 /**
- * 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.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {!ol.Extent} Extent.
  */
-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!
-  }
+ol.structs.RBush.prototype.getExtent = function(opt_extent) {
+  // FIXME add getExtent() to rbush
+  var data = this.rbush_.data;
+  return ol.extent.createOrUpdate(data.minX, data.minY, data.maxX, data.maxY, opt_extent);
 };
 
 
 /**
- * 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.
+ * @param {ol.structs.RBush} rbush R-Tree.
  */
-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);
+ol.structs.RBush.prototype.concat = function(rbush) {
+  this.rbush_.load(rbush.rbush_.all());
+  for (var i in rbush.items_) {
+    this.items_[i | 0] = rbush.items_[i | 0];
   }
-
-  // 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;
 };
 
+goog.provide('ol.render.webgl.PolygonReplay');
 
-/**
- * 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;
-};
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.color');
+goog.require('ol.extent');
+goog.require('ol.obj');
+goog.require('ol.geom.flat.contains');
+goog.require('ol.geom.flat.orient');
+goog.require('ol.geom.flat.transform');
+goog.require('ol.render.webgl.polygonreplay.defaultshader');
+goog.require('ol.render.webgl.LineStringReplay');
+goog.require('ol.render.webgl.Replay');
+goog.require('ol.render.webgl');
+goog.require('ol.style.Stroke');
+goog.require('ol.structs.LinkedList');
+goog.require('ol.structs.RBush');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Buffer');
 
 
-/**
- * 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;
-};
+if (ol.ENABLE_WEBGL) {
 
+  /**
+   * @constructor
+   * @extends {ol.render.webgl.Replay}
+   * @param {number} tolerance Tolerance.
+   * @param {ol.Extent} maxExtent Max extent.
+   * @struct
+   */
+  ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) {
+    ol.render.webgl.Replay.call(this, tolerance, maxExtent);
 
-/**
- * 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;
-};
+    this.lineStringReplay = new ol.render.webgl.LineStringReplay(
+        tolerance, maxExtent);
 
+    /**
+     * @private
+     * @type {ol.render.webgl.polygonreplay.defaultshader.Locations}
+     */
+    this.defaultLocations_ = null;
 
-/**
- * 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('');
-  }
+    /**
+     * @private
+     * @type {Array.<Array.<number>>}
+     */
+    this.styles_ = [];
 
-  // 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, '');
+    /**
+     * @private
+     * @type {Array.<number>}
+     */
+    this.styleIndices_ = [];
 
-  // 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*/, '');
-  }
+    /**
+     * @private
+     * @type {{fillColor: (Array.<number>|null),
+     *         changed: boolean}|null}
+     */
+    this.state_ = {
+      fillColor: null,
+      changed: false
+    };
 
-  return textContent;
-};
+  };
+  ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay);
 
 
-/**
- * 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);
+  /**
+   * Draw one polygon.
+   * @param {Array.<number>} flatCoordinates Flat coordinates.
+   * @param {Array.<Array.<number>>} holeFlatCoordinates Hole flat coordinates.
+   * @param {number} stride Stride.
+   * @private
+   */
+  ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function(
+      flatCoordinates, holeFlatCoordinates, stride) {
+    // Triangulate the polygon
+    var outerRing = new ol.structs.LinkedList();
+    var rtree = new ol.structs.RBush();
+    // Initialize the outer ring
+    var maxX = this.processFlatCoordinates_(flatCoordinates, stride, outerRing, rtree, true);
 
-  return buf.join('');
-};
+    // Eliminate holes, if there are any
+    if (holeFlatCoordinates.length) {
+      var i, ii;
+      var holeLists = [];
+      for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) {
+        var holeList = {
+          list: new ol.structs.LinkedList(),
+          maxX: undefined,
+          rtree: new ol.structs.RBush()
+        };
+        holeLists.push(holeList);
+        holeList.maxX = this.processFlatCoordinates_(holeFlatCoordinates[i],
+            stride, holeList.list, holeList.rtree, false);
+      }
+      holeLists.sort(function(a, b) {
+        return b.maxX[0] === a.maxX[0] ? a.maxX[1] - b.maxX[1] : b.maxX[0] - a.maxX[0];
+      });
+      for (i = 0; i < holeLists.length; ++i) {
+        var currList = holeLists[i].list;
+        var start = currList.firstItem();
+        var currItem = start;
+        var intersection;
+        do {
+          if (this.getIntersections_(currItem, rtree).length) {
+            intersection = true;
+            break;
+          }
+          currItem = currList.nextItem();
+        } while (start !== currItem);
+        if (!intersection) {
+          this.classifyPoints_(currList, holeLists[i].rtree, true);
+          if (this.bridgeHole_(currList, holeLists[i].maxX[0], outerRing, maxX[0], rtree)) {
+            rtree.concat(holeLists[i].rtree);
+            this.classifyPoints_(outerRing, rtree, false);
+          }
+        }
+      }
+    } else {
+      this.classifyPoints_(outerRing, rtree, false);
+    }
+    this.triangulate_(outerRing, rtree);
+  };
 
 
-/**
- * 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, ''));
+  /**
+   * Inserts flat coordinates in a linked list and adds them to the vertex buffer.
+   * @private
+   * @param {Array.<number>} flatCoordinates Flat coordinates.
+   * @param {number} stride Stride.
+   * @param {ol.structs.LinkedList} list Linked list.
+   * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+   * @param {boolean} clockwise Coordinate order should be clockwise.
+   * @return {Array.<number>} X and Y coords of maximum X value.
+   */
+  ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function(
+      flatCoordinates, stride, list, rtree, clockwise) {
+    var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates,
+        0, flatCoordinates.length, stride);
+    var i, ii, maxXX, maxXY;
+    var n = this.vertices.length / 2;
+    /** @type {ol.WebglPolygonVertex} */
+    var start;
+    /** @type {ol.WebglPolygonVertex} */
+    var p0;
+    /** @type {ol.WebglPolygonVertex} */
+    var p1;
+    var extents = [];
+    var segments = [];
+    if (clockwise === isClockwise) {
+      start = this.createPoint_(flatCoordinates[0], flatCoordinates[1], n++);
+      p0 = start;
+      maxXX = flatCoordinates[0];
+      maxXY = flatCoordinates[1];
+      for (i = stride, ii = flatCoordinates.length; i < ii; i += stride) {
+        p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++);
+        segments.push(this.insertItem_(p0, p1, list));
+        extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x),
+          Math.max(p0.y, p1.y)]);
+        if (flatCoordinates[i] > maxXX) {
+          maxXX = flatCoordinates[i];
+          maxXY = flatCoordinates[i + 1];
+        }
+        p0 = p1;
+      }
+      segments.push(this.insertItem_(p1, start, list));
+      extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x),
+        Math.max(p0.y, p1.y)]);
     } 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;
+      var end = flatCoordinates.length - stride;
+      start = this.createPoint_(flatCoordinates[end], flatCoordinates[end + 1], n++);
+      p0 = start;
+      maxXX = flatCoordinates[end];
+      maxXY = flatCoordinates[end + 1];
+      for (i = end - stride, ii = 0; i >= ii; i -= stride) {
+        p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++);
+        segments.push(this.insertItem_(p0, p1, list));
+        extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x),
+          Math.max(p0.y, p1.y)]);
+        if (flatCoordinates[i] > maxXX) {
+          maxXX = flatCoordinates[i];
+          maxXY = flatCoordinates[i + 1];
+        }
+        p0 = p1;
+      }
+      segments.push(this.insertItem_(p1, start, list));
+      extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x),
+        Math.max(p0.y, p1.y)]);
     }
-  }
-};
+    rtree.load(extents, segments);
 
-
-/**
- * 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;
-};
+    return [maxXX, maxXY];
+  };
 
 
-/**
- * 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;
-};
+  /**
+   * Classifies the points of a polygon list as convex, reflex. Removes collinear vertices.
+   * @private
+   * @param {ol.structs.LinkedList} list Polygon ring.
+   * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+   * @param {boolean} ccw The orientation of the polygon is counter-clockwise.
+   * @return {boolean} There were reclassified points.
+   */
+  ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, rtree, ccw) {
+    var start = list.firstItem();
+    var s0 = start;
+    var s1 = list.nextItem();
+    var pointsReclassified = false;
+    do {
+      var reflex = ccw ? ol.render.webgl.triangleIsCounterClockwise(s1.p1.x,
+          s1.p1.y, s0.p1.x, s0.p1.y, s0.p0.x, s0.p0.y) :
+          ol.render.webgl.triangleIsCounterClockwise(s0.p0.x, s0.p0.y, s0.p1.x,
+          s0.p1.y, s1.p1.x, s1.p1.y);
+      if (reflex === undefined) {
+        this.removeItem_(s0, s1, list, rtree);
+        pointsReclassified = true;
+        if (s1 === start) {
+          start = list.getNextItem();
+        }
+        s1 = s0;
+        list.prevItem();
+      } else if (s0.p1.reflex !== reflex) {
+        s0.p1.reflex = reflex;
+        pointsReclassified = true;
+      }
+      s0 = s1;
+      s1 = list.nextItem();
+    } while (s0 !== start);
+    return pointsReclassified;
+  };
 
 
-/**
- * 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]);
+  /**
+   * @private
+   * @param {ol.structs.LinkedList} hole Linked list of the hole.
+   * @param {number} holeMaxX Maximum X value of the hole.
+   * @param {ol.structs.LinkedList} list Linked list of the polygon.
+   * @param {number} listMaxX Maximum X value of the polygon.
+   * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+   * @return {boolean} Bridging was successful.
+   */
+  ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX,
+      list, listMaxX, rtree) {
+    var seg = hole.firstItem();
+    while (seg.p1.x !== holeMaxX) {
+      seg = hole.nextItem();
+    }
+
+    var p1 = seg.p1;
+    /** @type {ol.WebglPolygonVertex} */
+    var p2 = {x: listMaxX, y: p1.y, i: -1};
+    var minDist = Infinity;
+    var i, ii, bestPoint;
+    /** @type {ol.WebglPolygonVertex} */
+    var p5;
+
+    var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, rtree, true);
+    for (i = 0, ii = intersectingSegments.length; i < ii; ++i) {
+      var currSeg = intersectingSegments[i];
+      var intersection = this.calculateIntersection_(p1, p2, currSeg.p0,
+          currSeg.p1, true);
+      var dist = Math.abs(p1.x - intersection[0]);
+      if (dist < minDist && ol.render.webgl.triangleIsCounterClockwise(p1.x, p1.y,
+          currSeg.p0.x, currSeg.p0.y, currSeg.p1.x, currSeg.p1.y) !== undefined) {
+        minDist = dist;
+        p5 = {x: intersection[0], y: intersection[1], i: -1};
+        seg = currSeg;
       }
     }
-  }
-  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';
+    if (minDist === Infinity) {
+      return false;
+    }
+    bestPoint = seg.p1;
+
+    if (minDist > 0) {
+      var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1, rtree);
+      if (pointsInTriangle.length) {
+        var theta = Infinity;
+        for (i = 0, ii = pointsInTriangle.length; i < ii; ++i) {
+          var currPoint = pointsInTriangle[i];
+          var currTheta = Math.atan2(p1.y - currPoint.y, p2.x - currPoint.x);
+          if (currTheta < theta || (currTheta === theta && currPoint.x < bestPoint.x)) {
+            theta = currTheta;
+            bestPoint = currPoint;
+          }
+        }
+      }
     }
-  }
 
-  // Not a NodeList.
-  return false;
-};
+    seg = list.firstItem();
+    while (seg.p1.x !== bestPoint.x || seg.p1.y !== bestPoint.y) {
+      seg = list.nextItem();
+    }
 
+    //We clone the bridge points as they can have different convexity.
+    var p0Bridge = {x: p1.x, y: p1.y, i: p1.i, reflex: undefined};
+    var p1Bridge = {x: seg.p1.x, y: seg.p1.y, i: seg.p1.i, reflex: undefined};
 
-/**
- * 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));
-};
+    hole.getNextItem().p0 = p0Bridge;
+    this.insertItem_(p1, seg.p1, hole, rtree);
+    this.insertItem_(p1Bridge, p0Bridge, hole, rtree);
+    seg.p1 = p1Bridge;
+    hole.setFirstItem();
+    list.concat(hole);
 
+    return true;
+  };
 
-/**
- * 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);
-};
 
+  /**
+   * @private
+   * @param {ol.structs.LinkedList} list Linked list of the polygon.
+   * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+   */
+  ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) {
+    var ccw = false;
+    var simple = this.isSimple_(list, rtree);
 
-/**
- * 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;
+    // Start clipping ears
+    while (list.getLength() > 3) {
+      if (simple) {
+        if (!this.clipEars_(list, rtree, simple, ccw)) {
+          if (!this.classifyPoints_(list, rtree, ccw)) {
+            // Due to the behavior of OL's PIP algorithm, the ear clipping cannot
+            // introduce touching segments. However, the original data may have some.
+            if (!this.resolveLocalSelfIntersections_(list, rtree, true)) {
+              break;
+            }
+          }
+        }
+      } else {
+        if (!this.clipEars_(list, rtree, simple, ccw)) {
+          // We ran out of ears, try to reclassify.
+          if (!this.classifyPoints_(list, rtree, ccw)) {
+            // We have a bad polygon, try to resolve local self-intersections.
+            if (!this.resolveLocalSelfIntersections_(list, rtree)) {
+              simple = this.isSimple_(list, rtree);
+              if (!simple) {
+                // We have a really bad polygon, try more time consuming methods.
+                this.splitPolygon_(list, rtree);
+                break;
+              } else {
+                ccw = !this.isClockwise_(list);
+                this.classifyPoints_(list, rtree, ccw);
+              }
+            }
+          }
+        }
+      }
     }
-    element = element.parentNode;
-    steps++;
-  }
-  // Reached the root of the DOM without a match
-  return null;
-};
+    if (list.getLength() === 3) {
+      var numIndices = this.indices.length;
+      this.indices[numIndices++] = list.getPrevItem().p0.i;
+      this.indices[numIndices++] = list.getCurrItem().p0.i;
+      this.indices[numIndices++] = list.getNextItem().p0.i;
+    }
+  };
 
 
-/**
- * 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."
-  }
+  /**
+   * @private
+   * @param {ol.structs.LinkedList} list Linked list of the polygon.
+   * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+   * @param {boolean} simple The polygon is simple.
+   * @param {boolean} ccw Orientation of the polygon is counter-clockwise.
+   * @return {boolean} There were processed ears.
+   */
+  ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, rtree, simple, ccw) {
+    var numIndices = this.indices.length;
+    var start = list.firstItem();
+    var s0 = list.getPrevItem();
+    var s1 = start;
+    var s2 = list.nextItem();
+    var s3 = list.getNextItem();
+    var p0, p1, p2;
+    var processedEars = false;
+    do {
+      p0 = s1.p0;
+      p1 = s1.p1;
+      p2 = s2.p1;
+      if (p1.reflex === false) {
+        // We might have a valid ear
+        var diagonalIsInside = ccw ? this.diagonalIsInside_(s3.p1, p2, p1, p0,
+            s0.p0) : this.diagonalIsInside_(s0.p0, p0, p1, p2, s3.p1);
+        if ((simple || this.getIntersections_({p0: p0, p1: p2}, rtree).length === 0) &&
+            diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0) {
+          //The diagonal is completely inside the polygon
+          if (simple || p0.reflex === false || p2.reflex === false ||
+              ol.geom.flat.orient.linearRingIsClockwise([s0.p0.x, s0.p0.y, p0.x,
+                p0.y, p1.x, p1.y, p2.x, p2.y, s3.p1.x, s3.p1.y], 0, 10, 2) === !ccw) {
+            //The diagonal is persumably valid, we have an ear
+            this.indices[numIndices++] = p0.i;
+            this.indices[numIndices++] = p1.i;
+            this.indices[numIndices++] = p2.i;
+            this.removeItem_(s1, s2, list, rtree);
+            if (s2 === start) {
+              start = s3;
+            }
+            processedEars = true;
+          }
+        }
+      }
+      // Else we have a reflex point.
+      s0 = list.getPrevItem();
+      s1 = list.getCurrItem();
+      s2 = list.nextItem();
+      s3 = list.getNextItem();
+    } while (s1 !== start && list.getLength() > 3);
+
+    return processedEars;
+  };
 
-  return null;
-};
 
+  /**
+   * @private
+   * @param {ol.structs.LinkedList} list Linked list of the polygon.
+   * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+   * @param {boolean=} opt_touch Resolve touching segments.
+   * @return {boolean} There were resolved intersections.
+  */
+  ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = function(
+      list, rtree, opt_touch) {
+    var start = list.firstItem();
+    list.nextItem();
+    var s0 = start;
+    var s1 = list.nextItem();
+    var resolvedIntersections = false;
 
-/**
- * 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;
-};
+    do {
+      var intersection = this.calculateIntersection_(s0.p0, s0.p1, s1.p0, s1.p1,
+          opt_touch);
+      if (intersection) {
+        var breakCond = false;
+        var numVertices = this.vertices.length;
+        var numIndices = this.indices.length;
+        var n = numVertices / 2;
+        var seg = list.prevItem();
+        list.removeItem();
+        rtree.remove(seg);
+        breakCond = (seg === start);
+        var p;
+        if (opt_touch) {
+          if (intersection[0] === s0.p0.x && intersection[1] === s0.p0.y) {
+            list.prevItem();
+            p = s0.p0;
+            s1.p0 = p;
+            rtree.remove(s0);
+            breakCond = breakCond || (s0 === start);
+          } else {
+            p = s1.p1;
+            s0.p1 = p;
+            rtree.remove(s1);
+            breakCond = breakCond || (s1 === start);
+          }
+          list.removeItem();
+        } else {
+          p = this.createPoint_(intersection[0], intersection[1], n);
+          s0.p1 = p;
+          s1.p0 = p;
+          rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y),
+            Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0);
+          rtree.update([Math.min(s1.p0.x, s1.p1.x), Math.min(s1.p0.y, s1.p1.y),
+            Math.max(s1.p0.x, s1.p1.x), Math.max(s1.p0.y, s1.p1.y)], s1);
+        }
 
+        this.indices[numIndices++] = seg.p0.i;
+        this.indices[numIndices++] = seg.p1.i;
+        this.indices[numIndices++] = p.i;
 
-/**
- * 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;
-};
+        resolvedIntersections = true;
+        if (breakCond) {
+          break;
+        }
+      }
 
+      s0 = list.getPrevItem();
+      s1 = list.nextItem();
+    } while (s0 !== start);
+    return resolvedIntersections;
+  };
 
 
-/**
- * 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
+   * @param {ol.structs.LinkedList} list Linked list of the polygon.
+   * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+   * @return {boolean} The polygon is simple.
    */
-  this.document_ = opt_document || goog.global.document || document;
-};
+  ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list, rtree) {
+    var start = list.firstItem();
+    var seg = start;
+    do {
+      if (this.getIntersections_(seg, rtree).length) {
+        return false;
+      }
+      seg = list.nextItem();
+    } while (seg !== start);
+    return true;
+  };
 
 
-/**
- * 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;
+  /**
+   * @private
+   * @param {ol.structs.LinkedList} list Linked list of the polygon.
+   * @return {boolean} Orientation is clockwise.
+   */
+  ol.render.webgl.PolygonReplay.prototype.isClockwise_ = function(list) {
+    var length = list.getLength() * 2;
+    var flatCoordinates = new Array(length);
+    var start = list.firstItem();
+    var seg = start;
+    var i = 0;
+    do {
+      flatCoordinates[i++] = seg.p0.x;
+      flatCoordinates[i++] = seg.p0.y;
+      seg = list.nextItem();
+    } while (seg !== start);
+    return ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, 0, length, 2);
+  };
 
 
-/**
- * Sets the document object.
- * @param {!Document} document Document object.
- */
-goog.dom.DomHelper.prototype.setDocument = function(document) {
-  this.document_ = document;
-};
+  /**
+   * @private
+   * @param {ol.structs.LinkedList} list Linked list of the polygon.
+   * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+   */
+  ol.render.webgl.PolygonReplay.prototype.splitPolygon_ = function(list, rtree) {
+    var start = list.firstItem();
+    var s0 = start;
+    do {
+      var intersections = this.getIntersections_(s0, rtree);
+      if (intersections.length) {
+        var s1 = intersections[0];
+        var n = this.vertices.length / 2;
+        var intersection = this.calculateIntersection_(s0.p0,
+            s0.p1, s1.p0, s1.p1);
+        var p = this.createPoint_(intersection[0], intersection[1], n);
+        var newPolygon = new ol.structs.LinkedList();
+        var newRtree = new ol.structs.RBush();
+        this.insertItem_(p, s0.p1, newPolygon, newRtree);
+        s0.p1 = p;
+        rtree.update([Math.min(s0.p0.x, p.x), Math.min(s0.p0.y, p.y),
+          Math.max(s0.p0.x, p.x), Math.max(s0.p0.y, p.y)], s0);
+        var currItem = list.nextItem();
+        while (currItem !== s1) {
+          this.insertItem_(currItem.p0, currItem.p1, newPolygon, newRtree);
+          rtree.remove(currItem);
+          list.removeItem();
+          currItem = list.getCurrItem();
+        }
+        this.insertItem_(s1.p0, p, newPolygon, newRtree);
+        s1.p0 = p;
+        rtree.update([Math.min(s1.p1.x, p.x), Math.min(s1.p1.y, p.y),
+          Math.max(s1.p1.x, p.x), Math.max(s1.p1.y, p.y)], s1);
+        this.classifyPoints_(list, rtree, false);
+        this.triangulate_(list, rtree);
+        this.classifyPoints_(newPolygon, newRtree, false);
+        this.triangulate_(newPolygon, newRtree);
+        break;
+      }
+      s0 = list.nextItem();
+    } while (s0 !== start);
+  };
 
 
-/**
- * Gets the document object being used by the dom library.
- * @return {!Document} Document object.
- */
-goog.dom.DomHelper.prototype.getDocument = function() {
-  return this.document_;
-};
+  /**
+   * @private
+   * @param {number} x X coordinate.
+   * @param {number} y Y coordinate.
+   * @param {number} i Index.
+   * @return {ol.WebglPolygonVertex} List item.
+   */
+  ol.render.webgl.PolygonReplay.prototype.createPoint_ = function(x, y, i) {
+    var numVertices = this.vertices.length;
+    this.vertices[numVertices++] = x;
+    this.vertices[numVertices++] = y;
+    /** @type {ol.WebglPolygonVertex} */
+    var p = {
+      x: x,
+      y: y,
+      i: i,
+      reflex: undefined
+    };
+    return p;
+  };
 
 
-/**
- * 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);
-};
+  /**
+   * @private
+   * @param {ol.WebglPolygonVertex} p0 First point of segment.
+   * @param {ol.WebglPolygonVertex} p1 Second point of segment.
+   * @param {ol.structs.LinkedList} list Polygon ring.
+   * @param {ol.structs.RBush=} opt_rtree Insert the segment into the R-Tree.
+   * @return {ol.WebglPolygonSegment} segment.
+   */
+  ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt_rtree) {
+    var seg = {
+      p0: p0,
+      p1: p1
+    };
+    list.insertItem(seg);
+    if (opt_rtree) {
+      opt_rtree.insert([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y),
+        Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)], seg);
+    }
+    return seg;
+  };
 
 
-/**
- * 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);
-};
+   /**
+    * @private
+    * @param {ol.WebglPolygonSegment} s0 Segment before the remove candidate.
+    * @param {ol.WebglPolygonSegment} s1 Remove candidate segment.
+    * @param {ol.structs.LinkedList} list Polygon ring.
+    * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+    */
+  ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list, rtree) {
+    if (list.getCurrItem() === s1) {
+      list.removeItem();
+      s0.p1 = s1.p1;
+      rtree.remove(s1);
+      rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y),
+        Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0);
+    }
+  };
 
 
-/**
- * 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;
+  /**
+   * @private
+   * @param {ol.WebglPolygonVertex} p0 First point.
+   * @param {ol.WebglPolygonVertex} p1 Second point.
+   * @param {ol.WebglPolygonVertex} p2 Third point.
+   * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+   * @param {boolean=} opt_reflex Only include reflex points.
+   * @return {Array.<ol.WebglPolygonVertex>} Points in the triangle.
+   */
+  ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1,
+      p2, rtree, opt_reflex) {
+    var i, ii, j, p;
+    var result = [];
+    var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x, p2.x),
+      Math.min(p0.y, p1.y, p2.y), Math.max(p0.x, p1.x, p2.x), Math.max(p0.y,
+        p1.y, p2.y)]);
+    for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) {
+      for (j in segmentsInExtent[i]) {
+        p = segmentsInExtent[i][j];
+        if (typeof p === 'object' && (!opt_reflex || p.reflex)) {
+          if ((p.x !== p0.x || p.y !== p0.y) && (p.x !== p1.x || p.y !== p1.y) &&
+              (p.x !== p2.x || p.y !== p2.y) && result.indexOf(p) === -1 &&
+              ol.geom.flat.contains.linearRingContainsXY([p0.x, p0.y, p1.x, p1.y,
+                p2.x, p2.y], 0, 6, 2, p.x, p.y)) {
+            result.push(p);
+          }
+        }
+      }
+    }
+    return result;
+  };
 
 
-/**
- * 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);
-};
+  /**
+   * @private
+   * @param {ol.WebglPolygonSegment} segment Segment.
+   * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+   * @param {boolean=} opt_touch Touching segments should be considered an intersection.
+   * @return {Array.<ol.WebglPolygonSegment>} Intersecting segments.
+   */
+  ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, rtree, opt_touch) {
+    var p0 = segment.p0;
+    var p1 = segment.p1;
+    var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x),
+      Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)]);
+    var result = [];
+    var i, ii;
+    for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) {
+      var currSeg = segmentsInExtent[i];
+      if (segment !== currSeg && (opt_touch || currSeg.p0 !== p1 || currSeg.p1 !== p0) &&
+          this.calculateIntersection_(p0, p1, currSeg.p0, currSeg.p1, opt_touch)) {
+        result.push(currSeg);
+      }
+    }
+    return result;
+  };
 
 
-/**
- * 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);
-};
+  /**
+   * Line intersection algorithm by Paul Bourke.
+   * @see http://paulbourke.net/geometry/pointlineplane/
+   *
+   * @private
+   * @param {ol.WebglPolygonVertex} p0 First point.
+   * @param {ol.WebglPolygonVertex} p1 Second point.
+   * @param {ol.WebglPolygonVertex} p2 Third point.
+   * @param {ol.WebglPolygonVertex} p3 Fourth point.
+   * @param {boolean=} opt_touch Touching segments should be considered an intersection.
+   * @return {Array.<number>|undefined} Intersection coordinates.
+   */
+  ol.render.webgl.PolygonReplay.prototype.calculateIntersection_ = function(p0,
+      p1, p2, p3, opt_touch) {
+    var denom = (p3.y - p2.y) * (p1.x - p0.x) - (p3.x - p2.x) * (p1.y - p0.y);
+    if (denom !== 0) {
+      var ua = ((p3.x - p2.x) * (p0.y - p2.y) - (p3.y - p2.y) * (p0.x - p2.x)) / denom;
+      var ub = ((p1.x - p0.x) * (p0.y - p2.y) - (p1.y - p0.y) * (p0.x - p2.x)) / denom;
+      if ((!opt_touch && ua > ol.render.webgl.EPSILON && ua < 1 - ol.render.webgl.EPSILON &&
+          ub > ol.render.webgl.EPSILON && ub < 1 - ol.render.webgl.EPSILON) || (opt_touch &&
+          ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)) {
+        return [p0.x + ua * (p1.x - p0.x), p0.y + ua * (p1.y - p0.y)];
+      }
+    }
+    return undefined;
+  };
 
 
-/**
- * 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);
-};
+  /**
+   * @private
+   * @param {ol.WebglPolygonVertex} p0 Point before the start of the diagonal.
+   * @param {ol.WebglPolygonVertex} p1 Start point of the diagonal.
+   * @param {ol.WebglPolygonVertex} p2 Ear candidate.
+   * @param {ol.WebglPolygonVertex} p3 End point of the diagonal.
+   * @param {ol.WebglPolygonVertex} p4 Point after the end of the diagonal.
+   * @return {boolean} Diagonal is inside the polygon.
+   */
+  ol.render.webgl.PolygonReplay.prototype.diagonalIsInside_ = function(p0, p1, p2, p3, p4) {
+    if (p1.reflex === undefined || p3.reflex === undefined) {
+      return false;
+    }
+    var p1IsLeftOf = (p2.x - p3.x) * (p1.y - p3.y) > (p2.y - p3.y) * (p1.x - p3.x);
+    var p1IsRightOf = (p4.x - p3.x) * (p1.y - p3.y) < (p4.y - p3.y) * (p1.x - p3.x);
+    var p3IsLeftOf = (p0.x - p1.x) * (p3.y - p1.y) > (p0.y - p1.y) * (p3.x - p1.x);
+    var p3IsRightOf = (p2.x - p1.x) * (p3.y - p1.y) < (p2.y - p1.y) * (p3.x - p1.x);
+    var p1InCone = p3.reflex ? p1IsRightOf || p1IsLeftOf : p1IsRightOf && p1IsLeftOf;
+    var p3InCone = p1.reflex ? p3IsRightOf || p3IsLeftOf : p3IsRightOf && p3IsLeftOf;
+    return p1InCone && p3InCone;
+  };
 
 
-/**
- * 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);
-};
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {
+    var endss = multiPolygonGeometry.getEndss();
+    var stride = multiPolygonGeometry.getStride();
+    var currIndex = this.indices.length;
+    var currLineIndex = this.lineStringReplay.getCurrentIndex();
+    var flatCoordinates = multiPolygonGeometry.getFlatCoordinates();
+    var i, ii, j, jj;
+    var start = 0;
+    for (i = 0, ii = endss.length; i < ii; ++i) {
+      var ends = endss[i];
+      if (ends.length > 0) {
+        var outerRing = ol.geom.flat.transform.translate(flatCoordinates, start, ends[0],
+            stride, -this.origin[0], -this.origin[1]);
+        if (outerRing.length) {
+          var holes = [];
+          var holeFlatCoords;
+          for (j = 1, jj = ends.length; j < jj; ++j) {
+            if (ends[j] !== ends[j - 1]) {
+              holeFlatCoords = ol.geom.flat.transform.translate(flatCoordinates, ends[j - 1],
+                  ends[j], stride, -this.origin[0], -this.origin[1]);
+              holes.push(holeFlatCoords);
+            }
+          }
+          this.lineStringReplay.drawPolygonCoordinates(outerRing, holes, stride);
+          this.drawCoordinates_(outerRing, holes, stride);
+        }
+      }
+      start = ends[ends.length - 1];
+    }
+    if (this.indices.length > currIndex) {
+      this.startIndices.push(currIndex);
+      this.startIndicesFeature.push(feature);
+      if (this.state_.changed) {
+        this.styleIndices_.push(currIndex);
+        this.state_.changed = false;
+      }
+    }
+    if (this.lineStringReplay.getCurrentIndex() > currLineIndex) {
+      this.lineStringReplay.setPolygonStyle(feature, currLineIndex);
+    }
+  };
 
 
-/**
- * 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;
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) {
+    var ends = polygonGeometry.getEnds();
+    var stride = polygonGeometry.getStride();
+    if (ends.length > 0) {
+      var flatCoordinates = polygonGeometry.getFlatCoordinates().map(Number);
+      var outerRing = ol.geom.flat.transform.translate(flatCoordinates, 0, ends[0],
+          stride, -this.origin[0], -this.origin[1]);
+      if (outerRing.length) {
+        var holes = [];
+        var i, ii, holeFlatCoords;
+        for (i = 1, ii = ends.length; i < ii; ++i) {
+          if (ends[i] !== ends[i - 1]) {
+            holeFlatCoords = ol.geom.flat.transform.translate(flatCoordinates, ends[i - 1],
+                ends[i], stride, -this.origin[0], -this.origin[1]);
+            holes.push(holeFlatCoords);
+          }
+        }
 
+        this.startIndices.push(this.indices.length);
+        this.startIndicesFeature.push(feature);
+        if (this.state_.changed) {
+          this.styleIndices_.push(this.indices.length);
+          this.state_.changed = false;
+        }
+        this.lineStringReplay.setPolygonStyle(feature);
 
-/**
- * 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;
+        this.lineStringReplay.drawPolygonCoordinates(outerRing, holes, stride);
+        this.drawCoordinates_(outerRing, holes, stride);
+      }
+    }
+  };
 
 
-/**
- * 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());
-};
+  /**
+   * @inheritDoc
+   **/
+  ol.render.webgl.PolygonReplay.prototype.finish = function(context) {
+    // create, bind, and populate the vertices buffer
+    this.verticesBuffer = new ol.webgl.Buffer(this.vertices);
 
+    // create, bind, and populate the indices buffer
+    this.indicesBuffer = new ol.webgl.Buffer(this.indices);
 
-/**
- * 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());
-};
+    this.startIndices.push(this.indices.length);
 
+    this.lineStringReplay.finish(context);
 
-/**
- * Typedef for use with goog.dom.createDom and goog.dom.append.
- * @typedef {Object|string|Array|NodeList}
- */
-goog.dom.Appendable;
+    //Clean up, if there is nothing to draw
+    if (this.styleIndices_.length === 0 && this.styles_.length > 0) {
+      this.styles_ = [];
+    }
 
+    this.vertices = null;
+    this.indices = null;
+  };
 
-/**
- * Returns a dom node with a set of attributes.  This function accepts varargs
- * for subsequent nodes to be added.  Subsequent nodes will be added to the
- * first node as childNodes.
- *
- * So:
- * <code>createDom('div', null, createDom('p'), createDom('p'));</code>
- * would return a div with two child paragraphs
- *
- * An easy way to move all child nodes of an existing element to a new parent
- * element is:
- * <code>createDom('div', null, oldElement.childNodes);</code>
- * which will remove all child nodes from the old element and add them as
- * child nodes of the new DIV.
- *
- * @param {string} tagName Tag to create.
- * @param {Object|string=} opt_attributes If object, then a map of name-value
- *     pairs for attributes. If a string, then this is the className of the new
- *     element.
- * @param {...goog.dom.Appendable} var_args Further DOM nodes or
- *     strings for text nodes. If one of the var_args is an array or
- *     NodeList, its elements will be added as childNodes instead.
- * @return {!Element} Reference to a DOM node.
- */
-goog.dom.DomHelper.prototype.createDom = function(tagName,
-                                                  opt_attributes,
-                                                  var_args) {
-  return goog.dom.createDom_(this.document_, arguments);
-};
 
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(context) {
+    var verticesBuffer = this.verticesBuffer;
+    var indicesBuffer = this.indicesBuffer;
+    var lineDeleter = this.lineStringReplay.getDeleteResourcesFunction(context);
+    return function() {
+      context.deleteBuffer(verticesBuffer);
+      context.deleteBuffer(indicesBuffer);
+      lineDeleter();
+    };
+  };
 
-/**
- * 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;
 
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.PolygonReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {
+    // get the program
+    var fragmentShader, vertexShader;
+    fragmentShader = ol.render.webgl.polygonreplay.defaultshader.fragment;
+    vertexShader = ol.render.webgl.polygonreplay.defaultshader.vertex;
+    var program = context.getProgram(fragmentShader, vertexShader);
+
+    // get the locations
+    var locations;
+    if (!this.defaultLocations_) {
+      // eslint-disable-next-line openlayers-internal/no-missing-requires
+      locations = new ol.render.webgl.polygonreplay.defaultshader.Locations(gl, program);
+      this.defaultLocations_ = locations;
+    } else {
+      locations = this.defaultLocations_;
+    }
 
-/**
- * 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);
-};
+    context.useProgram(program);
 
+    // enable the vertex attrib arrays
+    gl.enableVertexAttribArray(locations.a_position);
+    gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT,
+        false, 8, 0);
 
-/**
- * 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));
-};
+    return locations;
+  };
 
 
-/**
- * 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);
-};
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.PolygonReplay.prototype.shutDownProgram = function(gl, locations) {
+    gl.disableVertexAttribArray(locations.a_position);
+  };
 
 
-/**
- * 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);
-};
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.PolygonReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) {
+    //Save GL parameters.
+    var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC));
+    var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK));
 
+    if (!hitDetection) {
+      gl.enable(gl.DEPTH_TEST);
+      gl.depthMask(true);
+      gl.depthFunc(gl.NOTEQUAL);
+    }
 
-/**
- * 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);
-};
+    if (!ol.obj.isEmpty(skippedFeaturesHash)) {
+      this.drawReplaySkipping_(gl, context, skippedFeaturesHash);
+    } else {
+      //Draw by style groups to minimize drawElements() calls.
+      var i, start, end, nextStyle;
+      end = this.startIndices[this.startIndices.length - 1];
+      for (i = this.styleIndices_.length - 1; i >= 0; --i) {
+        start = this.styleIndices_[i];
+        nextStyle = this.styles_[i];
+        this.setFillStyle_(gl, nextStyle);
+        this.drawElements(gl, context, start, end);
+        end = start;
+      }
+    }
+    if (!hitDetection) {
+      gl.disable(gl.DEPTH_TEST);
+      gl.clear(gl.DEPTH_BUFFER_BIT);
+      //Restore GL parameters.
+      gl.depthMask(tmpDepthMask);
+      gl.depthFunc(tmpDepthFunc);
+    }
+  };
 
 
-/**
- * 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_);
-};
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.PolygonReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash,
+      featureCallback, opt_hitExtent) {
+    var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex;
+    featureIndex = this.startIndices.length - 2;
+    end = this.startIndices[featureIndex + 1];
+    for (i = this.styleIndices_.length - 1; i >= 0; --i) {
+      nextStyle = this.styles_[i];
+      this.setFillStyle_(gl, nextStyle);
+      groupStart = this.styleIndices_[i];
+
+      while (featureIndex >= 0 &&
+          this.startIndices[featureIndex] >= groupStart) {
+        start = this.startIndices[featureIndex];
+        feature = this.startIndicesFeature[featureIndex];
+        featureUid = ol.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, context, start, end);
 
+          var result = featureCallback(feature);
 
-/**
- * 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_);
-};
+          if (result) {
+            return result;
+          }
 
+        }
+        featureIndex--;
+        end = start;
+      }
+    }
+    return undefined;
+  };
 
-/**
- * Gets the document scroll element.
- * @return {!Element} Scrolling element.
- */
-goog.dom.DomHelper.prototype.getDocumentScrollElement = function() {
-  return goog.dom.getDocumentScrollElement_(this.document_);
-};
 
+  /**
+   * @private
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {ol.webgl.Context} context Context.
+   * @param {Object} skippedFeaturesHash Ids of features to skip.
+   */
+  ol.render.webgl.PolygonReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) {
+    var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart;
+    featureIndex = this.startIndices.length - 2;
+    end = start = this.startIndices[featureIndex + 1];
+    for (i = this.styleIndices_.length - 1; i >= 0; --i) {
+      nextStyle = this.styles_[i];
+      this.setFillStyle_(gl, nextStyle);
+      groupStart = this.styleIndices_[i];
 
-/**
- * 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_);
-};
+      while (featureIndex >= 0 &&
+          this.startIndices[featureIndex] >= groupStart) {
+        featureStart = this.startIndices[featureIndex];
+        feature = this.startIndicesFeature[featureIndex];
+        featureUid = ol.getUid(feature).toString();
 
+        if (skippedFeaturesHash[featureUid]) {
+          if (start !== end) {
+            this.drawElements(gl, context, start, end);
+            gl.clear(gl.DEPTH_BUFFER_BIT);
+          }
+          end = featureStart;
+        }
+        featureIndex--;
+        start = featureStart;
+      }
+      if (start !== end) {
+        this.drawElements(gl, context, start, end);
+        gl.clear(gl.DEPTH_BUFFER_BIT);
+      }
+      start = end = groupStart;
+    }
+  };
 
-/**
- * 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_);
-};
 
+  /**
+   * @private
+   * @param {WebGLRenderingContext} gl gl.
+   * @param {Array.<number>} color Color.
+   */
+  ol.render.webgl.PolygonReplay.prototype.setFillStyle_ = function(gl, color) {
+    gl.uniform4fv(this.defaultLocations_.u_color, color);
+  };
 
-/**
- * Appends a child to a node.
- * @param {Node} parent Parent.
- * @param {Node} child Child.
- */
-goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild;
 
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
+    var fillStyleColor = fillStyle ? fillStyle.getColor() : [0, 0, 0, 0];
+    if (!(fillStyleColor instanceof CanvasGradient) &&
+        !(fillStyleColor instanceof CanvasPattern)) {
+      fillStyleColor = ol.color.asArray(fillStyleColor).map(function(c, i) {
+        return i != 3 ? c / 255 : c;
+      }) || ol.render.webgl.defaultFillStyle;
+    } else {
+      fillStyleColor = ol.render.webgl.defaultFillStyle;
+    }
+    if (!this.state_.fillColor || !ol.array.equals(fillStyleColor, this.state_.fillColor)) {
+      this.state_.fillColor = fillStyleColor;
+      this.state_.changed = true;
+      this.styles_.push(fillStyleColor);
+    }
+    //Provide a null stroke style, if no strokeStyle is provided. Required for the draw interaction to work.
+    if (strokeStyle) {
+      this.lineStringReplay.setFillStrokeStyle(null, strokeStyle);
+    } else {
+      var nullStrokeStyle = new ol.style.Stroke({
+        color: [0, 0, 0, 0],
+        lineWidth: 0
+      });
+      this.lineStringReplay.setFillStrokeStyle(null, nullStrokeStyle);
+    }
+  };
 
-/**
- * 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;
+}
 
+goog.provide('ol.render.webgl.TextReplay');
 
-/**
- * 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;
+goog.require('ol');
 
 
-/**
- * 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;
+if (ol.ENABLE_WEBGL) {
 
+  /**
+   * @constructor
+   * @abstract
+   * @param {number} tolerance Tolerance.
+   * @param {ol.Extent} maxExtent Max extent.
+   * @struct
+   */
+  ol.render.webgl.TextReplay = function(tolerance, maxExtent) {};
 
-/**
- * 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;
+  /**
+   * @param {ol.style.Text} textStyle Text style.
+   */
+  ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) {};
 
+  /**
+   * @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|ol.render.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.TextReplay.prototype.replay = function(context,
+      center, resolution, rotation, size, pixelRatio,
+      opacity, skippedFeaturesHash,
+      featureCallback, oneByOne, opt_hitExtent) {
+    return undefined;
+  };
 
-/**
- * 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;
+  /**
+   * @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.webgl.TextReplay.prototype.drawText = function(flatCoordinates, offset,
+      end, stride, geometry, feature) {};
 
+  /**
+   * @abstract
+   * @param {ol.webgl.Context} context Context.
+   */
+  ol.render.webgl.TextReplay.prototype.finish = function(context) {};
 
-/**
- * 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;
+  /**
+   * @param {ol.webgl.Context} context WebGL context.
+   * @return {function()} Delete resources function.
+   */
+  ol.render.webgl.TextReplay.prototype.getDeleteResourcesFunction = function(context) {
+    return ol.nullFunction;
+  };
 
+}
 
-/**
- * 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;
+goog.provide('ol.render.webgl.ReplayGroup');
 
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.extent');
+goog.require('ol.obj');
+goog.require('ol.render.replay');
+goog.require('ol.render.ReplayGroup');
+goog.require('ol.render.webgl.CircleReplay');
+goog.require('ol.render.webgl.ImageReplay');
+goog.require('ol.render.webgl.LineStringReplay');
+goog.require('ol.render.webgl.PolygonReplay');
+goog.require('ol.render.webgl.TextReplay');
 
-/**
- * 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;
 
+if (ol.ENABLE_WEBGL) {
 
-/**
- * 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;
+  /**
+   * @constructor
+   * @extends {ol.render.ReplayGroup}
+   * @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) {
+    ol.render.ReplayGroup.call(this);
 
+    /**
+     * @type {ol.Extent}
+     * @private
+     */
+    this.maxExtent_ = maxExtent;
 
-/**
- * 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;
+    /**
+     * @type {number}
+     * @private
+     */
+    this.tolerance_ = tolerance;
 
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    this.renderBuffer_ = opt_renderBuffer;
 
-/**
- * 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;
+    /**
+     * @private
+     * @type {!Object.<string,
+     *        Object.<ol.render.ReplayType, ol.render.webgl.Replay>>}
+     */
+    this.replaysByZIndex_ = {};
 
+  };
+  ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup);
 
-/**
- * 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;
 
+  /**
+   * @param {ol.webgl.Context} context WebGL context.
+   * @return {function()} Delete resources function.
+   */
+  ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) {
+    var functions = [];
+    var zKey;
+    for (zKey in this.replaysByZIndex_) {
+      var replays = this.replaysByZIndex_[zKey];
+      var replayKey;
+      for (replayKey in replays) {
+        functions.push(
+            replays[replayKey].getDeleteResourcesFunction(context));
+      }
+    }
+    return function() {
+      var length = functions.length;
+      var result;
+      for (var i = 0; i < length; i++) {
+        result = functions[i].apply(this, arguments);
+      }
+      return result;
+    };
+  };
 
-/**
- * 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;
 
+  /**
+   * @param {ol.webgl.Context} context Context.
+   */
+  ol.render.webgl.ReplayGroup.prototype.finish = function(context) {
+    var zKey;
+    for (zKey in this.replaysByZIndex_) {
+      var replays = this.replaysByZIndex_[zKey];
+      var replayKey;
+      for (replayKey in replays) {
+        replays[replayKey].finish(context);
+      }
+    }
+  };
 
-/**
- * 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;
 
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.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) {
+      /**
+       * @type {Function}
+       */
+      var Constructor = ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_[replayType];
+      replay = new Constructor(this.tolerance_, this.maxExtent_);
+      replays[replayType] = replay;
+    }
+    return replay;
+  };
 
-/**
- * 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;
 
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.ReplayGroup.prototype.isEmpty = function() {
+    return ol.obj.isEmpty(this.replaysByZIndex_);
+  };
 
-/**
- * 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;
 
+  /**
+   * @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) {
+    /** @type {Array.<number>} */
+    var zs = Object.keys(this.replaysByZIndex_).map(Number);
+    zs.sort(ol.array.numberSafeCompareFunction);
 
-/**
- * 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;
+    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,
+              center, resolution, rotation, size, pixelRatio,
+              opacity, skippedFeaturesHash,
+              undefined, false);
+        }
+      }
+    }
+  };
 
 
-/**
- * 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;
+  /**
+   * @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|ol.render.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) {
+    /** @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.replay(context,
+              center, resolution, rotation, size, pixelRatio, opacity,
+              skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent);
+          if (result) {
+            return result;
+          }
+        }
+      }
+    }
+    return undefined;
+  };
 
-/**
- * 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;
 
+  /**
+   * @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|ol.render.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());
 
-/**
- * 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;
 
+    /**
+     * @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.ReplayGroup.HIT_DETECTION_SIZE_,
+        pixelRatio, opacity, skippedFeaturesHash,
+        /**
+         * @param {ol.Feature|ol.render.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);
 
-/**
- * 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;
+          if (imageData[3] > 0) {
+            var result = callback(feature);
+            if (result) {
+              return result;
+            }
+          }
+        }, true, hitExtent);
+  };
 
 
-/**
- * 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;
+  /**
+   * @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.ReplayGroup.HIT_DETECTION_SIZE_,
+        pixelRatio, opacity, skippedFeaturesHash,
+        /**
+         * @param {ol.Feature|ol.render.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;
+  };
 
-/**
- * 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;
+  /**
+   * @const
+   * @private
+   * @type {Array.<number>}
+   */
+  ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_ = [1, 1];
 
+  /**
+   * @const
+   * @private
+   * @type {Object.<ol.render.ReplayType,
+   *                function(new: ol.render.webgl.Replay, number,
+   *                ol.Extent)>}
+   */
+  ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_ = {
+    'Circle': ol.render.webgl.CircleReplay,
+    'Image': ol.render.webgl.ImageReplay,
+    'LineString': ol.render.webgl.LineStringReplay,
+    'Polygon': ol.render.webgl.PolygonReplay,
+    'Text': ol.render.webgl.TextReplay
+  };
 
-/**
- * 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;
+}
 
+goog.provide('ol.render.webgl.Immediate');
 
-/**
- * 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;
+goog.require('ol');
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.render.ReplayType');
+goog.require('ol.render.VectorContext');
+goog.require('ol.render.webgl.ReplayGroup');
 
 
-/**
- * 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;
+if (ol.ENABLE_WEBGL) {
 
+  /**
+   * @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) {
+    ol.render.VectorContext.call(this);
 
-/**
- * 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;
+    /**
+     * @private
+     */
+    this.context_ = context;
 
+    /**
+     * @private
+     */
+    this.center_ = center;
 
-/**
- * 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;
+    /**
+     * @private
+     */
+    this.extent_ = extent;
 
+    /**
+     * @private
+     */
+    this.pixelRatio_ = pixelRatio;
 
-/**
- * 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;
+    /**
+     * @private
+     */
+    this.size_ = size;
 
+    /**
+     * @private
+     */
+    this.rotation_ = rotation;
 
-/**
- * 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;
+    /**
+     * @private
+     */
+    this.resolution_ = resolution;
 
+    /**
+     * @private
+     * @type {ol.style.Image}
+     */
+    this.imageStyle_ = null;
 
-/**
- * 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;
+    /**
+     * @private
+     * @type {ol.style.Fill}
+     */
+    this.fillStyle_ = null;
 
+    /**
+     * @private
+     * @type {ol.style.Stroke}
+     */
+    this.strokeStyle_ = null;
 
-/**
- * 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;
+  };
+  ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext);
 
 
-/**
- * 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;
+  /**
+   * Set the rendering style.  Note that since this is an immediate rendering API,
+   * any `zIndex` on the provided style will be ignored.
+   *
+   * @param {ol.style.Style} style The rendering style.
+   * @override
+   * @api
+   */
+  ol.render.webgl.Immediate.prototype.setStyle = function(style) {
+    this.setFillStrokeStyle(style.getFill(), style.getStroke());
+    this.setImageStyle(style.getImage());
+  };
 
 
-/**
- * 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;
+  /**
+   * Render a geometry into the canvas.  Call
+   * {@link ol.render.webgl.Immediate#setStyle} first to set the rendering style.
+   *
+   * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render.
+   * @override
+   * @api
+   */
+  ol.render.webgl.Immediate.prototype.drawGeometry = function(geometry) {
+    var type = geometry.getType();
+    switch (type) {
+      case ol.geom.GeometryType.POINT:
+        this.drawPoint(/** @type {ol.geom.Point} */ (geometry), null);
+        break;
+      case ol.geom.GeometryType.LINE_STRING:
+        this.drawLineString(/** @type {ol.geom.LineString} */ (geometry), null);
+        break;
+      case ol.geom.GeometryType.POLYGON:
+        this.drawPolygon(/** @type {ol.geom.Polygon} */ (geometry), null);
+        break;
+      case ol.geom.GeometryType.MULTI_POINT:
+        this.drawMultiPoint(/** @type {ol.geom.MultiPoint} */ (geometry), null);
+        break;
+      case ol.geom.GeometryType.MULTI_LINE_STRING:
+        this.drawMultiLineString(/** @type {ol.geom.MultiLineString} */ (geometry), null);
+        break;
+      case ol.geom.GeometryType.MULTI_POLYGON:
+        this.drawMultiPolygon(/** @type {ol.geom.MultiPolygon} */ (geometry), null);
+        break;
+      case ol.geom.GeometryType.GEOMETRY_COLLECTION:
+        this.drawGeometryCollection(/** @type {ol.geom.GeometryCollection} */ (geometry), null);
+        break;
+      case ol.geom.GeometryType.CIRCLE:
+        this.drawCircle(/** @type {ol.geom.Circle} */ (geometry), null);
+        break;
+      default:
+        // pass
+    }
+  };
 
 
-/**
- * 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;
+  /**
+   * @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;
+    }
+    this.setStyle(style);
+    this.drawGeometry(geometry);
+  };
 
 
-/**
- * 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;
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.Immediate.prototype.drawGeometryCollection = function(geometry, data) {
+    var geometries = geometry.getGeometriesArray();
+    var i, ii;
+    for (i = 0, ii = geometries.length; i < ii; ++i) {
+      this.drawGeometry(geometries[i]);
+    }
+  };
 
 
-/**
- * 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;
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.Immediate.prototype.drawPoint = function(geometry, 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.drawPoint(geometry, 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)();
+  };
 
 
-/**
- * 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;
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.Immediate.prototype.drawMultiPoint = function(geometry, 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.drawMultiPoint(geometry, 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)();
+  };
 
 
-/**
- * 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;
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.Immediate.prototype.drawLineString = function(geometry, data) {
+    var context = this.context_;
+    var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
+    var replay = /** @type {ol.render.webgl.LineStringReplay} */ (
+        replayGroup.getReplay(0, ol.render.ReplayType.LINE_STRING));
+    replay.setFillStrokeStyle(null, this.strokeStyle_);
+    replay.drawLineString(geometry, 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)();
+  };
 
 
-/**
- * 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;
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.Immediate.prototype.drawMultiLineString = function(geometry, data) {
+    var context = this.context_;
+    var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
+    var replay = /** @type {ol.render.webgl.LineStringReplay} */ (
+        replayGroup.getReplay(0, ol.render.ReplayType.LINE_STRING));
+    replay.setFillStrokeStyle(null, this.strokeStyle_);
+    replay.drawMultiLineString(geometry, 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)();
+  };
 
 
-/**
- * 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;
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.Immediate.prototype.drawPolygon = function(geometry, data) {
+    var context = this.context_;
+    var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
+    var replay = /** @type {ol.render.webgl.PolygonReplay} */ (
+        replayGroup.getReplay(0, ol.render.ReplayType.POLYGON));
+    replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_);
+    replay.drawPolygon(geometry, 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)();
+  };
 
-// 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).
- */
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.Immediate.prototype.drawMultiPolygon = function(geometry, data) {
+    var context = this.context_;
+    var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
+    var replay = /** @type {ol.render.webgl.PolygonReplay} */ (
+        replayGroup.getReplay(0, ol.render.ReplayType.POLYGON));
+    replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_);
+    replay.drawMultiPolygon(geometry, 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
+   */
+  ol.render.webgl.Immediate.prototype.drawCircle = function(geometry, data) {
+    var context = this.context_;
+    var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
+    var replay = /** @type {ol.render.webgl.CircleReplay} */ (
+        replayGroup.getReplay(0, ol.render.ReplayType.CIRCLE));
+    replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_);
+    replay.drawCircle(geometry, 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)();
+  };
 
-goog.provide('goog.dom.classlist');
 
-goog.require('goog.array');
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) {
+    this.imageStyle_ = imageStyle;
+  };
 
 
-/**
- * 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);
+  /**
+   * @inheritDoc
+   */
+  ol.render.webgl.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
+    this.fillStyle_ = fillStyle;
+    this.strokeStyle_ = strokeStyle;
+  };
 
+}
 
-/**
- * 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;
-  }
+goog.provide('ol.structs.LRUCache');
 
-  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) || [];
-};
+goog.require('ol.asserts');
 
 
 /**
- * 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.
+ * Implements a Least-Recently-Used cache where the keys do not conflict with
+ * Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring
+ * items from the cache is the responsibility of the user.
+ * @constructor
+ * @struct
+ * @template T
  */
-goog.dom.classlist.set = function(element, className) {
-  element.className = className;
-};
+ol.structs.LRUCache = function() {
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.count_ = 0;
 
-/**
- * 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);
-};
+  /**
+   * @private
+   * @type {!Object.<string, ol.LRUCacheEntry>}
+   */
+  this.entries_ = {};
 
+  /**
+   * @private
+   * @type {?ol.LRUCacheEntry}
+   */
+  this.oldest_ = null;
 
-/**
- * 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;
-  }
+  /**
+   * @private
+   * @type {?ol.LRUCacheEntry}
+   */
+  this.newest_ = null;
 
-  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.
+ * FIXME empty description for jsdoc
  */
-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;
-  }
+ol.structs.LRUCache.prototype.clear = function() {
+  this.count_ = 0;
+  this.entries_ = {};
+  this.oldest_ = null;
+  this.newest_ = null;
 };
 
 
 /**
- * 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.
+ * @param {string} key Key.
+ * @return {boolean} Contains key.
  */
-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(' ');
-  }
+ol.structs.LRUCache.prototype.containsKey = function(key) {
+  return this.entries_.hasOwnProperty(key);
 };
 
 
 /**
- * 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.
+ * @param {function(this: S, T, string, ol.structs.LRUCache): ?} f The function
+ *     to call for every entry from the oldest to the newer. This function takes
+ *     3 arguments (the entry value, the entry key and the LRUCache object).
+ *     The return value is ignored.
+ * @param {S=} opt_this The object to use as `this` in `f`.
+ * @template S
  */
-goog.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;
+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;
   }
-  // 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).
+ * @param {string} key Key.
+ * @return {T} Value.
  */
-goog.dom.classlist.enable = function(element, className, enabled) {
-  if (enabled) {
-    goog.dom.classlist.add(element, className);
+ol.structs.LRUCache.prototype.get = function(key) {
+  var entry = this.entries_[key];
+  ol.asserts.assert(entry !== undefined,
+      15); // Tried to get a value for a key that does not exist in the cache
+  if (entry === this.newest_) {
+    return entry.value_;
+  } else if (entry === this.oldest_) {
+    this.oldest_ = /** @type {ol.LRUCacheEntry} */ (this.oldest_.newer);
+    this.oldest_.older = null;
   } else {
-    goog.dom.classlist.remove(element, className);
+    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_;
 };
 
 
 /**
- * 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).
+ * @return {number} Count.
  */
-goog.dom.classlist.enableAll = function(element, classesToEnable, enabled) {
-  var f = enabled ? goog.dom.classlist.addAll :
-      goog.dom.classlist.removeAll;
-  f(element, classesToEnable);
+ol.structs.LRUCache.prototype.getCount = function() {
+  return this.count_;
 };
 
 
 /**
- * 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.
+ * @return {Array.<string>} Keys.
  */
-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;
+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_;
   }
-  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;
+  return keys;
 };
 
 
 /**
- * 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.
+ * @return {Array.<T>} Values.
  */
-goog.dom.classlist.addRemove = function(element, classToRemove, classToAdd) {
-  goog.dom.classlist.remove(element, classToRemove);
-  goog.dom.classlist.add(element, classToAdd);
+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_;
+  }
+  return values;
 };
 
-// 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.
+ * @return {T} Last value.
  */
-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;
+ol.structs.LRUCache.prototype.peekLast = function() {
+  return this.oldest_.value_;
 };
 
 
 /**
- * Returns the vendor prefix used in CSS properties.
- *
- * @return {?string} The vendor prefix or null if there is none.
+ * @return {string} Last key.
  */
-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;
+ol.structs.LRUCache.prototype.peekLastKey = function() {
+  return this.oldest_.key_;
 };
 
 
 /**
- * @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.
+ * @return {T} value Value.
  */
-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;
+ol.structs.LRUCache.prototype.pop = function() {
+  var entry = this.oldest_;
+  delete this.entries_[entry.key_];
+  if (entry.newer) {
+    entry.newer.older = null;
   }
-  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;
+  this.oldest_ = /** @type {ol.LRUCacheEntry} */ (entry.newer);
+  if (!this.oldest_) {
+    this.newest_ = null;
   }
-  return null;
+  --this.count_;
+  return entry.value_;
 };
 
 
 /**
- * @param {string} eventType An event type.
- * @return {string} A lower-cased vendor prefixed event type.
+ * @param {string} key Key.
+ * @param {T} value Value.
  */
-goog.dom.vendor.getPrefixedEventType = function(eventType) {
-  var prefix = goog.dom.vendor.getVendorJsPrefix() || '';
-  return (prefix + eventType).toLowerCase();
+ol.structs.LRUCache.prototype.replace = function(key, value) {
+  this.get(key);  // update `newest_`
+  this.entries_[key].value_ = value;
 };
 
-// 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.
+ * @param {string} key Key.
+ * @param {T} value Value.
  */
+ol.structs.LRUCache.prototype.set = function(key, value) {
+  ol.asserts.assert(!(key in this.entries_),
+      16); // Tried to set a value for a key that is used already
+  var entry = /** @type {ol.LRUCacheEntry} */ ({
+    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_;
+};
 
+// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE)
 
-goog.provide('goog.math.Box');
-
-goog.require('goog.math.Coordinate');
+goog.provide('ol.renderer.webgl.Map');
 
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.css');
+goog.require('ol.dom');
+goog.require('ol.events');
+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.Map');
+goog.require('ol.renderer.Type');
+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.ContextEventType');
 
 
-/**
- * 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;
+if (ol.ENABLE_WEBGL) {
 
   /**
-   * Right
-   * @type {number}
+   * @constructor
+   * @extends {ol.renderer.Map}
+   * @param {Element} container Container.
+   * @param {ol.Map} map Map.
    */
-  this.right = right;
+  ol.renderer.webgl.Map = function(container, map) {
+    ol.renderer.Map.call(this, container, map);
 
-  /**
-   * Bottom
-   * @type {number}
-   */
-  this.bottom = bottom;
+    /**
+     * @private
+     * @type {HTMLCanvasElement}
+     */
+    this.canvas_ = /** @type {HTMLCanvasElement} */
+        (document.createElement('CANVAS'));
+    this.canvas_.style.width = '100%';
+    this.canvas_.style.height = '100%';
+    this.canvas_.style.display = 'block';
+    this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
+    container.insertBefore(this.canvas_, container.childNodes[0] || null);
 
-  /**
-   * Left
-   * @type {number}
-   */
-  this.left = left;
-};
+    /**
+     * @private
+     * @type {number}
+     */
+    this.clipTileCanvasWidth_ = 0;
 
+    /**
+     * @private
+     * @type {number}
+     */
+    this.clipTileCanvasHeight_ = 0;
 
-/**
- * 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;
-};
+    /**
+     * @private
+     * @type {CanvasRenderingContext2D}
+     */
+    this.clipTileContext_ = ol.dom.createCanvasContext2D();
 
+    /**
+     * @private
+     * @type {boolean}
+     */
+    this.renderedVisible_ = true;
 
-/**
- * @return {number} width The width of this Box.
- */
-goog.math.Box.prototype.getWidth = function() {
-  return this.right - this.left;
-};
+    /**
+     * @private
+     * @type {WebGLRenderingContext}
+     */
+    this.gl_ = ol.webgl.getContext(this.canvas_, {
+      antialias: true,
+      depth: true,
+      failIfMajorPerformanceCaveat: true,
+      preserveDrawingBuffer: false,
+      stencil: true
+    });
 
+    /**
+     * @private
+     * @type {ol.webgl.Context}
+     */
+    this.context_ = new ol.webgl.Context(this.canvas_, this.gl_);
 
-/**
- * @return {number} height The height of this Box.
- */
-goog.math.Box.prototype.getHeight = function() {
-  return this.bottom - this.top;
-};
+    ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST,
+        this.handleWebGLContextLost, this);
+    ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED,
+        this.handleWebGLContextRestored, this);
 
+    /**
+     * @private
+     * @type {ol.structs.LRUCache.<ol.WebglTextureCacheEntry|null>}
+     */
+    this.textureCache_ = new ol.structs.LRUCache();
 
-/**
- * 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);
-};
+    /**
+     * @private
+     * @type {ol.Coordinate}
+     */
+    this.focus_ = null;
 
+    /**
+     * @private
+     * @type {ol.structs.PriorityQueue.<Array>}
+     */
+    this.tileTextureQueue_ = new ol.structs.PriorityQueue(
+        /**
+         * @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;
+        }).bind(this),
+        /**
+         * @param {Array.<*>} element Element.
+         * @return {string} Key.
+         */
+        function(element) {
+          return /** @type {ol.Tile} */ (element[0]).getKey();
+        });
 
-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)';
-  };
-}
 
+    /**
+     * @param {ol.Map} map Map.
+     * @param {?olx.FrameState} frameState Frame state.
+     * @return {boolean} false.
+     * @this {ol.renderer.webgl.Map}
+     */
+    this.loadNextTileTexture_ =
+        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, ol.webgl.LINEAR, ol.webgl.LINEAR);
+          }
+          return false;
+        }.bind(this);
 
-/**
- * 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);
-};
 
+    /**
+     * @private
+     * @type {number}
+     */
+    this.textureCacheFrameMarkerCount_ = 0;
 
-/**
- * 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;
-  }
+    this.initializeGL_();
+  };
+  ol.inherits(ol.renderer.webgl.Map, ol.renderer.Map);
 
-  return this;
-};
 
+  /**
+   * @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);
+      gl.bindTexture(ol.webgl.TEXTURE_2D, textureCacheEntry.texture);
+      if (textureCacheEntry.magFilter != magFilter) {
+        gl.texParameteri(
+            ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter);
+        textureCacheEntry.magFilter = magFilter;
+      }
+      if (textureCacheEntry.minFilter != minFilter) {
+        gl.texParameteri(
+            ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter);
+        textureCacheEntry.minFilter = minFilter;
+      }
+    } else {
+      var texture = gl.createTexture();
+      gl.bindTexture(ol.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(ol.webgl.TEXTURE_2D, 0,
+            ol.webgl.RGBA, ol.webgl.RGBA,
+            ol.webgl.UNSIGNED_BYTE, clipTileCanvas);
+      } else {
+        gl.texImage2D(ol.webgl.TEXTURE_2D, 0,
+            ol.webgl.RGBA, ol.webgl.RGBA,
+            ol.webgl.UNSIGNED_BYTE, tile.getImage());
+      }
+      gl.texParameteri(
+          ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter);
+      gl.texParameteri(
+          ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter);
+      gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S,
+          ol.webgl.CLAMP_TO_EDGE);
+      gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T,
+          ol.webgl.CLAMP_TO_EDGE);
+      this.textureCache_.set(tileKey, {
+        texture: texture,
+        magFilter: magFilter,
+        minFilter: minFilter
+      });
+    }
+  };
 
-/**
- * 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);
-};
 
+  /**
+   * @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_;
 
-/**
- * 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);
-};
+      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;
 
-/**
- * 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;
-};
+      var vectorContext = new ol.render.webgl.Immediate(context,
+          center, resolution, rotation, size, extent, pixelRatio);
+      var composeEvent = new ol.render.Event(type, vectorContext,
+          frameState, null, context);
+      map.dispatchEvent(composeEvent);
+    }
+  };
 
 
-/**
- * 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;
-  }
+  /**
+   * @inheritDoc
+   */
+  ol.renderer.webgl.Map.prototype.disposeInternal = function() {
+    var gl = this.getGL();
+    if (!gl.isContextLost()) {
+      this.textureCache_.forEach(
+          /**
+           * @param {?ol.WebglTextureCacheEntry} textureCacheEntry
+           *     Texture cache entry.
+           */
+          function(textureCacheEntry) {
+            if (textureCacheEntry) {
+              gl.deleteTexture(textureCacheEntry.texture);
+            }
+          });
+    }
+    this.context_.dispose();
+    ol.renderer.Map.prototype.disposeInternal.call(this);
+  };
 
-  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;
-};
+  /**
+   * @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();
+    }
+  };
 
 
-/**
- * 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;
-};
+  /**
+   * @return {ol.webgl.Context} The context.
+   */
+  ol.renderer.webgl.Map.prototype.getContext = function() {
+    return this.context_;
+  };
 
 
-/**
- * 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;
-};
+  /**
+   * @return {WebGLRenderingContext} GL.
+   */
+  ol.renderer.webgl.Map.prototype.getGL = function() {
+    return this.gl_;
+  };
 
 
-/**
- * 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);
-};
+  /**
+   * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue.
+   */
+  ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() {
+    return this.tileTextureQueue_;
+  };
 
 
-/**
- * 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);
-};
+  /**
+   * @inheritDoc
+   */
+  ol.renderer.webgl.Map.prototype.getType = function() {
+    return ol.renderer.Type.WEBGL;
+  };
 
 
-/**
- * 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);
-};
+  /**
+   * @param {ol.events.Event} event Event.
+   * @protected
+   */
+  ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) {
+    event.preventDefault();
+    this.textureCache_.clear();
+    this.textureCacheFrameMarkerCount_ = 0;
 
+    var renderers = this.getLayerRenderers();
+    for (var id in renderers) {
+      var renderer = /** @type {ol.renderer.webgl.Layer} */ (renderers[id]);
+      renderer.handleWebGLContextLost();
+    }
+  };
 
-/**
- * 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;
-};
 
+  /**
+   * @protected
+   */
+  ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() {
+    this.initializeGL_();
+    this.getMap().render();
+  };
 
-/**
- * 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;
-};
 
+  /**
+   * @private
+   */
+  ol.renderer.webgl.Map.prototype.initializeGL_ = function() {
+    var gl = this.gl_;
+    gl.activeTexture(ol.webgl.TEXTURE0);
+    gl.blendFuncSeparate(
+        ol.webgl.SRC_ALPHA, ol.webgl.ONE_MINUS_SRC_ALPHA,
+        ol.webgl.ONE, ol.webgl.ONE_MINUS_SRC_ALPHA);
+    gl.disable(ol.webgl.CULL_FACE);
+    gl.disable(ol.webgl.DEPTH_TEST);
+    gl.disable(ol.webgl.SCISSOR_TEST);
+    gl.disable(ol.webgl.STENCIL_TEST);
+  };
 
-/**
- * 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;
-};
 
+  /**
+   * @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());
+  };
 
-/**
- * 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;
-};
 
+  /**
+   * @inheritDoc
+   */
+  ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
 
-/**
- * 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;
-};
+    var context = this.getContext();
+    var gl = this.getGL();
 
-// 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.
+    if (gl.isContextLost()) {
+      return false;
+    }
 
-/**
- * @fileoverview A utility class for representing rectangles.
- */
+    if (!frameState) {
+      if (this.renderedVisible_) {
+        this.canvas_.style.display = 'none';
+        this.renderedVisible_ = false;
+      }
+      return false;
+    }
 
-goog.provide('goog.math.Rect');
+    this.focus_ = frameState.focus;
 
-goog.require('goog.math.Box');
-goog.require('goog.math.Coordinate');
-goog.require('goog.math.Size');
+    this.textureCache_.set((-frameState.index).toString(), null);
+    ++this.textureCacheFrameMarkerCount_;
 
+    this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
 
+    /** @type {Array.<ol.LayerState>} */
+    var layerStatesToDraw = [];
+    var layerStatesArray = frameState.layerStatesArray;
+    ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);
 
-/**
- * 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;
+    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 = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer));
+        if (layerRenderer.prepareFrame(frameState, layerState, context)) {
+          layerStatesToDraw.push(layerState);
+        }
+      }
+    }
 
-  /** @type {number} */
-  this.top = y;
+    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;
+    }
 
-  /** @type {number} */
-  this.width = w;
+    gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, null);
 
-  /** @type {number} */
-  this.height = h;
-};
+    gl.clearColor(0, 0, 0, 0);
+    gl.clear(ol.webgl.COLOR_BUFFER_BIT);
+    gl.enable(ol.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 = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer));
+      layerRenderer.composeFrame(frameState, layerState, context);
+    }
 
-/**
- * @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);
-};
+    if (!this.renderedVisible_) {
+      this.canvas_.style.display = '';
+      this.renderedVisible_ = true;
+    }
 
+    this.calculateMatrices2D(frameState);
 
-/**
- * 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);
-};
+    if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ >
+        ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) {
+      frameState.postRenderFunctions.push(
+        /** @type {ol.PostRenderFunction} */ (this.expireCache_.bind(this))
+      );
+    }
 
+    if (!this.tileTextureQueue_.isEmpty()) {
+      frameState.postRenderFunctions.push(this.loadNextTileTexture_);
+      frameState.animate = true;
+    }
 
-/**
- * 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);
-};
+    this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState);
 
+    this.scheduleRemoveUnusedLayerRenderers(frameState);
+    this.scheduleExpireIconCache(frameState);
 
-/**
- * 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
+   * @inheritDoc
    */
-  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;
+  ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg,
+          layerFilter, thisArg2) {
+    var result;
 
-      return true;
+    if (this.getGL().isContextLost()) {
+      return false;
     }
-  }
-  return false;
-};
 
+    var viewState = frameState.viewState;
 
-/**
- * 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 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, hitTolerance, callback, thisArg);
+        if (result) {
+          return result;
+        }
+      }
+    }
+    return undefined;
+  };
 
-  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);
+  /**
+   * @inheritDoc
+   */
+  ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) {
+    var hasFeature = false;
 
-    if (y0 <= y1) {
-      return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0);
+    if (this.getGL().isContextLost()) {
+      return false;
     }
-  }
-  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);
-};
 
+    var viewState = frameState.viewState;
 
-/**
- * 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);
-};
+    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;
+  };
 
 
-/**
- * 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()];
-  }
+  /**
+   * @inheritDoc
+   */
+  ol.renderer.webgl.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg,
+          layerFilter, thisArg2) {
+    if (this.getGL().isContextLost()) {
+      return false;
+    }
 
-  var result = [];
+    var viewState = frameState.viewState;
+    var result;
 
-  var top = a.top;
-  var height = a.height;
+    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 = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layer));
+        result = layerRenderer.forEachLayerAtPixel(
+            pixel, frameState, callback, thisArg);
+        if (result) {
+          return result;
+        }
+      }
+    }
+    return undefined;
+  };
 
-  var ar = a.left + a.width;
-  var ab = a.top + a.height;
+}
 
-  var br = b.left + b.width;
-  var bb = b.top + b.height;
+// FIXME recheck layer/map projection compatibility when projection changes
+// FIXME layer renderers should skip when they can't reproject
+// FIXME add tilt and 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));
-  }
+goog.provide('ol.Map');
 
-  return result;
-};
+goog.require('ol');
+goog.require('ol.Collection');
+goog.require('ol.CollectionEventType');
+goog.require('ol.MapBrowserEvent');
+goog.require('ol.MapBrowserEventHandler');
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.MapEvent');
+goog.require('ol.MapEventType');
+goog.require('ol.MapProperty');
+goog.require('ol.Object');
+goog.require('ol.ObjectEventType');
+goog.require('ol.TileQueue');
+goog.require('ol.View');
+goog.require('ol.ViewHint');
+goog.require('ol.asserts');
+goog.require('ol.control');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.Event');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
+goog.require('ol.functions');
+goog.require('ol.has');
+goog.require('ol.interaction');
+goog.require('ol.layer.Group');
+goog.require('ol.obj');
+goog.require('ol.renderer.Map');
+goog.require('ol.renderer.Type');
+goog.require('ol.renderer.canvas.Map');
+goog.require('ol.renderer.webgl.Map');
+goog.require('ol.size');
+goog.require('ol.structs.PriorityQueue');
+goog.require('ol.transform');
 
 
 /**
- * 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.
+ * @const
+ * @type {string}
  */
-goog.math.Rect.prototype.difference = function(rect) {
-  return goog.math.Rect.difference(this, rect);
-};
+ol.OL_URL = 'https://openlayers.org/';
 
 
 /**
- * Expand this rectangle to also include the area of the given rectangle.
- * @param {goog.math.Rect} rect The other rectangle.
+ * @const
+ * @type {string}
  */
-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;
-};
+ol.OL_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';
 
 
 /**
- * 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.
+ * @type {Array.<ol.renderer.Type>}
+ * @const
  */
-goog.math.Rect.boundingRect = function(a, b) {
-  if (!a || !b) {
-    return null;
-  }
-
-  var clone = a.clone();
-  clone.boundingRect(b);
-
-  return clone;
-};
+ol.DEFAULT_RENDERER_TYPES = [
+  ol.renderer.Type.CANVAS,
+  ol.renderer.Type.WEBGL
+];
 
 
 /**
- * Tests whether this rectangle entirely contains another rectangle or
- * coordinate.
+ * @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:
  *
- * @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.
+ *     var map = new ol.Map({
+ *       view: new ol.View({
+ *         center: [0, 0],
+ *         zoom: 1
+ *       }),
+ *       layers: [
+ *         new ol.layer.Tile({
+ *           source: new ol.source.OSM()
+ *         })
+ *       ],
+ *       target: 'map'
+ *     });
+ *
+ * The above snippet creates a map using a {@link ol.layer.Tile} to display
+ * {@link ol.source.OSM} 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.
+ *
+ * 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
  */
-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;
-};
+ol.Map = function(options) {
 
+  ol.Object.call(this);
 
-/**
- * @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));
-};
+  var optionsInternal = ol.Map.createOptionsInternal(options);
 
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.loadTilesWhileAnimating_ =
+      options.loadTilesWhileAnimating !== undefined ?
+          options.loadTilesWhileAnimating : false;
 
-/**
- * @return {!goog.math.Size} The size of this rectangle.
- */
-goog.math.Rect.prototype.getSize = function() {
-  return new goog.math.Size(this.width, this.height);
-};
+  /**
+   * @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;
 
-/**
- * @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);
-};
+  /**
+   * @private
+   * @type {Object.<string, string>}
+   */
+  this.logos_ = optionsInternal.logos;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.animationDelayKey_;
 
-/**
- * @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);
-};
+  /**
+   * @private
+   */
+  this.animationDelay_ = function() {
+    this.animationDelayKey_ = undefined;
+    this.renderFrame_.call(this, Date.now());
+  }.bind(this);
 
+  /**
+   * @private
+   * @type {ol.Transform}
+   */
+  this.coordinateToPixelTransform_ = ol.transform.create();
 
-/**
- * @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);
-};
+  /**
+   * @private
+   * @type {ol.Transform}
+   */
+  this.pixelToCoordinateTransform_ = ol.transform.create();
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.frameIndex_ = 0;
 
-/**
- * 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;
-};
+  /**
+   * @private
+   * @type {?olx.FrameState}
+   */
+  this.frameState_ = null;
 
+  /**
+   * The extent at the previous 'moveend' event.
+   * @private
+   * @type {ol.Extent}
+   */
+  this.previousExtent_ = null;
 
-/**
- * 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;
-};
+  /**
+   * @private
+   * @type {?ol.EventsKey}
+   */
+  this.viewPropertyListenerKey_ = null;
 
+  /**
+   * @private
+   * @type {?ol.EventsKey}
+   */
+  this.viewChangeListenerKey_ = null;
 
-/**
- * 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;
-};
+  /**
+   * @private
+   * @type {Array.<ol.EventsKey>}
+   */
+  this.layerGroupPropertyListenerKeys_ = null;
 
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.viewport_ = document.createElement('DIV');
+  this.viewport_.className = 'ol-viewport' + (ol.has.TOUCH ? ' ol-touch' : '');
+  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';
 
-/**
- * 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;
-};
+  /**
+   * @private
+   * @type {!Element}
+   */
+  this.overlayContainer_ = document.createElement('DIV');
+  this.overlayContainer_.className = 'ol-overlaycontainer';
+  this.viewport_.appendChild(this.overlayContainer_);
 
-// 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.
+  /**
+   * @private
+   * @type {!Element}
+   */
+  this.overlayContainerStopEvent_ = document.createElement('DIV');
+  this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent';
+  var overlayEvents = [
+    ol.events.EventType.CLICK,
+    ol.events.EventType.DBLCLICK,
+    ol.events.EventType.MOUSEDOWN,
+    ol.events.EventType.TOUCHSTART,
+    ol.events.EventType.MSPOINTERDOWN,
+    ol.MapBrowserEventType.POINTERDOWN,
+    ol.events.EventType.MOUSEWHEEL,
+    ol.events.EventType.WHEEL
+  ];
+  for (var i = 0, ii = overlayEvents.length; i < ii; ++i) {
+    ol.events.listen(this.overlayContainerStopEvent_, overlayEvents[i],
+        ol.events.Event.stopPropagation);
+  }
+  this.viewport_.appendChild(this.overlayContainerStopEvent_);
 
-/**
- * @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
- */
+  /**
+   * @private
+   * @type {ol.MapBrowserEventHandler}
+   */
+  this.mapBrowserEventHandler_ = new ol.MapBrowserEventHandler(this, options.moveTolerance);
+  for (var key in ol.MapBrowserEventType) {
+    ol.events.listen(this.mapBrowserEventHandler_, ol.MapBrowserEventType[key],
+        this.handleMapBrowserEvent, this);
+  }
 
-goog.provide('goog.style');
+  /**
+   * @private
+   * @type {Element|Document}
+   */
+  this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget;
 
+  /**
+   * @private
+   * @type {Array.<ol.EventsKey>}
+   */
+  this.keyHandlerKeys_ = null;
 
-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');
+  ol.events.listen(this.viewport_, ol.events.EventType.WHEEL,
+      this.handleBrowserEvent, this);
+  ol.events.listen(this.viewport_, ol.events.EventType.MOUSEWHEEL,
+      this.handleBrowserEvent, this);
 
-goog.forwardDeclare('goog.events.BrowserEvent');
-goog.forwardDeclare('goog.events.Event');
+  /**
+   * @type {ol.Collection.<ol.control.Control>}
+   * @private
+   */
+  this.controls_ = optionsInternal.controls;
 
+  /**
+   * @type {ol.Collection.<ol.interaction.Interaction>}
+   * @private
+   */
+  this.interactions_ = optionsInternal.interactions;
 
-/**
- * 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);
-    }
-  }
-};
+  /**
+   * @type {ol.Collection.<ol.Overlay>}
+   * @private
+   */
+  this.overlays_ = optionsInternal.overlays;
 
+  /**
+   * A lookup of overlays by id.
+   * @private
+   * @type {Object.<string, ol.Overlay>}
+   */
+  this.overlayIdIndex_ = {};
 
-/**
- * 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);
+  /**
+   * @type {ol.renderer.Map}
+   * @private
+   */
+  this.renderer_ = new /** @type {Function} */ (optionsInternal.rendererConstructor)(this.viewport_, this);
 
-  if (propertyName) {
-    element.style[propertyName] = value;
-  }
-};
+  /**
+   * @type {function(Event)|undefined}
+   * @private
+   */
+  this.handleResize_;
 
+  /**
+   * @private
+   * @type {ol.Coordinate}
+   */
+  this.focus_ = null;
 
-/**
- * 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_ = {};
+  /**
+   * @private
+   * @type {Array.<ol.PostRenderFunction>}
+   */
+  this.postRenderFunctions_ = [];
 
+  /**
+   * @private
+   * @type {ol.TileQueue}
+   */
+  this.tileQueue_ = new ol.TileQueue(
+      this.getTilePriority.bind(this),
+      this.handleTileChange_.bind(this));
 
-/**
- * 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;
+  /**
+   * Uids of features to skip at rendering time.
+   * @type {Object.<string, boolean>}
+   * @private
+   */
+  this.skippedFeatureUids_ = {};
 
-    if (element.style[camelStyle] === undefined) {
-      var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
-          goog.string.toTitleCase(camelStyle);
+  ol.events.listen(
+      this, ol.Object.getChangeEventType(ol.MapProperty.LAYERGROUP),
+      this.handleLayerGroupChanged_, this);
+  ol.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.VIEW),
+      this.handleViewChanged_, this);
+  ol.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.SIZE),
+      this.handleSizeChanged_, this);
+  ol.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.TARGET),
+      this.handleTargetChanged_, this);
 
-      if (element.style[prefixedStyle] !== undefined) {
-        propertyName = prefixedStyle;
-      }
-    }
-    goog.style.styleNameCache_[style] = propertyName;
-  }
+  // setProperties will trigger the rendering of the map if the map
+  // is "defined" already.
+  this.setProperties(optionsInternal.values);
 
-  return propertyName;
-};
+  this.controls_.forEach(
+      /**
+       * @param {ol.control.Control} control Control.
+       * @this {ol.Map}
+       */
+      function(control) {
+        control.setMap(this);
+      }, this);
 
+  ol.events.listen(this.controls_, ol.CollectionEventType.ADD,
+      /**
+       * @param {ol.Collection.Event} event Collection event.
+       */
+      function(event) {
+        event.element.setMap(this);
+      }, this);
 
-/**
- * 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);
+  ol.events.listen(this.controls_, ol.CollectionEventType.REMOVE,
+      /**
+       * @param {ol.Collection.Event} event Collection event.
+       */
+      function(event) {
+        event.element.setMap(null);
+      }, this);
 
-  if (element.style[camelStyle] === undefined) {
-    var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
-        goog.string.toTitleCase(camelStyle);
+  this.interactions_.forEach(
+      /**
+       * @param {ol.interaction.Interaction} interaction Interaction.
+       * @this {ol.Map}
+       */
+      function(interaction) {
+        interaction.setMap(this);
+      }, this);
 
-    if (element.style[prefixedStyle] !== undefined) {
-      return goog.dom.vendor.getVendorPrefix() + '-' + style;
-    }
-  }
+  ol.events.listen(this.interactions_, ol.CollectionEventType.ADD,
+      /**
+       * @param {ol.Collection.Event} event Collection event.
+       */
+      function(event) {
+        event.element.setMap(this);
+      }, this);
 
-  return style;
-};
+  ol.events.listen(this.interactions_, ol.CollectionEventType.REMOVE,
+      /**
+       * @param {ol.Collection.Event} event Collection event.
+       */
+      function(event) {
+        event.element.setMap(null);
+      }, this);
 
+  this.overlays_.forEach(this.addOverlayInternal_, this);
 
-/**
- * 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)];
+  ol.events.listen(this.overlays_, ol.CollectionEventType.ADD,
+      /**
+       * @param {ol.Collection.Event} event Collection event.
+       */
+      function(event) {
+        this.addOverlayInternal_(/** @type {ol.Overlay} */ (event.element));
+      }, this);
 
-  // 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;
-  }
+  ol.events.listen(this.overlays_, ol.CollectionEventType.REMOVE,
+      /**
+       * @param {ol.Collection.Event} event Collection event.
+       */
+      function(event) {
+        var overlay = /** @type {ol.Overlay} */ (event.element);
+        var id = overlay.getId();
+        if (id !== undefined) {
+          delete this.overlayIdIndex_[id.toString()];
+        }
+        event.element.setMap(null);
+      }, this);
 
-  return element.style[goog.style.getVendorJsStyleName_(element, property)] ||
-      '';
 };
+ol.inherits(ol.Map, ol.Object);
 
 
 /**
- * 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.
+ * Add the given control to the map.
+ * @param {ol.control.Control} control Control.
+ * @api
  */
-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 '';
+ol.Map.prototype.addControl = function(control) {
+  this.getControls().push(control);
 };
 
 
 /**
- * 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.
+ * Add the given interaction to the map.
+ * @param {ol.interaction.Interaction} interaction Interaction to add.
+ * @api
  */
-goog.style.getCascadedStyle = function(element, style) {
-  // TODO(nicksantos): This should be documented to return null. #fixTypes
-  return element.currentStyle ? element.currentStyle[style] : null;
+ol.Map.prototype.addInteraction = function(interaction) {
+  this.getInteractions().push(interaction);
 };
 
 
 /**
- * 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
+ * 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
  */
-goog.style.getStyle_ = function(element, style) {
-  return goog.style.getComputedStyle(element, style) ||
-         goog.style.getCascadedStyle(element, style) ||
-         (element.style && element.style[style]);
+ol.Map.prototype.addLayer = function(layer) {
+  var layers = this.getLayerGroup().getLayers();
+  layers.push(layer);
 };
 
 
 /**
- * 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).
+ * Add the given overlay to the map.
+ * @param {ol.Overlay} overlay Overlay.
+ * @api
  */
-goog.style.getComputedBoxSizing = function(element) {
-  return goog.style.getStyle_(element, 'boxSizing') ||
-      goog.style.getStyle_(element, 'MozBoxSizing') ||
-      goog.style.getStyle_(element, 'WebkitBoxSizing') || null;
+ol.Map.prototype.addOverlay = function(overlay) {
+  this.getOverlays().push(overlay);
 };
 
 
 /**
- * Retrieves the computed value of the position CSS attribute.
- * @param {Element} element The element to get the position of.
- * @return {string} Position value.
+ * This deals with map's overlay collection changes.
+ * @param {ol.Overlay} overlay Overlay.
+ * @private
  */
-goog.style.getComputedPosition = function(element) {
-  return goog.style.getStyle_(element, 'position');
+ol.Map.prototype.addOverlayInternal_ = function(overlay) {
+  var id = overlay.getId();
+  if (id !== undefined) {
+    this.overlayIdIndex_[id.toString()] = overlay;
+  }
+  overlay.setMap(this);
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.style.getBackgroundColor = function(element) {
-  return goog.style.getStyle_(element, 'backgroundColor');
+ol.Map.prototype.disposeInternal = function() {
+  this.mapBrowserEventHandler_.dispose();
+  this.renderer_.dispose();
+  ol.events.unlisten(this.viewport_, ol.events.EventType.WHEEL,
+      this.handleBrowserEvent, this);
+  ol.events.unlisten(this.viewport_, ol.events.EventType.MOUSEWHEEL,
+      this.handleBrowserEvent, this);
+  if (this.handleResize_ !== undefined) {
+    window.removeEventListener(ol.events.EventType.RESIZE,
+        this.handleResize_, false);
+    this.handleResize_ = undefined;
+  }
+  if (this.animationDelayKey_) {
+    cancelAnimationFrame(this.animationDelayKey_);
+    this.animationDelayKey_ = undefined;
+  }
+  this.setTarget(null);
+  ol.Object.prototype.disposeInternal.call(this);
 };
 
 
 /**
- * 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.
+ * 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 {olx.AtPixelOptions=} opt_options Optional options.
+ * @return {T|undefined} Callback result, i.e. the return value of last
+ * callback execution, or the first truthy callback return value.
+ * @template S,T
+ * @api
  */
-goog.style.getComputedOverflowX = function(element) {
-  return goog.style.getStyle_(element, 'overflowX');
+ol.Map.prototype.forEachFeatureAtPixel = function(pixel, callback, opt_options) {
+  if (!this.frameState_) {
+    return;
+  }
+  var coordinate = this.getCoordinateFromPixel(pixel);
+  opt_options = opt_options !== undefined ? opt_options : {};
+  var hitTolerance = opt_options.hitTolerance !== undefined ?
+    opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
+  var layerFilter = opt_options.layerFilter !== undefined ?
+    opt_options.layerFilter : ol.functions.TRUE;
+  return this.renderer_.forEachFeatureAtCoordinate(
+      coordinate, this.frameState_, hitTolerance, callback, null,
+      layerFilter, null);
 };
 
 
 /**
- * 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.
+ * 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, (Uint8ClampedArray|Uint8Array)): T} callback
+ *     Layer callback. This callback will receive two arguments: first is the
+ *     {@link ol.layer.Layer layer}, second argument is an array representing
+ *     [R, G, B, A] pixel values (0 - 255) and will be `null` for layer types
+ *     that do not currently support this argument. 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
  */
-goog.style.getComputedOverflowY = function(element) {
-  return goog.style.getStyle_(element, 'overflowY');
+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 : ol.functions.TRUE;
+  var thisArg2 = opt_this2 !== undefined ? opt_this2 : null;
+  return this.renderer_.forEachLayerAtPixel(
+      pixel, this.frameState_, callback, thisArg,
+      layerFilter, thisArg2);
 };
 
 
 /**
- * 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.
+ * 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 {olx.AtPixelOptions=} opt_options Optional options.
+ * @return {boolean} Is there a feature at the given pixel?
+ * @template U
+ * @api
  */
-goog.style.getComputedZIndex = function(element) {
-  return goog.style.getStyle_(element, 'zIndex');
+ol.Map.prototype.hasFeatureAtPixel = function(pixel, opt_options) {
+  if (!this.frameState_) {
+    return false;
+  }
+  var coordinate = this.getCoordinateFromPixel(pixel);
+  opt_options = opt_options !== undefined ? opt_options : {};
+  var layerFilter = opt_options.layerFilter !== undefined ?
+      opt_options.layerFilter : ol.functions.TRUE;
+  var hitTolerance = opt_options.hitTolerance !== undefined ?
+    opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
+  return this.renderer_.hasFeatureAtCoordinate(
+      coordinate, this.frameState_, hitTolerance, layerFilter, null);
 };
 
 
 /**
- * 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.
+ * Returns the coordinate in view projection for a browser event.
+ * @param {Event} event Event.
+ * @return {ol.Coordinate} Coordinate.
+ * @api
  */
-goog.style.getComputedTextAlign = function(element) {
-  return goog.style.getStyle_(element, 'textAlign');
+ol.Map.prototype.getEventCoordinate = function(event) {
+  return this.getCoordinateFromPixel(this.getEventPixel(event));
 };
 
 
 /**
- * Retrieves the computed value of the cursor CSS attribute.
- * @param {Element} element The element to get the cursor of.
- * @return {string} The computed string value of the cursor attribute.
+ * Returns the map pixel position for a browser event relative to the viewport.
+ * @param {Event} event Event.
+ * @return {ol.Pixel} Pixel.
+ * @api
  */
-goog.style.getComputedCursor = function(element) {
-  return goog.style.getStyle_(element, 'cursor');
+ol.Map.prototype.getEventPixel = function(event) {
+  var viewportPosition = this.viewport_.getBoundingClientRect();
+  var eventPosition = event.changedTouches ? event.changedTouches[0] : event;
+  return [
+    eventPosition.clientX - viewportPosition.left,
+    eventPosition.clientY - viewportPosition.top
+  ];
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.style.getComputedTransform = function(element) {
-  var property = goog.style.getVendorStyleName_(element, 'transform');
-  return goog.style.getStyle_(element, property) ||
-      goog.style.getStyle_(element, 'transform');
+ol.Map.prototype.getTarget = function() {
+  return /** @type {Element|string|undefined} */ (
+      this.get(ol.MapProperty.TARGET));
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.style.setPosition = function(el, arg1, opt_arg2) {
-  var x, y;
-
-  if (arg1 instanceof goog.math.Coordinate) {
-    x = arg1.x;
-    y = arg1.y;
+ol.Map.prototype.getTargetElement = function() {
+  var target = this.getTarget();
+  if (target !== undefined) {
+    return typeof target === 'string' ?
+      document.getElementById(target) :
+      target;
   } else {
-    x = arg1;
-    y = opt_arg2;
+    return null;
   }
-
-  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.
+ * 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
  */
-goog.style.getClientViewportElement = function(opt_node) {
-  var doc;
-  if (opt_node) {
-    doc = goog.dom.getOwnerDocument(opt_node);
+ol.Map.prototype.getCoordinateFromPixel = function(pixel) {
+  var frameState = this.frameState_;
+  if (!frameState) {
+    return null;
   } 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 ol.transform.apply(frameState.pixelToCoordinateTransform, pixel.slice());
   }
-  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.
+ * Get the map controls. Modifying this collection changes the controls
+ * associated with the map.
+ * @return {ol.Collection.<ol.control.Control>} Controls.
+ * @api
  */
-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);
+ol.Map.prototype.getControls = function() {
+  return this.controls_;
 };
 
 
 /**
- * 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
+ * Get the map overlays. Modifying this collection changes the overlays
+ * associated with the map.
+ * @return {ol.Collection.<ol.Overlay>} Overlays.
+ * @api
  */
-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;
+ol.Map.prototype.getOverlays = function() {
+  return this.overlays_;
 };
 
 
 /**
- * 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.
+ * 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
  */
-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;
+ol.Map.prototype.getOverlayById = function(id) {
+  var overlay = this.overlayIdIndex_[id.toString()];
+  return overlay !== undefined ? overlay : 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);
+ * 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
+ */
+ol.Map.prototype.getInteractions = function() {
+  return this.interactions_;
 };
 
 
 /**
- * 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.
+ * Get the layergroup associated with this map.
+ * @return {ol.layer.Group} A layer group containing the layers in this map.
+ * @observable
+ * @api
  */
-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;
+ol.Map.prototype.getLayerGroup = function() {
+  return /** @type {ol.layer.Group} */ (this.get(ol.MapProperty.LAYERGROUP));
 };
 
 
 /**
- * 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.
+ * Get the collection of layers associated with this map.
+ * @return {!ol.Collection.<ol.layer.Base>} Layers.
+ * @api
  */
-goog.style.getClientLeftTop = function(el) {
-  return new goog.math.Coordinate(el.clientLeft, el.clientTop);
+ol.Map.prototype.getLayers = function() {
+  var layers = this.getLayerGroup().getLayers();
+  return layers;
 };
 
 
 /**
- * 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.
+ * 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
  */
-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;
+ol.Map.prototype.getPixelFromCoordinate = function(coordinate) {
+  var frameState = this.frameState_;
+  if (!frameState) {
+    return null;
+  } else {
+    return ol.transform.apply(frameState.coordinateToPixelTransform,
+        coordinate.slice(0, 2));
   }
-
-  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.
+ * Get the map renderer.
+ * @return {ol.renderer.Map} Renderer
  */
-goog.style.getPageOffsetLeft = function(el) {
-  return goog.style.getPageOffset(el).x;
+ol.Map.prototype.getRenderer = function() {
+  return this.renderer_;
 };
 
 
 /**
- * Returns the top coordinate of an element relative to the HTML document
- * @param {Element} el Elements.
- * @return {number} The top coordinate.
+ * Get the size of this map.
+ * @return {ol.Size|undefined} The size in pixels of the map in the DOM.
+ * @observable
+ * @api
  */
-goog.style.getPageOffsetTop = function(el) {
-  return goog.style.getPageOffset(el).y;
+ol.Map.prototype.getSize = function() {
+  return /** @type {ol.Size|undefined} */ (this.get(ol.MapProperty.SIZE));
 };
 
 
 /**
- * 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.
+ * 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
  */
-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;
+ol.Map.prototype.getView = function() {
+  return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW));
 };
 
 
 /**
- * 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.
+ * Get the element that serves as the map viewport.
+ * @return {Element} Viewport.
+ * @api
  */
-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;
-  }
+ol.Map.prototype.getViewport = function() {
+  return this.viewport_;
 };
 
 
 /**
- * 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.
+ * 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.
  */
-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);
+ol.Map.prototype.getOverlayContainer = function() {
+  return this.overlayContainer_;
 };
 
 
 /**
- * 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
+ * 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.
  */
-goog.style.getClientPositionForElement_ = function(el) {
-  var box = goog.style.getBoundingClientRect_(el);
-  return new goog.math.Coordinate(box.left, box.top);
+ol.Map.prototype.getOverlayContainerStopEvent = function() {
+  return this.overlayContainerStopEvent_;
 };
 
 
 /**
- * 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.
+ * @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.
  */
-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);
+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;
+  }
+  if (!frameState.wantedTiles[tileSourceKey][tile.getKey()]) {
+    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;
 };
 
 
 /**
- * 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.
+ * @param {Event} browserEvent Browser event.
+ * @param {string=} opt_type Type.
  */
-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);
+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);
 };
 
 
 /**
- * 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.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle.
  */
-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;
+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;
   }
-
-  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';
+  this.focus_ = mapBrowserEvent.coordinate;
+  mapBrowserEvent.frameState = this.frameState_;
+  var interactionsArray = this.getInteractions().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;
+      }
+    }
   }
-
-  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.
+ * @protected
  */
-goog.style.setHeight = function(element, height) {
-  element.style.height = goog.style.getPixelStyleValue_(height, true);
-};
+ol.Map.prototype.handlePostRender = function() {
 
+  var frameState = this.frameState_;
 
-/**
- * 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);
+  // 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;
+    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;
+      }
+    }
+    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;
 };
 
 
 /**
- * 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.
+ * @private
  */
-goog.style.getSize = function(element) {
-  return goog.style.evaluateWithTemporaryDisplay_(
-      goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element));
+ol.Map.prototype.handleSizeChanged_ = function() {
+  this.render();
 };
 
 
 /**
- * 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);
+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;
+  if (this.getTarget()) {
+    targetElement = this.getTargetElement();
   }
 
-  var style = element.style;
-  var originalDisplay = style.display;
-  var originalVisibility = style.visibility;
-  var originalPosition = style.position;
+  if (this.keyHandlerKeys_) {
+    for (var i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
+      ol.events.unlistenByKey(this.keyHandlerKeys_[i]);
+    }
+    this.keyHandlerKeys_ = null;
+  }
 
-  style.visibility = 'hidden';
-  style.position = 'absolute';
-  style.display = 'inline';
+  if (!targetElement) {
+    ol.dom.removeNode(this.viewport_);
+    if (this.handleResize_ !== undefined) {
+      window.removeEventListener(ol.events.EventType.RESIZE,
+          this.handleResize_, false);
+      this.handleResize_ = undefined;
+    }
+  } else {
+    targetElement.appendChild(this.viewport_);
 
-  var retVal = fn(element);
+    var keyboardEventTarget = !this.keyboardEventTarget_ ?
+        targetElement : this.keyboardEventTarget_;
+    this.keyHandlerKeys_ = [
+      ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYDOWN,
+          this.handleBrowserEvent, this),
+      ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYPRESS,
+          this.handleBrowserEvent, this)
+    ];
 
-  style.display = originalDisplay;
-  style.position = originalPosition;
-  style.visibility = originalVisibility;
+    if (!this.handleResize_) {
+      this.handleResize_ = this.updateSize.bind(this);
+      window.addEventListener(ol.events.EventType.RESIZE,
+          this.handleResize_, false);
+    }
+  }
 
-  return retVal;
+  this.updateSize();
+  // updateSize calls setSize, so no need to call this.render
+  // ourselves here.
 };
 
 
 /**
- * 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);
+ol.Map.prototype.handleTileChange_ = function() {
+  this.render();
 };
 
 
 /**
- * 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.
+ * @private
  */
-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);
+ol.Map.prototype.handleViewPropertyChanged_ = function() {
+  this.render();
 };
 
 
 /**
- * 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.
+ * @private
  */
-goog.style.toCamelCase = function(selector) {
-  return goog.string.toCamelCase(String(selector));
+ol.Map.prototype.handleViewChanged_ = function() {
+  if (this.viewPropertyListenerKey_) {
+    ol.events.unlistenByKey(this.viewPropertyListenerKey_);
+    this.viewPropertyListenerKey_ = null;
+  }
+  if (this.viewChangeListenerKey_) {
+    ol.events.unlistenByKey(this.viewChangeListenerKey_);
+    this.viewChangeListenerKey_ = null;
+  }
+  var view = this.getView();
+  if (view) {
+    this.viewport_.setAttribute('data-view', ol.getUid(view));
+    this.viewPropertyListenerKey_ = ol.events.listen(
+        view, ol.ObjectEventType.PROPERTYCHANGE,
+        this.handleViewPropertyChanged_, this);
+    this.viewChangeListenerKey_ = ol.events.listen(
+        view, ol.events.EventType.CHANGE,
+        this.handleViewPropertyChanged_, this);
+  }
+  this.render();
 };
 
 
 /**
- * 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.
+ * @private
  */
-goog.style.toSelectorCase = function(selector) {
-  return goog.string.toSelectorCase(selector);
+ol.Map.prototype.handleLayerGroupChanged_ = function() {
+  if (this.layerGroupPropertyListenerKeys_) {
+    this.layerGroupPropertyListenerKeys_.forEach(ol.events.unlistenByKey);
+    this.layerGroupPropertyListenerKeys_ = null;
+  }
+  var layerGroup = this.getLayerGroup();
+  if (layerGroup) {
+    this.layerGroupPropertyListenerKeys_ = [
+      ol.events.listen(
+          layerGroup, ol.ObjectEventType.PROPERTYCHANGE,
+          this.render, this),
+      ol.events.listen(
+          layerGroup, ol.events.EventType.CHANGE,
+          this.render, this)
+    ];
+  }
+  this.render();
 };
 
 
 /**
- * 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.
+ * @return {boolean} Is rendered.
  */
-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);
+ol.Map.prototype.isRendered = function() {
+  return !!this.frameState_;
 };
 
 
 /**
- * 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.
+ * Requests an immediate render in a synchronous manner.
+ * @api
  */
-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 + ')';
-    }
+ol.Map.prototype.renderSync = function() {
+  if (this.animationDelayKey_) {
+    cancelAnimationFrame(this.animationDelayKey_);
   }
+  this.animationDelay_();
 };
 
 
 /**
- * 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.
+ * Request a map rendering (at the next animation frame).
+ * @api
  */
-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';
+ol.Map.prototype.render = function() {
+  if (this.animationDelayKey_ === undefined) {
+    this.animationDelayKey_ = requestAnimationFrame(
+        this.animationDelay_);
   }
 };
 
 
 /**
- * Clears the background image of an element in a browser independent manner.
- * @param {Element} el The element to clear background image for.
+ * 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
  */
-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';
-  }
+ol.Map.prototype.removeControl = function(control) {
+  return this.getControls().remove(control);
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.style.showElement = function(el, display) {
-  goog.style.setElementShown(el, display);
+ol.Map.prototype.removeInteraction = function(interaction) {
+  return this.getInteractions().remove(interaction);
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.style.setElementShown = function(el, isShown) {
-  el.style.display = isShown ? '' : 'none';
+ol.Map.prototype.removeLayer = function(layer) {
+  var layers = this.getLayerGroup().getLayers();
+  return layers.remove(layer);
 };
 
 
 /**
- * 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
+ * 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
  */
-goog.style.isElementShown = function(el) {
-  return el.style.display != 'none';
+ol.Map.prototype.removeOverlay = function(overlay) {
+  return this.getOverlays().remove(overlay);
 };
 
 
 /**
- * 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.
+ * @param {number} time Time.
+ * @private
  */
-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];
+ol.Map.prototype.renderFrame_ = function(time) {
+  var i, ii, viewState;
 
-    // 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);
+  var size = this.getSize();
+  var view = this.getView();
+  var extent = ol.extent.createEmpty();
+  var previousFrameState = this.frameState_;
+  /** @type {?olx.FrameState} */
+  var frameState = null;
+  if (size !== undefined && ol.size.hasArea(size) && view && view.isDef()) {
+    var viewHints = view.getHints(this.frameState_ ? this.frameState_.viewHints : undefined);
+    var layerStatesArray = this.getLayerGroup().getLayerStatesArray();
+    var layerStates = {};
+    for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
+      layerStates[ol.getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
     }
-    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);
+    viewState = view.getState();
+    frameState = /** @type {olx.FrameState} */ ({
+      animate: false,
+      attributions: {},
+      coordinateToPixelTransform: this.coordinateToPixelTransform_,
+      extent: extent,
+      focus: !this.focus_ ? viewState.center : this.focus_,
+      index: this.frameIndex_++,
+      layerStates: layerStates,
+      layerStatesArray: layerStatesArray,
+      logos: ol.obj.assign({}, this.logos_),
+      pixelRatio: this.pixelRatio_,
+      pixelToCoordinateTransform: this.pixelToCoordinateTransform_,
+      postRenderFunctions: [],
+      size: size,
+      skippedFeatureUids: this.skippedFeatureUids_,
+      tileQueue: this.tileQueue_,
+      time: time,
+      usedTiles: {},
+      viewState: viewState,
+      viewHints: viewHints,
+      wantedTiles: {}
+    });
   }
-  return styleSheet;
-};
 
+  if (frameState) {
+    frameState.extent = ol.extent.getForViewAndSize(viewState.center,
+        viewState.resolution, viewState.rotation, frameState.size, extent);
+  }
 
-/**
- * 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);
-};
+  this.frameState_ = frameState;
+  this.renderer_.renderFrame(frameState);
 
+  if (frameState) {
+    if (frameState.animate) {
+      this.render();
+    }
+    Array.prototype.push.apply(
+        this.postRenderFunctions_, frameState.postRenderFunctions);
 
-/**
- * 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;
-  }
-};
+    if (previousFrameState) {
+      var moveStart = !this.previousExtent_ ||
+          (!ol.extent.isEmpty(this.previousExtent_) &&
+          !ol.extent.equals(frameState.extent, this.previousExtent_));
+      if (moveStart) {
+        this.dispatchEvent(
+            new ol.MapEvent(ol.MapEventType.MOVESTART, this, previousFrameState));
+        this.previousExtent_ = ol.extent.createOrUpdateEmpty(this.previousExtent_);
+      }
+    }
 
+    var idle = this.previousExtent_ &&
+        !frameState.viewHints[ol.ViewHint.ANIMATING] &&
+        !frameState.viewHints[ol.ViewHint.INTERACTING] &&
+        !ol.extent.equals(frameState.extent, this.previousExtent_);
 
-/**
- * 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';
+    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));
 
-/**
- * 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';
+  setTimeout(this.handlePostRender.bind(this), 0);
 
-  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.
+ * Sets the layergroup of this map.
+ * @param {ol.layer.Group} layerGroup A layer group containing the layers in
+ *     this map.
+ * @observable
+ * @api
  */
-goog.style.isRightToLeft = function(el) {
-  return 'rtl' == goog.style.getStyle_(el, 'direction');
+ol.Map.prototype.setLayerGroup = function(layerGroup) {
+  this.set(ol.MapProperty.LAYERGROUP, layerGroup);
 };
 
 
 /**
- * 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.
+ * Set the size of this map.
+ * @param {ol.Size|undefined} size The size in pixels of the map in the DOM.
+ * @observable
+ * @api
  */
-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);
-      }
-    }
-  }
+ol.Map.prototype.setSize = function(size) {
+  this.set(ol.MapProperty.SIZE, size);
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.style.getBorderBoxSize = function(element) {
-  return new goog.math.Size(
-      /** @type {!HTMLElement} */ (element).offsetWidth,
-      /** @type {!HTMLElement} */ (element).offsetHeight);
+ol.Map.prototype.setTarget = function(target) {
+  this.set(ol.MapProperty.TARGET, target);
 };
 
 
 /**
- * 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.
+ * Set the view for this map.
+ * @param {ol.View} view The view that controls this map.
+ * @observable
+ * @api
  */
-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');
-  }
+ol.Map.prototype.setView = function(view) {
+  this.set(ol.MapProperty.VIEW, view);
 };
 
 
 /**
- * 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.
+ * @param {ol.Feature} feature Feature.
  */
-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');
-  }
+ol.Map.prototype.skipFeature = function(feature) {
+  var featureUid = ol.getUid(feature).toString();
+  this.skippedFeatureUids_[featureUid] = true;
+  this.render();
 };
 
 
 /**
- * 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
+ * Force a recalculation of the map viewport size.  This should be called when
+ * third-party code changes the size of the map viewport.
+ * @api
  */
-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';
-};
-
+ol.Map.prototype.updateSize = function() {
+  var targetElement = this.getTargetElement();
 
-/**
- * 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);
+  if (!targetElement) {
+    this.setSize(undefined);
   } 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;
+    var computedStyle = getComputedStyle(targetElement);
+    this.setSize([
+      targetElement.offsetWidth -
+          parseFloat(computedStyle['borderLeftWidth']) -
+          parseFloat(computedStyle['paddingLeft']) -
+          parseFloat(computedStyle['paddingRight']) -
+          parseFloat(computedStyle['borderRightWidth']),
+      targetElement.offsetHeight -
+          parseFloat(computedStyle['borderTopWidth']) -
+          parseFloat(computedStyle['paddingTop']) -
+          parseFloat(computedStyle['paddingBottom']) -
+          parseFloat(computedStyle['borderBottomWidth'])
+    ]);
   }
 };
 
 
 /**
- * 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
+ * @param {ol.Feature} feature Feature.
  */
-goog.style.getIePixelDistance_ = function(element, propName) {
-  var value = goog.style.getCascadedStyle(element, propName);
-  return value ?
-      goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : 0;
+ol.Map.prototype.unskipFeature = function(feature) {
+  var featureUid = ol.getUid(feature).toString();
+  delete this.skippedFeatureUids_[featureUid];
+  this.render();
 };
 
 
 /**
- * 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
+ * @param {olx.MapOptions} options Map options.
+ * @return {ol.MapOptionsInternal} Internal map options.
  */
-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');
+ol.Map.createOptionsInternal = function(options) {
 
-    // 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));
+  /**
+   * @type {Element|Document}
+   */
+  var keyboardEventTarget = null;
+  if (options.keyboardEventTarget !== undefined) {
+    keyboardEventTarget = typeof options.keyboardEventTarget === 'string' ?
+        document.getElementById(options.keyboardEventTarget) :
+        options.keyboardEventTarget;
   }
-};
-
 
-/**
- * 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');
-};
+  /**
+   * @type {Object.<string, *>}
+   */
+  var values = {};
 
+  var logos = {};
+  if (options.logo === undefined ||
+      (typeof options.logo === 'boolean' && options.logo)) {
+    logos[ol.OL_LOGO_URL] = ol.OL_URL;
+  } else {
+    var logo = options.logo;
+    if (typeof logo === 'string') {
+      logos[logo] = '';
+    } else if (logo instanceof HTMLElement) {
+      logos[ol.getUid(logo).toString()] = logo;
+    } else if (logo) {
+      ol.asserts.assert(typeof logo.href == 'string', 44); // `logo.href` should be a string.
+      ol.asserts.assert(typeof logo.src == 'string', 45); // `logo.src` should be a string.
+      logos[logo.src] = logo.href;
+    }
+  }
 
-/**
- * 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');
-};
+  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;
 
-/**
- * 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
-};
+  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;
 
-/**
- * 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];
+  /**
+   * @type {Array.<ol.renderer.Type>}
+   */
+  var rendererTypes;
+  if (options.renderer !== undefined) {
+    if (Array.isArray(options.renderer)) {
+      rendererTypes = options.renderer;
+    } else if (typeof options.renderer === 'string') {
+      rendererTypes = [options.renderer];
+    } else {
+      ol.asserts.assert(false, 46); // Incorrect format for `renderer` option
+    }
+    if (rendererTypes.indexOf(/** @type {ol.renderer.Type} */ ('dom')) >= 0) {
+      rendererTypes = rendererTypes.concat(ol.DEFAULT_RENDERER_TYPES);
+    }
+  } else {
+    rendererTypes = ol.DEFAULT_RENDERER_TYPES;
   }
-  return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft');
-};
 
+  var i, ii;
+  for (i = 0, ii = rendererTypes.length; i < ii; ++i) {
+    /** @type {ol.renderer.Type} */
+    var rendererType = rendererTypes[i];
+    if (ol.ENABLE_CANVAS && rendererType == ol.renderer.Type.CANVAS) {
+      if (ol.has.CANVAS) {
+        rendererConstructor = ol.renderer.canvas.Map;
+        break;
+      }
+    } else if (ol.ENABLE_WEBGL && rendererType == ol.renderer.Type.WEBGL) {
+      if (ol.has.WEBGL) {
+        rendererConstructor = ol.renderer.webgl.Map;
+        break;
+      }
+    }
+  }
 
-/**
- * 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);
+  var controls;
+  if (options.controls !== undefined) {
+    if (Array.isArray(options.controls)) {
+      controls = new ol.Collection(options.controls.slice());
+    } else {
+      ol.asserts.assert(options.controls instanceof ol.Collection,
+          47); // Expected `controls` to be an array or an `ol.Collection`
+      controls = options.controls;
+    }
   } 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));
+    controls = ol.control.defaults();
   }
-};
-
 
-/**
- * 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 = '';
+  var interactions;
+  if (options.interactions !== undefined) {
+    if (Array.isArray(options.interactions)) {
+      interactions = new ol.Collection(options.interactions.slice());
+    } else {
+      ol.asserts.assert(options.interactions instanceof ol.Collection,
+          48); // Expected `interactions` to be an array or an `ol.Collection`
+      interactions = options.interactions;
     }
+  } else {
+    interactions = ol.interaction.defaults();
   }
-  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');
+
+  var overlays;
+  if (options.overlays !== undefined) {
+    if (Array.isArray(options.overlays)) {
+      overlays = new ol.Collection(options.overlays.slice());
+    } else {
+      ol.asserts.assert(options.overlays instanceof ol.Collection,
+          49); // Expected `overlays` to be an array or an `ol.Collection`
+      overlays = options.overlays;
+    }
+  } else {
+    overlays = new ol.Collection();
   }
 
-  // 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];
+  return {
+    controls: controls,
+    interactions: interactions,
+    keyboardEventTarget: keyboardEventTarget,
+    logos: logos,
+    overlays: overlays,
+    rendererConstructor: rendererConstructor,
+    values: values
+  };
 
-  // Sanitize for x-browser consistency:
-  // Strip quotes because browsers aren't consistent with how they're
-  // applied; Opera always encloses, Firefox sometimes, and IE never.
-  return goog.string.stripQuotes(font, '"\'');
 };
 
+goog.provide('ol.OverlayPositioning');
 
 /**
- * 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.
+ * Overlay position: `'bottom-left'`, `'bottom-center'`,  `'bottom-right'`,
+ * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`,
+ * `'top-center'`, `'top-right'`
+ * @enum {string}
  */
-goog.style.getLengthUnits = function(value) {
-  var units = value.match(goog.style.lengthUnitRegex_);
-  return units && units[0] || null;
+ol.OverlayPositioning = {
+  BOTTOM_LEFT: 'bottom-left',
+  BOTTOM_CENTER: 'bottom-center',
+  BOTTOM_RIGHT: 'bottom-right',
+  CENTER_LEFT: 'center-left',
+  CENTER_CENTER: 'center-center',
+  CENTER_RIGHT: 'center-right',
+  TOP_LEFT: 'top-left',
+  TOP_CENTER: 'top-center',
+  TOP_RIGHT: 'top-right'
 };
 
+goog.provide('ol.Overlay');
 
-/**
- * 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
-};
+goog.require('ol');
+goog.require('ol.MapEventType');
+goog.require('ol.Object');
+goog.require('ol.OverlayPositioning');
+goog.require('ol.css');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.extent');
 
 
 /**
- * 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
+ * @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
  */
-goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = {
-  'em' : 1,
-  'ex' : 1
-};
+ol.Overlay = function(options) {
 
-
-/**
- * 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 = {
+  ol.Object.call(this);
 
   /**
-   * Triggered after a map frame is rendered.
-   * @event ol.MapEvent#postrender
-   * @api
+   * @private
+   * @type {number|string|undefined}
    */
-  POSTRENDER: 'postrender',
+  this.id_ = options.id;
 
   /**
-   * Triggered after the map is moved.
-   * @event ol.MapEvent#moveend
-   * @api stable
+   * @private
+   * @type {boolean}
    */
-  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);
+  this.insertFirst_ = options.insertFirst !== undefined ?
+      options.insertFirst : true;
 
   /**
-   * The map where the event occurred.
-   * @type {ol.Map}
-   * @api stable
+   * @private
+   * @type {boolean}
    */
-  this.map = map;
+  this.stopEvent_ = options.stopEvent !== undefined ? options.stopEvent : true;
 
   /**
-   * The frame state at the time of the event.
-   * @type {?olx.FrameState}
-   * @api
+   * @private
+   * @type {Element}
    */
-  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);
+  this.element_ = document.createElement('DIV');
+  this.element_.className = 'ol-overlay-container ' + ol.css.CLASS_SELECTABLE;
+  this.element_.style.position = 'absolute';
 
   /**
    * @protected
-   * @type {Element}
+   * @type {boolean}
    */
-  this.element = options.element ? options.element : null;
+  this.autoPan = options.autoPan !== undefined ? options.autoPan : false;
 
   /**
    * @private
-   * @type {Element}
+   * @type {olx.OverlayPanOptions}
    */
-  this.target_ = null;
+  this.autoPanAnimation_ = options.autoPanAnimation ||
+      /** @type {olx.OverlayPanOptions} */ ({});
 
   /**
    * @private
-   * @type {ol.Map}
+   * @type {number}
    */
-  this.map_ = null;
+  this.autoPanMargin_ = options.autoPanMargin !== undefined ?
+      options.autoPanMargin : 20;
 
   /**
-   * @protected
-   * @type {!Array.<?number>}
+   * @private
+   * @type {{bottom_: string,
+   *         left_: string,
+   *         right_: string,
+   *         top_: string,
+   *         visible: boolean}}
    */
-  this.listenerKeys = [];
+  this.rendered_ = {
+    bottom_: '',
+    left_: '',
+    right_: '',
+    top_: '',
+    visible: true
+  };
 
   /**
-   * @type {function(ol.MapEvent)}
+   * @private
+   * @type {?ol.EventsKey}
    */
-  this.render = options.render ? options.render : ol.nullFunction;
+  this.mapPostrenderListenerKey_ = null;
 
-  if (options.target) {
-    this.setTarget(options.target);
+  ol.events.listen(
+      this, ol.Object.getChangeEventType(ol.Overlay.Property_.ELEMENT),
+      this.handleElementChanged, this);
+
+  ol.events.listen(
+      this, ol.Object.getChangeEventType(ol.Overlay.Property_.MAP),
+      this.handleMapChanged, this);
+
+  ol.events.listen(
+      this, ol.Object.getChangeEventType(ol.Overlay.Property_.OFFSET),
+      this.handleOffsetChanged, this);
+
+  ol.events.listen(
+      this, ol.Object.getChangeEventType(ol.Overlay.Property_.POSITION),
+      this.handlePositionChanged, this);
+
+  ol.events.listen(
+      this, ol.Object.getChangeEventType(ol.Overlay.Property_.POSITIONING),
+      this.handlePositioningChanged, 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.control.Control, ol.Object);
+ol.inherits(ol.Overlay, ol.Object);
 
 
 /**
- * @inheritDoc
+ * Get the DOM element of this overlay.
+ * @return {Element|undefined} The Element containing the overlay.
+ * @observable
+ * @api
  */
-ol.control.Control.prototype.disposeInternal = function() {
-  goog.dom.removeNode(this.element);
-  goog.base(this, 'disposeInternal');
+ol.Overlay.prototype.getElement = function() {
+  return /** @type {Element|undefined} */ (
+      this.get(ol.Overlay.Property_.ELEMENT));
 };
 
 
 /**
- * Get the map associated with this control.
- * @return {ol.Map} Map.
- * @api stable
+ * Get the overlay identifier which is set on constructor.
+ * @return {number|string|undefined} Id.
+ * @api
  */
-ol.control.Control.prototype.getMap = function() {
-  return this.map_;
+ol.Overlay.prototype.getId = function() {
+  return this.id_;
 };
 
 
 /**
- * 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
+ * Get the map associated with this overlay.
+ * @return {ol.Map|undefined} The map that the overlay is part of.
+ * @observable
+ * @api
  */
-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();
-  }
+ol.Overlay.prototype.getMap = function() {
+  return /** @type {ol.Map|undefined} */ (
+      this.get(ol.Overlay.Property_.MAP));
 };
 
 
 /**
- * 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.
+ * Get the offset of this overlay.
+ * @return {Array.<number>} The offset.
+ * @observable
  * @api
  */
-ol.control.Control.prototype.setTarget = function(target) {
-  this.target_ = goog.dom.getElement(target);
+ol.Overlay.prototype.getOffset = function() {
+  return /** @type {Array.<number>} */ (
+      this.get(ol.Overlay.Property_.OFFSET));
 };
 
-goog.provide('ol.css');
-
 
 /**
- * The CSS class for hidden feature.
- *
- * @const
- * @type {string}
+ * Get the current position of this overlay.
+ * @return {ol.Coordinate|undefined} The spatial point that the overlay is
+ *     anchored at.
+ * @observable
+ * @api
  */
-ol.css.CLASS_HIDDEN = 'ol-hidden';
+ol.Overlay.prototype.getPosition = function() {
+  return /** @type {ol.Coordinate|undefined} */ (
+      this.get(ol.Overlay.Property_.POSITION));
+};
 
 
 /**
- * The CSS class that we'll give the DOM elements to have them unselectable.
- *
- * @const
- * @type {string}
+ * Get the current positioning of this overlay.
+ * @return {ol.OverlayPositioning} How the overlay is positioned
+ *     relative to its point on the map.
+ * @observable
+ * @api
  */
-ol.css.CLASS_UNSELECTABLE = 'ol-unselectable';
+ol.Overlay.prototype.getPositioning = function() {
+  return /** @type {ol.OverlayPositioning} */ (
+      this.get(ol.Overlay.Property_.POSITIONING));
+};
 
 
 /**
- * The CSS class for unsupported feature.
- *
- * @const
- * @type {string}
+ * @protected
  */
-ol.css.CLASS_UNSUPPORTED = 'ol-unsupported';
+ol.Overlay.prototype.handleElementChanged = function() {
+  ol.dom.removeChildren(this.element_);
+  var element = this.getElement();
+  if (element) {
+    this.element_.appendChild(element);
+  }
+};
 
 
 /**
- * The CSS class for controls.
- *
- * @const
- * @type {string}
+ * @protected
  */
-ol.css.CLASS_CONTROL = 'ol-control';
-
-goog.provide('ol.structs.LRUCache');
-
-goog.require('goog.asserts');
-goog.require('goog.object');
-
+ol.Overlay.prototype.handleMapChanged = function() {
+  if (this.mapPostrenderListenerKey_) {
+    ol.dom.removeNode(this.element_);
+    ol.events.unlistenByKey(this.mapPostrenderListenerKey_);
+    this.mapPostrenderListenerKey_ = null;
+  }
+  var map = this.getMap();
+  if (map) {
+    this.mapPostrenderListenerKey_ = ol.events.listen(map,
+        ol.MapEventType.POSTRENDER, this.render, this);
+    this.updatePixelPosition();
+    var container = this.stopEvent_ ?
+        map.getOverlayContainerStopEvent() : map.getOverlayContainer();
+    if (this.insertFirst_) {
+      container.insertBefore(this.element_, container.childNodes[0] || null);
+    } else {
+      container.appendChild(this.element_);
+    }
+  }
+};
 
 
 /**
- * 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
+ * @protected
  */
-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;
+ol.Overlay.prototype.render = function() {
+  this.updatePixelPosition();
+};
 
-  /**
-   * @private
-   * @type {?ol.structs.LRUCacheEntry}
-   */
-  this.newest_ = null;
 
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handleOffsetChanged = function() {
+  this.updatePixelPosition();
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * @protected
  */
-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');
+ol.Overlay.prototype.handlePositionChanged = function() {
+  this.updatePixelPosition();
+  if (this.get(ol.Overlay.Property_.POSITION) && this.autoPan) {
+    this.panIntoView_();
   }
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * @protected
  */
-ol.structs.LRUCache.prototype.clear = function() {
-  this.count_ = 0;
-  this.entries_ = {};
-  this.oldest_ = null;
-  this.newest_ = null;
+ol.Overlay.prototype.handlePositioningChanged = function() {
+  this.updatePixelPosition();
 };
 
 
 /**
- * @param {string} key Key.
- * @return {boolean} Contains key.
+ * Set the DOM element to be associated with this overlay.
+ * @param {Element|undefined} element The Element containing the overlay.
+ * @observable
+ * @api
  */
-ol.structs.LRUCache.prototype.containsKey = function(key) {
-  return this.entries_.hasOwnProperty(key);
+ol.Overlay.prototype.setElement = function(element) {
+  this.set(ol.Overlay.Property_.ELEMENT, element);
 };
 
 
 /**
- * @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
+ * Set the map to be associated with this overlay.
+ * @param {ol.Map|undefined} map The map that the overlay is part of.
+ * @observable
+ * @api
  */
-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;
-  }
+ol.Overlay.prototype.setMap = function(map) {
+  this.set(ol.Overlay.Property_.MAP, map);
 };
 
 
 /**
- * @param {string} key Key.
- * @return {T} Value.
+ * Set the offset for this overlay.
+ * @param {Array.<number>} offset Offset.
+ * @observable
+ * @api
  */
-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_;
+ol.Overlay.prototype.setOffset = function(offset) {
+  this.set(ol.Overlay.Property_.OFFSET, offset);
 };
 
 
 /**
- * @return {number} Count.
+ * 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
  */
-ol.structs.LRUCache.prototype.getCount = function() {
-  return this.count_;
+ol.Overlay.prototype.setPosition = function(position) {
+  this.set(ol.Overlay.Property_.POSITION, position);
 };
 
 
 /**
- * @return {Array.<string>} Keys.
+ * Pan the map so that the overlay is entirely visible in the current viewport
+ * (if necessary).
+ * @private
  */
-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_;
+ol.Overlay.prototype.panIntoView_ = function() {
+  var map = this.getMap();
+
+  if (!map || !map.getTargetElement()) {
+    return;
   }
-  goog.asserts.assert(i == this.count_, 'iterated correct number of times');
-  return keys;
-};
 
+  var mapRect = this.getRect_(map.getTargetElement(), map.getSize());
+  var element = /** @type {!Element} */ (this.getElement());
+  var overlayRect = this.getRect_(element,
+      [ol.dom.outerWidth(element), ol.dom.outerHeight(element)]);
 
-/**
- * @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_;
+  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 = /** @type {ol.Coordinate} */ (map.getView().getCenter());
+      var centerPx = map.getPixelFromCoordinate(center);
+      var newCenterPx = [
+        centerPx[0] + delta[0],
+        centerPx[1] + delta[1]
+      ];
+
+      map.getView().animate({
+        center: map.getCoordinateFromPixel(newCenterPx),
+        duration: this.autoPanAnimation_.duration,
+        easing: this.autoPanAnimation_.easing
+      });
+    }
   }
-  goog.asserts.assert(i == this.count_, 'iterated correct number of times');
-  return values;
 };
 
 
 /**
- * @return {T} Last value.
+ * 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} The extent.
+ * @private
  */
-ol.structs.LRUCache.prototype.peekLast = function() {
-  goog.asserts.assert(this.oldest_, 'oldest must not be null');
-  return this.oldest_.value_;
+ol.Overlay.prototype.getRect_ = function(element, size) {
+  var box = element.getBoundingClientRect();
+  var offsetX = box.left + window.pageXOffset;
+  var offsetY = box.top + window.pageYOffset;
+  return [
+    offsetX,
+    offsetY,
+    offsetX + size[0],
+    offsetY + size[1]
+  ];
 };
 
 
 /**
- * @return {string} Last key.
+ * Set the positioning for this overlay.
+ * @param {ol.OverlayPositioning} positioning how the overlay is
+ *     positioned relative to its point on the map.
+ * @observable
+ * @api
  */
-ol.structs.LRUCache.prototype.peekLastKey = function() {
-  goog.asserts.assert(this.oldest_, 'oldest must not be null');
-  return this.oldest_.key_;
+ol.Overlay.prototype.setPositioning = function(positioning) {
+  this.set(ol.Overlay.Property_.POSITIONING, positioning);
 };
 
 
 /**
- * @return {T} value Value.
+ * Modify the visibility of the element.
+ * @param {boolean} visible Element visibility.
+ * @protected
  */
-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;
+ol.Overlay.prototype.setVisible = function(visible) {
+  if (this.rendered_.visible !== visible) {
+    this.element_.style.display = visible ? '' : 'none';
+    this.rendered_.visible = visible;
   }
-  --this.count_;
-  return entry.value_;
 };
 
 
 /**
- * @param {string} key Key.
- * @param {T} value Value.
+ * Update pixel position.
+ * @protected
  */
-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;
+ol.Overlay.prototype.updatePixelPosition = function() {
+  var map = this.getMap();
+  var position = this.getPosition();
+  if (!map || !map.isRendered() || !position) {
+    this.setVisible(false);
+    return;
   }
-  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;
 
+  var pixel = map.getPixelFromCoordinate(position);
+  var mapSize = map.getSize();
+  this.updateRenderedPosition(pixel, mapSize);
 };
-goog.inherits(ol.TileCache, ol.structs.LRUCache);
 
 
 /**
- * @return {boolean} Can expire cache.
+ * @param {ol.Pixel} pixel The pixel location.
+ * @param {ol.Size|undefined} mapSize The map size.
+ * @protected
  */
-ol.TileCache.prototype.canExpireCache = function() {
-  return this.getCount() > this.highWaterMark_;
-};
+ol.Overlay.prototype.updateRenderedPosition = function(pixel, mapSize) {
+  var style = this.element_.style;
+  var offset = this.getOffset();
 
+  var positioning = this.getPositioning();
 
-/**
- * @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();
+  this.setVisible(true);
+
+  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 -= this.element_.offsetWidth / 2;
+    }
+    var left = Math.round(pixel[0] + offsetX) + 'px';
+    if (this.rendered_.left_ != left) {
+      this.rendered_.left_ = style.left = left;
     }
   }
-};
-
-
-/**
- * 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);
+  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 -= this.element_.offsetHeight / 2;
+    }
+    var top = Math.round(pixel[1] + offsetY) + 'px';
+    if (this.rendered_.top_ != top) {
+      this.rendered_.top_ = style.top = top;
     }
   }
 };
 
-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}
+ * @enum {string}
+ * @private
  */
-ol.TileState = {
-  IDLE: 0,
-  LOADING: 1,
-  LOADED: 2,
-  ERROR: 3,
-  EMPTY: 4
+ol.Overlay.Property_ = {
+  ELEMENT: 'element',
+  MAP: 'map',
+  OFFSET: 'offset',
+  POSITION: 'position',
+  POSITIONING: 'positioning'
 };
 
+goog.provide('ol.control.OverviewMap');
+
+goog.require('ol');
+goog.require('ol.Collection');
+goog.require('ol.Map');
+goog.require('ol.MapEventType');
+goog.require('ol.MapProperty');
+goog.require('ol.Object');
+goog.require('ol.ObjectEventType');
+goog.require('ol.Overlay');
+goog.require('ol.OverlayPositioning');
+goog.require('ol.ViewProperty');
+goog.require('ol.control.Control');
+goog.require('ol.coordinate');
+goog.require('ol.css');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
 
 
 /**
- * @classdesc
- * Base class for tiles.
- *
+ * Create a new control with a map acting as an overview map for an other
+ * defined map.
  * @constructor
- * @extends {goog.events.EventTarget}
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.TileState} state State.
+ * @extends {ol.control.Control}
+ * @param {olx.control.OverviewMapOptions=} opt_options OverviewMap options.
+ * @api
  */
-ol.Tile = function(tileCoord, state) {
+ol.control.OverviewMap = function(opt_options) {
 
-  goog.base(this);
+  var options = opt_options ? opt_options : {};
 
   /**
-   * @type {ol.TileCoord}
+   * @type {boolean}
+   * @private
    */
-  this.tileCoord = tileCoord;
+  this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;
 
   /**
-   * @protected
-   * @type {ol.TileState}
+   * @private
+   * @type {boolean}
    */
-  this.state = state;
+  this.collapsible_ = options.collapsible !== undefined ?
+      options.collapsible : true;
 
-};
-goog.inherits(ol.Tile, goog.events.EventTarget);
+  if (!this.collapsible_) {
+    this.collapsed_ = false;
+  }
 
+  var className = options.className !== undefined ? options.className : 'ol-overviewmap';
 
-/**
- * @protected
- */
-ol.Tile.prototype.changed = function() {
-  this.dispatchEvent(goog.events.EventType.CHANGE);
-};
+  var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Overview map';
 
+  var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00AB';
 
-/**
- * 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;
+  if (typeof collapseLabel === 'string') {
+    /**
+     * @private
+     * @type {Node}
+     */
+    this.collapseLabel_ = document.createElement('span');
+    this.collapseLabel_.textContent = collapseLabel;
+  } else {
+    this.collapseLabel_ = collapseLabel;
+  }
 
+  var label = options.label !== undefined ? options.label : '\u00BB';
 
-/**
- * @return {string} Key.
- */
-ol.Tile.prototype.getKey = function() {
-  return goog.getUid(this).toString();
-};
 
+  if (typeof label === 'string') {
+    /**
+     * @private
+     * @type {Node}
+     */
+    this.label_ = document.createElement('span');
+    this.label_.textContent = label;
+  } else {
+    this.label_ = label;
+  }
 
-/**
- * Get the tile coordinate for this tile.
- * @return {ol.TileCoord}
- * @api
- */
-ol.Tile.prototype.getTileCoord = function() {
-  return this.tileCoord;
-};
+  var activeLabel = (this.collapsible_ && !this.collapsed_) ?
+      this.collapseLabel_ : this.label_;
+  var button = document.createElement('button');
+  button.setAttribute('type', 'button');
+  button.title = tipLabel;
+  button.appendChild(activeLabel);
 
+  ol.events.listen(button, ol.events.EventType.CLICK,
+      this.handleClick_, this);
 
-/**
- * @return {ol.TileState} State.
- */
-ol.Tile.prototype.getState = function() {
-  return this.state;
-};
+  /**
+   * @type {Element}
+   * @private
+   */
+  this.ovmapDiv_ = document.createElement('DIV');
+  this.ovmapDiv_.className = 'ol-overviewmap-map';
 
+  /**
+   * @type {ol.Map}
+   * @private
+   */
+  this.ovmap_ = new ol.Map({
+    controls: new ol.Collection(),
+    interactions: new ol.Collection(),
+    view: options.view
+  });
+  var ovmap = this.ovmap_;
 
-/**
- * FIXME empty description for jsdoc
- */
-ol.Tile.prototype.load = goog.abstractMethod;
+  if (options.layers) {
+    options.layers.forEach(
+        /**
+       * @param {ol.layer.Layer} layer Layer.
+       */
+        function(layer) {
+          ovmap.addLayer(layer);
+        }, this);
+  }
 
-goog.provide('ol.source.Source');
-goog.provide('ol.source.State');
+  var box = document.createElement('DIV');
+  box.className = 'ol-overviewmap-box';
+  box.style.boxSizing = 'border-box';
 
-goog.require('ol');
-goog.require('ol.Attribution');
-goog.require('ol.Object');
-goog.require('ol.proj');
+  /**
+   * @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 = document.createElement('div');
+  element.className = cssClasses;
+  element.appendChild(this.ovmapDiv_);
+  element.appendChild(button);
 
-/**
- * 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'
-};
+  var render = options.render ? options.render : ol.control.OverviewMap.render;
 
+  ol.control.Control.call(this, {
+    element: element,
+    render: render,
+    target: options.target
+  });
 
-/**
- * @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;
+  /* Interactive map */
 
+  var scope = this;
 
+  var overlay = this.boxOverlay_;
+  var overlayBox = this.boxOverlay_.getElement();
 
-/**
- * @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) {
+  /* Functions definition */
+
+  var computeDesiredMousePosition = function(mousePosition) {
+    return {
+      clientX: mousePosition.clientX - (overlayBox.offsetWidth / 2),
+      clientY: mousePosition.clientY + (overlayBox.offsetHeight / 2)
+    };
+  };
 
-  goog.base(this);
+  var move = function(event) {
+    var coordinates = ovmap.getEventCoordinate(computeDesiredMousePosition(event));
 
-  /**
-   * @private
-   * @type {ol.proj.Projection}
-   */
-  this.projection_ = ol.proj.get(options.projection);
+    overlay.setPosition(coordinates);
+  };
 
-  /**
-   * @private
-   * @type {Array.<ol.Attribution>}
-   */
-  this.attributions_ = options.attributions !== undefined ?
-      options.attributions : null;
+  var endMoving = function(event) {
+    var coordinates = ovmap.getEventCoordinate(event);
 
-  /**
-   * @private
-   * @type {string|olx.LogoOptions|undefined}
-   */
-  this.logo_ = options.logo;
+    scope.getMap().getView().setCenter(coordinates);
 
-  /**
-   * @private
-   * @type {ol.source.State}
-   */
-  this.state_ = options.state !== undefined ?
-      options.state : ol.source.State.READY;
+    window.removeEventListener('mousemove', move);
+    window.removeEventListener('mouseup', endMoving);
+  };
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.wrapX_ = options.wrapX !== undefined ? options.wrapX : false;
+  /* Binding */
 
+  overlayBox.addEventListener('mousedown', function() {
+    window.addEventListener('mousemove', move);
+    window.addEventListener('mouseup', endMoving);
+  });
 };
-goog.inherits(ol.source.Source, ol.Object);
+ol.inherits(ol.control.OverviewMap, ol.control.Control);
 
 
 /**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} resolution Resolution.
- * @param {number} rotation Rotation.
- * @param {Object.<string, boolean>} skippedFeatureUids Skipped feature uids.
- * @param {function(ol.Feature): T} callback Feature callback.
- * @return {T|undefined} Callback result.
- * @template T
+ * @inheritDoc
+ * @api
  */
-ol.source.Source.prototype.forEachFeatureAtCoordinate = ol.nullFunction;
-
+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);
+    }
+    this.ovmap_.setTarget(null);
+  }
+  ol.control.Control.prototype.setMap.call(this, map);
 
-/**
- * Get the attributions of the source.
- * @return {Array.<ol.Attribution>} Attributions.
- * @api stable
- */
-ol.source.Source.prototype.getAttributions = function() {
-  return this.attributions_;
-};
+  if (map) {
+    this.ovmap_.setTarget(this.ovmapDiv_);
+    this.listenerKeys.push(ol.events.listen(
+        map, ol.ObjectEventType.PROPERTYCHANGE,
+        this.handleMapPropertyChange_, this));
 
+    // TODO: to really support map switching, this would need to be reworked
+    if (this.ovmap_.getLayers().getLength() === 0) {
+      this.ovmap_.setLayerGroup(map.getLayerGroup());
+    }
 
-/**
- * Get the logo of the source.
- * @return {string|olx.LogoOptions|undefined} Logo.
- * @api stable
- */
-ol.source.Source.prototype.getLogo = function() {
-  return this.logo_;
+    var view = map.getView();
+    if (view) {
+      this.bindView_(view);
+      if (view.isDef()) {
+        this.ovmap_.updateSize();
+        this.resetExtent_();
+      }
+    }
+  }
 };
 
 
 /**
- * Get the projection of the source.
- * @return {ol.proj.Projection} Projection.
- * @api
+ * Handle map property changes.  This only deals with changes to the map's view.
+ * @param {ol.Object.Event} event The propertychange event.
+ * @private
  */
-ol.source.Source.prototype.getProjection = function() {
-  return this.projection_;
+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);
+  }
 };
 
 
 /**
- * @return {Array.<number>|undefined} Resolutions.
+ * Register listeners for view property changes.
+ * @param {ol.View} view The view.
+ * @private
  */
-ol.source.Source.prototype.getResolutions = goog.abstractMethod;
+ol.control.OverviewMap.prototype.bindView_ = function(view) {
+  ol.events.listen(view,
+      ol.Object.getChangeEventType(ol.ViewProperty.ROTATION),
+      this.handleRotationChanged_, this);
+};
 
 
 /**
- * Get the state of the source, see {@link ol.source.State} for possible states.
- * @return {ol.source.State} State.
- * @api
+ * Unregister listeners for view property changes.
+ * @param {ol.View} view The view.
+ * @private
  */
-ol.source.Source.prototype.getState = function() {
-  return this.state_;
+ol.control.OverviewMap.prototype.unbindView_ = function(view) {
+  ol.events.unlisten(view,
+      ol.Object.getChangeEventType(ol.ViewProperty.ROTATION),
+      this.handleRotationChanged_, this);
 };
 
 
 /**
- * @return {boolean|undefined} Wrap X.
+ * Handle rotation changes to the main map.
+ * TODO: This should rotate the extent rectrangle instead of the
+ * overview map's view.
+ * @private
  */
-ol.source.Source.prototype.getWrapX = function() {
-  return this.wrapX_;
+ol.control.OverviewMap.prototype.handleRotationChanged_ = function() {
+  this.ovmap_.getView().setRotation(this.getMap().getView().getRotation());
 };
 
 
 /**
- * Set the attributions of the source.
- * @param {Array.<ol.Attribution>} attributions Attributions.
+ * Update the overview map element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.OverviewMap}
  * @api
  */
-ol.source.Source.prototype.setAttributions = function(attributions) {
-  this.attributions_ = attributions;
-  this.changed();
+ol.control.OverviewMap.render = function(mapEvent) {
+  this.validateExtent_();
+  this.updateBox_();
 };
 
 
 /**
- * Set the logo of the source.
- * @param {string|olx.LogoOptions|undefined} logo Logo.
+ * 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.source.Source.prototype.setLogo = function(logo) {
-  this.logo_ = logo;
-};
+ol.control.OverviewMap.prototype.validateExtent_ = function() {
+  var map = this.getMap();
+  var ovmap = this.ovmap_;
 
+  if (!map.isRendered() || !ovmap.isRendered()) {
+    return;
+  }
 
-/**
- * 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();
-};
+  var mapSize = /** @type {ol.Size} */ (map.getSize());
 
+  var view = map.getView();
+  var extent = view.calculateExtent(mapSize);
 
-/**
- * Set the projection of the source.
- * @param {ol.proj.Projection} projection Projection.
- */
-ol.source.Source.prototype.setProjection = function(projection) {
-  this.projection_ = projection;
-};
+  var ovmapSize = /** @type {ol.Size} */ (ovmap.getSize());
 
-goog.provide('ol.tilegrid.TileGrid');
+  var ovview = ovmap.getView();
+  var ovextent = ovview.calculateExtent(ovmapSize);
 
-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');
+  var topLeftPixel =
+      ovmap.getPixelFromCoordinate(ol.extent.getTopLeft(extent));
+  var bottomRightPixel =
+      ovmap.getPixelFromCoordinate(ol.extent.getBottomRight(extent));
 
+  var boxWidth = Math.abs(topLeftPixel[0] - bottomRightPixel[0]);
+  var boxHeight = Math.abs(topLeftPixel[1] - bottomRightPixel[1]);
 
+  var ovmapWidth = ovmapSize[0];
+  var ovmapHeight = ovmapSize[1];
 
-/**
- * @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
+  if (boxWidth < ovmapWidth * ol.OVERVIEWMAP_MIN_RATIO ||
+      boxHeight < ovmapHeight * ol.OVERVIEWMAP_MIN_RATIO ||
+      boxWidth > ovmapWidth * ol.OVERVIEWMAP_MAX_RATIO ||
+      boxHeight > 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.tilegrid.TileGrid = function(options) {
+ol.control.OverviewMap.prototype.resetExtent_ = function() {
+  if (ol.OVERVIEWMAP_MAX_RATIO === 0 || ol.OVERVIEWMAP_MIN_RATIO === 0) {
+    return;
+  }
 
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.minZoom = options.minZoom !== undefined ? options.minZoom : 0;
+  var map = this.getMap();
+  var ovmap = this.ovmap_;
 
-  /**
-   * @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');
+  var mapSize = /** @type {ol.Size} */ (map.getSize());
 
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.maxZoom = this.resolutions_.length - 1;
+  var view = map.getView();
+  var extent = view.calculateExtent(mapSize);
 
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.origin_ = options.origin !== undefined ? options.origin : null;
+  var ovview = ovmap.getView();
 
-  /**
-   * @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');
-  }
+  // 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);
+};
 
-  var extent = options.extent;
 
-  if (extent !== undefined &&
-      !this.origin_ && !this.origins_) {
-    this.origin_ = ol.extent.getTopLeft(extent);
-  }
+/**
+ * Set the center of the overview map to the map center without changing its
+ * resolution.
+ * @private
+ */
+ol.control.OverviewMap.prototype.recenter_ = function() {
+  var map = this.getMap();
+  var ovmap = this.ovmap_;
 
-  goog.asserts.assert(
-      (!this.origin_ && this.origins_) ||
-      (this.origin_ && !this.origins_),
-      'either origin or origins must be configured, never both');
+  var view = map.getView();
 
-  /**
-   * @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');
+  var ovview = ovmap.getView();
+
+  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;
   }
 
-  /**
-   * @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');
+  var mapSize = /** @type {ol.Size} */ (map.getSize());
 
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.extent_ = extent !== undefined ? extent : null;
+  var view = map.getView();
 
+  var ovview = ovmap.getView();
 
-  /**
-   * @private
-   * @type {Array.<ol.TileRange>}
-   */
-  this.fullTileRanges_ = null;
+  var rotation = view.getRotation();
 
-  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);
-  }
+  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);
 
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.tmpSize_ = [0, 0];
+  // 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) {
+    box.style.width = Math.abs((bottomLeft[0] - topRight[0]) / ovresolution) + 'px';
+    box.style.height = Math.abs((topRight[1] - bottomLeft[1]) / ovresolution) + 'px';
+  }
 };
 
 
 /**
+ * @param {number} rotation Target rotation.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {ol.Coordinate|undefined} Coordinate for rotation and center anchor.
  * @private
- * @type {ol.TileCoord}
  */
-ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0];
+ol.control.OverviewMap.prototype.calculateCoordinateRotate_ = function(
+    rotation, coordinate) {
+  var coordinateRotate;
 
+  var map = this.getMap();
+  var view = map.getView();
 
-/**
- * @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;
+  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 false;
+  return coordinateRotate;
 };
 
 
 /**
- * Get the extent for this tile grid, if it was configured.
- * @return {ol.Extent} Extent.
+ * @param {Event} event The event to handle
+ * @private
  */
-ol.tilegrid.TileGrid.prototype.getExtent = function() {
-  return this.extent_;
+ol.control.OverviewMap.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  this.handleToggle_();
 };
 
 
 /**
- * Get the maximum zoom level for the grid.
- * @return {number} Max zoom.
- * @api
+ * @private
  */
-ol.tilegrid.TileGrid.prototype.getMaxZoom = function() {
-  return this.maxZoom;
+ol.control.OverviewMap.prototype.handleToggle_ = function() {
+  this.element.classList.toggle('ol-collapsed');
+  if (this.collapsed_) {
+    ol.dom.replaceNode(this.collapseLabel_, this.label_);
+  } else {
+    ol.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_();
+    ol.events.listenOnce(ovmap, ol.MapEventType.POSTRENDER,
+        function(event) {
+          this.updateBox_();
+        },
+        this);
+  }
 };
 
 
 /**
- * Get the minimum zoom level for the grid.
- * @return {number} Min zoom.
+ * Return `true` if the overview map is collapsible, `false` otherwise.
+ * @return {boolean} True if the widget is collapsible.
  * @api
  */
-ol.tilegrid.TileGrid.prototype.getMinZoom = function() {
-  return this.minZoom;
+ol.control.OverviewMap.prototype.getCollapsible = function() {
+  return this.collapsible_;
 };
 
 
 /**
- * Get the origin for the grid at the given zoom level.
- * @param {number} z Z.
- * @return {ol.Coordinate} Origin.
- * @api stable
+ * Set whether the overview map should be collapsible.
+ * @param {boolean} collapsible True if the widget is collapsible.
+ * @api
  */
-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];
+ol.control.OverviewMap.prototype.setCollapsible = function(collapsible) {
+  if (this.collapsible_ === collapsible) {
+    return;
+  }
+  this.collapsible_ = collapsible;
+  this.element.classList.toggle('ol-uncollapsible');
+  if (!collapsible && this.collapsed_) {
+    this.handleToggle_();
   }
 };
 
 
 /**
- * Get the resolution for the given zoom level.
- * @param {number} z Z.
- * @return {number} Resolution.
- * @api stable
+ * 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
  */
-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];
+ol.control.OverviewMap.prototype.setCollapsed = function(collapsed) {
+  if (!this.collapsible_ || this.collapsed_ === collapsed) {
+    return;
+  }
+  this.handleToggle_();
 };
 
 
 /**
- * Get the list of resolutions for the tile grid.
- * @return {Array.<number>} Resolutions.
- * @api stable
+ * Determine if the overview map is collapsed.
+ * @return {boolean} The overview map is collapsed.
+ * @api
  */
-ol.tilegrid.TileGrid.prototype.getResolutions = function() {
-  return this.resolutions_;
+ol.control.OverviewMap.prototype.getCollapsed = function() {
+  return this.collapsed_;
 };
 
 
 /**
- * @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.
+ * Return the overview map.
+ * @return {ol.Map} Overview map.
+ * @api
  */
-ol.tilegrid.TileGrid.prototype.getTileCoordChildTileRange =
-    function(tileCoord, opt_tileRange, opt_extent) {
-  if (tileCoord[0] < this.maxZoom) {
-    var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent);
-    return this.getTileRangeForExtentAndZ(
-        tileCoordExtent, tileCoord[0] + 1, opt_tileRange);
-  } else {
-    return null;
-  }
+ol.control.OverviewMap.prototype.getOverviewMap = function() {
+  return this.ovmap_;
 };
 
+goog.provide('ol.control.ScaleLineUnits');
 
 /**
- * @param {number} z Z.
- * @param {ol.TileRange} tileRange Tile range.
- * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
- * @return {ol.Extent} Extent.
+ * Units for the scale line. Supported values are `'degrees'`, `'imperial'`,
+ * `'nautical'`, `'metric'`, `'us'`.
+ * @enum {string}
  */
-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);
+ol.control.ScaleLineUnits = {
+  DEGREES: 'degrees',
+  IMPERIAL: 'imperial',
+  NAUTICAL: 'nautical',
+  METRIC: 'metric',
+  US: 'us'
 };
 
+goog.provide('ol.control.ScaleLine');
 
-/**
- * @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);
-};
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.asserts');
+goog.require('ol.control.Control');
+goog.require('ol.control.ScaleLineUnits');
+goog.require('ol.css');
+goog.require('ol.events');
+goog.require('ol.proj');
+goog.require('ol.proj.Units');
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {number} z Z.
- * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
- * @return {ol.TileRange} Tile range.
+ * @classdesc
+ * A control displaying rough y-axis distances, calculated for the center of the
+ * viewport. For conformal projections (e.g. EPSG:3857, the default view
+ * projection in OpenLayers), the scale is valid for all directions.
+ * No scale line will be shown when the y-axis distance of a pixel at the
+ * viewport center cannot be calculated in the view projection.
+ * 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
  */
-ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ =
-    function(extent, z, opt_tileRange) {
-  var resolution = this.getResolution(z);
-  return this.getTileRangeForExtentAndResolution(
-      extent, resolution, opt_tileRange);
-};
+ol.control.ScaleLine = function(opt_options) {
 
+  var options = opt_options ? opt_options : {};
+
+  var className = options.className !== undefined ? options.className : 'ol-scale-line';
+
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.innerElement_ = document.createElement('DIV');
+  this.innerElement_.className = className + '-inner';
+
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.element_ = document.createElement('DIV');
+  this.element_.className = className + ' ' + ol.css.CLASS_UNSELECTABLE;
+  this.element_.appendChild(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_ = '';
+
+  var render = options.render ? options.render : ol.control.ScaleLine.render;
+
+  ol.control.Control.call(this, {
+    element: this.element_,
+    render: render,
+    target: options.target
+  });
+
+  ol.events.listen(
+      this, ol.Object.getChangeEventType(ol.control.ScaleLine.Property_.UNITS),
+      this.handleUnitsChanged_, this);
+
+  this.setUnits(/** @type {ol.control.ScaleLineUnits} */ (options.units) ||
+      ol.control.ScaleLineUnits.METRIC);
 
-/**
- * @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
-  ];
 };
+ol.inherits(ol.control.ScaleLine, ol.control.Control);
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.Extent=} opt_extent Temporary extent object.
- * @return {ol.Extent} Extent.
+ * @const
+ * @type {Array.<number>}
  */
-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);
-};
+ol.control.ScaleLine.LEADING_DIGITS = [1, 2, 5];
 
 
 /**
- * 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.
+ * Return the units to use in the scale line.
+ * @return {ol.control.ScaleLineUnits|undefined} The units to use in the scale
+ *     line.
+ * @observable
  * @api
  */
-ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution =
-    function(coordinate, resolution, opt_tileCoord) {
-  return this.getTileCoordForXYAndResolution_(
-      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
+ol.control.ScaleLine.prototype.getUnits = function() {
+  return /** @type {ol.control.ScaleLineUnits|undefined} */ (
+      this.get(ol.control.ScaleLine.Property_.UNITS));
 };
 
 
 /**
- * @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
+ * Update the scale line element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.ScaleLine}
+ * @api
  */
-ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function(
-    x, y, resolution, reverseIntersectionPolicy, opt_tileCoord) {
-  var z = this.getZForResolution(resolution);
-  var scale = resolution / this.getResolution(z);
-  var origin = this.getOrigin(z);
-  var tileSize = 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;
+ol.control.ScaleLine.render = function(mapEvent) {
+  var frameState = mapEvent.frameState;
+  if (!frameState) {
+    this.viewState_ = null;
   } else {
-    tileCoordX = Math.floor(tileCoordX);
-    tileCoordY = Math.floor(tileCoordY);
+    this.viewState_ = frameState.viewState;
   }
-
-  return ol.tilecoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord);
+  this.updateElement_();
 };
 
 
 /**
- * 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
+ * @private
  */
-ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ =
-    function(coordinate, z, opt_tileCoord) {
-  var resolution = this.getResolution(z);
-  return this.getTileCoordForXYAndResolution_(
-      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
+ol.control.ScaleLine.prototype.handleUnitsChanged_ = function() {
+  this.updateElement_();
 };
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @return {number} Tile resolution.
+ * Set the units to use in the scale line.
+ * @param {ol.control.ScaleLineUnits} units The units to use in the scale line.
+ * @observable
+ * @api
  */
-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]];
+ol.control.ScaleLine.prototype.setUnits = function(units) {
+  this.set(ol.control.ScaleLine.Property_.UNITS, units);
 };
 
 
 /**
- * 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
+ * @private
  */
-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];
-  }
-};
-
+ol.control.ScaleLine.prototype.updateElement_ = function() {
+  var viewState = this.viewState_;
 
-/**
- * @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];
+  if (!viewState) {
+    if (this.renderedVisible_) {
+      this.element_.style.display = 'none';
+      this.renderedVisible_ = false;
+    }
+    return;
   }
-};
-
-
-/**
- * @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);
-};
 
+  var center = viewState.center;
+  var projection = viewState.projection;
+  var metersPerUnit = projection.getMetersPerUnit();
+  var pointResolution =
+      ol.proj.getPointResolution(projection, viewState.resolution, center) *
+      metersPerUnit;
 
-/**
- * @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);
+  var nominalCount = this.minWidth_ * pointResolution;
+  var suffix = '';
+  var units = this.getUnits();
+  if (units == ol.control.ScaleLineUnits.DEGREES) {
+    var metersPerDegree = ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES];
+    pointResolution /= metersPerDegree;
+    if (nominalCount < metersPerDegree / 60) {
+      suffix = '\u2033'; // seconds
+      pointResolution *= 3600;
+    } else if (nominalCount < metersPerDegree) {
+      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 < 0.001) {
+      suffix = 'μm';
+      pointResolution *= 1000000;
+    } else 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 {
+    ol.asserts.assert(false, 33); // Invalid units
   }
-  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);
+  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) + 3) % 3] *
+        Math.pow(10, Math.floor(i / 3));
+    width = Math.round(count / pointResolution);
+    if (isNaN(width)) {
+      this.element_.style.display = 'none';
+      this.renderedVisible_ = false;
+      return;
+    } else if (width >= this.minWidth_) {
+      break;
+    }
+    ++i;
   }
-  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();
+  var html = count + ' ' + suffix;
+  if (this.renderedHTML_ != html) {
+    this.innerElement_.innerHTML = html;
+    this.renderedHTML_ = html;
   }
-  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);
+  if (this.renderedWidth_ != width) {
+    this.innerElement_.style.width = width + 'px';
+    this.renderedWidth_ = width;
   }
-  return resolutions;
-};
 
+  if (!this.renderedVisible_) {
+    this.element_.style.display = '';
+    this.renderedVisible_ = true;
+  }
 
-/**
- * @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.
+ * @enum {string}
+ * @private
  */
-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;
+ol.control.ScaleLine.Property_ = {
+  UNITS: 'units'
 };
 
-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.size');
-goog.require('ol.source.Source');
-goog.require('ol.tilecoord');
-goog.require('ol.tilegrid.TileGrid');
-
+// FIXME should possibly show tooltip when dragging?
 
-/**
- * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
- *            extent: (ol.Extent|undefined),
- *            logo: (string|olx.LogoOptions|undefined),
- *            opaque: (boolean|undefined),
- *            tilePixelRatio: (number|undefined),
- *            projection: ol.proj.ProjectionLike,
- *            state: (ol.source.State|undefined),
- *            tileGrid: (ol.tilegrid.TileGrid|undefined),
- *            wrapX: (boolean|undefined)}}
- */
-ol.source.TileOptions;
+goog.provide('ol.control.ZoomSlider');
 
+goog.require('ol');
+goog.require('ol.ViewHint');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.easing');
+goog.require('ol.events');
+goog.require('ol.events.Event');
+goog.require('ol.events.EventType');
+goog.require('ol.math');
+goog.require('ol.pointer.EventType');
+goog.require('ol.pointer.PointerEventHandler');
 
 
 /**
  * @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.
+ * A slider type of control for zooming.
+ *
+ * Example:
+ *
+ *     map.addControl(new ol.control.ZoomSlider());
  *
  * @constructor
- * @extends {ol.source.Source}
- * @param {ol.source.TileOptions} options Tile source options.
+ * @extends {ol.control.Control}
+ * @param {olx.control.ZoomSliderOptions=} opt_options Zoom slider options.
  * @api
  */
-ol.source.Tile = function(options) {
+ol.control.ZoomSlider = function(opt_options) {
 
-  goog.base(this, {
-    attributions: options.attributions,
-    extent: options.extent,
-    logo: options.logo,
-    projection: options.projection,
-    state: options.state,
-    wrapX: options.wrapX
-  });
+  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;
+
+  /**
    * @type {boolean}
+   * @private
    */
-  this.opaque_ = options.opaque !== undefined ? options.opaque : false;
+  this.dragging_;
 
   /**
+   * @type {number}
    * @private
+   */
+  this.heightLimit_ = 0;
+
+  /**
    * @type {number}
+   * @private
    */
-  this.tilePixelRatio_ = options.tilePixelRatio !== undefined ?
-      options.tilePixelRatio : 1;
+  this.widthLimit_ = 0;
 
   /**
-   * @protected
-   * @type {ol.tilegrid.TileGrid}
+   * @type {number|undefined}
+   * @private
    */
-  this.tileGrid = options.tileGrid !== undefined ? options.tileGrid : null;
+  this.previousX_;
 
   /**
-   * @protected
-   * @type {ol.TileCache}
+   * @type {number|undefined}
+   * @private
    */
-  this.tileCache = new ol.TileCache();
+  this.previousY_;
 
   /**
-   * @protected
+   * The calculated thumb size (border box plus margins).  Set when initSlider_
+   * is called.
    * @type {ol.Size}
+   * @private
    */
-  this.tmpSize = [0, 0];
+  this.thumbSize_ = null;
 
-};
-goog.inherits(ol.source.Tile, ol.source.Source);
+  /**
+   * Whether the slider is initialized.
+   * @type {boolean}
+   * @private
+   */
+  this.sliderInitialized_ = false;
+
+  /**
+   * @type {number}
+   * @private
+   */
+  this.duration_ = options.duration !== undefined ? options.duration : 200;
+
+  var className = options.className !== undefined ? options.className : 'ol-zoomslider';
+  var thumbElement = document.createElement('button');
+  thumbElement.setAttribute('type', 'button');
+  thumbElement.className = className + '-thumb ' + ol.css.CLASS_UNSELECTABLE;
+  var containerElement = document.createElement('div');
+  containerElement.className = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + ol.css.CLASS_CONTROL;
+  containerElement.appendChild(thumbElement);
+  /**
+   * @type {ol.pointer.PointerEventHandler}
+   * @private
+   */
+  this.dragger_ = new ol.pointer.PointerEventHandler(containerElement);
 
+  ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERDOWN,
+      this.handleDraggerStart_, this);
+  ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERMOVE,
+      this.handleDraggerDrag_, this);
+  ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERUP,
+      this.handleDraggerEnd_, this);
 
-/**
- * @return {boolean} Can expire cache.
- */
-ol.source.Tile.prototype.canExpireCache = function() {
-  return this.tileCache.canExpireCache();
+  ol.events.listen(containerElement, ol.events.EventType.CLICK,
+      this.handleContainerClick_, this);
+  ol.events.listen(thumbElement, ol.events.EventType.CLICK,
+      ol.events.Event.stopPropagation);
+
+  var render = options.render ? options.render : ol.control.ZoomSlider.render;
+
+  ol.control.Control.call(this, {
+    element: containerElement,
+    render: render
+  });
 };
+ol.inherits(ol.control.ZoomSlider, ol.control.Control);
 
 
 /**
- * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
+ * @inheritDoc
  */
-ol.source.Tile.prototype.expireCache = function(usedTiles) {
-  this.tileCache.expireCache(usedTiles);
+ol.control.ZoomSlider.prototype.disposeInternal = function() {
+  this.dragger_.dispose();
+  ol.control.Control.prototype.disposeInternal.call(this);
 };
 
 
 /**
- * @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.
+ * The enum for available directions.
+ *
+ * @enum {number}
+ * @private
  */
-ol.source.Tile.prototype.forEachLoadedTile = function(z, tileRange, callback) {
-  var covered = true;
-  var tile, tileCoordKey, loaded;
-  for (var x = tileRange.minX; x <= tileRange.maxX; ++x) {
-    for (var y = tileRange.minY; y <= tileRange.maxY; ++y) {
-      tileCoordKey = this.getKeyZXY(z, x, y);
-      loaded = false;
-      if (this.tileCache.containsKey(tileCoordKey)) {
-        tile = /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
-        loaded = tile.getState() === ol.TileState.LOADED;
-        if (loaded) {
-          loaded = (callback(tile) !== false);
-        }
-      }
-      if (!loaded) {
-        covered = false;
-      }
-    }
-  }
-  return covered;
+ol.control.ZoomSlider.Direction_ = {
+  VERTICAL: 0,
+  HORIZONTAL: 1
 };
 
 
 /**
- * @return {number} Gutter.
+ * @inheritDoc
  */
-ol.source.Tile.prototype.getGutter = function() {
-  return 0;
+ol.control.ZoomSlider.prototype.setMap = function(map) {
+  ol.control.Control.prototype.setMap.call(this, map);
+  if (map) {
+    map.render();
+  }
 };
 
 
 /**
- * @param {number} z Z.
- * @param {number} x X.
- * @param {number} y Y.
- * @return {string} Key.
- * @protected
+ * 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.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY;
+ol.control.ZoomSlider.prototype.initSlider_ = function() {
+  var container = this.element;
+  var containerSize = {
+    width: container.offsetWidth, height: container.offsetHeight
+  };
 
+  var thumb = container.firstElementChild;
+  var computedStyle = getComputedStyle(thumb);
+  var thumbWidth = thumb.offsetWidth +
+      parseFloat(computedStyle['marginRight']) +
+      parseFloat(computedStyle['marginLeft']);
+  var thumbHeight = thumb.offsetHeight +
+      parseFloat(computedStyle['marginTop']) +
+      parseFloat(computedStyle['marginBottom']);
+  this.thumbSize_ = [thumbWidth, thumbHeight];
 
-/**
- * @return {boolean} Opaque.
- */
-ol.source.Tile.prototype.getOpaque = function() {
-  return this.opaque_;
+  if (containerSize.width > containerSize.height) {
+    this.direction_ = ol.control.ZoomSlider.Direction_.HORIZONTAL;
+    this.widthLimit_ = containerSize.width - thumbWidth;
+  } else {
+    this.direction_ = ol.control.ZoomSlider.Direction_.VERTICAL;
+    this.heightLimit_ = containerSize.height - thumbHeight;
+  }
+  this.sliderInitialized_ = true;
 };
 
 
 /**
- * @inheritDoc
+ * Update the zoomslider element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.ZoomSlider}
+ * @api
  */
-ol.source.Tile.prototype.getResolutions = function() {
-  return this.tileGrid.getResolutions();
+ol.control.ZoomSlider.render = function(mapEvent) {
+  if (!mapEvent.frameState) {
+    return;
+  }
+  if (!this.sliderInitialized_) {
+    this.initSlider_();
+  }
+  var res = mapEvent.frameState.viewState.resolution;
+  if (res !== this.currentResolution_) {
+    this.currentResolution_ = res;
+    this.setThumbPosition_(res);
+  }
 };
 
 
 /**
- * @param {number} z Tile coordinate z.
- * @param {number} x Tile coordinate x.
- * @param {number} y Tile coordinate y.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection=} opt_projection Projection.
- * @return {!ol.Tile} Tile.
+ * @param {Event} event The browser event to handle.
+ * @private
  */
-ol.source.Tile.prototype.getTile = goog.abstractMethod;
+ol.control.ZoomSlider.prototype.handleContainerClick_ = function(event) {
+  var view = this.getMap().getView();
 
+  var relativePosition = this.getRelativePosition_(
+      event.offsetX - this.thumbSize_[0] / 2,
+      event.offsetY - this.thumbSize_[1] / 2);
 
-/**
- * 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;
+  var resolution = this.getResolutionForPosition_(relativePosition);
+
+  view.animate({
+    resolution: view.constrainResolution(resolution),
+    duration: this.duration_,
+    easing: ol.easing.easeOut
+  });
 };
 
 
 /**
- * @param {ol.proj.Projection} projection Projection.
- * @return {ol.tilegrid.TileGrid} Tile grid.
+ * Handle dragger start events.
+ * @param {ol.pointer.PointerEvent} event The drag event.
+ * @private
  */
-ol.source.Tile.prototype.getTileGridForProjection = function(projection) {
-  if (!this.tileGrid) {
-    return ol.tilegrid.getForProjection(projection);
-  } else {
-    return this.tileGrid;
+ol.control.ZoomSlider.prototype.handleDraggerStart_ = function(event) {
+  if (!this.dragging_ && event.originalEvent.target === this.element.firstElementChild) {
+    this.getMap().getView().setHint(ol.ViewHint.INTERACTING, 1);
+    this.previousX_ = event.clientX;
+    this.previousY_ = event.clientY;
+    this.dragging_ = true;
   }
 };
 
 
 /**
- * @param {number} z Z.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {ol.Size} Tile size.
+ * Handle dragger drag events.
+ *
+ * @param {ol.pointer.PointerEvent|Event} event The drag event.
+ * @private
  */
-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);
+ol.control.ZoomSlider.prototype.handleDraggerDrag_ = function(event) {
+  if (this.dragging_) {
+    var element = this.element.firstElementChild;
+    var deltaX = event.clientX - this.previousX_ + parseInt(element.style.left, 10);
+    var deltaY = event.clientY - this.previousY_ + parseInt(element.style.top, 10);
+    var relativePosition = this.getRelativePosition_(deltaX, deltaY);
+    this.currentResolution_ = this.getResolutionForPosition_(relativePosition);
+    this.getMap().getView().setResolution(this.currentResolution_);
+    this.setThumbPosition_(this.currentResolution_);
+    this.previousX_ = event.clientX;
+    this.previousY_ = event.clientY;
+  }
 };
 
 
 /**
- * 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`.
+ * Handle dragger end events.
+ * @param {ol.pointer.PointerEvent|Event} event The drag event.
+ * @private
  */
-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);
+ol.control.ZoomSlider.prototype.handleDraggerEnd_ = function(event) {
+  if (this.dragging_) {
+    var view = this.getMap().getView();
+    view.setHint(ol.ViewHint.INTERACTING, -1);
+
+    view.animate({
+      resolution: view.constrainResolution(this.currentResolution_),
+      duration: this.duration_,
+      easing: ol.easing.easeOut
+    });
+
+    this.dragging_ = false;
+    this.previousX_ = undefined;
+    this.previousY_ = undefined;
   }
-  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.
+ * Positions the thumb inside its container according to the given resolution.
+ *
+ * @param {number} res The res.
+ * @private
  */
-ol.source.Tile.prototype.useTile = ol.nullFunction;
+ol.control.ZoomSlider.prototype.setThumbPosition_ = function(res) {
+  var position = this.getPositionForResolution_(res);
+  var thumb = this.element.firstElementChild;
 
+  if (this.direction_ == ol.control.ZoomSlider.Direction_.HORIZONTAL) {
+    thumb.style.left = this.widthLimit_ * position + 'px';
+  } else {
+    thumb.style.top = this.heightLimit_ * position + 'px';
+  }
+};
 
 
 /**
- * @classdesc
- * Events emitted by {@link ol.source.Tile} instances are instances of this
- * type.
+ * 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.
  *
- * @constructor
- * @extends {goog.events.Event}
- * @implements {oli.source.TileEvent}
- * @param {string} type Type.
- * @param {ol.Tile} tile The tile.
+ * @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.source.TileEvent = function(type, tile) {
-
-  goog.base(this, type);
-
-  /**
-   * The tile related to the event.
-   * @type {ol.Tile}
-   * @api
-   */
-  this.tile = tile;
-
+ol.control.ZoomSlider.prototype.getRelativePosition_ = function(x, y) {
+  var amount;
+  if (this.direction_ === ol.control.ZoomSlider.Direction_.HORIZONTAL) {
+    amount = x / this.widthLimit_;
+  } else {
+    amount = y / this.heightLimit_;
+  }
+  return ol.math.clamp(amount, 0, 1);
 };
-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',
+ * 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);
+};
 
-  /**
-   * Triggered if tile loading results in an error.
-   * @event ol.source.TileEvent#tileloaderror
-   * @api stable
-   */
-  TILELOADERROR: 'tileloaderror'
 
+/**
+ * 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);
 };
 
-// FIXME handle date line wrap
-
-goog.provide('ol.control.Attribution');
+goog.provide('ol.control.ZoomToExtent');
 
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.dom.classlist');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.object');
-goog.require('goog.style');
 goog.require('ol');
-goog.require('ol.Attribution');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
 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`.
+ * 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.AttributionOptions=} opt_options Attribution options.
- * @api stable
+ * @param {olx.control.ZoomToExtentOptions=} opt_options Options.
+ * @api
  */
-ol.control.Attribution = function(opt_options) {
-
+ol.control.ZoomToExtent = function(opt_options) {
   var options = opt_options ? opt_options : {};
 
   /**
+   * @type {ol.Extent}
    * @private
-   * @type {Element}
-   */
-  this.ulElement_ = goog.dom.createElement(goog.dom.TagName.UL);
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.logoLi_ = goog.dom.createElement(goog.dom.TagName.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;
-  }
+  this.extent_ = options.extent ? options.extent : null;
 
-  var className = options.className ? options.className : 'ol-attribution';
+  var className = options.className !== undefined ? options.className :
+      'ol-zoom-extent';
 
-  var tipLabel = options.tipLabel ? options.tipLabel : 'Attributions';
+  var label = options.label !== undefined ? options.label : 'E';
+  var tipLabel = options.tipLabel !== undefined ?
+      options.tipLabel : 'Fit to extent';
+  var button = document.createElement('button');
+  button.setAttribute('type', 'button');
+  button.title = tipLabel;
+  button.appendChild(
+    typeof label === 'string' ? document.createTextNode(label) : label
+  );
 
-  var collapseLabel = options.collapseLabel ? options.collapseLabel : '\u00BB';
+  ol.events.listen(button, ol.events.EventType.CLICK,
+      this.handleClick_, this);
 
-  /**
-   * @private
-   * @type {Node}
-   */
-  this.collapseLabel_ = goog.isString(collapseLabel) ?
-      goog.dom.createDom(goog.dom.TagName.SPAN, {}, collapseLabel) :
-      collapseLabel;
+  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
+      ol.css.CLASS_CONTROL;
+  var element = document.createElement('div');
+  element.className = cssClasses;
+  element.appendChild(button);
 
-  var label = options.label ? options.label : 'i';
+  ol.control.Control.call(this, {
+    element: element,
+    target: options.target
+  });
+};
+ol.inherits(ol.control.ZoomToExtent, ol.control.Control);
 
-  /**
-   * @private
-   * @type {Node}
-   */
-  this.label_ = goog.isString(label) ?
-      goog.dom.createDom(goog.dom.TagName.SPAN, {}, label) :
-      label;
 
-  var activeLabel = (this.collapsible_ && !this.collapsed_) ?
-      this.collapseLabel_ : this.label_;
-  var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
-    'type': 'button',
-    'title': tipLabel
-  }, activeLabel);
+/**
+ * @param {Event} event The event to handle
+ * @private
+ */
+ol.control.ZoomToExtent.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  this.handleZoomToExtent_();
+};
 
-  goog.events.listen(button, goog.events.EventType.CLICK,
-      this.handleClick_, false, this);
 
-  goog.events.listen(button, [
-    goog.events.EventType.MOUSEOUT,
-    goog.events.EventType.FOCUSOUT
-  ], function() {
-    this.blur();
-  }, false);
+/**
+ * @private
+ */
+ol.control.ZoomToExtent.prototype.handleZoomToExtent_ = function() {
+  var map = this.getMap();
+  var view = map.getView();
+  var extent = !this.extent_ ? view.getProjection().getExtent() : this.extent_;
+  view.fit(extent);
+};
 
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL +
-      (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') +
-      (this.collapsible_ ? '' : ' ol-uncollapsible');
-  var element = goog.dom.createDom(goog.dom.TagName.DIV,
-      cssClasses, this.ulElement_, button);
+goog.provide('ol.DeviceOrientation');
 
-  var render = options.render ? options.render : ol.control.Attribution.render;
+goog.require('ol.events');
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.has');
+goog.require('ol.math');
 
-  goog.base(this, {
-    element: element,
-    render: render,
-    target: options.target
-  });
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderedVisible_ = true;
+/**
+ * @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.
+ *
+ * To get notified of device orientation changes, register a listener for the
+ * generic `change` event on your `ol.DeviceOrientation` instance.
+ *
+ * @see {@link http://www.w3.org/TR/orientation-event/}
+ *
+ * @constructor
+ * @extends {ol.Object}
+ * @param {olx.DeviceOrientationOptions=} opt_options Options.
+ * @api
+ */
+ol.DeviceOrientation = function(opt_options) {
 
-  /**
-   * @private
-   * @type {Object.<string, Element>}
-   */
-  this.attributionElements_ = {};
+  ol.Object.call(this);
 
-  /**
-   * @private
-   * @type {Object.<string, boolean>}
-   */
-  this.attributionElementRenderedVisible_ = {};
+  var options = opt_options ? opt_options : {};
 
   /**
    * @private
-   * @type {Object.<string, Element>}
+   * @type {?ol.EventsKey}
    */
-  this.logoElements_ = {};
+  this.listenerKey_ = null;
 
-};
-goog.inherits(ol.control.Attribution, ol.control.Control);
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.DeviceOrientation.Property_.TRACKING),
+      this.handleTrackingChanged_, this);
 
+  this.setTracking(options.tracking !== undefined ? options.tracking : false);
 
-/**
- * @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];
 };
+ol.inherits(ol.DeviceOrientation, ol.Object);
 
 
 /**
- * Update the attribution element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.Attribution}
- * @api
+ * @inheritDoc
  */
-ol.control.Attribution.render = function(mapEvent) {
-  this.updateElement_(mapEvent.frameState);
+ol.DeviceOrientation.prototype.disposeInternal = function() {
+  this.setTracking(false);
+  ol.Object.prototype.disposeInternal.call(this);
 };
 
 
 /**
  * @private
- * @param {?olx.FrameState} frameState Frame state.
+ * @param {Event} originalEvent Event.
  */
-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];
+ol.DeviceOrientation.prototype.orientationChange_ = function(originalEvent) {
+  var event = /** @type {DeviceOrientationEvent} */ (originalEvent);
+  if (event.alpha !== null) {
+    var alpha = ol.math.toRadians(event.alpha);
+    this.set(ol.DeviceOrientation.Property_.ALPHA, alpha);
+    // event.absolute is undefined in iOS.
+    if (typeof event.absolute === 'boolean' && event.absolute) {
+      this.set(ol.DeviceOrientation.Property_.HEADING, alpha);
+    } else if (typeof event.webkitCompassHeading === 'number' &&
+               event.webkitCompassAccuracy != -1) {
+      var heading = ol.math.toRadians(event.webkitCompassHeading);
+      this.set(ol.DeviceOrientation.Property_.HEADING, heading);
     }
   }
-  for (attributionKey in visibleAttributions) {
-    attributionElement = goog.dom.createElement(goog.dom.TagName.LI);
-    attributionElement.innerHTML =
-        visibleAttributions[attributionKey].getHTML();
-    this.ulElement_.appendChild(attributionElement);
-    this.attributionElements_[attributionKey] = attributionElement;
-    this.attributionElementRenderedVisible_[attributionKey] = true;
-  }
-  for (attributionKey in hiddenAttributions) {
-    attributionElement = goog.dom.createElement(goog.dom.TagName.LI);
-    attributionElement.innerHTML =
-        hiddenAttributions[attributionKey].getHTML();
-    goog.style.setElementShown(attributionElement, false);
-    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 (event.beta !== null) {
+    this.set(ol.DeviceOrientation.Property_.BETA,
+        ol.math.toRadians(event.beta));
   }
-  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');
+  if (event.gamma !== null) {
+    this.set(ol.DeviceOrientation.Property_.GAMMA,
+        ol.math.toRadians(event.gamma));
   }
-
-  this.insertLogos_(frameState);
-
+  this.changed();
 };
 
 
 /**
- * @param {?olx.FrameState} frameState Frame state.
- * @private
+ * 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.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(goog.dom.TagName.A, {
-          'href': logoValue
-        });
-        logoElement.appendChild(image);
-      }
-      this.logoLi_.appendChild(logoElement);
-      logoElements[logoKey] = logoElement;
-    }
-  }
+ol.DeviceOrientation.prototype.getAlpha = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.DeviceOrientation.Property_.ALPHA));
+};
 
-  goog.style.setElementShown(this.logoLi_, !goog.object.isEmpty(logos));
 
+/**
+ * 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.DeviceOrientation.Property_.BETA));
 };
 
 
 /**
- * @param {goog.events.BrowserEvent} event The event to handle
- * @private
+ * Rotation around the device y-axis (in radians).
+ * @return {number|undefined} The euler angle in radians of the device from the
+ *     planar Y axis.
+ * @observable
+ * @api
  */
-ol.control.Attribution.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  this.handleToggle_();
+ol.DeviceOrientation.prototype.getGamma = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.DeviceOrientation.Property_.GAMMA));
 };
 
 
 /**
- * @private
+ * 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.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_;
+ol.DeviceOrientation.prototype.getHeading = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.DeviceOrientation.Property_.HEADING));
 };
 
 
 /**
- * Return `true` if the attribution is collapsible, `false` otherwise.
- * @return {boolean} True if the widget is collapsible.
- * @api stable
+ * Determine if orientation is being tracked.
+ * @return {boolean} Changes in device orientation are being tracked.
+ * @observable
+ * @api
  */
-ol.control.Attribution.prototype.getCollapsible = function() {
-  return this.collapsible_;
+ol.DeviceOrientation.prototype.getTracking = function() {
+  return /** @type {boolean} */ (
+      this.get(ol.DeviceOrientation.Property_.TRACKING));
 };
 
 
 /**
- * Set whether the attribution should be collapsible.
- * @param {boolean} collapsible True if the widget is collapsible.
- * @api stable
+ * @private
  */
-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_();
+ol.DeviceOrientation.prototype.handleTrackingChanged_ = function() {
+  if (ol.has.DEVICE_ORIENTATION) {
+    var tracking = this.getTracking();
+    if (tracking && !this.listenerKey_) {
+      this.listenerKey_ = ol.events.listen(window, 'deviceorientation',
+          this.orientationChange_, this);
+    } else if (!tracking && this.listenerKey_ !== null) {
+      ol.events.unlistenByKey(this.listenerKey_);
+      this.listenerKey_ = null;
+    }
   }
 };
 
 
 /**
- * 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
+ * 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.control.Attribution.prototype.setCollapsed = function(collapsed) {
-  if (!this.collapsible_ || this.collapsed_ === collapsed) {
-    return;
-  }
-  this.handleToggle_();
+ol.DeviceOrientation.prototype.setTracking = function(tracking) {
+  this.set(ol.DeviceOrientation.Property_.TRACKING, tracking);
 };
 
 
 /**
- * Return `true` when the attribution is currently collapsed or `false`
- * otherwise.
- * @return {boolean} True if the widget is collapsed.
- * @api stable
+ * @enum {string}
+ * @private
  */
-ol.control.Attribution.prototype.getCollapsed = function() {
-  return this.collapsed_;
+ol.DeviceOrientation.Property_ = {
+  ALPHA: 'alpha',
+  BETA: 'beta',
+  GAMMA: 'gamma',
+  HEADING: 'heading',
+  TRACKING: 'tracking'
 };
 
-goog.provide('ol.control.Rotate');
+goog.provide('ol.ImageState');
 
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.dom.classlist');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('ol');
-goog.require('ol.animation');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.easing');
+/**
+ * @enum {number}
+ */
+ol.ImageState = {
+  IDLE: 0,
+  LOADING: 1,
+  LOADED: 2,
+  ERROR: 3
+};
 
+goog.provide('ol.style.Image');
 
 
 /**
  * @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.
+ * 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
- * @extends {ol.control.Control}
- * @param {olx.control.RotateOptions=} opt_options Rotate options.
- * @api stable
+ * @abstract
+ * @param {ol.StyleImageOptions} options Options.
+ * @api
  */
-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';
+ol.style.Image = function(options) {
 
   /**
-   * @type {Element}
    * @private
+   * @type {number}
    */
-  this.label_ = null;
-
-  if (goog.isString(label)) {
-    this.label_ = goog.dom.createDom(goog.dom.TagName.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(goog.dom.TagName.BUTTON, {
-    'class': className + '-reset',
-    'type' : 'button',
-    'title': tipLabel
-  }, this.label_);
-
-  goog.events.listen(button, goog.events.EventType.CLICK,
-      ol.control.Rotate.prototype.handleClick_, false, this);
-
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL;
-  var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button);
-
-  var render = options.render ? options.render : ol.control.Rotate.render;
-
-  goog.base(this, {
-    element: element,
-    render: render,
-    target: options.target
-  });
+  this.opacity_ = options.opacity;
 
   /**
-   * @type {number}
    * @private
+   * @type {boolean}
    */
-  this.duration_ = options.duration ? options.duration : 250;
+  this.rotateWithView_ = options.rotateWithView;
 
   /**
-   * @type {boolean}
    * @private
+   * @type {number}
    */
-  this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true;
+  this.rotation_ = options.rotation;
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {number}
    */
-  this.rotation_ = undefined;
+  this.scale_ = options.scale;
 
-  if (this.autoHide_) {
-    goog.dom.classlist.add(this.element, ol.css.CLASS_HIDDEN);
-  }
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.snapToPixel_ = options.snapToPixel;
 
 };
-goog.inherits(ol.control.Rotate, ol.control.Control);
 
 
 /**
- * @param {goog.events.BrowserEvent} event The event to handle
- * @private
+ * Get the symbolizer opacity.
+ * @return {number} Opacity.
+ * @api
  */
-ol.control.Rotate.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  this.resetNorth_();
+ol.style.Image.prototype.getOpacity = function() {
+  return this.opacity_;
 };
 
 
 /**
- * @private
+ * Determine whether the symbolizer rotates with the map.
+ * @return {boolean} Rotate with map.
+ * @api
  */
-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);
-  }
+ol.style.Image.prototype.getRotateWithView = function() {
+  return this.rotateWithView_;
 };
 
 
 /**
- * Update the rotate control element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.Rotate}
+ * Get the symoblizer rotation.
+ * @return {number} Rotation.
  * @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;
+ol.style.Image.prototype.getRotation = function() {
+  return this.rotation_;
 };
 
-goog.provide('ol.control.Zoom');
-
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('ol.animation');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.easing');
-
-
 
 /**
- * @classdesc
- * A control with 2 buttons, one for zoom in and one for zoom out.
- * This control is one of the default controls of a map. To style this control
- * use css selectors `.ol-zoom-in` and `.ol-zoom-out`.
- *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.ZoomOptions=} opt_options Zoom options.
- * @api stable
+ * Get the symbolizer scale.
+ * @return {number} Scale.
+ * @api
  */
-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(goog.dom.TagName.BUTTON, {
-    'class': className + '-in',
-    'type' : 'button',
-    'title': zoomInTipLabel
-  }, zoomInLabel);
-
-  goog.events.listen(inElement,
-      goog.events.EventType.CLICK, goog.partial(
-          ol.control.Zoom.prototype.handleClick_, delta), false, this);
-
-  var outElement = goog.dom.createDom(goog.dom.TagName.BUTTON, {
-    'class': className + '-out',
-    'type' : 'button',
-    'title': zoomOutTipLabel
-  }, zoomOutLabel);
-
-  goog.events.listen(outElement,
-      goog.events.EventType.CLICK, goog.partial(
-          ol.control.Zoom.prototype.handleClick_, -delta), false, this);
-
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL;
-  var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, inElement,
-      outElement);
-
-  goog.base(this, {
-    element: element,
-    target: options.target
-  });
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.duration_ = options.duration ? options.duration : 250;
-
+ol.style.Image.prototype.getScale = function() {
+  return this.scale_;
 };
-goog.inherits(ol.control.Zoom, ol.control.Control);
 
 
 /**
- * @param {number} delta Zoom delta.
- * @param {goog.events.BrowserEvent} event The event to handle
- * @private
+ * Determine whether the symbolizer should be snapped to a pixel.
+ * @return {boolean} The symbolizer should snap to a pixel.
+ * @api
  */
-ol.control.Zoom.prototype.handleClick_ = function(delta, event) {
-  event.preventDefault();
-  this.zoomByDelta_(delta);
+ol.style.Image.prototype.getSnapToPixel = function() {
+  return this.snapToPixel_;
 };
 
 
 /**
- * @param {number} delta Zoom delta.
- * @private
+ * Get the anchor point in pixels. The anchor determines the center point for the
+ * symbolizer.
+ * @abstract
+ * @return {Array.<number>} Anchor.
  */
-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);
-  }
-};
+ol.style.Image.prototype.getAnchor = function() {};
 
-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');
+/**
+ * Get the image element for the symbolizer.
+ * @abstract
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
+ */
+ol.style.Image.prototype.getImage = function(pixelRatio) {};
 
 
 /**
- * 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
+ * @abstract
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
  */
-ol.control.defaults = function(opt_options) {
+ol.style.Image.prototype.getHitDetectionImage = function(pixelRatio) {};
 
-  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));
-  }
+/**
+ * @abstract
+ * @return {ol.ImageState} Image state.
+ */
+ol.style.Image.prototype.getImageState = function() {};
 
-  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));
-  }
+/**
+ * @abstract
+ * @return {ol.Size} Image size.
+ */
+ol.style.Image.prototype.getImageSize = function() {};
 
-  return controls;
 
-};
+/**
+ * @abstract
+ * @return {ol.Size} Size of the hit-detection image.
+ */
+ol.style.Image.prototype.getHitDetectionImageSize = function() {};
 
-// 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.
- *
+ * Get the origin of the symbolizer.
+ * @abstract
+ * @return {Array.<number>} Origin.
  */
+ol.style.Image.prototype.getOrigin = function() {};
 
-goog.provide('goog.dom.fullscreen');
-goog.provide('goog.dom.fullscreen.EventType');
 
-goog.require('goog.dom');
-goog.require('goog.userAgent');
+/**
+ * Get the size of the symbolizer (in pixels).
+ * @abstract
+ * @return {ol.Size} Size.
+ */
+ol.style.Image.prototype.getSize = function() {};
 
 
 /**
- * Event types for full screen.
- * @enum {string}
+ * Set the opacity.
+ *
+ * @param {number} opacity Opacity.
+ * @api
  */
-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';
-  })()
+ol.style.Image.prototype.setOpacity = function(opacity) {
+  this.opacity_ = opacity;
 };
 
 
 /**
- * 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.
+ * Set whether to rotate the style with the view.
+ *
+ * @param {boolean} rotateWithView Rotate with map.
  */
-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));
+ol.style.Image.prototype.setRotateWithView = function(rotateWithView) {
+  this.rotateWithView_ = rotateWithView;
 };
 
 
 /**
- * Requests putting the element in full screen.
- * @param {!Element} element The element to put full screen.
+ * Set the rotation.
+ *
+ * @param {number} rotation Rotation.
+ * @api
  */
-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();
-  }
+ol.style.Image.prototype.setRotation = function(rotation) {
+  this.rotation_ = rotation;
 };
 
 
 /**
- * Requests putting the element in full screen with full keyboard access.
- * @param {!Element} element The element to put full screen.
+ * Set the scale.
+ *
+ * @param {number} scale Scale.
+ * @api
  */
-goog.dom.fullscreen.requestFullScreenWithKeys = function(
-    element) {
-  if (element.mozRequestFullScreenWithKeys) {
-    element.mozRequestFullScreenWithKeys();
-  } else if (element.webkitRequestFullscreen) {
-    element.webkitRequestFullscreen();
-  } else {
-    goog.dom.fullscreen.requestFullScreen(element);
-  }
+ol.style.Image.prototype.setScale = function(scale) {
+  this.scale_ = scale;
 };
 
 
 /**
- * Exits full screen.
- * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being
- *     queried. If not provided, use the current DOM.
+ * Set whether to snap the image to the closest pixel.
+ *
+ * @param {boolean} snapToPixel Snap to pixel?
  */
-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();
-  }
+ol.style.Image.prototype.setSnapToPixel = function(snapToPixel) {
+  this.snapToPixel_ = snapToPixel;
 };
 
 
 /**
- * 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.
+ * @abstract
+ * @param {function(this: T, ol.events.Event)} listener Listener function.
+ * @param {T} thisArg Value to use as `this` when executing `listener`.
+ * @return {ol.EventsKey|undefined} Listener key.
+ * @template T
  */
-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);
-};
+ol.style.Image.prototype.listenImageChange = function(listener, thisArg) {};
 
 
 /**
- * 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.
+ * Load not yet loaded URI.
+ * @abstract
  */
-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;
-};
+ol.style.Image.prototype.load = function() {};
 
 
 /**
- * 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
+ * @abstract
+ * @param {function(this: T, ol.events.Event)} listener Listener function.
+ * @param {T} thisArg Value to use as `this` when executing `listener`.
+ * @template T
  */
-goog.dom.fullscreen.getDocument_ = function(opt_domHelper) {
-  return opt_domHelper ?
-      opt_domHelper.getDocument() :
-      goog.dom.getDomHelper().getDocument();
-};
+ol.style.Image.prototype.unlistenImageChange = function(listener, thisArg) {};
 
-goog.provide('ol.control.FullScreen');
+goog.provide('ol.style.RegularShape');
 
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.dom.classlist');
-goog.require('goog.dom.fullscreen');
-goog.require('goog.dom.fullscreen.EventType');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
 goog.require('ol');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-
+goog.require('ol.colorlike');
+goog.require('ol.dom');
+goog.require('ol.has');
+goog.require('ol.ImageState');
+goog.require('ol.render.canvas');
+goog.require('ol.style.Image');
 
 
 /**
  * @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.
- *
+ * 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
- * @extends {ol.control.Control}
- * @param {olx.control.FullScreenOptions=} opt_options Options.
- * @api stable
+ * @param {olx.style.RegularShapeOptions} options Options.
+ * @extends {ol.style.Image}
+ * @api
  */
-ol.control.FullScreen = function(opt_options) {
+ol.style.RegularShape = function(options) {
+  /**
+   * @private
+   * @type {Array.<string>}
+   */
+  this.checksums_ = null;
 
-  var options = opt_options ? opt_options : {};
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.canvas_ = null;
 
   /**
    * @private
-   * @type {string}
+   * @type {HTMLCanvasElement}
+   */
+  this.hitDetectionCanvas_ = null;
+
+  /**
+   * @private
+   * @type {ol.style.Fill}
    */
-  this.cssClassName_ = options.className ? options.className : 'ol-full-screen';
+  this.fill_ = options.fill !== undefined ? options.fill : null;
 
-  var label = options.label ? options.label : '\u2194';
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.origin_ = [0, 0];
 
   /**
    * @private
-   * @type {Node}
+   * @type {number}
    */
-  this.labelNode_ = goog.isString(label) ?
-      goog.dom.createTextNode(label) : label;
+  this.points_ = options.points;
 
-  var labelActive = options.labelActive ? options.labelActive : '\u00d7';
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.radius_ = /** @type {number} */ (options.radius !== undefined ?
+      options.radius : options.radius1);
 
   /**
    * @private
-   * @type {Node}
+   * @type {number|undefined}
    */
-  this.labelActiveNode_ = goog.isString(labelActive) ?
-      goog.dom.createTextNode(labelActive) : labelActive;
+  this.radius2_ = options.radius2;
 
-  var tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen';
-  var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
-    'class': this.cssClassName_ + '-' + goog.dom.fullscreen.isFullScreen(),
-    'type': 'button',
-    'title': tipLabel
-  }, this.labelNode_);
+  /**
+   * @private
+   * @type {number}
+   */
+  this.angle_ = options.angle !== undefined ? options.angle : 0;
 
-  goog.events.listen(button, goog.events.EventType.CLICK,
-      this.handleClick_, false, this);
+  /**
+   * @private
+   * @type {ol.style.Stroke}
+   */
+  this.stroke_ = options.stroke !== undefined ? options.stroke : null;
 
-  goog.events.listen(goog.global.document,
-      goog.dom.fullscreen.EventType.CHANGE,
-      this.handleFullScreenChange_, false, this);
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.anchor_ = null;
 
-  var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE +
-      ' ' + ol.css.CLASS_CONTROL + ' ' +
-      (!goog.dom.fullscreen.isSupported() ? ol.css.CLASS_UNSUPPORTED : '');
-  var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button);
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.size_ = null;
 
-  goog.base(this, {
-    element: element,
-    target: options.target
-  });
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.imageSize_ = null;
 
   /**
    * @private
+   * @type {ol.Size}
+   */
+  this.hitDetectionImageSize_ = null;
+
+  /**
+   * @protected
+   * @type {ol.style.AtlasManager|undefined}
+   */
+  this.atlasManager_ = options.atlasManager;
+
+  this.render_(this.atlasManager_);
+
+  /**
    * @type {boolean}
    */
-  this.keys_ = options.keys !== undefined ? options.keys : false;
+  var snapToPixel = options.snapToPixel !== undefined ?
+      options.snapToPixel : true;
+
+  /**
+   * @type {boolean}
+   */
+  var rotateWithView = options.rotateWithView !== undefined ?
+      options.rotateWithView : false;
 
+  ol.style.Image.call(this, {
+    opacity: 1,
+    rotateWithView: rotateWithView,
+    rotation: options.rotation !== undefined ? options.rotation : 0,
+    scale: 1,
+    snapToPixel: snapToPixel
+  });
 };
-goog.inherits(ol.control.FullScreen, ol.control.Control);
+ol.inherits(ol.style.RegularShape, ol.style.Image);
 
 
 /**
- * @param {goog.events.BrowserEvent} event The event to handle
- * @private
+ * Clones the style. If an atlasmanager was provided to the original style it will be used in the cloned style, too.
+ * @return {ol.style.RegularShape} The cloned style.
+ * @api
  */
-ol.control.FullScreen.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  this.handleFullScreen_();
+ol.style.RegularShape.prototype.clone = function() {
+  var style = new ol.style.RegularShape({
+    fill: this.getFill() ? this.getFill().clone() : undefined,
+    points: this.getPoints(),
+    radius: this.getRadius(),
+    radius2: this.getRadius2(),
+    angle: this.getAngle(),
+    snapToPixel: this.getSnapToPixel(),
+    stroke: this.getStroke() ?  this.getStroke().clone() : undefined,
+    rotation: this.getRotation(),
+    rotateWithView: this.getRotateWithView(),
+    atlasManager: this.atlasManager_
+  });
+  style.setOpacity(this.getOpacity());
+  style.setScale(this.getScale());
+  return style;
 };
 
 
 /**
- * @private
+ * @inheritDoc
+ * @api
  */
-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);
-    }
-  }
+ol.style.RegularShape.prototype.getAnchor = function() {
+  return this.anchor_;
 };
 
 
 /**
- * @private
+ * Get the angle used in generating the shape.
+ * @return {number} Shape's rotation in radians.
+ * @api
  */
-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();
-  }
+ol.style.RegularShape.prototype.getAngle = function() {
+  return this.angle_;
 };
 
-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
+ * Get the fill style for the shape.
+ * @return {ol.style.Fill} Fill style.
+ * @api
  */
-ol.Pixel;
+ol.style.RegularShape.prototype.getFill = function() {
+  return this.fill_;
+};
 
-// FIXME should listen on appropriate pane, once it is defined
 
-goog.provide('ol.control.MousePosition');
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.getHitDetectionImage = function(pixelRatio) {
+  return this.hitDetectionCanvas_;
+};
 
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('ol.CoordinateFormatType');
-goog.require('ol.Object');
-goog.require('ol.Pixel');
-goog.require('ol.TransformFunction');
-goog.require('ol.control.Control');
-goog.require('ol.proj');
-goog.require('ol.proj.Projection');
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.RegularShape.prototype.getImage = function(pixelRatio) {
+  return this.canvas_;
+};
 
 
 /**
- * @enum {string}
+ * @inheritDoc
  */
-ol.control.MousePositionProperty = {
-  PROJECTION: 'projection',
-  COORDINATE_FORMAT: 'coordinateFormat'
+ol.style.RegularShape.prototype.getImageSize = function() {
+  return this.imageSize_;
 };
 
 
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.getHitDetectionImageSize = function() {
+  return this.hitDetectionImageSize_;
+};
+
 
 /**
- * @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
+ * @inheritDoc
  */
-ol.control.MousePosition = function(opt_options) {
+ol.style.RegularShape.prototype.getImageState = function() {
+  return ol.ImageState.LOADED;
+};
 
-  var options = opt_options ? opt_options : {};
 
-  var className = options.className ? options.className : 'ol-mouse-position';
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.RegularShape.prototype.getOrigin = function() {
+  return this.origin_;
+};
 
-  var element = goog.dom.createDom(goog.dom.TagName.DIV, className);
 
-  var render = options.render ?
-      options.render : ol.control.MousePosition.render;
+/**
+ * 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_;
+};
 
-  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);
+/**
+ * Get the (primary) radius for the shape.
+ * @return {number} Radius.
+ * @api
+ */
+ol.style.RegularShape.prototype.getRadius = function() {
+  return this.radius_;
+};
 
-  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 : '';
+/**
+ * Get the secondary radius for the shape.
+ * @return {number|undefined} Radius2.
+ * @api
+ */
+ol.style.RegularShape.prototype.getRadius2 = function() {
+  return this.radius2_;
+};
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.renderedHTML_ = element.innerHTML;
 
-  /**
-   * @private
-   * @type {ol.proj.Projection}
-   */
-  this.mapProjection_ = null;
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.RegularShape.prototype.getSize = function() {
+  return this.size_;
+};
 
-  /**
-   * @private
-   * @type {?ol.TransformFunction}
-   */
-  this.transform_ = null;
 
-  /**
-   * @private
-   * @type {ol.Pixel}
-   */
-  this.lastMouseMovePixel_ = null;
+/**
+ * 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 = function(listener, thisArg) {};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.load = function() {};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.unlistenImageChange = function(listener, thisArg) {};
+
+
+/**
+ * @protected
+ * @param {ol.style.AtlasManager|undefined} atlasManager An atlas manager.
+ */
+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 = this.stroke_.getColor();
+    if (strokeStyle === null) {
+      strokeStyle = ol.render.canvas.defaultStrokeStyle;
+    }
+    strokeStyle = ol.colorlike.asColorLike(strokeStyle);
+    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.RegularShapeRenderOptions} */
+  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
+    var context = ol.dom.createCanvasContext2D(size, size);
+    this.canvas_ = context.canvas;
+
+    // canvas.width and height are rounded to the closest integer
+    size = this.canvas_.width;
+    imageSize = size;
+
+    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 =
+          this.drawHitDetectionCanvas_.bind(this, renderOptions);
+    }
+
+    var id = this.getChecksum();
+    var info = atlasManager.add(
+        id, size, size, this.draw_.bind(this, renderOptions),
+        renderHitDetectionCallback);
+
+    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];
 };
-goog.inherits(ol.control.MousePosition, ol.control.Control);
 
 
 /**
- * Update the mouseposition element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.MousePosition}
- * @api
+ * @private
+ * @param {ol.RegularShapeRenderOptions} renderOptions Render options.
+ * @param {CanvasRenderingContext2D} context The rendering context.
+ * @param {number} x The origin for the symbol (x).
+ * @param {number} y The origin for the symbol (y).
  */
-ol.control.MousePosition.render = function(mapEvent) {
-  var frameState = mapEvent.frameState;
-  if (!frameState) {
-    this.mapProjection_ = null;
+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();
+
+  var points = this.points_;
+  if (points === Infinity) {
+    context.arc(
+        renderOptions.size / 2, renderOptions.size / 2,
+        this.radius_, 0, 2 * Math.PI, true);
   } else {
-    if (this.mapProjection_ != frameState.viewState.projection) {
-      this.mapProjection_ = frameState.viewState.projection;
-      this.transform_ = null;
+    var radius2 = (this.radius2_ !== undefined) ? this.radius2_
+      : this.radius_;
+    if (radius2 !== this.radius_) {
+      points = 2 * points;
+    }
+    for (i = 0; i <= points; i++) {
+      angle0 = i * 2 * Math.PI / points - Math.PI / 2 + this.angle_;
+      radiusC = i % 2 === 0 ? this.radius_ : radius2;
+      context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
+                     renderOptions.size / 2 + radiusC * Math.sin(angle0));
     }
   }
-  this.updateHTML_(this.lastMouseMovePixel_);
+
+
+  if (this.fill_) {
+    var color = this.fill_.getColor();
+    if (color === null) {
+      color = ol.render.canvas.defaultFillStyle;
+    }
+    context.fillStyle = ol.colorlike.asColorLike(color);
+    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.RegularShapeRenderOptions} renderOptions Render options.
  */
-ol.control.MousePosition.prototype.handleProjectionChanged_ = function() {
-  this.transform_ = null;
+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
+  var context = ol.dom.createCanvasContext2D(renderOptions.size, renderOptions.size);
+  this.hitDetectionCanvas_ = context.canvas;
+
+  this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
 };
 
 
 /**
- * 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
+ * @private
+ * @param {ol.RegularShapeRenderOptions} renderOptions Render options.
+ * @param {CanvasRenderingContext2D} context The context.
+ * @param {number} x The origin for the symbol (x).
+ * @param {number} y The origin for the symbol (y).
  */
-ol.control.MousePosition.prototype.getCoordinateFormat = function() {
-  return /** @type {ol.CoordinateFormatType|undefined} */ (
-      this.get(ol.control.MousePositionProperty.COORDINATE_FORMAT));
+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();
+
+  var points = this.points_;
+  if (points === Infinity) {
+    context.arc(
+        renderOptions.size / 2, renderOptions.size / 2,
+        this.radius_, 0, 2 * Math.PI, true);
+  } else {
+    var radius2 = (this.radius2_ !== undefined) ? this.radius2_
+      : this.radius_;
+    if (radius2 !== this.radius_) {
+      points = 2 * points;
+    }
+    var i, radiusC, angle0;
+    for (i = 0; i <= points; i++) {
+      angle0 = i * 2 * Math.PI / points - Math.PI / 2 + this.angle_;
+      radiusC = i % 2 === 0 ? this.radius_ : 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();
 };
 
 
 /**
- * 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
+ * @return {string} The checksum.
  */
-ol.control.MousePosition.prototype.getProjection = function() {
-  return /** @type {ol.proj.Projection|undefined} */ (
-      this.get(ol.control.MousePositionProperty.PROJECTION));
+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];
 };
 
+goog.provide('ol.style.Circle');
+
+goog.require('ol');
+goog.require('ol.style.RegularShape');
+
 
 /**
- * @param {goog.events.BrowserEvent} browserEvent Browser event.
- * @protected
+ * @classdesc
+ * Set circle style for vector features.
+ *
+ * @constructor
+ * @param {olx.style.CircleOptions=} opt_options Options.
+ * @extends {ol.style.RegularShape}
+ * @api
  */
-ol.control.MousePosition.prototype.handleMouseMove = function(browserEvent) {
-  var map = this.getMap();
-  this.lastMouseMovePixel_ = map.getEventPixel(browserEvent.getBrowserEvent());
-  this.updateHTML_(this.lastMouseMovePixel_);
+ol.style.Circle = function(opt_options) {
+
+  var options = opt_options || {};
+
+  ol.style.RegularShape.call(this, {
+    points: Infinity,
+    fill: options.fill,
+    radius: options.radius,
+    snapToPixel: options.snapToPixel,
+    stroke: options.stroke,
+    atlasManager: options.atlasManager
+  });
+
 };
+ol.inherits(ol.style.Circle, ol.style.RegularShape);
 
 
 /**
- * @param {goog.events.BrowserEvent} browserEvent Browser event.
- * @protected
+ * Clones the style.  If an atlasmanager was provided to the original style it will be used in the cloned style, too.
+ * @return {ol.style.Circle} The cloned style.
+ * @override
+ * @api
  */
-ol.control.MousePosition.prototype.handleMouseOut = function(browserEvent) {
-  this.updateHTML_(null);
-  this.lastMouseMovePixel_ = null;
+ol.style.Circle.prototype.clone = function() {
+  var style = new ol.style.Circle({
+    fill: this.getFill() ? this.getFill().clone() : undefined,
+    stroke: this.getStroke() ? this.getStroke().clone() : undefined,
+    radius: this.getRadius(),
+    snapToPixel: this.getSnapToPixel(),
+    atlasManager: this.atlasManager_
+  });
+  style.setOpacity(this.getOpacity());
+  style.setScale(this.getScale());
+  return style;
 };
 
 
 /**
- * @inheritDoc
- * @api stable
+ * Set the circle radius.
+ *
+ * @param {number} radius Circle radius.
+ * @api
  */
-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)
-    );
-  }
+ol.style.Circle.prototype.setRadius = function(radius) {
+  this.radius_ = radius;
+  this.render_(this.atlasManager_);
 };
 
+goog.provide('ol.style.Fill');
+
+goog.require('ol');
+goog.require('ol.color');
+
 
 /**
- * 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
+ * @classdesc
+ * Set fill style for vector features.
+ *
+ * @constructor
+ * @param {olx.style.FillOptions=} opt_options Options.
+ * @api
  */
-ol.control.MousePosition.prototype.setCoordinateFormat = function(format) {
-  this.set(ol.control.MousePositionProperty.COORDINATE_FORMAT, format);
+ol.style.Fill = function(opt_options) {
+
+  var options = opt_options || {};
+
+  /**
+   * @private
+   * @type {ol.Color|ol.ColorLike}
+   */
+  this.color_ = options.color !== undefined ? options.color : null;
+
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.checksum_ = undefined;
 };
 
 
 /**
- * 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
+ * Clones the style. The color is not cloned if it is an {@link ol.ColorLike}.
+ * @return {ol.style.Fill} The cloned style.
+ * @api
  */
-ol.control.MousePosition.prototype.setProjection = function(projection) {
-  this.set(ol.control.MousePositionProperty.PROJECTION, projection);
+ol.style.Fill.prototype.clone = function() {
+  var color = this.getColor();
+  return new ol.style.Fill({
+    color: (color && color.slice) ? color.slice() : color || undefined
+  });
 };
 
 
 /**
- * @param {?ol.Pixel} pixel Pixel.
- * @private
+ * Get the fill color.
+ * @return {ol.Color|ol.ColorLike} Color.
+ * @api
  */
-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;
-  }
+ol.style.Fill.prototype.getColor = function() {
+  return this.color_;
 };
 
-// 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.
+ * Set the color.
  *
- * @author nicksantos@google.com (Nick Santos)
+ * @param {ol.Color|ol.ColorLike} color Color.
+ * @api
  */
+ol.style.Fill.prototype.setColor = function(color) {
+  this.color_ = color;
+  this.checksum_ = undefined;
+};
 
-goog.provide('goog.async.AnimationDelay');
-
-goog.require('goog.Disposable');
-goog.require('goog.events');
-goog.require('goog.functions');
 
+/**
+ * @return {string} The checksum.
+ */
+ol.style.Fill.prototype.getChecksum = function() {
+  if (this.checksum_ === undefined) {
+    if (
+        this.color_ instanceof CanvasPattern ||
+        this.color_ instanceof CanvasGradient
+    ) {
+      this.checksum_ = ol.getUid(this.color_).toString();
+    } else {
+      this.checksum_ = 'f' + (this.color_ ?
+          ol.color.asString(this.color_) : '-');
+    }
+  }
 
+  return this.checksum_;
+};
 
-// 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?
+goog.provide('ol.style.Style');
 
+goog.require('ol.asserts');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.style.Circle');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Stroke');
 
 
 /**
- * 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/
+ * @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.
  *
- * @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
+ * @param {olx.style.StyleOptions=} opt_options Style options.
+ * @api
  */
-goog.async.AnimationDelay = function(listener, opt_window, opt_handler) {
-  goog.async.AnimationDelay.base(this, 'constructor');
+ol.style.Style = function(opt_options) {
+
+  var options = opt_options || {};
+
+  /**
+   * @private
+   * @type {string|ol.geom.Geometry|ol.StyleGeometryFunction}
+   */
+  this.geometry_ = null;
 
   /**
-   * Identifier of the active delay timeout, or event listener,
-   * or null when inactive.
-   * @private {goog.events.Key|number}
+   * @private
+   * @type {!ol.StyleGeometryFunction}
    */
-  this.id_ = null;
+  this.geometryFunction_ = ol.style.Style.defaultGeometryFunction;
+
+  if (options.geometry !== undefined) {
+    this.setGeometry(options.geometry);
+  }
 
   /**
-   * If we're using dom listeners.
-   * @private {?boolean}
+   * @private
+   * @type {ol.style.Fill}
    */
-  this.usingListeners_ = false;
+  this.fill_ = options.fill !== undefined ? options.fill : null;
 
   /**
-   * The function that will be invoked after a delay.
-   * @private {function(number)}
+   * @private
+   * @type {ol.style.Image}
    */
-  this.listener_ = listener;
+  this.image_ = options.image !== undefined ? options.image : null;
 
   /**
-   * The object context to invoke the callback in.
-   * @private {Object|undefined}
+   * @private
+   * @type {ol.style.Stroke}
    */
-  this.handler_ = opt_handler;
+  this.stroke_ = options.stroke !== undefined ? options.stroke : null;
 
   /**
-   * @private {Window}
+   * @private
+   * @type {ol.style.Text}
    */
-  this.win_ = opt_window || window;
+  this.text_ = options.text !== undefined ? options.text : null;
 
   /**
-   * Cached callback function invoked when the delay finishes.
-   * @private {function()}
+   * @private
+   * @type {number|undefined}
    */
-  this.callback_ = goog.bind(this.doAction_, this);
+  this.zIndex_ = options.zIndex;
+
 };
-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
+ * Clones the style.
+ * @return {ol.style.Style} The cloned style.
+ * @api
  */
-goog.async.AnimationDelay.TIMEOUT = 20;
+ol.style.Style.prototype.clone = function() {
+  var geometry = this.getGeometry();
+  if (geometry && geometry.clone) {
+    geometry = geometry.clone();
+  }
+  return new ol.style.Style({
+    geometry: geometry,
+    fill: this.getFill() ? this.getFill().clone() : undefined,
+    image: this.getImage() ? this.getImage().clone() : undefined,
+    stroke: this.getStroke() ? this.getStroke().clone() : undefined,
+    text: this.getText() ? this.getText().clone() : undefined,
+    zIndex: this.getZIndex()
+  });
+};
 
 
 /**
- * Name of event received from the requestAnimationFrame in Firefox.
- *
- * @type {string}
- * @const
- * @private
+ * Get the geometry to be rendered.
+ * @return {string|ol.geom.Geometry|ol.StyleGeometryFunction}
+ * Feature property or geometry or function that returns the geometry that will
+ * be rendered with this style.
+ * @api
  */
-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);
-  }
+ol.style.Style.prototype.getGeometry = function() {
+  return this.geometry_;
 };
 
 
 /**
- * Stops the delay timer if it is active. No action is taken if the timer is not
- * in use.
+ * Get the function used to generate a geometry for rendering.
+ * @return {!ol.StyleGeometryFunction} Function that is called with a feature
+ * and returns the geometry to render instead of the feature's geometry.
+ * @api
  */
-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;
+ol.style.Style.prototype.getGeometryFunction = function() {
+  return this.geometryFunction_;
 };
 
 
 /**
- * Fires delay's action even if timer has already gone off or has not been
- * started yet; guarantees action firing. Stops the delay timer.
+ * Get the fill style.
+ * @return {ol.style.Fill} Fill style.
+ * @api
  */
-goog.async.AnimationDelay.prototype.fire = function() {
-  this.stop();
-  this.doAction_();
+ol.style.Style.prototype.getFill = function() {
+  return this.fill_;
 };
 
 
 /**
- * Fires delay's action only if timer is currently active. Stops the delay
- * timer.
+ * Set the fill style.
+ * @param {ol.style.Fill} fill Fill style.
+ * @api
  */
-goog.async.AnimationDelay.prototype.fireIfActive = function() {
-  if (this.isActive()) {
-    this.fire();
-  }
+ol.style.Style.prototype.setFill = function(fill) {
+  this.fill_ = fill;
 };
 
 
 /**
- * @return {boolean} True if the delay is currently active, false otherwise.
+ * Get the image style.
+ * @return {ol.style.Image} Image style.
+ * @api
  */
-goog.async.AnimationDelay.prototype.isActive = function() {
-  return this.id_ != null;
+ol.style.Style.prototype.getImage = function() {
+  return this.image_;
 };
 
 
 /**
- * Invokes the callback function after the delay successfully completes.
- * @private
+ * Set the image style.
+ * @param {ol.style.Image} image Image style.
+ * @api
  */
-goog.async.AnimationDelay.prototype.doAction_ = function() {
-  if (this.usingListeners_ && this.id_) {
-    goog.events.unlistenByKey(this.id_);
-  }
-  this.id_ = null;
+ol.style.Style.prototype.setImage = function(image) {
+  this.image_ = image;
+};
 
-  // 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());
+
+/**
+ * Get the stroke style.
+ * @return {ol.style.Stroke} Stroke style.
+ * @api
+ */
+ol.style.Style.prototype.getStroke = function() {
+  return this.stroke_;
 };
 
 
-/** @override */
-goog.async.AnimationDelay.prototype.disposeInternal = function() {
-  this.stop();
-  goog.async.AnimationDelay.base(this, 'disposeInternal');
+/**
+ * Set the stroke style.
+ * @param {ol.style.Stroke} stroke Stroke style.
+ * @api
+ */
+ol.style.Style.prototype.setStroke = function(stroke) {
+  this.stroke_ = stroke;
 };
 
 
 /**
- * @return {?function(function(number)): number} The requestAnimationFrame
- *     function, or null if not available on this browser.
- * @private
+ * Get the text style.
+ * @return {ol.style.Text} Text style.
+ * @api
  */
-goog.async.AnimationDelay.prototype.getRaf_ = function() {
-  var win = this.win_;
-  return win.requestAnimationFrame ||
-      win.webkitRequestAnimationFrame ||
-      win.mozRequestAnimationFrame ||
-      win.oRequestAnimationFrame ||
-      win.msRequestAnimationFrame ||
-      null;
+ol.style.Style.prototype.getText = function() {
+  return this.text_;
 };
 
 
 /**
- * @return {?function(number): number} The cancelAnimationFrame function,
- *     or null if not available on this browser.
- * @private
+ * Set the text style.
+ * @param {ol.style.Text} text Text style.
+ * @api
  */
-goog.async.AnimationDelay.prototype.getCancelRaf_ = function() {
-  var win = this.win_;
-  return win.cancelAnimationFrame ||
-      win.cancelRequestAnimationFrame ||
-      win.webkitCancelRequestAnimationFrame ||
-      win.mozCancelRequestAnimationFrame ||
-      win.oCancelRequestAnimationFrame ||
-      win.msCancelRequestAnimationFrame ||
-      null;
+ol.style.Style.prototype.setText = function(text) {
+  this.text_ = text;
 };
 
-// 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
+ * Get the z-index for the style.
+ * @return {number|undefined} ZIndex.
+ * @api
  */
-goog.async.throwException = function(exception) {
-  // Each throw needs to be in its own context.
-  goog.global.setTimeout(function() { throw exception; }, 0);
+ol.style.Style.prototype.getZIndex = function() {
+  return this.zIndex_;
 };
 
 
 /**
- * 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.
+ * Set a geometry that is rendered instead of the feature's geometry.
  *
- * @param {function(this:SCOPE)} callback Callback function to fire as soon as
- *     possible.
- * @param {SCOPE=} opt_context Object in whose scope to call the listener.
- * @param {boolean=} opt_useSetImmediate Avoid the IE workaround that
- *     ensures correctness at the cost of speed. See comments for details.
- * @template SCOPE
+ * @param {string|ol.geom.Geometry|ol.StyleGeometryFunction} geometry
+ *     Feature property or geometry or function returning a geometry to render
+ *     for this style.
+ * @api
  */
-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);
+ol.style.Style.prototype.setGeometry = function(geometry) {
+  if (typeof geometry === 'function') {
+    this.geometryFunction_ = geometry;
+  } else if (typeof geometry === 'string') {
+    this.geometryFunction_ = function(feature) {
+      return /** @type {ol.geom.Geometry} */ (feature.get(geometry));
     };
-  }
-  // 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);
+  } else if (!geometry) {
+    this.geometryFunction_ = ol.style.Style.defaultGeometryFunction;
+  } else if (geometry !== undefined) {
+    this.geometryFunction_ = function() {
+      return /** @type {ol.geom.Geometry} */ (geometry);
     };
   }
-  // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms
-  // or more.
-  return function(cb) {
-    goog.global.setTimeout(cb, 0);
-  };
+  this.geometry_ = geometry;
 };
 
 
 /**
- * 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.
+ * Set the z-index.
  *
- * TODO(xtof): Link to document stating type contract.
+ * @param {number|undefined} zIndex ZIndex.
+ * @api
  */
-
-goog.provide('goog.html.SafeScript');
-
-goog.require('goog.asserts');
-goog.require('goog.string.Const');
-goog.require('goog.string.TypedString');
-
+ol.style.Style.prototype.setZIndex = function(zIndex) {
+  this.zIndex_ = zIndex;
+};
 
 
 /**
- * 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}
+ * 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.StyleFunction|Array.<ol.style.Style>|ol.style.Style} obj
+ *     A style function, a single style, or an array of styles.
+ * @return {ol.StyleFunction} A style function.
  */
-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_ = '';
+ol.style.Style.createFunction = function(obj) {
+  var styleFunction;
 
-  /**
-   * 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_;
+  if (typeof obj === 'function') {
+    styleFunction = obj;
+  } else {
+    /**
+     * @type {Array.<ol.style.Style>}
+     */
+    var styles;
+    if (Array.isArray(obj)) {
+      styles = obj;
+    } else {
+      ol.asserts.assert(obj instanceof ol.style.Style,
+          41); // Expected an `ol.style.Style` or an array of `ol.style.Style`
+      styles = [obj];
+    }
+    styleFunction = function() {
+      return styles;
+    };
+  }
+  return styleFunction;
 };
 
 
 /**
- * @override
- * @const
- */
-goog.html.SafeScript.prototype.implementsGoogStringTypedString = true;
-
-
-/**
- * Type marker for the SafeScript type, used to implement additional
- * run-time type checking.
- * @const {!Object}
+ * @type {Array.<ol.style.Style>}
  * @private
  */
-goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
+ol.style.Style.default_ = null;
 
 
 /**
- * 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}.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {number} resolution Resolution.
+ * @return {Array.<ol.style.Style>} Style.
  */
-goog.html.SafeScript.fromConstant = function(script) {
-  var scriptString = goog.string.Const.unwrap(script);
-  if (scriptString.length === 0) {
-    return goog.html.SafeScript.EMPTY;
+ol.style.Style.defaultFunction = 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.Style.default_) {
+    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.Style.default_ = [
+      new ol.style.Style({
+        image: new ol.style.Circle({
+          fill: fill,
+          stroke: stroke,
+          radius: 5
+        }),
+        fill: fill,
+        stroke: stroke
+      })
+    ];
   }
-  return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
-      scriptString);
+  return ol.style.Style.default_;
 };
 
 
 /**
- * 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
+ * Default styles for editing features.
+ * @return {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} Styles
  */
-goog.html.SafeScript.prototype.getTypedStringValue = function() {
-  return this.privateDoNotAccessOrElseSafeScriptWrappedValue_;
-};
+ol.style.Style.createDefaultEditing = 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];
 
-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_ + '}';
-  };
-}
+  styles[ol.geom.GeometryType.CIRCLE] =
+      styles[ol.geom.GeometryType.POLYGON].concat(
+          styles[ol.geom.GeometryType.LINE_STRING]
+      );
 
 
-/**
- * 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';
-  }
-};
+  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]
+      );
 
-/**
- * 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);
+  return styles;
 };
 
 
 /**
- * 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
+ * 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.
  */
-goog.html.SafeScript.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
-    script) {
-  this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = script;
-  return this;
+ol.style.Style.defaultGeometryFunction = function(feature) {
+  return feature.getGeometry();
 };
 
+goog.provide('ol.Feature');
 
-/**
- * A SafeScript instance corresponding to the empty string.
- * @const {!goog.html.SafeScript}
- */
-goog.html.SafeScript.EMPTY =
-    goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse('');
+goog.require('ol.asserts');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.geom.Geometry');
+goog.require('ol.style.Style');
 
-// 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.
+ * @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'
+ * });
  *
- * 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.
+ * // 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();
+ * ```
  *
- * @visibility {//closure/goog/html:approved_for_unchecked_conversion}
- * @visibility {//closure/goog/bin/sizetests:__pkg__}
+ * @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
  */
+ol.Feature = function(opt_geometryOrProperties) {
 
+  ol.Object.call(this);
 
-goog.provide('goog.html.uncheckedconversions');
+  /**
+   * @private
+   * @type {number|string|undefined}
+   */
+  this.id_ = undefined;
+
+  /**
+   * @type {string}
+   * @private
+   */
+  this.geometryName_ = 'geometry';
 
-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');
+  /**
+   * 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;
 
-/**
- * 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);
+  /**
+   * @private
+   * @type {?ol.EventsKey}
+   */
+  this.geometryChangeKey_ = null;
+
+  ol.events.listen(
+      this, ol.Object.getChangeEventType(this.geometryName_),
+      this.handleGeometryChanged_, this);
+
+  if (opt_geometryOrProperties !== undefined) {
+    if (opt_geometryOrProperties instanceof ol.geom.Geometry ||
+        !opt_geometryOrProperties) {
+      var geometry = opt_geometryOrProperties;
+      this.setGeometry(geometry);
+    } else {
+      /** @type {Object.<string, *>} */
+      var properties = opt_geometryOrProperties;
+      this.setProperties(properties);
+    }
+  }
 };
+ol.inherits(ol.Feature, ol.Object);
 
 
 /**
- * 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.
+ * 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
  */
-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);
+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;
 };
 
 
 /**
- * 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.
+ * 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
+ * @observable
  */
-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);
+ol.Feature.prototype.getGeometry = function() {
+  return /** @type {ol.geom.Geometry|undefined} */ (
+      this.get(this.geometryName_));
 };
 
 
 /**
- * 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.
+ * 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
  */
-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);
+ol.Feature.prototype.getId = function() {
+  return this.id_;
 };
 
 
 /**
- * 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.
+ * 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
  */
-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);
+ol.Feature.prototype.getGeometryName = function() {
+  return this.geometryName_;
 };
 
 
 /**
- * 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.
+ * Get the feature's style. Will return what was provided to the
+ * {@link ol.Feature#setStyle} method.
+ * @return {ol.style.Style|Array.<ol.style.Style>|
+ *     ol.FeatureStyleFunction|ol.StyleFunction} The feature style.
+ * @api
  */
-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);
+ol.Feature.prototype.getStyle = function() {
+  return this.style_;
 };
 
-// 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.
+ * Get the feature's style function.
+ * @return {ol.FeatureStyleFunction|undefined} Return a function
+ * representing the current style of this feature.
+ * @api
  */
-
-
-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.
+ol.Feature.prototype.getStyleFunction = function() {
+  return this.styleFunction_;
+};
 
 
 /**
- * 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.
+ * @private
  */
-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);
+ol.Feature.prototype.handleGeometryChange_ = function() {
+  this.changed();
 };
 
 
 /**
- * Returns the values of the collection-like object.
- * @param {Object} col The collection-like object.
- * @return {!Array<?>} The values in the collection-like object.
+ * @private
  */
-goog.structs.getValues = function(col) {
-  if (typeof col.getValues == 'function') {
-    return col.getValues();
-  }
-  if (goog.isString(col)) {
-    return col.split('');
+ol.Feature.prototype.handleGeometryChanged_ = function() {
+  if (this.geometryChangeKey_) {
+    ol.events.unlistenByKey(this.geometryChangeKey_);
+    this.geometryChangeKey_ = null;
   }
-  if (goog.isArrayLike(col)) {
-    var rv = [];
-    var l = col.length;
-    for (var i = 0; i < l; i++) {
-      rv.push(col[i]);
-    }
-    return rv;
+  var geometry = this.getGeometry();
+  if (geometry) {
+    this.geometryChangeKey_ = ol.events.listen(geometry,
+        ol.events.EventType.CHANGE, this.handleGeometryChange_, this);
   }
-  return goog.object.getValues(col);
+  this.changed();
 };
 
 
 /**
- * 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.
+ * 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
+ * @observable
  */
-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);
+ol.Feature.prototype.setGeometry = function(geometry) {
+  this.set(this.geometryName_, geometry);
 };
 
 
 /**
- * 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.
+ * 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|ol.StyleFunction} style Style for this feature.
+ * @api
+ * @fires ol.events.Event#event:change
  */
-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);
+ol.Feature.prototype.setStyle = function(style) {
+  this.style_ = style;
+  this.styleFunction_ = !style ?
+      undefined : ol.Feature.createStyleFunction(style);
+  this.changed();
 };
 
 
 /**
- * Whether the collection is empty.
- * @param {Object} col The collection-like object.
- * @return {boolean} True if empty.
+ * 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
+ * @fires ol.events.Event#event:change
  */
-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);
+ol.Feature.prototype.setId = function(id) {
+  this.id_ = id;
+  this.changed();
 };
 
 
 /**
- * Removes all the elements from the collection.
- * @param {Object} col The collection-like object.
+ * 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
  */
-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);
-  }
+ol.Feature.prototype.setGeometryName = function(name) {
+  ol.events.unlisten(
+      this, ol.Object.getChangeEventType(this.geometryName_),
+      this.handleGeometryChanged_, this);
+  this.geometryName_ = name;
+  ol.events.listen(
+      this, ol.Object.getChangeEventType(this.geometryName_),
+      this.handleGeometryChanged_, this);
+  this.handleGeometryChanged_();
 };
 
 
 /**
- * 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
+ * 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.
  */
-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);
+ol.Feature.createStyleFunction = function(obj) {
+  var styleFunction;
+
+  if (typeof obj === 'function') {
+    if (obj.length == 2) {
+      styleFunction = function(resolution) {
+        return /** @type {ol.StyleFunction} */ (obj)(this, resolution);
+      };
+    } else {
+      styleFunction = 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);
+    /**
+     * @type {Array.<ol.style.Style>}
+     */
+    var styles;
+    if (Array.isArray(obj)) {
+      styles = obj;
+    } else {
+      ol.asserts.assert(obj instanceof ol.style.Style,
+          41); // Expected an `ol.style.Style` or an array of `ol.style.Style`
+      styles = [obj];
     }
+    styleFunction = function() {
+      return styles;
+    };
   }
+  return styleFunction;
 };
 
+goog.provide('ol.format.FormatType');
+
 
 /**
- * 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
+ * @enum {string}
  */
-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;
+ol.format.FormatType = {
+  ARRAY_BUFFER: 'arraybuffer',
+  JSON: 'json',
+  TEXT: 'text',
+  XML: 'xml'
 };
 
+goog.provide('ol.xml');
 
-/**
- * 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;
-};
+goog.require('ol.array');
 
 
 /**
- * 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
+ * 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}
  */
-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;
-};
+ol.xml.DOCUMENT = document.implementation.createDocument('', '', null);
 
 
 /**
- * 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
+ * @param {string} namespaceURI Namespace URI.
+ * @param {string} qualifiedName Qualified name.
+ * @return {Node} Node.
  */
-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;
+ol.xml.createElementNS = function(namespaceURI, qualifiedName) {
+  return ol.xml.DOCUMENT.createElementNS(namespaceURI, qualifiedName);
 };
 
-// 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.
+ * Recursively grab all text content of child nodes into a single string.
+ * @param {Node} node Node.
+ * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line
+ * breaks.
+ * @return {string} All text content.
+ * @api
  */
-goog.structs.Collection.prototype.add;
+ol.xml.getAllTextContent = function(node, normalizeWhitespace) {
+  return ol.xml.getAllTextContent_(node, normalizeWhitespace, []).join('');
+};
 
 
 /**
- * @param {T} value Value to remove from the collection.
+ * 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>} accumulator Accumulator.
+ * @private
+ * @return {Array.<string>} Accumulator.
  */
-goog.structs.Collection.prototype.remove;
+ol.xml.getAllTextContent_ = function(node, normalizeWhitespace, accumulator) {
+  if (node.nodeType == Node.CDATA_SECTION_NODE ||
+      node.nodeType == Node.TEXT_NODE) {
+    if (normalizeWhitespace) {
+      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 {T} value Value to find in the collection.
- * @return {boolean} Whether the collection contains the specified value.
+ * @param {?} value Value.
+ * @return {boolean} Is document.
  */
-goog.structs.Collection.prototype.contains;
+ol.xml.isDocument = function(value) {
+  return value instanceof Document;
+};
 
 
 /**
- * @return {number} The number of values stored in the collection.
+ * @param {?} value Value.
+ * @return {boolean} Is node.
  */
-goog.structs.Collection.prototype.getCount;
-
+ol.xml.isNode = function(value) {
+  return value instanceof Node;
+};
 
-// 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)
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @return {string} Value
  */
-
-
-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');
+ol.xml.getAttributeNS = function(node, namespaceURI, name) {
+  return node.getAttributeNS(namespaceURI, name) || '';
+};
 
 
 /**
- * @typedef {goog.iter.Iterator|{length:number}|{__iterator__}}
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @param {string|number} value Value.
  */
-goog.iter.Iterable;
+ol.xml.setAttributeNS = function(node, namespaceURI, name, value) {
+  node.setAttributeNS(namespaceURI, name, value);
+};
 
 
 /**
- * Singleton Error object that is used to terminate iterations.
- * @const {!Error}
+ * Parse an XML string to an XML Document.
+ * @param {string} xml XML.
+ * @return {Document} Document.
+ * @api
  */
-goog.iter.StopIteration = ('StopIteration' in goog.global) ?
-    // For script engines that support legacy iterators.
-    goog.global['StopIteration'] :
-    { message: 'StopIteration', stack: ''};
-
+ol.xml.parse = function(xml) {
+  return new DOMParser().parseFromString(xml, 'application/xml');
+};
 
 
 /**
- * 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
+ * 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.XmlParser} Parser.
+ * @template T
  */
-goog.iter.Iterator = function() {};
+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) {
+          var array = /** @type {Array.<*>} */
+              (objectStack[objectStack.length - 1]);
+          ol.array.extend(array, value);
+        }
+      });
+};
 
 
 /**
- * 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.
+ * 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.XmlParser} Parser.
+ * @template T
  */
-goog.iter.Iterator.prototype.next = function() {
-  throw goog.iter.StopIteration;
+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];
+          array.push(value);
+        }
+      });
 };
 
 
 /**
- * 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.
+ * 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.XmlParser} Parser.
+ * @template T
  */
-goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) {
-  return this;
+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;
+        }
+      });
 };
 
 
 /**
- * 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
+ * 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.XmlParser} Parser.
+ * @template T
  */
-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;
+ol.xml.makeObjectPropertyPusher = function(valueReader, opt_property, 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 object = /** @type {Object} */
+              (objectStack[objectStack.length - 1]);
+          var property = opt_property !== undefined ?
+              opt_property : node.localName;
+          var array;
+          if (property in object) {
+            array = object[property];
+          } else {
+            array = object[property] = [];
+          }
+          array.push(value);
         }
-        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
+ * 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.XmlParser} Parser.
+ * @template T
  */
-goog.iter.forEach = function(iterable, f, opt_obj) {
-  if (goog.isArrayLike(iterable)) {
-    /** @preserveTry */
-    try {
-      // NOTES: this passes the index number to the second parameter
-      // of the callback contrary to the documentation above.
-      goog.array.forEach(/** @type {goog.array.ArrayLike} */(iterable), f,
-                         opt_obj);
-    } catch (ex) {
-      if (ex !== goog.iter.StopIteration) {
-        throw ex;
-      }
-    }
-  } else {
-    iterable = goog.iter.toIterator(iterable);
-    /** @preserveTry */
-    try {
-      while (true) {
-        f.call(opt_obj, iterable.next(), undefined, iterable);
-      }
-    } catch (ex) {
-      if (ex !== goog.iter.StopIteration) {
-        throw ex;
-      }
-    }
-  }
+ol.xml.makeObjectPropertySetter = function(valueReader, opt_property, 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 object = /** @type {Object} */
+              (objectStack[objectStack.length - 1]);
+          var property = opt_property !== undefined ?
+              opt_property : node.localName;
+          object[property] = value;
+        }
+      });
 };
 
 
 /**
- * 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
+ * Create a serializer that appends nodes written by its `nodeWriter` to its
+ * designated parent. The parent is the `node` of the
+ * {@link ol.XmlNodeStackItem} 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.XmlSerializer} Serializer.
+ * @template T, V
  */
-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;
-      }
-    }
+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];
+    var parentNode = parent.node;
+    parentNode.appendChild(node);
   };
-  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
+ * 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.XmlSerializer} Serializer.
+ * @template T, V
  */
-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;
+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);
   };
-  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
+ * Create a node factory which can use the `opt_keys` passed to
+ * {@link ol.xml.serialize} or {@link ol.xml.pushSerializeAndPop} as node names,
+ * or a fixed node name. The namespace of the created nodes can either be fixed,
+ * or the parent namespace will be used.
+ * @param {string=} opt_nodeName Fixed node name which will be used for all
+ *     created nodes. If not provided, the 3rd argument to the resulting node
+ *     factory needs to be provided and will be the nodeName.
+ * @param {string=} opt_namespaceURI Fixed namespace URI which will be used for
+ *     all created nodes. If not provided, the namespace of the parent node will
+ *     be used.
+ * @return {function(*, Array.<*>, string=): (Node|undefined)} Node factory.
  */
-goog.iter.join = function(iterable, deliminator) {
-  return goog.iter.toArray(iterable).join(deliminator);
+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;
+        var nodeName = fixedNodeName;
+        if (nodeName === undefined) {
+          nodeName = opt_nodeName;
+        }
+        var namespaceURI = opt_namespaceURI;
+        if (opt_namespaceURI === undefined) {
+          namespaceURI = node.namespaceURI;
+        }
+        return ol.xml.createElementNS(namespaceURI, /** @type {string} */ (nodeName));
+      }
+  );
 };
 
 
 /**
- * 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
+ * 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)}
  */
-goog.iter.map = function(iterable, f, opt_obj) {
-  var iterator = goog.iter.toIterator(iterable);
-  var newIter = new goog.iter.Iterator;
-  newIter.next = function() {
-    var val = iterator.next();
-    return f.call(opt_obj, val, undefined, iterator);
-  };
-  return newIter;
-};
+ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory();
 
 
 /**
- * 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
+ * Create an array of `values` to be used with {@link ol.xml.serialize} or
+ * {@link ol.xml.pushSerializeAndPop}, where `orderedKeys` has to be provided as
+ * `opt_key` argument.
+ * @param {Object.<string, V>} object Key-value pairs for the sequence. Keys can
+ *     be a subset of the `orderedKeys`.
+ * @param {Array.<string>} orderedKeys Keys in the order of the sequence.
+ * @return {Array.<V>} Values in the order of the sequence. The resulting array
+ *     has the same length as the `orderedKeys` array. Values that are not
+ *     present in `object` will be `undefined` in the resulting array.
+ * @template V
  */
-goog.iter.reduce = function(iterable, f, val, opt_obj) {
-  var rval = val;
-  goog.iter.forEach(iterable, function(val) {
-    rval = f.call(opt_obj, rval, val);
-  });
-  return rval;
+ol.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;
 };
 
 
 /**
- * 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;
-    }
+ * 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 false;
+  return structureNS;
 };
 
 
 /**
- * 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;
+ * Parse a node using the parsers and object stack.
+ * @param {Object.<string, Object.<string, ol.XmlParser>>} 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);
       }
     }
-  } 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
+ * Push an object on top of the stack, parse and return the popped object.
+ * @param {T} object Object.
+ * @param {Object.<string, Object.<string, ol.XmlParser>>} parsersNS
+ *     Parsers by namespace.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {*=} opt_this The object to use as `this`.
+ * @return {T} Object.
+ * @template T
  */
-goog.iter.chain = function(var_args) {
-  return goog.iter.chainFromIterable(arguments);
+ol.xml.pushParseAndPop = function(
+    object, parsersNS, node, objectStack, opt_this) {
+  objectStack.push(object);
+  ol.xml.parseNode(parsersNS, node, objectStack, opt_this);
+  return objectStack.pop();
 };
 
 
 /**
- * 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
+ * Walk through an array of `values` and call a serializer for each value.
+ * @param {Object.<string, Object.<string, ol.XmlSerializer>>} serializersNS
+ *     Namespaced serializers.
+ * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory
+ *     Node factory. The `nodeFactory` creates the node whose namespace and name
+ *     will be used to choose a node writer from `serializersNS`. This
+ *     separation allows us to decide what kind of node to create, depending on
+ *     the value we want to serialize. An example for this would be different
+ *     geometry writers based on the geometry type.
+ * @param {Array.<*>} values Values to serialize. An example would be an array
+ *     of {@link ol.Feature} instances.
+ * @param {Array.<*>} objectStack Node stack.
+ * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the
+ *     `nodeFactory`. This is used for serializing object literals where the
+ *     node name relates to the property key. The array length of `opt_keys` has
+ *     to match the length of `values`. For serializing a sequence, `opt_keys`
+ *     determines the order of the sequence.
+ * @param {T=} opt_this The object to use as `this` for the node factory and
+ *     serializers.
+ * @template T
  */
-goog.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;
+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);
       }
-      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
+ * @param {O} object Object.
+ * @param {Object.<string, Object.<string, ol.XmlSerializer>>} serializersNS
+ *     Namespaced serializers.
+ * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory
+ *     Node factory. The `nodeFactory` creates the node whose namespace and name
+ *     will be used to choose a node writer from `serializersNS`. This
+ *     separation allows us to decide what kind of node to create, depending on
+ *     the value we want to serialize. An example for this would be different
+ *     geometry writers based on the geometry type.
+ * @param {Array.<*>} values Values to serialize. An example would be an array
+ *     of {@link ol.Feature} instances.
+ * @param {Array.<*>} objectStack Node stack.
+ * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the
+ *     `nodeFactory`. This is used for serializing object literals where the
+ *     node name relates to the property key. The array length of `opt_keys` has
+ *     to match the length of `values`. For serializing a sequence, `opt_keys`
+ *     determines the order of the sequence.
+ * @param {T=} opt_this The object to use as `this` for the node factory and
+ *     serializers.
+ * @return {O|undefined} Object.
+ * @template O, T
  */
-goog.iter.nextOrValue = function(iterable, defaultValue) {
-  try {
-    return goog.iter.toIterator(iterable).next();
-  } catch (e) {
-    if (e != goog.iter.StopIteration) {
-      throw e;
-    }
-    return defaultValue;
-  }
+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');
 
-/**
- * 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;
-};
+goog.require('ol');
+goog.require('ol.format.FormatType');
+goog.require('ol.xml');
 
 
 /**
- * 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
+ * @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.
  */
-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;
+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 xhr = new XMLHttpRequest();
+        xhr.open('GET',
+            typeof url === 'function' ? url(extent, resolution, projection) : url,
+            true);
+        if (format.getType() == ol.format.FormatType.ARRAY_BUFFER) {
+          xhr.responseType = 'arraybuffer';
         }
-        // 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;
+        /**
+         * @param {Event} event Event.
+         * @private
+         */
+        xhr.onload = function(event) {
+          // status will be 0 for file:// urls
+          if (!xhr.status || xhr.status >= 200 && xhr.status < 300) {
+            var type = format.getType();
+            /** @type {Document|Node|Object|string|undefined} */
+            var source;
+            if (type == ol.format.FormatType.JSON ||
+                type == ol.format.FormatType.TEXT) {
+              source = xhr.responseText;
+            } else if (type == ol.format.FormatType.XML) {
+              source = xhr.responseXML;
+              if (!source) {
+                source = ol.xml.parse(xhr.responseText);
+              }
+            } else if (type == ol.format.FormatType.ARRAY_BUFFER) {
+              source = /** @type {ArrayBuffer} */ (xhr.response);
+            }
+            if (source) {
+              success.call(this, format.readFeatures(source,
+                  {featureProjection: projection}),
+                  format.readProjection(source));
+            } else {
+              failure.call(this);
+            }
+          } else {
+            failure.call(this);
           }
-          returnValue = fillValue;
-        }
-        return returnValue;
+        }.bind(this);
+        /**
+         * @private
+         */
+        xhr.onerror = function() {
+          failure.call(this);
+        }.bind(this);
+        xhr.send();
       });
-
-      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
+ * 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
  */
-goog.iter.compress = function(iterable, selectors) {
-  var selectorIterator = goog.iter.toIterator(selectors);
-
-  return goog.iter.filter(iterable, function() {
-    return !!selectorIterator.next();
-  });
+ol.featureloader.xhr = function(url, format) {
+  return ol.featureloader.loadFeaturesXhr(url, format,
+      /**
+       * @param {Array.<ol.Feature>} features The loaded features.
+       * @param {ol.proj.Projection} dataProjection Data projection.
+       * @this {ol.source.Vector}
+       */
+      function(features, dataProjection) {
+        this.addFeatures(features);
+      }, /* FIXME handle error */ ol.nullFunction);
 };
 
+goog.provide('ol.format.Feature');
+
+goog.require('ol.geom.Geometry');
+goog.require('ol.obj');
+goog.require('ol.proj');
 
 
 /**
- * Implements the {@code goog.iter.groupBy} iterator.
- * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable to group.
- * @param {function(...VALUE): KEY=} opt_keyFunc  Optional function for
- *     determining the key value for each group in the {@code iterable}. Default
- *     is the identity function.
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for 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
- * @extends {goog.iter.Iterator<!Array<?>>}
- * @template KEY, VALUE
- * @private
+ * @abstract
+ * @api
  */
-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;
+ol.format.Feature = function() {
 
   /**
-   * The current key visited during iteration.
-   * @type {KEY}
+   * @protected
+   * @type {ol.proj.Projection}
    */
-  this.currentKey;
+  this.defaultDataProjection = null;
 
   /**
-   * The current value being added to the group.
-   * @type {VALUE}
+   * @protected
+   * @type {ol.proj.Projection}
    */
-  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));
-  };
+  this.defaultFeatureProjection = null;
 
-  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
+ * 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
  */
-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();
+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 iter;
-  };
-
-  return goog.array.map(buffers, createIterator);
+  }
+  return this.adaptOptions(options);
 };
 
 
 /**
- * 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
+ * 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.
  */
-goog.iter.enumerate = function(iterable, opt_start) {
-  return goog.iter.zip(goog.iter.count(opt_start), iterable);
+ol.format.Feature.prototype.adaptOptions = function(options) {
+  return ol.obj.assign({
+    dataProjection: this.defaultDataProjection,
+    featureProjection: this.defaultFeatureProjection
+  }, options);
 };
 
 
 /**
- * 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
+ * @abstract
+ * @return {ol.format.FormatType} Format.
  */
-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;
-};
+ol.format.Feature.prototype.getType = function() {};
 
 
 /**
- * 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
+ * Read a single feature from a source.
+ *
+ * @abstract
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
  */
-goog.iter.consume = function(iterable, count) {
-  goog.asserts.assert(goog.math.isInt(count) && count >= 0);
-
-  var iterator = goog.iter.toIterator(iterable);
-
-  while (count-- > 0) {
-    goog.iter.nextOrValue(iterator, null);
-  }
-
-  return iterator;
-};
+ol.format.Feature.prototype.readFeature = function(source, opt_options) {};
 
 
 /**
- * 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
+ * Read all features from a source.
+ *
+ * @abstract
+ * @param {Document|Node|ArrayBuffer|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
  */
-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;
-};
+ol.format.Feature.prototype.readFeatures = function(source, opt_options) {};
 
 
 /**
- * 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
+ * Read a single geometry from a source.
+ *
+ * @abstract
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.geom.Geometry} Geometry.
  */
-// TODO(user): Consider moving this into goog.array as a public function.
-goog.iter.hasDuplicates_ = function(arr) {
-  var deduped = [];
-  goog.array.removeDuplicates(arr, deduped);
-  return arr.length != deduped.length;
-};
+ol.format.Feature.prototype.readGeometry = function(source, opt_options) {};
 
 
 /**
- * Creates an iterator that returns permutations of elements in
- * {@code iterable}.
+ * Read the projection from a source.
  *
- * 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
+ * @abstract
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
  */
-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);
-  });
-};
+ol.format.Feature.prototype.readProjection = function(source) {};
 
 
 /**
- * Creates an iterator that returns combinations of elements from
- * {@code iterable}.
+ * Encode a feature in this format.
  *
- * 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
+ * @abstract
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} Result.
  */
-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;
-};
+ol.format.Feature.prototype.writeFeature = function(feature, opt_options) {};
 
 
 /**
- * Creates an iterator that returns combinations of elements from
- * {@code iterable}, with repeated elements possible.
+ * Encode an array of features in this format.
  *
- * 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
+ * @abstract
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} Result.
  */
-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;
-};
+ol.format.Feature.prototype.writeFeatures = function(features, opt_options) {};
 
-// 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)
+ * Write a single geometry in this format.
  *
- * 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.
+ * @abstract
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} Result.
  */
-
-
-goog.provide('goog.structs.Map');
-
-goog.require('goog.iter.Iterator');
-goog.require('goog.iter.StopIteration');
-goog.require('goog.object');
-
+ol.format.Feature.prototype.writeGeometry = function(geometry, opt_options) {};
 
 
 /**
- * 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
+ * @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
  */
-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;
-
+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;
   /**
-   * Version used to detect changes while iterating.
-   * @private {number}
+   * @type {ol.geom.Geometry|ol.Extent}
    */
-  this.version_ = 0;
-
-  var argLength = arguments.length;
-
-  if (argLength > 1) {
-    if (argLength % 2) {
-      throw Error('Uneven number of arguments');
+  var transformed;
+  if (featureProjection && dataProjection &&
+      !ol.proj.equivalent(featureProjection, dataProjection)) {
+    if (geometry instanceof ol.geom.Geometry) {
+      transformed = (write ? geometry.clone() : geometry).transform(
+          write ? featureProjection : dataProjection,
+          write ? dataProjection : featureProjection);
+    } else {
+      // FIXME this is necessary because ol.format.GML treats extents
+      // as geometries
+      transformed = ol.proj.transformExtent(
+          geometry,
+          dataProjection,
+          featureProjection);
     }
-    for (var i = 0; i < argLength; i += 2) {
-      this.set(arguments[i], arguments[i + 1]);
+  } else {
+    transformed = geometry;
+  }
+  if (write && opt_options && opt_options.decimals !== undefined) {
+    var power = Math.pow(10, opt_options.decimals);
+    // if decimals option on write, round each coordinate appropriately
+    /**
+     * @param {Array.<number>} coordinates Coordinates.
+     * @return {Array.<number>} Transformed coordinates.
+     */
+    var transform = function(coordinates) {
+      for (var i = 0, ii = coordinates.length; i < ii; ++i) {
+        coordinates[i] = Math.round(coordinates[i] * power) / power;
+      }
+      return coordinates;
+    };
+    if (transformed === geometry) {
+      transformed = transformed.clone();
     }
-  } else if (opt_map) {
-    this.addAll(/** @type {Object} */ (opt_map));
+    transformed.applyTransform(transform);
   }
+  return transformed;
 };
 
+goog.provide('ol.format.JSONFeature');
+
+goog.require('ol');
+goog.require('ol.format.Feature');
+goog.require('ol.format.FormatType');
+
 
 /**
- * @return {number} The number of key-value pairs in the map.
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for JSON feature formats.
+ *
+ * @constructor
+ * @abstract
+ * @extends {ol.format.Feature}
  */
-goog.structs.Map.prototype.getCount = function() {
-  return this.count_;
+ol.format.JSONFeature = function() {
+  ol.format.Feature.call(this);
 };
+ol.inherits(ol.format.JSONFeature, ol.format.Feature);
 
 
 /**
- * Returns the values of the map.
- * @return {!Array<V>} The values in the map.
+ * @param {Document|Node|Object|string} source Source.
+ * @private
+ * @return {Object} Object.
  */
-goog.structs.Map.prototype.getValues = function() {
-  this.cleanupKeysArray_();
-
-  var rv = [];
-  for (var i = 0; i < this.keys_.length; i++) {
-    var key = this.keys_[i];
-    rv.push(this.map_[key]);
+ol.format.JSONFeature.prototype.getObject_ = function(source) {
+  if (typeof source === 'string') {
+    var object = JSON.parse(source);
+    return object ? /** @type {Object} */ (object) : null;
+  } else if (source !== null) {
+    return source;
+  } else {
+    return null;
   }
-  return rv;
 };
 
 
 /**
- * Returns the keys of the map.
- * @return {!Array<string>} Array of string values.
+ * @inheritDoc
  */
-goog.structs.Map.prototype.getKeys = function() {
-  this.cleanupKeysArray_();
-  return /** @type {!Array<string>} */ (this.keys_.concat());
+ol.format.JSONFeature.prototype.getType = function() {
+  return ol.format.FormatType.JSON;
 };
 
 
 /**
- * Whether the map contains the given key.
- * @param {*} key The key to check for.
- * @return {boolean} Whether the map contains the key.
+ * @inheritDoc
  */
-goog.structs.Map.prototype.containsKey = function(key) {
-  return goog.structs.Map.hasKey_(this.map_, key);
+ol.format.JSONFeature.prototype.readFeature = function(source, opt_options) {
+  return this.readFeatureFromObject(
+      this.getObject_(source), this.getReadOptions(source, opt_options));
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.structs.Map.prototype.containsValue = function(val) {
-  for (var i = 0; i < this.keys_.length; i++) {
-    var key = this.keys_[i];
-    if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {
-      return true;
-    }
-  }
-  return false;
+ol.format.JSONFeature.prototype.readFeatures = function(source, opt_options) {
+  return this.readFeaturesFromObject(
+      this.getObject_(source), this.getReadOptions(source, opt_options));
 };
 
 
 /**
- * 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.
+ * @abstract
+ * @param {Object} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {ol.Feature} Feature.
  */
-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;
-};
+ol.format.JSONFeature.prototype.readFeatureFromObject = function(object, opt_options) {};
 
 
 /**
- * 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.
+ * @abstract
+ * @param {Object} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {Array.<ol.Feature>} Features.
  */
-goog.structs.Map.defaultEquals = function(a, b) {
-  return a === b;
-};
+ol.format.JSONFeature.prototype.readFeaturesFromObject = function(object, opt_options) {};
 
 
 /**
- * @return {boolean} Whether the map is empty.
+ * @inheritDoc
  */
-goog.structs.Map.prototype.isEmpty = function() {
-  return this.count_ == 0;
+ol.format.JSONFeature.prototype.readGeometry = function(source, opt_options) {
+  return this.readGeometryFromObject(
+      this.getObject_(source), this.getReadOptions(source, opt_options));
 };
 
 
 /**
- * Removes all key-value pairs from the map.
+ * @abstract
+ * @param {Object} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {ol.geom.Geometry} Geometry.
  */
-goog.structs.Map.prototype.clear = function() {
-  this.map_ = {};
-  this.keys_.length = 0;
-  this.count_ = 0;
-  this.version_ = 0;
-};
+ol.format.JSONFeature.prototype.readGeometryFromObject = function(object, opt_options) {};
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.structs.Map.prototype.remove = function(key) {
-  if (goog.structs.Map.hasKey_(this.map_, key)) {
-    delete this.map_[key];
-    this.count_--;
-    this.version_++;
-
-    // clean up the keys array if the threshhold is hit
-    if (this.keys_.length > 2 * this.count_) {
-      this.cleanupKeysArray_();
-    }
-
-    return true;
-  }
-  return false;
+ol.format.JSONFeature.prototype.readProjection = function(source) {
+  return this.readProjectionFromObject(this.getObject_(source));
 };
 
 
 /**
- * Cleans up the temp keys array by removing entries that are no longer in the
- * map.
- * @private
+ * @abstract
+ * @param {Object} object Object.
+ * @protected
+ * @return {ol.proj.Projection} Projection.
  */
-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;
-  }
-};
+ol.format.JSONFeature.prototype.readProjectionFromObject = function(object) {};
 
 
 /**
- * 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
+ * @inheritDoc
  */
-goog.structs.Map.prototype.get = function(key, opt_val) {
-  if (goog.structs.Map.hasKey_(this.map_, key)) {
-    return this.map_[key];
-  }
-  return opt_val;
+ol.format.JSONFeature.prototype.writeFeature = function(feature, opt_options) {
+  return JSON.stringify(this.writeFeatureObject(feature, opt_options));
 };
 
 
 /**
- * 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.
+ * @abstract
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {Object} Object.
  */
-goog.structs.Map.prototype.set = function(key, value) {
-  if (!(goog.structs.Map.hasKey_(this.map_, key))) {
-    this.count_++;
-    this.keys_.push(key);
-    // Only change the version if we add a new key.
-    this.version_++;
-  }
-  this.map_[key] = value;
-};
+ol.format.JSONFeature.prototype.writeFeatureObject = function(feature, opt_options) {};
 
 
 /**
- * Adds multiple key-value pairs from another goog.structs.Map or Object.
- * @param {Object} map  Object containing the data to add.
+ * @inheritDoc
  */
-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]);
-  }
+ol.format.JSONFeature.prototype.writeFeatures = function(features, opt_options) {
+  return JSON.stringify(this.writeFeaturesObject(features, opt_options));
 };
 
 
 /**
- * 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
+ * @abstract
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {Object} Object.
  */
-goog.structs.Map.prototype.forEach = function(f, opt_obj) {
-  var keys = this.getKeys();
-  for (var i = 0; i < keys.length; i++) {
-    var key = keys[i];
-    var value = this.get(key);
-    f.call(opt_obj, value, key, this);
-  }
-};
+ol.format.JSONFeature.prototype.writeFeaturesObject = function(features, opt_options) {};
 
 
 /**
- * Clones a map and returns a new map.
- * @return {!goog.structs.Map} A new map with the same key-value pairs.
+ * @inheritDoc
  */
-goog.structs.Map.prototype.clone = function() {
-  return new goog.structs.Map(this);
+ol.format.JSONFeature.prototype.writeGeometry = function(geometry, opt_options) {
+  return JSON.stringify(this.writeGeometryObject(geometry, opt_options));
 };
 
 
 /**
- * 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.
+ * @abstract
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {Object} Object.
  */
-goog.structs.Map.prototype.transpose = function() {
-  var transposed = new goog.structs.Map();
-  for (var i = 0; i < this.keys_.length; i++) {
-    var key = this.keys_[i];
-    var value = this.map_[key];
-    transposed.set(value, key);
-  }
+ol.format.JSONFeature.prototype.writeGeometryObject = function(geometry, opt_options) {};
 
-  return transposed;
-};
+goog.provide('ol.geom.flat.interpolate');
+
+goog.require('ol.array');
+goog.require('ol.math');
 
 
 /**
- * @return {!Object} Object representation of the map.
+ * @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.
  */
-goog.structs.Map.prototype.toObject = function() {
-  this.cleanupKeysArray_();
-  var obj = {};
-  for (var i = 0; i < this.keys_.length; i++) {
-    var key = this.keys_[i];
-    obj[key] = this.map_[key];
+ol.geom.flat.interpolate.lineString = function(flatCoordinates, offset, end, stride, fraction, opt_dest) {
+  var pointX = NaN;
+  var pointY = NaN;
+  var n = (end - offset) / stride;
+  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 if (n !== 0) {
+    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 = ol.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 = ol.math.lerp(
+          flatCoordinates[o], flatCoordinates[o + stride], t);
+      pointY = ol.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];
   }
-  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.
+ * @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.
  */
-goog.structs.Map.prototype.getKeyIterator = function() {
-  return this.__iterator__(true);
+ol.geom.flat.interpolate.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];
+  var t = (m - m0) / (m1 - m0);
+  coordinate = [];
+  var i;
+  for (i = 0; i < stride - 1; ++i) {
+    coordinate.push(ol.math.lerp(flatCoordinates[(lo - 1) * stride + i],
+        flatCoordinates[lo * stride + i], t));
+  }
+  coordinate.push(m);
+  return coordinate;
 };
 
 
 /**
- * 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.
+ * @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.
  */
-goog.structs.Map.prototype.getValueIterator = function() {
-  return this.__iterator__(false);
+ol.geom.flat.interpolate.lineStringsCoordinateAtM = function(
+    flatCoordinates, offset, ends, stride, m, extrapolate, interpolate) {
+  if (interpolate) {
+    return ol.geom.flat.interpolate.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.interpolate.lineStringCoordinateAtM(
+          flatCoordinates, offset, end, stride, m, false);
+    }
+    offset = end;
+  }
+  return null;
 };
 
+goog.provide('ol.geom.flat.length');
+
 
 /**
- * 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.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} Length.
  */
-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;
+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;
 };
 
 
 /**
- * Safe way to test for hasOwnProperty.  It even allows testing for
- * 'hasOwnProperty'.
- * @param {Object} obj The object to test for presence of the given key.
- * @param {*} key The key to check for.
- * @return {boolean} Whether the object has the key.
- * @private
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} Perimeter.
  */
-goog.structs.Map.hasKey_ = function(obj, key) {
-  return Object.prototype.hasOwnProperty.call(obj, key);
+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;
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+goog.provide('ol.geom.LineString');
+
+goog.require('ol');
+goog.require('ol.array');
+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');
+
 
 /**
- * @fileoverview Datastructure: Set.
- *
- * @author arv@google.com (Erik Arvidsson)
+ * @classdesc
+ * Linestring geometry.
  *
- * 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.
+ * @constructor
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api
  */
+ol.geom.LineString = function(coordinates, opt_layout) {
 
+  ol.geom.SimpleGeometry.call(this);
 
-goog.provide('goog.structs.Set');
+  /**
+   * @private
+   * @type {ol.Coordinate}
+   */
+  this.flatMidpoint_ = null;
 
-goog.require('goog.structs');
-goog.require('goog.structs.Collection');
-goog.require('goog.structs.Map');
+  /**
+   * @private
+   * @type {number}
+   */
+  this.flatMidpointRevision_ = -1;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDelta_ = -1;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDeltaRevision_ = -1;
+
+  this.setCoordinates(coordinates, opt_layout);
 
-/**
- * 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);
-  }
 };
+ol.inherits(ol.geom.LineString, ol.geom.SimpleGeometry);
 
 
 /**
- * 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
+ * Append the passed coordinate to the coordinates of the linestring.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @api
  */
-goog.structs.Set.getKey_ = function(val) {
-  var type = typeof val;
-  if (type == 'object' && val || type == 'function') {
-    return 'o' + goog.getUid(/** @type {Object} */ (val));
+ol.geom.LineString.prototype.appendCoordinate = function(coordinate) {
+  if (!this.flatCoordinates) {
+    this.flatCoordinates = coordinate.slice();
   } else {
-    return type.substr(0, 1) + val;
+    ol.array.extend(this.flatCoordinates, coordinate);
   }
+  this.changed();
 };
 
 
 /**
- * @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.
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.LineString} Clone.
  * @override
+ * @api
  */
-goog.structs.Set.prototype.add = function(element) {
-  this.map_.set(goog.structs.Set.getKey_(element), element);
+ol.geom.LineString.prototype.clone = function() {
+  var lineString = new ol.geom.LineString(null);
+  lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+  return lineString;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-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]);
+ol.geom.LineString.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
+  if (minSquaredDistance <
+      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
+    return minSquaredDistance;
   }
-};
-
-
-/**
- * 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]);
+  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);
 };
 
 
 /**
- * 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
+ * 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
  */
-goog.structs.Set.prototype.remove = function(element) {
-  return this.map_.remove(goog.structs.Set.getKey_(element));
+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);
 };
 
 
 /**
- * Removes all elements from this set.
+ * 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
  */
-goog.structs.Set.prototype.clear = function() {
-  this.map_.clear();
+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.interpolate.lineStringCoordinateAtM(this.flatCoordinates, 0,
+      this.flatCoordinates.length, this.stride, m, extrapolate);
 };
 
 
 /**
- * Tests whether this set is empty.
- * @return {boolean} True if there are no elements in this set.
+ * Return the coordinates of the linestring.
+ * @return {Array.<ol.Coordinate>} Coordinates.
+ * @override
+ * @api
  */
-goog.structs.Set.prototype.isEmpty = function() {
-  return this.map_.isEmpty();
+ol.geom.LineString.prototype.getCoordinates = function() {
+  return ol.geom.flat.inflate.coordinates(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
 };
 
 
 /**
- * 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
+ * Return the coordinate at the provided fraction along the linestring.
+ * The `fraction` is a number between 0 and 1, where 0 is the start of the
+ * linestring and 1 is the end.
+ * @param {number} fraction Fraction.
+ * @param {ol.Coordinate=} opt_dest Optional coordinate whose values will
+ *     be modified. If not provided, a new coordinate will be returned.
+ * @return {ol.Coordinate} Coordinate of the interpolated point.
+ * @api
  */
-goog.structs.Set.prototype.contains = function(element) {
-  return this.map_.containsKey(goog.structs.Set.getKey_(element));
+ol.geom.LineString.prototype.getCoordinateAt = function(fraction, opt_dest) {
+  return ol.geom.flat.interpolate.lineString(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
+      fraction, opt_dest);
 };
 
 
 /**
- * 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.
+ * Return the length of the linestring on projected plane.
+ * @return {number} Length (on projected plane).
+ * @api
  */
-goog.structs.Set.prototype.containsAll = function(col) {
-  return goog.structs.every(col, this.contains, this);
+ol.geom.LineString.prototype.getLength = function() {
+  return ol.geom.flat.length.lineString(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
 };
 
 
 /**
- * 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
+ * @return {Array.<number>} Flat midpoint.
  */
-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);
-    }
+ol.geom.LineString.prototype.getFlatMidpoint = function() {
+  if (this.flatMidpointRevision_ != this.getRevision()) {
+    this.flatMidpoint_ = this.getCoordinateAt(0.5, this.flatMidpoint_);
+    this.flatMidpointRevision_ = this.getRevision();
   }
-
-  return result;
+  return this.flatMidpoint_;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.structs.Set.prototype.difference = function(col) {
-  var result = this.clone();
-  result.removeAll(col);
-  return result;
+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;
 };
 
 
 /**
- * Returns an array containing all the elements in this set.
- * @return {!Array<T>} An array containing all the elements in this set.
+ * @inheritDoc
+ * @api
  */
-goog.structs.Set.prototype.getValues = function() {
-  return this.map_.getValues();
+ol.geom.LineString.prototype.getType = function() {
+  return ol.geom.GeometryType.LINE_STRING;
 };
 
 
 /**
- * Creates a shallow clone of this set.
- * @return {!goog.structs.Set<T>} A new set containing all the same elements as
- *     this set.
+ * @inheritDoc
+ * @api
  */
-goog.structs.Set.prototype.clone = function() {
-  return new goog.structs.Set(this);
+ol.geom.LineString.prototype.intersectsExtent = function(extent) {
+  return ol.geom.flat.intersectsextent.lineString(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
+      extent);
 };
 
 
 /**
- * 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.
+ * Set the coordinates of the linestring.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @override
+ * @api
  */
-goog.structs.Set.prototype.equals = function(col) {
-  return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col);
+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();
+  }
 };
 
 
 /**
- * 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.
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
  */
-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);
-  });
+ol.geom.LineString.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
 };
 
+goog.provide('ol.geom.MultiLineString');
 
-/**
- * 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);
-};
+goog.require('ol');
+goog.require('ol.array');
+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');
 
-// 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.
+ * @classdesc
+ * Multi-linestring geometry.
  *
- * @see ../demos/debug.html
+ * @constructor
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api
  */
+ol.geom.MultiLineString = function(coordinates, opt_layout) {
+
+  ol.geom.SimpleGeometry.call(this);
+
+  /**
+   * @type {Array.<number>}
+   * @private
+   */
+  this.ends_ = [];
 
-goog.provide('goog.debug');
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDelta_ = -1;
 
-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');
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDeltaRevision_ = -1;
 
+  this.setCoordinates(coordinates, opt_layout);
 
-/** @define {boolean} Whether logging should be enabled. */
-goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
+};
+ol.inherits(ol.geom.MultiLineString, ol.geom.SimpleGeometry);
 
 
 /**
- * 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.
+ * Append the passed linestring to the multilinestring.
+ * @param {ol.geom.LineString} lineString LineString.
+ * @api
  */
-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;
+ol.geom.MultiLineString.prototype.appendLineString = function(lineString) {
+  if (!this.flatCoordinates) {
+    this.flatCoordinates = lineString.getFlatCoordinates().slice();
+  } else {
+    ol.array.extend(
+        this.flatCoordinates, lineString.getFlatCoordinates().slice());
   }
+  this.ends_.push(this.flatCoordinates.length);
+  this.changed();
+};
 
-  /**
-   * 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;
-  };
+
+/**
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.MultiLineString} Clone.
+ * @override
+ * @api
+ */
+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;
 };
 
 
 /**
- * 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}.
+ * @inheritDoc
  */
-goog.debug.expose = function(obj, opt_showFn) {
-  if (typeof obj == 'undefined') {
-    return 'undefined';
-  }
-  if (obj == null) {
-    return 'NULL';
+ol.geom.MultiLineString.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
+  if (minSquaredDistance <
+      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
+    return minSquaredDistance;
   }
-  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);
+  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 str.join('\n');
+  return ol.geom.flat.closest.getsClosestPoint(
+      this.flatCoordinates, 0, this.ends_, this.stride,
+      this.maxDelta_, false, x, y, closestPoint, minSquaredDistance);
 };
 
 
 /**
- * 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('');
+ * 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
+ */
+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.interpolate.lineStringsCoordinateAtM(this.flatCoordinates, 0,
+      this.ends_, this.stride, m, extrapolate, interpolate);
 };
 
 
 /**
- * Recursively outputs a nested array as a string.
- * @param {Array<?>} arr The array.
- * @return {string} String representing nested array.
+ * Return the coordinates of the multilinestring.
+ * @return {Array.<Array.<ol.Coordinate>>} Coordinates.
+ * @override
+ * @api
  */
-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(', ') + ' ]';
+ol.geom.MultiLineString.prototype.getCoordinates = function() {
+  return ol.geom.flat.inflate.coordinatess(
+      this.flatCoordinates, 0, this.ends_, this.stride);
 };
 
 
 /**
- * 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.
+ * @return {Array.<number>} Ends.
  */
-goog.debug.exposeException = function(err, opt_fn) {
-  var html = goog.debug.exposeExceptionAsHtml(err, opt_fn);
-  return goog.html.SafeHtml.unwrap(html);
+ol.geom.MultiLineString.prototype.getEnds = function() {
+  return this.ends_;
 };
 
 
 /**
- * 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.
+ * Return the linestring at the specified index.
+ * @param {number} index Index.
+ * @return {ol.geom.LineString} LineString.
+ * @api
  */
-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);
+ol.geom.MultiLineString.prototype.getLineString = function(index) {
+  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;
 };
 
 
 /**
- * @param {?string=} opt_fileName
- * @return {!goog.html.SafeUrl} SafeUrl with view-source scheme, pointing at
- *     fileName.
- * @private
+ * Return the linestrings of this multilinestring.
+ * @return {Array.<ol.geom.LineString>} LineStrings.
+ * @api
  */
-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'));
+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;
   }
-  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));
+  return lineStrings;
 };
 
 
 /**
- * Normalizes the error/exception object between browsers.
- * @param {Object} err Raw error object.
- * @return {!Object} Normalized error object.
+ * @return {Array.<number>} Flat midpoints.
  */
-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'
-    };
+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);
+    ol.array.extend(midpoints, midpoint);
+    offset = end;
   }
-
-  // Standards error object
-  return err;
+  return midpoints;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-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;
+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;
 };
 
 
 /**
- * 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}
+ * @inheritDoc
+ * @api
  */
-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('');
+ol.geom.MultiLineString.prototype.getType = function() {
+  return ol.geom.GeometryType.MULTI_LINE_STRING;
 };
 
 
 /**
- * Max length of stack to try and output
- * @type {number}
+ * @inheritDoc
+ * @api
  */
-goog.debug.MAX_STACK_DEPTH = 50;
+ol.geom.MultiLineString.prototype.intersectsExtent = function(extent) {
+  return ol.geom.flat.intersectsextent.lineStrings(
+      this.flatCoordinates, 0, this.ends_, this.stride, extent);
+};
 
 
 /**
- * @param {Function} fn The function to start getting the trace from.
- * @return {?string}
- * @private
+ * Set the coordinates of the multilinestring.
+ * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @override
+ * @api
  */
-goog.debug.getNativeStackTrace_ = function(fn) {
-  var tempErr = new Error();
-  if (Error.captureStackTrace) {
-    Error.captureStackTrace(tempErr, fn);
-    return String(tempErr.stack);
+ol.geom.MultiLineString.prototype.setCoordinates = function(coordinates, opt_layout) {
+  if (!coordinates) {
+    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
   } 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);
+    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();
   }
-  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}
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Array.<number>} ends Ends.
  */
-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;
+ol.geom.MultiLineString.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.ends_ = ends;
+  this.changed();
 };
 
 
 /**
- * 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
+ * @param {Array.<ol.geom.LineString>} lineStrings LineStrings.
  */
-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');
+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 if (fn) {
-    sb.push('[...long stack...]');
-  } else {
-    sb.push('[end]');
+    ol.array.extend(flatCoordinates, lineString.getFlatCoordinates());
+    ends.push(flatCoordinates.length);
   }
-  return sb.join('');
+  this.setFlatCoordinates(layout, flatCoordinates, ends);
 };
 
+goog.provide('ol.geom.MultiPoint');
+
+goog.require('ol');
+goog.require('ol.array');
+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');
+
 
 /**
- * Set a custom function name resolver.
- * @param {function(Function): string} resolver Resolves functions to their
- *     names.
+ * @classdesc
+ * Multi-point geometry.
+ *
+ * @constructor
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api
  */
-goog.debug.setFunctionResolver = function(resolver) {
-  goog.debug.fnNameResolver_ = resolver;
+ol.geom.MultiPoint = function(coordinates, opt_layout) {
+  ol.geom.SimpleGeometry.call(this);
+  this.setCoordinates(coordinates, opt_layout);
 };
+ol.inherits(ol.geom.MultiPoint, ol.geom.SimpleGeometry);
 
 
 /**
- * Gets a function name
- * @param {Function} fn Function to get name of.
- * @return {string} Function's name.
+ * Append the passed point to this multipoint.
+ * @param {ol.geom.Point} point Point.
+ * @api
  */
-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]';
-    }
+ol.geom.MultiPoint.prototype.appendPoint = function(point) {
+  if (!this.flatCoordinates) {
+    this.flatCoordinates = point.getFlatCoordinates().slice();
+  } else {
+    ol.array.extend(this.flatCoordinates, point.getFlatCoordinates());
   }
-
-  return goog.debug.fnNameCache_[functionSource];
+  this.changed();
 };
 
 
 /**
- * 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.
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.MultiPoint} Clone.
+ * @override
+ * @api
  */
-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]');
+ol.geom.MultiPoint.prototype.clone = function() {
+  var multiPoint = new ol.geom.MultiPoint(null);
+  multiPoint.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+  return multiPoint;
 };
 
 
 /**
- * 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'.
+ * @inheritDoc
  */
-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;
+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;
 };
 
 
 /**
- * Hash map for storing function names that have already been looked up.
- * @type {Object}
- * @private
+ * Return the coordinates of the multipoint.
+ * @return {Array.<ol.Coordinate>} Coordinates.
+ * @override
+ * @api
  */
-goog.debug.fnNameCache_ = {};
+ol.geom.MultiPoint.prototype.getCoordinates = function() {
+  return ol.geom.flat.inflate.coordinates(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
+};
 
 
 /**
- * Resolves functions to their names.  Resolved function names will be cached.
- * @type {function(Function):string}
- * @private
+ * Return the point at the specified index.
+ * @param {number} index Index.
+ * @return {ol.geom.Point} Point.
+ * @api
  */
-goog.debug.fnNameResolver_;
+ol.geom.MultiPoint.prototype.getPoint = function(index) {
+  var n = !this.flatCoordinates ?
+      0 : this.flatCoordinates.length / this.stride;
+  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;
+};
 
-// 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.
- *
+ * Return the points of this multipoint.
+ * @return {Array.<ol.geom.Point>} Points.
+ * @api
  */
-
-goog.provide('goog.debug.LogRecord');
-
+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;
+};
 
 
 /**
- * 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.
+ * @inheritDoc
+ * @api
  */
-goog.debug.LogRecord = function(level, msg, loggerName,
-    opt_time, opt_sequenceNumber) {
-  this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber);
+ol.geom.MultiPoint.prototype.getType = function() {
+  return ol.geom.GeometryType.MULTI_POINT;
 };
 
 
 /**
- * Time the LogRecord was created.
- * @type {number}
- * @private
+ * @inheritDoc
+ * @api
  */
-goog.debug.LogRecord.prototype.time_;
+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;
+};
 
 
 /**
- * Level of the LogRecord
- * @type {goog.debug.Logger.Level}
- * @private
+ * Set the coordinates of the multipoint.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @override
+ * @api
  */
-goog.debug.LogRecord.prototype.level_;
+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();
+  }
+};
 
 
 /**
- * Message associated with the record
- * @type {string}
- * @private
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
  */
-goog.debug.LogRecord.prototype.msg_;
+ol.geom.MultiPoint.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
+};
 
+goog.provide('ol.geom.flat.center');
 
-/**
- * Name of the logger that created the record.
- * @type {string}
- * @private
- */
-goog.debug.LogRecord.prototype.loggerName_;
+goog.require('ol.extent');
 
 
 /**
- * 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
+ * @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.
  */
-goog.debug.LogRecord.prototype.sequenceNumber_ = 0;
+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');
 
-/**
- * Exception associated with the record
- * @type {Object}
- * @private
- */
-goog.debug.LogRecord.prototype.exception_ = null;
+goog.require('ol');
+goog.require('ol.array');
+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');
 
 
 /**
- * @define {boolean} Whether to enable log sequence numbers.
+ * @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
  */
-goog.define('goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS', true);
-
+ol.geom.MultiPolygon = function(coordinates, opt_layout) {
 
-/**
- * A sequence counter for assigning increasing sequence numbers to LogRecord
- * objects.
- * @type {number}
- * @private
- */
-goog.debug.LogRecord.nextSequenceNumber_ = 0;
+  ol.geom.SimpleGeometry.call(this);
 
+  /**
+   * @type {Array.<Array.<number>>}
+   * @private
+   */
+  this.endss_ = [];
 
-/**
- * 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_++;
-  }
+  /**
+   * @private
+   * @type {number}
+   */
+  this.flatInteriorPointsRevision_ = -1;
 
-  this.time_ = opt_time || goog.now();
-  this.level_ = level;
-  this.msg_ = msg;
-  this.loggerName_ = loggerName;
-  delete this.exception_;
-};
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.flatInteriorPoints_ = null;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDelta_ = -1;
 
-/**
- * Get the source Logger's name.
- *
- * @return {string} source logger name (may be null).
- */
-goog.debug.LogRecord.prototype.getLoggerName = function() {
-  return this.loggerName_;
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDeltaRevision_ = -1;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.orientedRevision_ = -1;
 
-/**
- * Get the exception that is part of the log record.
- *
- * @return {Object} the exception.
- */
-goog.debug.LogRecord.prototype.getException = function() {
-  return this.exception_;
-};
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.orientedFlatCoordinates_ = null;
 
+  this.setCoordinates(coordinates, opt_layout);
 
-/**
- * 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;
 };
+ol.inherits(ol.geom.MultiPolygon, ol.geom.SimpleGeometry);
 
 
 /**
- * Get the source Logger's name.
- *
- * @param {string} loggerName source logger name (may be null).
+ * Append the passed polygon to this multipolygon.
+ * @param {ol.geom.Polygon} polygon Polygon.
+ * @api
  */
-goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) {
-  this.loggerName_ = loggerName;
+ol.geom.MultiPolygon.prototype.appendPolygon = function(polygon) {
+  /** @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;
+    ol.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();
 };
 
 
 /**
- * Get the logging message level, for example Level.SEVERE.
- * @return {goog.debug.Logger.Level} the logging message level.
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.MultiPolygon} Clone.
+ * @override
+ * @api
  */
-goog.debug.LogRecord.prototype.getLevel = function() {
-  return this.level_;
-};
+ol.geom.MultiPolygon.prototype.clone = function() {
+  var multiPolygon = new ol.geom.MultiPolygon(null);
 
+  var len = this.endss_.length;
+  var newEndss = new Array(len);
+  for (var i = 0; i < len; ++i) {
+    newEndss[i] = this.endss_[i].slice();
+  }
 
-/**
- * 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;
+  multiPolygon.setFlatCoordinates(
+      this.layout, this.flatCoordinates.slice(), newEndss);
+  return multiPolygon;
 };
 
 
 /**
- * Get the "raw" log message, before localization or formatting.
- *
- * @return {string} the raw message string.
+ * @inheritDoc
  */
-goog.debug.LogRecord.prototype.getMessage = function() {
-  return this.msg_;
+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);
 };
 
 
 /**
- * Set the "raw" log message, before localization or formatting.
- *
- * @param {string} msg the raw message string.
+ * @inheritDoc
  */
-goog.debug.LogRecord.prototype.setMessage = function(msg) {
-  this.msg_ = msg;
+ol.geom.MultiPolygon.prototype.containsXY = function(x, y) {
+  return ol.geom.flat.contains.linearRingssContainsXY(
+      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, x, y);
 };
 
 
 /**
- * Get event time in milliseconds since 1970.
- *
- * @return {number} event time in millis since 1970.
+ * Return the area of the multipolygon on projected plane.
+ * @return {number} Area (on projected plane).
+ * @api
  */
-goog.debug.LogRecord.prototype.getMillis = function() {
-  return this.time_;
+ol.geom.MultiPolygon.prototype.getArea = function() {
+  return ol.geom.flat.area.linearRingss(
+      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride);
 };
 
 
 /**
- * Set event time in milliseconds since 1970.
+ * Get the coordinate array for this geometry.  This array has the structure
+ * of a GeoJSON coordinate array for multi-polygons.
  *
- * @param {number} time event time in millis since 1970.
+ * @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.
+ * @override
+ * @api
  */
-goog.debug.LogRecord.prototype.setMillis = function(time) {
-  this.time_ = time;
-};
-
+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;
+  }
 
-/**
- * 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_;
+  return ol.geom.flat.inflate.coordinatesss(
+      flatCoordinates, 0, this.endss_, this.stride);
 };
 
 
-// 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
+ * @return {Array.<Array.<number>>} Endss.
  */
-goog.debug.LogBuffer = function() {
-  goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(),
-      'Cannot use goog.debug.LogBuffer without defining ' +
-      'goog.debug.LogBuffer.CAPACITY.');
-  this.clear();
+ol.geom.MultiPolygon.prototype.getEndss = function() {
+  return this.endss_;
 };
 
 
 /**
- * A static method that always returns the same instance of LogBuffer.
- * @return {!goog.debug.LogBuffer} The LogBuffer singleton instance.
+ * @return {Array.<number>} Flat interior points.
  */
-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();
+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 goog.debug.LogBuffer.instance_;
+  return this.flatInteriorPoints_;
 };
 
 
 /**
- * @define {number} The number of log records to buffer. 0 means disable
- * buffering.
+ * Return the interior points as {@link ol.geom.MultiPoint multipoint}.
+ * @return {ol.geom.MultiPoint} Interior points.
+ * @api
  */
-goog.define('goog.debug.LogBuffer.CAPACITY', 0);
+ol.geom.MultiPolygon.prototype.getInteriorPoints = function() {
+  var interiorPoints = new ol.geom.MultiPoint(null);
+  interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XY,
+      this.getFlatInteriorPoints().slice());
+  return interiorPoints;
+};
 
 
 /**
- * The array to store the records.
- * @type {!Array<!goog.debug.LogRecord|undefined>}
- * @private
+ * @return {Array.<number>} Oriented flat coordinates.
  */
-goog.debug.LogBuffer.prototype.buffer_;
+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_;
+};
 
 
 /**
- * The index of the most recently added record or -1 if there are no records.
- * @type {number}
- * @private
+ * @inheritDoc
  */
-goog.debug.LogBuffer.prototype.curIndex_;
+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;
+};
 
 
 /**
- * Whether the buffer is at capacity.
- * @type {boolean}
- * @private
+ * Return the polygon at the specified index.
+ * @param {number} index Index.
+ * @return {ol.geom.Polygon} Polygon.
+ * @api
  */
-goog.debug.LogBuffer.prototype.isFull_;
+ol.geom.MultiPolygon.prototype.getPolygon = function(index) {
+  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;
+};
 
 
 /**
- * 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.
+ * Return the polygons of this multipolygon.
+ * @return {Array.<ol.geom.Polygon>} Polygons.
+ * @api
  */
-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;
+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;
   }
-  this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1;
-  return this.buffer_[curIndex] =
-      new goog.debug.LogRecord(level, msg, loggerName);
+  return polygons;
 };
 
 
 /**
- * @return {boolean} Whether the log buffer is enabled.
+ * @inheritDoc
+ * @api
  */
-goog.debug.LogBuffer.isBufferingEnabled = function() {
-  return goog.debug.LogBuffer.CAPACITY > 0;
+ol.geom.MultiPolygon.prototype.getType = function() {
+  return ol.geom.GeometryType.MULTI_POLYGON;
 };
 
 
 /**
- * Removes all buffered log records.
+ * @inheritDoc
+ * @api
  */
-goog.debug.LogBuffer.prototype.clear = function() {
-  this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY);
-  this.curIndex_ = -1;
-  this.isFull_ = false;
+ol.geom.MultiPolygon.prototype.intersectsExtent = function(extent) {
+  return ol.geom.flat.intersectsextent.linearRingss(
+      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, extent);
 };
 
 
 /**
- * Calls the given function for each buffered log record, starting with the
- * oldest one.
- * @param {function(!goog.debug.LogRecord)} func The function to call.
+ * Set the coordinates of the multipolygon.
+ * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @override
+ * @api
  */
-goog.debug.LogBuffer.prototype.forEachRecord = function(func) {
-  var buffer = this.buffer_;
-  // Corner case: no records.
-  if (!buffer[0]) {
-    return;
+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();
   }
-  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}
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Array.<Array.<number>>} endss Endss.
  */
-goog.debug.Loggable;
-
+ol.geom.MultiPolygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, endss) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.endss_ = endss;
+  this.changed();
+};
 
 
 /**
- * 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
+ * @param {Array.<ol.geom.Polygon>} polygons Polygons.
  */
-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;
+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();
+    }
+    var offset = flatCoordinates.length;
+    ends = polygon.getEnds();
+    var j, jj;
+    for (j = 0, jj = ends.length; j < jj; ++j) {
+      ends[j] += offset;
+    }
+    ol.array.extend(flatCoordinates, polygon.getFlatCoordinates());
+    endss.push(ends);
+  }
+  this.setFlatCoordinates(layout, flatCoordinates, endss);
 };
 
+goog.provide('ol.format.EsriJSON');
 
-/** @const */
-goog.debug.Logger.ROOT_LOGGER_NAME = '';
+goog.require('ol');
+goog.require('ol.Feature');
+goog.require('ol.asserts');
+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.deflate');
+goog.require('ol.geom.flat.orient');
+goog.require('ol.obj');
+goog.require('ol.proj');
 
 
 /**
- * @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.
+ * @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
  */
-goog.define('goog.debug.Logger.ENABLE_HIERARCHY', true);
-
+ol.format.EsriJSON = function(opt_options) {
 
-if (!goog.debug.Logger.ENABLE_HIERARCHY) {
-  /**
-   * @type {!Array<Function>}
-   * @private
-   */
-  goog.debug.Logger.rootHandlers_ = [];
+  var options = opt_options ? opt_options : {};
 
+  ol.format.JSONFeature.call(this);
 
   /**
-   * @type {goog.debug.Logger.Level}
+   * Name of the geometry attribute for features.
+   * @type {string|undefined}
    * @private
    */
-  goog.debug.Logger.rootLevel_;
-}
+  this.geometryName_ = options.geometryName;
 
+};
+ol.inherits(ol.format.EsriJSON, ol.format.JSONFeature);
 
 
 /**
- * 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
+ * @param {EsriJSONGeometry} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @private
+ * @return {ol.geom.Geometry} Geometry.
  */
-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;
+ol.format.EsriJSON.readGeometry_ = function(object, opt_options) {
+  if (!object) {
+    return null;
+  }
+  /** @type {ol.geom.GeometryType} */
+  var type;
+  if (typeof object.x === 'number' && typeof object.y === 'number') {
+    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} */(ol.obj.assign({}, object));
+    if (rings.length === 1) {
+      type = ol.geom.GeometryType.POLYGON;
+      object.rings = rings[0];
+    } else {
+      type = ol.geom.GeometryType.MULTI_POLYGON;
+      object.rings = rings;
+    }
+  }
+  var geometryReader = ol.format.EsriJSON.GEOMETRY_READERS_[type];
+  return /** @type {ol.geom.Geometry} */ (
+      ol.format.Feature.transformWithOptions(
+          geometryReader(object), false, opt_options));
 };
 
 
 /**
- * @return {string} String representation of the logger level.
- * @override
+ * 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>>>} Transformed rings.
  */
-goog.debug.Logger.Level.prototype.toString = function() {
-  return this.name;
+ol.format.EsriJSON.convertRings_ = function(rings, layout) {
+  var flatRing = [];
+  var outerRings = [];
+  var holes = [];
+  var i, ii;
+  for (i = 0, ii = rings.length; i < ii; ++i) {
+    flatRing.length = 0;
+    ol.geom.flat.deflate.coordinates(flatRing, 0, rings[i], layout.length);
+    // 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;
 };
 
 
 /**
- * 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}
+ * @param {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Geometry} Point.
  */
-goog.debug.Logger.Level.OFF =
-    new goog.debug.Logger.Level('OFF', Infinity);
+ol.format.EsriJSON.readPointGeometry_ = function(object) {
+  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;
+};
 
 
 /**
- * SHOUT is a message level for extra debugging loudness.
- * This level is initialized to <CODE>1200</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @param {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Geometry} LineString.
  */
-goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level('SHOUT', 1200);
+ol.format.EsriJSON.readLineStringGeometry_ = function(object) {
+  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
+  return new ol.geom.LineString(object.paths[0], layout);
+};
 
 
 /**
- * SEVERE is a message level indicating a serious failure.
- * This level is initialized to <CODE>1000</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @param {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Geometry} MultiLineString.
  */
-goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level('SEVERE', 1000);
+ol.format.EsriJSON.readMultiLineStringGeometry_ = function(object) {
+  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
+  return new ol.geom.MultiLineString(object.paths, layout);
+};
 
 
 /**
- * WARNING is a message level indicating a potential problem.
- * This level is initialized to <CODE>900</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @param {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.GeometryLayout} The geometry layout to use.
  */
-goog.debug.Logger.Level.WARNING = new goog.debug.Logger.Level('WARNING', 900);
+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;
+};
 
 
 /**
- * INFO is a message level for informational messages.
- * This level is initialized to <CODE>800</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @param {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Geometry} MultiPoint.
  */
-goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level('INFO', 800);
+ol.format.EsriJSON.readMultiPointGeometry_ = function(object) {
+  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
+  return new ol.geom.MultiPoint(object.points, layout);
+};
 
 
 /**
- * CONFIG is a message level for static configuration messages.
- * This level is initialized to <CODE>700</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @param {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Geometry} MultiPolygon.
  */
-goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level('CONFIG', 700);
+ol.format.EsriJSON.readMultiPolygonGeometry_ = function(object) {
+  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
+  return new ol.geom.MultiPolygon(
+      /** @type {Array.<Array.<Array.<Array.<number>>>>} */(object.rings),
+      layout);
+};
 
 
 /**
- * FINE is a message level providing tracing information.
- * This level is initialized to <CODE>500</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @param {EsriJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Geometry} Polygon.
  */
-goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level('FINE', 500);
+ol.format.EsriJSON.readPolygonGeometry_ = function(object) {
+  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
+  return new ol.geom.Polygon(object.rings, layout);
+};
 
 
 /**
- * FINER indicates a fairly detailed tracing message.
- * This level is initialized to <CODE>400</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {EsriJSONGeometry} EsriJSON geometry.
  */
-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];
+ol.format.EsriJSON.writePointGeometry_ = function(geometry, opt_options) {
+  var coordinates = /** @type {ol.geom.Point} */ (geometry).getCoordinates();
+  var esriJSON;
+  var layout = /** @type {ol.geom.Point} */ (geometry).getLayout();
+  if (layout === ol.geom.GeometryLayout.XYZ) {
+    esriJSON = /** @type {EsriJSONPoint} */ ({
+      x: coordinates[0],
+      y: coordinates[1],
+      z: coordinates[2]
+    });
+  } else if (layout === ol.geom.GeometryLayout.XYM) {
+    esriJSON = /** @type {EsriJSONPoint} */ ({
+      x: coordinates[0],
+      y: coordinates[1],
+      m: coordinates[2]
+    });
+  } else if (layout === ol.geom.GeometryLayout.XYZM) {
+    esriJSON = /** @type {EsriJSONPoint} */ ({
+      x: coordinates[0],
+      y: coordinates[1],
+      z: coordinates[2],
+      m: coordinates[3]
+    });
+  } else if (layout === ol.geom.GeometryLayout.XY) {
+    esriJSON = /** @type {EsriJSONPoint} */ ({
+      x: coordinates[0],
+      y: coordinates[1]
+    });
+  } else {
+    ol.asserts.assert(false, 34); // Invalid geometry layout
+  }
+  return /** @type {EsriJSONGeometry} */ (esriJSON);
+};
 
 
 /**
- * A lookup map used to find the level object based on the name or value of
- * the level object.
- * @type {Object}
+ * @param {ol.geom.SimpleGeometry} geometry Geometry.
  * @private
+ * @return {Object} Object with boolean hasZ and hasM keys.
  */
-goog.debug.Logger.Level.predefinedLevelsCache_ = null;
+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)
+  };
+};
 
 
 /**
- * Creates the predefined levels cache and populates it.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
  * @private
+ * @return {EsriJSONPolyline} EsriJSON geometry.
  */
-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;
-  }
+ol.format.EsriJSON.writeLineStringGeometry_ = function(geometry, opt_options) {
+  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.LineString} */ (geometry));
+  return /** @type {EsriJSONPolyline} */ ({
+    hasZ: hasZM.hasZ,
+    hasM: hasZM.hasM,
+    paths: [
+      /** @type {ol.geom.LineString} */ (geometry).getCoordinates()
+    ]
+  });
 };
 
 
 /**
- * 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.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {EsriJSONPolygon} EsriJSON geometry.
  */
-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;
+ol.format.EsriJSON.writePolygonGeometry_ = function(geometry, opt_options) {
+  // Esri geometries use the left-hand rule
+  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.Polygon} */ (geometry));
+  return /** @type {EsriJSONPolygon} */ ({
+    hasZ: hasZM.hasZ,
+    hasM: hasZM.hasM,
+    rings: /** @type {ol.geom.Polygon} */ (geometry).getCoordinates(false)
+  });
 };
 
 
 /**
- * Gets the highest predefined level <= #value.
- * @param {number} value Level value.
- * @return {goog.debug.Logger.Level} The level, or null if none found.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {EsriJSONPolyline} EsriJSON geometry.
  */
-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;
+ol.format.EsriJSON.writeMultiLineStringGeometry_ = function(geometry, opt_options) {
+  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiLineString} */ (geometry));
+  return /** @type {EsriJSONPolyline} */ ({
+    hasZ: hasZM.hasZ,
+    hasM: hasZM.hasM,
+    paths: /** @type {ol.geom.MultiLineString} */ (geometry).getCoordinates()
+  });
 };
 
 
 /**
- * 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
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {EsriJSONMultipoint} EsriJSON geometry.
  */
-goog.debug.Logger.getLogger = function(name) {
-  return goog.debug.LogManager.getLogger(name);
+ol.format.EsriJSON.writeMultiPointGeometry_ = function(geometry, opt_options) {
+  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPoint} */ (geometry));
+  return /** @type {EsriJSONMultipoint} */ ({
+    hasZ: hasZM.hasZ,
+    hasM: hasZM.hasM,
+    points: /** @type {ol.geom.MultiPoint} */ (geometry).getCoordinates()
+  });
 };
 
 
 /**
- * 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.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {EsriJSONPolygon} EsriJSON geometry.
  */
-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);
+ol.format.EsriJSON.writeMultiPolygonGeometry_ = function(geometry,
+    opt_options) {
+  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPolygon} */ (geometry));
+  var coordinates = /** @type {ol.geom.MultiPolygon} */ (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]);
     }
   }
-
-  if (goog.global['msWriteProfilerMark']) {
-    // Logs a message to the Microsoft profiler
-    goog.global['msWriteProfilerMark'](msg);
-  }
+  return /** @type {EsriJSONPolygon} */ ({
+    hasZ: hasZM.hasZ,
+    hasM: hasZM.hasM,
+    rings: output
+  });
 };
 
 
 /**
- * Gets the name of this logger.
- * @return {string} The name of this logger.
+ * @const
+ * @private
+ * @type {Object.<ol.geom.GeometryType, function(EsriJSONGeometry): ol.geom.Geometry>}
  */
-goog.debug.Logger.prototype.getName = function() {
-  return this.name_;
-};
+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_;
 
 
 /**
- * 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.
+ * @const
+ * @private
+ * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (EsriJSONGeometry)>}
  */
-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);
-    }
-  }
-};
+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_;
 
 
 /**
- * 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.
+ * 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
  */
-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;
-  }
-};
+ol.format.EsriJSON.prototype.readFeature;
 
 
 /**
- * Returns the parent of this logger.
- * @return {goog.debug.Logger} The parent logger or null if this is the root.
+ * 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
  */
-goog.debug.Logger.prototype.getParent = function() {
-  return this.parent_;
-};
+ol.format.EsriJSON.prototype.readFeatures;
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.debug.Logger.prototype.getChildren = function() {
-  if (!this.children_) {
-    this.children_ = {};
+ol.format.EsriJSON.prototype.readFeatureFromObject = function(
+    object, opt_options) {
+  var esriJSONFeature = /** @type {EsriJSONFeature} */ (object);
+  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]) {
+    feature.setId(/** @type {number} */(
+        esriJSONFeature.attributes[opt_options.idField]));
+  }
+  if (esriJSONFeature.attributes) {
+    feature.setProperties(esriJSONFeature.attributes);
   }
-  return this.children_;
+  return feature;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-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;
+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)];
   }
 };
 
 
 /**
- * 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.
+ * Read a geometry from a EsriJSON source.
  *
- * @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.
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.geom.Geometry} Geometry.
+ * @api
  */
-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;
-};
+ol.format.EsriJSON.prototype.readGeometry;
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.debug.Logger.prototype.isLoggable = function(level) {
-  return goog.debug.LOGGING_ENABLED &&
-      level.value >= this.getEffectiveLevel().value;
+ol.format.EsriJSON.prototype.readGeometryFromObject = function(
+    object, opt_options) {
+  return ol.format.EsriJSON.readGeometry_(
+      /** @type {EsriJSONGeometry} */ (object), opt_options);
 };
 
 
 /**
- * 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.
+ * Read the projection from a EsriJSON source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
  */
-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));
-  }
-};
+ol.format.EsriJSON.prototype.readProjection;
 
 
 /**
- * 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}
+ * @inheritDoc
  */
-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_);
+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 {
-    logRecord = new goog.debug.LogRecord(level, String(msg), this.name_);
-  }
-  if (opt_exception) {
-    logRecord.setException(opt_exception);
+    return null;
   }
-  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.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {EsriJSONGeometry} EsriJSON geometry.
  */
-goog.debug.Logger.prototype.shout = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception);
-  }
+ol.format.EsriJSON.writeGeometry_ = function(geometry, opt_options) {
+  var geometryWriter = ol.format.EsriJSON.GEOMETRY_WRITERS_[geometry.getType()];
+  return geometryWriter(/** @type {ol.geom.Geometry} */ (
+      ol.format.Feature.transformWithOptions(geometry, true, opt_options)),
+      opt_options);
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.debug.Logger.prototype.severe = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception);
-  }
-};
+ol.format.EsriJSON.prototype.writeGeometry;
 
 
 /**
- * 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.
+ * Encode a geometry as a EsriJSON object.
+ *
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {EsriJSONGeometry} Object.
+ * @override
+ * @api
  */
-goog.debug.Logger.prototype.warning = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception);
-  }
+ol.format.EsriJSON.prototype.writeGeometryObject = function(geometry,
+    opt_options) {
+  return ol.format.EsriJSON.writeGeometry_(geometry,
+      this.adaptOptions(opt_options));
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.debug.Logger.prototype.info = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.INFO, msg, opt_exception);
-  }
-};
+ol.format.EsriJSON.prototype.writeFeature;
 
 
 /**
- * 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.
+ * Encode a feature as a esriJSON Feature object.
+ *
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {Object} Object.
+ * @override
+ * @api
  */
-goog.debug.Logger.prototype.config = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception);
+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 (!ol.obj.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;
 };
 
 
 /**
- * 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.
+ * 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
  */
-goog.debug.Logger.prototype.fine = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.FINE, msg, opt_exception);
-  }
-};
+ol.format.EsriJSON.prototype.writeFeatures;
 
 
 /**
- * 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.
+ * 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.
+ * @override
+ * @api
  */
-goog.debug.Logger.prototype.finer = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.FINER, msg, opt_exception);
+ol.format.EsriJSON.prototype.writeFeaturesObject = function(features, opt_options) {
+  opt_options = this.adaptOptions(opt_options);
+  var objects = [];
+  var i, ii;
+  for (i = 0, ii = features.length; i < ii; ++i) {
+    objects.push(this.writeFeatureObject(features[i], opt_options));
   }
+  return /** @type {EsriJSONFeatureCollection} */ ({
+    'features': objects
+  });
 };
 
+goog.provide('ol.format.filter.Filter');
+
 
 /**
- * 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.
+ * @classdesc
+ * Abstract class; normally only used for creating subclasses and not instantiated in apps.
+ * Base class for WFS GetFeature filters.
+ *
+ * @constructor
+ * @param {!string} tagName The XML tag name for this filter.
+ * @struct
+ * @api
  */
-goog.debug.Logger.prototype.finest = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception);
-  }
-};
+ol.format.filter.Filter = function(tagName) {
 
+  /**
+   * @private
+   * @type {!string}
+   */
+  this.tagName_ = tagName;
+};
 
 /**
- * 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.
+ * The XML tag name for a filter.
+ * @returns {!string} Name.
  */
-goog.debug.Logger.prototype.logRecord = function(logRecord) {
-  if (goog.debug.LOGGING_ENABLED && this.isLoggable(logRecord.getLevel())) {
-    this.doLogRecord_(logRecord);
-  }
+ol.format.filter.Filter.prototype.getTagName = function() {
+  return this.tagName_;
 };
 
+goog.provide('ol.format.filter.LogicalNary');
 
-/**
- * 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);
-    }
-  }
-};
+goog.require('ol');
+goog.require('ol.asserts');
+goog.require('ol.format.filter.Filter');
 
 
 /**
- * Calls the handlers for publish.
- * @param {goog.debug.LogRecord} logRecord The log record to publish.
- * @private
+ * @classdesc
+ * Abstract class; normally only used for creating subclasses and not instantiated in apps.
+ * Base class for WFS GetFeature n-ary logical filters.
+ *
+ * @constructor
+ * @param {!string} tagName The XML tag name for this filter.
+ * @param {...ol.format.filter.Filter} conditions Conditions.
+ * @extends {ol.format.filter.Filter}
  */
-goog.debug.Logger.prototype.callPublish_ = function(logRecord) {
-  if (this.handlers_) {
-    for (var i = 0, handler; handler = this.handlers_[i]; i++) {
-      handler(logRecord);
-    }
-  }
-};
+ol.format.filter.LogicalNary = function(tagName, conditions) {
 
+  ol.format.filter.Filter.call(this, tagName);
 
-/**
- * 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;
+  /**
+   * @public
+   * @type {Array.<ol.format.filter.Filter>}
+   */
+  this.conditions = Array.prototype.slice.call(arguments, 1);
+  ol.asserts.assert(this.conditions.length >= 2, 57); // At least 2 conditions are required.
 };
+ol.inherits(ol.format.filter.LogicalNary, ol.format.filter.Filter);
 
+goog.provide('ol.format.filter.And');
+
+goog.require('ol');
+goog.require('ol.format.filter.LogicalNary');
 
 /**
- * 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
+ * @classdesc
+ * Represents a logical `<And>` operator between two or more filter conditions.
+ *
+ * @constructor
+ * @param {...ol.format.filter.Filter} conditions Conditions.
+ * @extends {ol.format.filter.LogicalNary}
+ * @api
  */
-goog.debug.Logger.prototype.addChild_ = function(name, logger) {
-  this.getChildren()[name] = logger;
+ol.format.filter.And = function(conditions) {
+  var params = ['And'].concat(Array.prototype.slice.call(arguments));
+  ol.format.filter.LogicalNary.apply(this, params);
 };
+ol.inherits(ol.format.filter.And, ol.format.filter.LogicalNary);
 
+goog.provide('ol.format.filter.Bbox');
 
-/**
- * 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 = {};
+goog.require('ol');
+goog.require('ol.format.filter.Filter');
 
 
 /**
- * Map of logger names to logger objects.
+ * @classdesc
+ * Represents a `<BBOX>` operator to test whether a geometry-valued property
+ * intersects a fixed bounding box
  *
- * @type {!Object<string, !goog.debug.Logger>}
- * @private
+ * @constructor
+ * @param {!string} geometryName Geometry name to use.
+ * @param {!ol.Extent} extent Extent.
+ * @param {string=} opt_srsName SRS name. No srsName attribute will be
+ *    set on geometries when this is not provided.
+ * @extends {ol.format.filter.Filter}
+ * @api
  */
-goog.debug.LogManager.loggers_ = {};
+ol.format.filter.Bbox = function(geometryName, extent, opt_srsName) {
 
+  ol.format.filter.Filter.call(this, 'BBOX');
 
-/**
- * The root logger which is the root of the logger tree.
- * @type {goog.debug.Logger}
- * @private
- */
-goog.debug.LogManager.rootLogger_ = null;
+  /**
+   * @public
+   * @type {!string}
+   */
+  this.geometryName = geometryName;
 
+  /**
+   * @public
+   * @type {ol.Extent}
+   */
+  this.extent = extent;
 
-/**
- * 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);
-  }
+  /**
+   * @public
+   * @type {string|undefined}
+   */
+  this.srsName = opt_srsName;
 };
+ol.inherits(ol.format.filter.Bbox, ol.format.filter.Filter);
 
+goog.provide('ol.format.filter.Comparison');
 
-/**
- * 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_;
-};
+goog.require('ol');
+goog.require('ol.format.filter.Filter');
 
 
 /**
- * Returns the root of the logger tree namespace, the logger with the empty
- * string as its name.
+ * @classdesc
+ * Abstract class; normally only used for creating subclasses and not instantiated in apps.
+ * Base class for WFS GetFeature property comparison filters.
  *
- * @return {!goog.debug.Logger} The root logger.
+ * @constructor
+ * @param {!string} tagName The XML tag name for this filter.
+ * @param {!string} propertyName Name of the context property to compare.
+ * @extends {ol.format.filter.Filter}
+ * @api
  */
-goog.debug.LogManager.getRoot = function() {
-  goog.debug.LogManager.initialize();
-  return /** @type {!goog.debug.Logger} */ (goog.debug.LogManager.rootLogger_);
+ol.format.filter.Comparison = function(tagName, propertyName) {
+
+  ol.format.filter.Filter.call(this, tagName);
+
+  /**
+   * @public
+   * @type {!string}
+   */
+  this.propertyName = propertyName;
 };
+ol.inherits(ol.format.filter.Comparison, ol.format.filter.Filter);
+
+goog.provide('ol.format.filter.During');
+
+goog.require('ol');
+goog.require('ol.format.filter.Comparison');
 
 
 /**
- * Finds a named logger.
+ * @classdesc
+ * Represents a `<During>` comparison operator.
  *
- * @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.
+ * @constructor
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!string} begin The begin date in ISO-8601 format.
+ * @param {!string} end The end date in ISO-8601 format.
+ * @extends {ol.format.filter.Comparison}
+ * @api
  */
-goog.debug.LogManager.getLogger = function(name) {
-  goog.debug.LogManager.initialize();
-  var ret = goog.debug.LogManager.loggers_[name];
-  return ret || goog.debug.LogManager.createLogger_(name);
-};
+ol.format.filter.During = function(propertyName, begin, end) {
+  ol.format.filter.Comparison.call(this, 'During', propertyName);
 
+  /**
+   * @public
+   * @type {!string}
+   */
+  this.begin = begin;
 
-/**
- * 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 + ')');
-  };
+  /**
+   * @public
+   * @type {!string}
+   */
+  this.end = end;
 };
+ol.inherits(ol.format.filter.During, ol.format.filter.Comparison);
+
+goog.provide('ol.format.filter.ComparisonBinary');
+
+goog.require('ol');
+goog.require('ol.format.filter.Comparison');
 
 
 /**
- * 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
+ * @classdesc
+ * Abstract class; normally only used for creating subclasses and not instantiated in apps.
+ * Base class for WFS GetFeature property binary comparison filters.
+ *
+ * @constructor
+ * @param {!string} tagName The XML tag name for this filter.
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!(string|number)} expression The value to compare.
+ * @param {boolean=} opt_matchCase Case-sensitive?
+ * @extends {ol.format.filter.Comparison}
+ * @api
  */
-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);
+ol.format.filter.ComparisonBinary = function(
+    tagName, propertyName, expression, opt_matchCase) {
 
-    // tell the parent about the child and the child about the parent
-    parentLogger.addChild_(leafName, logger);
-    logger.setParent_(parentLogger);
-  }
+  ol.format.filter.Comparison.call(this, tagName, propertyName);
+
+  /**
+   * @public
+   * @type {!(string|number)}
+   */
+  this.expression = expression;
 
-  goog.debug.LogManager.loggers_[name] = logger;
-  return logger;
+  /**
+   * @public
+   * @type {boolean|undefined}
+   */
+  this.matchCase = opt_matchCase;
 };
+ol.inherits(ol.format.filter.ComparisonBinary, ol.format.filter.Comparison);
+
+goog.provide('ol.format.filter.EqualTo');
+
+goog.require('ol');
+goog.require('ol.format.filter.ComparisonBinary');
 
-// 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.
+ * @classdesc
+ * Represents a `<PropertyIsEqualTo>` comparison operator.
  *
+ * @constructor
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!(string|number)} expression The value to compare.
+ * @param {boolean=} opt_matchCase Case-sensitive?
+ * @extends {ol.format.filter.ComparisonBinary}
+ * @api
  */
+ol.format.filter.EqualTo = function(propertyName, expression, opt_matchCase) {
+  ol.format.filter.ComparisonBinary.call(this, 'PropertyIsEqualTo', propertyName, expression, opt_matchCase);
+};
+ol.inherits(ol.format.filter.EqualTo, ol.format.filter.ComparisonBinary);
 
-goog.provide('goog.debug.RelativeTimeProvider');
+goog.provide('ol.format.filter.GreaterThan');
 
+goog.require('ol');
+goog.require('ol.format.filter.ComparisonBinary');
 
 
 /**
- * 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.
+ * @classdesc
+ * Represents a `<PropertyIsGreaterThan>` comparison operator.
  *
  * @constructor
- * @final
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!number} expression The value to compare.
+ * @extends {ol.format.filter.ComparisonBinary}
+ * @api
  */
-goog.debug.RelativeTimeProvider = function() {
-  /**
-   * The start time.
-   * @type {number}
-   * @private
-   */
-  this.relativeTimeStart_ = goog.now();
+ol.format.filter.GreaterThan = function(propertyName, expression) {
+  ol.format.filter.ComparisonBinary.call(this, 'PropertyIsGreaterThan', propertyName, expression);
 };
+ol.inherits(ol.format.filter.GreaterThan, ol.format.filter.ComparisonBinary);
 
+goog.provide('ol.format.filter.GreaterThanOrEqualTo');
 
-/**
- * Default instance.
- * @type {goog.debug.RelativeTimeProvider}
- * @private
- */
-goog.debug.RelativeTimeProvider.defaultInstance_ =
-    new goog.debug.RelativeTimeProvider();
+goog.require('ol');
+goog.require('ol.format.filter.ComparisonBinary');
 
 
 /**
- * Sets the start time to the specified time.
- * @param {number} timeStamp The start time.
+ * @classdesc
+ * Represents a `<PropertyIsGreaterThanOrEqualTo>` comparison operator.
+ *
+ * @constructor
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!number} expression The value to compare.
+ * @extends {ol.format.filter.ComparisonBinary}
+ * @api
  */
-goog.debug.RelativeTimeProvider.prototype.set = function(timeStamp) {
-  this.relativeTimeStart_ = timeStamp;
+ol.format.filter.GreaterThanOrEqualTo = function(propertyName, expression) {
+  ol.format.filter.ComparisonBinary.call(this, 'PropertyIsGreaterThanOrEqualTo', propertyName, expression);
 };
+ol.inherits(ol.format.filter.GreaterThanOrEqualTo, ol.format.filter.ComparisonBinary);
 
+goog.provide('ol.format.filter.Spatial');
 
-/**
- * Resets the start time to now.
- */
-goog.debug.RelativeTimeProvider.prototype.reset = function() {
-  this.set(goog.now());
-};
+goog.require('ol');
+goog.require('ol.format.filter.Filter');
 
 
 /**
- * @return {number} The start time.
+ * @classdesc
+ * Represents a spatial operator to test whether a geometry-valued property
+ * relates to a given geometry.
+ *
+ * @constructor
+ * @param {!string} tagName The XML tag name for this filter.
+ * @param {!string} geometryName Geometry name to use.
+ * @param {!ol.geom.Geometry} geometry Geometry.
+ * @param {string=} opt_srsName SRS name. No srsName attribute will be
+ *    set on geometries when this is not provided.
+ * @extends {ol.format.filter.Filter}
+ * @api
  */
-goog.debug.RelativeTimeProvider.prototype.get = function() {
-  return this.relativeTimeStart_;
-};
+ol.format.filter.Spatial = function(tagName, geometryName, geometry, opt_srsName) {
 
+  ol.format.filter.Filter.call(this, tagName);
 
-/**
- * @return {goog.debug.RelativeTimeProvider} The default instance.
- */
-goog.debug.RelativeTimeProvider.getDefaultInstance = function() {
-  return goog.debug.RelativeTimeProvider.defaultInstance_;
-};
+  /**
+   * @public
+   * @type {!string}
+   */
+  this.geometryName = geometryName || 'the_geom';
+
+  /**
+   * @public
+   * @type {ol.geom.Geometry}
+   */
+  this.geometry = geometry;
+
+  /**
+   * @public
+   * @type {string|undefined}
+   */
+  this.srsName = opt_srsName;
+};
+ol.inherits(ol.format.filter.Spatial, ol.format.filter.Filter);
+
+goog.provide('ol.format.filter.Intersects');
+
+goog.require('ol');
+goog.require('ol.format.filter.Spatial');
 
-// 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.
+ * @classdesc
+ * Represents a `<Intersects>` operator to test whether a geometry-valued property
+ * intersects a given geometry.
  *
+ * @constructor
+ * @param {!string} geometryName Geometry name to use.
+ * @param {!ol.geom.Geometry} geometry Geometry.
+ * @param {string=} opt_srsName SRS name. No srsName attribute will be
+ *    set on geometries when this is not provided.
+ * @extends {ol.format.filter.Spatial}
+ * @api
  */
+ol.format.filter.Intersects = function(geometryName, geometry, opt_srsName) {
 
-goog.provide('goog.debug.Formatter');
-goog.provide('goog.debug.HtmlFormatter');
-goog.provide('goog.debug.TextFormatter');
+  ol.format.filter.Spatial.call(this, 'Intersects', geometryName, geometry, opt_srsName);
 
-goog.require('goog.debug');
-goog.require('goog.debug.Logger');
-goog.require('goog.debug.RelativeTimeProvider');
-goog.require('goog.html.SafeHtml');
+};
+ol.inherits(ol.format.filter.Intersects, ol.format.filter.Spatial);
+
+goog.provide('ol.format.filter.IsBetween');
 
+goog.require('ol');
+goog.require('ol.format.filter.Comparison');
 
 
 /**
- * Base class for Formatters. A Formatter is used to format a LogRecord into
- * something that can be displayed to the user.
+ * @classdesc
+ * Represents a `<PropertyIsBetween>` comparison operator.
  *
- * @param {string=} opt_prefix The prefix to place before text records.
  * @constructor
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!number} lowerBoundary The lower bound of the range.
+ * @param {!number} upperBoundary The upper bound of the range.
+ * @extends {ol.format.filter.Comparison}
+ * @api
  */
-goog.debug.Formatter = function(opt_prefix) {
-  this.prefix_ = opt_prefix || '';
+ol.format.filter.IsBetween = function(propertyName, lowerBoundary, upperBoundary) {
+  ol.format.filter.Comparison.call(this, 'PropertyIsBetween', propertyName);
 
   /**
-   * A provider that returns the relative start time.
-   * @type {goog.debug.RelativeTimeProvider}
-   * @private
+   * @public
+   * @type {!number}
+   */
+  this.lowerBoundary = lowerBoundary;
+
+  /**
+   * @public
+   * @type {!number}
    */
-  this.startTimeProvider_ =
-      goog.debug.RelativeTimeProvider.getDefaultInstance();
+  this.upperBoundary = upperBoundary;
 };
+ol.inherits(ol.format.filter.IsBetween, ol.format.filter.Comparison);
 
+goog.provide('ol.format.filter.IsLike');
 
-/**
- * Whether to append newlines to the end of formatted log records.
- * @type {boolean}
- */
-goog.debug.Formatter.prototype.appendNewline = true;
+goog.require('ol');
+goog.require('ol.format.filter.Comparison');
 
 
 /**
- * Whether to show absolute time in the DebugWindow.
- * @type {boolean}
+ * @classdesc
+ * Represents a `<PropertyIsLike>` comparison operator.
+ *
+ * @constructor
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!string} pattern Text pattern.
+ * @param {string=} opt_wildCard Pattern character which matches any sequence of
+ *    zero or more string characters. Default is '*'.
+ * @param {string=} opt_singleChar pattern character which matches any single
+ *    string character. Default is '.'.
+ * @param {string=} opt_escapeChar Escape character which can be used to escape
+ *    the pattern characters. Default is '!'.
+ * @param {boolean=} opt_matchCase Case-sensitive?
+ * @extends {ol.format.filter.Comparison}
+ * @api
  */
-goog.debug.Formatter.prototype.showAbsoluteTime = true;
+ol.format.filter.IsLike = function(propertyName, pattern,
+    opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase) {
+  ol.format.filter.Comparison.call(this, 'PropertyIsLike', propertyName);
 
+  /**
+   * @public
+   * @type {!string}
+   */
+  this.pattern = pattern;
 
-/**
- * Whether to show relative time in the DebugWindow.
- * @type {boolean}
- */
-goog.debug.Formatter.prototype.showRelativeTime = true;
-
+  /**
+   * @public
+   * @type {!string}
+   */
+  this.wildCard = (opt_wildCard !== undefined) ? opt_wildCard : '*';
 
-/**
- * Whether to show the logger name in the DebugWindow.
- * @type {boolean}
- */
-goog.debug.Formatter.prototype.showLoggerName = true;
+  /**
+   * @public
+   * @type {!string}
+   */
+  this.singleChar = (opt_singleChar !== undefined) ? opt_singleChar : '.';
 
+  /**
+   * @public
+   * @type {!string}
+   */
+  this.escapeChar = (opt_escapeChar !== undefined) ? opt_escapeChar : '!';
 
-/**
- * Whether to show the logger exception text.
- * @type {boolean}
- */
-goog.debug.Formatter.prototype.showExceptionText = false;
+  /**
+   * @public
+   * @type {boolean|undefined}
+   */
+  this.matchCase = opt_matchCase;
+};
+ol.inherits(ol.format.filter.IsLike, ol.format.filter.Comparison);
 
+goog.provide('ol.format.filter.IsNull');
 
-/**
- * Whether to show the severity level.
- * @type {boolean}
- */
-goog.debug.Formatter.prototype.showSeverityLevel = false;
+goog.require('ol');
+goog.require('ol.format.filter.Comparison');
 
 
 /**
- * Formats a record.
- * @param {goog.debug.LogRecord} logRecord the logRecord to format.
- * @return {string} The formatted string.
+ * @classdesc
+ * Represents a `<PropertyIsNull>` comparison operator.
+ *
+ * @constructor
+ * @param {!string} propertyName Name of the context property to compare.
+ * @extends {ol.format.filter.Comparison}
+ * @api
  */
-goog.debug.Formatter.prototype.formatRecord = goog.abstractMethod;
+ol.format.filter.IsNull = function(propertyName) {
+  ol.format.filter.Comparison.call(this, 'PropertyIsNull', propertyName);
+};
+ol.inherits(ol.format.filter.IsNull, ol.format.filter.Comparison);
 
+goog.provide('ol.format.filter.LessThan');
 
-/**
- * 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;
+goog.require('ol');
+goog.require('ol.format.filter.ComparisonBinary');
 
 
 /**
- * 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.
+ * @classdesc
+ * Represents a `<PropertyIsLessThan>` comparison operator.
+ *
+ * @constructor
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!number} expression The value to compare.
+ * @extends {ol.format.filter.ComparisonBinary}
+ * @api
  */
-goog.debug.Formatter.prototype.setStartTimeProvider = function(provider) {
-  this.startTimeProvider_ = provider;
+ol.format.filter.LessThan = function(propertyName, expression) {
+  ol.format.filter.ComparisonBinary.call(this, 'PropertyIsLessThan', propertyName, expression);
 };
+ol.inherits(ol.format.filter.LessThan, ol.format.filter.ComparisonBinary);
 
+goog.provide('ol.format.filter.LessThanOrEqualTo');
 
-/**
- * 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_;
-};
+goog.require('ol');
+goog.require('ol.format.filter.ComparisonBinary');
 
 
 /**
- * Resets the start relative time.
+ * @classdesc
+ * Represents a `<PropertyIsLessThanOrEqualTo>` comparison operator.
+ *
+ * @constructor
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!number} expression The value to compare.
+ * @extends {ol.format.filter.ComparisonBinary}
+ * @api
  */
-goog.debug.Formatter.prototype.resetRelativeTimeStart = function() {
-  this.startTimeProvider_.reset();
+ol.format.filter.LessThanOrEqualTo = function(propertyName, expression) {
+  ol.format.filter.ComparisonBinary.call(this, 'PropertyIsLessThanOrEqualTo', propertyName, expression);
 };
+ol.inherits(ol.format.filter.LessThanOrEqualTo, ol.format.filter.ComparisonBinary);
 
+goog.provide('ol.format.filter.Not');
 
-/**
- * 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));
-};
+goog.require('ol');
+goog.require('ol.format.filter.Filter');
 
 
 /**
- * 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
+ * @classdesc
+ * Represents a logical `<Not>` operator for a filter condition.
+ *
+ * @constructor
+ * @param {!ol.format.filter.Filter} condition Filter condition.
+ * @extends {ol.format.filter.Filter}
+ * @api
  */
-goog.debug.Formatter.getTwoDigitString_ = function(n) {
-  if (n < 10) {
-    return '0' + n;
-  }
-  return String(n);
-};
-
+ol.format.filter.Not = function(condition) {
 
-/**
- * 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);
+  ol.format.filter.Filter.call(this, 'Not');
 
-  var spacesToPrepend = 0;
-  if (sec < 1) {
-    spacesToPrepend = 2;
-  } else {
-    while (sec < 100) {
-      spacesToPrepend++;
-      sec *= 10;
-    }
-  }
-  while (spacesToPrepend-- > 0) {
-    str = ' ' + str;
-  }
-  return str;
+  /**
+   * @public
+   * @type {!ol.format.filter.Filter}
+   */
+  this.condition = condition;
 };
+ol.inherits(ol.format.filter.Not, ol.format.filter.Filter);
+
+goog.provide('ol.format.filter.NotEqualTo');
 
+goog.require('ol');
+goog.require('ol.format.filter.ComparisonBinary');
 
 
 /**
- * Formatter that returns formatted html. See formatRecord for the classes
- * it uses for various types of formatted output.
+ * @classdesc
+ * Represents a `<PropertyIsNotEqualTo>` comparison operator.
  *
- * @param {string=} opt_prefix The prefix to place before text records.
  * @constructor
- * @extends {goog.debug.Formatter}
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!(string|number)} expression The value to compare.
+ * @param {boolean=} opt_matchCase Case-sensitive?
+ * @extends {ol.format.filter.ComparisonBinary}
+ * @api
  */
-goog.debug.HtmlFormatter = function(opt_prefix) {
-  goog.debug.Formatter.call(this, opt_prefix);
+ol.format.filter.NotEqualTo = function(propertyName, expression, opt_matchCase) {
+  ol.format.filter.ComparisonBinary.call(this, 'PropertyIsNotEqualTo', propertyName, expression, opt_matchCase);
 };
-goog.inherits(goog.debug.HtmlFormatter, goog.debug.Formatter);
+ol.inherits(ol.format.filter.NotEqualTo, ol.format.filter.ComparisonBinary);
 
+goog.provide('ol.format.filter.Or');
 
-/**
- * Whether to show the logger exception text
- * @type {boolean}
- * @override
- */
-goog.debug.HtmlFormatter.prototype.showExceptionText = true;
+goog.require('ol');
+goog.require('ol.format.filter.LogicalNary');
 
 
 /**
- * Formats a record
- * @param {goog.debug.LogRecord} logRecord the logRecord to format.
- * @return {string} The formatted string as html.
- * @override
+ * @classdesc
+ * Represents a logical `<Or>` operator between two ore more filter conditions.
+ *
+ * @constructor
+ * @param {...ol.format.filter.Filter} conditions Conditions.
+ * @extends {ol.format.filter.LogicalNary}
+ * @api
  */
-goog.debug.HtmlFormatter.prototype.formatRecord = function(logRecord) {
-  if (!logRecord) {
-    return '';
-  }
-  // OK not to use goog.html.SafeHtml.unwrap() here.
-  return this.formatRecordAsHtml(logRecord).getTypedStringValue();
+ol.format.filter.Or = function(conditions) {
+  var params = ['Or'].concat(Array.prototype.slice.call(arguments));
+  ol.format.filter.LogicalNary.apply(this, params);
 };
+ol.inherits(ol.format.filter.Or, ol.format.filter.LogicalNary);
 
+goog.provide('ol.format.filter.Within');
 
-/**
- * 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;
-  }
+goog.require('ol');
+goog.require('ol.format.filter.Spatial');
 
-  // 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));
+/**
+ * @classdesc
+ * Represents a `<Within>` operator to test whether a geometry-valued property
+ * is within a given geometry.
+ *
+ * @constructor
+ * @param {!string} geometryName Geometry name to use.
+ * @param {!ol.geom.Geometry} geometry Geometry.
+ * @param {string=} opt_srsName SRS name. No srsName attribute will be
+ *    set on geometries when this is not provided.
+ * @extends {ol.format.filter.Spatial}
+ * @api
+ */
+ol.format.filter.Within = function(geometryName, geometry, opt_srsName) {
 
+  ol.format.filter.Spatial.call(this, 'Within', geometryName, geometry, opt_srsName);
 
-  // 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;
 };
+ol.inherits(ol.format.filter.Within, ol.format.filter.Spatial);
+
+goog.provide('ol.format.filter');
 
+goog.require('ol.format.filter.And');
+goog.require('ol.format.filter.Bbox');
+goog.require('ol.format.filter.During');
+goog.require('ol.format.filter.EqualTo');
+goog.require('ol.format.filter.GreaterThan');
+goog.require('ol.format.filter.GreaterThanOrEqualTo');
+goog.require('ol.format.filter.Intersects');
+goog.require('ol.format.filter.IsBetween');
+goog.require('ol.format.filter.IsLike');
+goog.require('ol.format.filter.IsNull');
+goog.require('ol.format.filter.LessThan');
+goog.require('ol.format.filter.LessThanOrEqualTo');
+goog.require('ol.format.filter.Not');
+goog.require('ol.format.filter.NotEqualTo');
+goog.require('ol.format.filter.Or');
+goog.require('ol.format.filter.Within');
 
 
 /**
- * Formatter that returns formatted plain text
+ * Create a logical `<And>` operator between two or more filter conditions.
  *
- * @param {string=} opt_prefix The prefix to place before text records.
- * @constructor
- * @extends {goog.debug.Formatter}
- * @final
+ * @param {...ol.format.filter.Filter} conditions Filter conditions.
+ * @returns {!ol.format.filter.And} `<And>` operator.
+ * @api
  */
-goog.debug.TextFormatter = function(opt_prefix) {
-  goog.debug.Formatter.call(this, opt_prefix);
+ol.format.filter.and = function(conditions) {
+  var params = [null].concat(Array.prototype.slice.call(arguments));
+  return new (Function.prototype.bind.apply(ol.format.filter.And, params));
 };
-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
+ * Create a logical `<Or>` operator between two or more filter conditions.
+ *
+ * @param {...ol.format.filter.Filter} conditions Filter conditions.
+ * @returns {!ol.format.filter.Or} `<Or>` operator.
+ * @api
  */
-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('');
+ol.format.filter.or = function(conditions) {
+  var params = [null].concat(Array.prototype.slice.call(arguments));
+  return new (Function.prototype.bind.apply(ol.format.filter.Or, params));
 };
 
 
 /**
- * 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
+ * Represents a logical `<Not>` operator for a filter condition.
+ *
+ * @param {!ol.format.filter.Filter} condition Filter condition.
+ * @returns {!ol.format.filter.Not} `<Not>` operator.
+ * @api
  */
-goog.debug.TextFormatter.prototype.formatRecordAsHtml = function(logRecord) {
-  return goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
-      goog.debug.TextFormatter.prototype.formatRecord(logRecord));
+ol.format.filter.not = function(condition) {
+  return new ol.format.filter.Not(condition);
 };
 
-// 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
+ * Create a `<BBOX>` operator to test whether a geometry-valued property
+ * intersects a fixed bounding box
  *
+ * @param {!string} geometryName Geometry name to use.
+ * @param {!ol.Extent} extent Extent.
+ * @param {string=} opt_srsName SRS name. No srsName attribute will be
+ *    set on geometries when this is not provided.
+ * @returns {!ol.format.filter.Bbox} `<BBOX>` operator.
+ * @api
  */
-
-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_ = {};
+ol.format.filter.bbox = function(geometryName, extent, opt_srsName) {
+  return new ol.format.filter.Bbox(geometryName, extent, opt_srsName);
 };
 
-
 /**
- * Returns the text formatter used by this console
- * @return {!goog.debug.TextFormatter} The text formatter.
+ * Create a `<Intersects>` operator to test whether a geometry-valued property
+ * intersects a given geometry.
+ *
+ * @param {!string} geometryName Geometry name to use.
+ * @param {!ol.geom.Geometry} geometry Geometry.
+ * @param {string=} opt_srsName SRS name. No srsName attribute will be
+ *    set on geometries when this is not provided.
+ * @returns {!ol.format.filter.Intersects} `<Intersects>` operator.
+ * @api
  */
-goog.debug.Console.prototype.getFormatter = function() {
-  return this.formatter_;
+ol.format.filter.intersects = function(geometryName, geometry, opt_srsName) {
+  return new ol.format.filter.Intersects(geometryName, geometry, opt_srsName);
 };
 
-
 /**
- * Sets whether we are currently capturing logger output.
- * @param {boolean} capturing Whether to capture logger output.
+ * Create a `<Within>` operator to test whether a geometry-valued property
+ * is within a given geometry.
+ *
+ * @param {!string} geometryName Geometry name to use.
+ * @param {!ol.geom.Geometry} geometry Geometry.
+ * @param {string=} opt_srsName SRS name. No srsName attribute will be
+ *    set on geometries when this is not provided.
+ * @returns {!ol.format.filter.Within} `<Within>` operator.
+ * @api
  */
-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;
+ol.format.filter.within = function(geometryName, geometry, opt_srsName) {
+  return new ol.format.filter.Within(geometryName, geometry, opt_srsName);
 };
 
 
 /**
- * Adds a log record.
- * @param {goog.debug.LogRecord} logRecord The log entry.
+ * Creates a `<PropertyIsEqualTo>` comparison operator.
+ *
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!(string|number)} expression The value to compare.
+ * @param {boolean=} opt_matchCase Case-sensitive?
+ * @returns {!ol.format.filter.EqualTo} `<PropertyIsEqualTo>` operator.
+ * @api
  */
-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;
-  }
+ol.format.filter.equalTo = function(propertyName, expression, opt_matchCase) {
+  return new ol.format.filter.EqualTo(propertyName, expression, opt_matchCase);
 };
 
 
 /**
- * Adds a logger name to be filtered.
- * @param {string} loggerName the logger name to add.
+ * Creates a `<PropertyIsNotEqualTo>` comparison operator.
+ *
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!(string|number)} expression The value to compare.
+ * @param {boolean=} opt_matchCase Case-sensitive?
+ * @returns {!ol.format.filter.NotEqualTo} `<PropertyIsNotEqualTo>` operator.
+ * @api
  */
-goog.debug.Console.prototype.addFilter = function(loggerName) {
-  this.filteredLoggers_[loggerName] = true;
+ol.format.filter.notEqualTo = function(propertyName, expression, opt_matchCase) {
+  return new ol.format.filter.NotEqualTo(propertyName, expression, opt_matchCase);
 };
 
 
 /**
- * Removes a logger name to be filtered.
- * @param {string} loggerName the logger name to remove.
+ * Creates a `<PropertyIsLessThan>` comparison operator.
+ *
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!number} expression The value to compare.
+ * @returns {!ol.format.filter.LessThan} `<PropertyIsLessThan>` operator.
+ * @api
  */
-goog.debug.Console.prototype.removeFilter = function(loggerName) {
-  delete this.filteredLoggers_[loggerName];
+ol.format.filter.lessThan = function(propertyName, expression) {
+  return new ol.format.filter.LessThan(propertyName, expression);
 };
 
 
 /**
- * Global console logger instance
- * @type {goog.debug.Console}
+ * Creates a `<PropertyIsLessThanOrEqualTo>` comparison operator.
+ *
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!number} expression The value to compare.
+ * @returns {!ol.format.filter.LessThanOrEqualTo} `<PropertyIsLessThanOrEqualTo>` operator.
+ * @api
  */
-goog.debug.Console.instance = null;
+ol.format.filter.lessThanOrEqualTo = function(propertyName, expression) {
+  return new ol.format.filter.LessThanOrEqualTo(propertyName, expression);
+};
 
 
 /**
- * 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
+ * Creates a `<PropertyIsGreaterThan>` comparison operator.
+ *
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!number} expression The value to compare.
+ * @returns {!ol.format.filter.GreaterThan} `<PropertyIsGreaterThan>` operator.
+ * @api
  */
-goog.debug.Console.console_ = goog.global['console'];
+ol.format.filter.greaterThan = function(propertyName, expression) {
+  return new ol.format.filter.GreaterThan(propertyName, expression);
+};
 
 
 /**
- * Sets the console to which to log.
- * @param {!Object} console The console to which to log.
+ * Creates a `<PropertyIsGreaterThanOrEqualTo>` comparison operator.
+ *
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!number} expression The value to compare.
+ * @returns {!ol.format.filter.GreaterThanOrEqualTo} `<PropertyIsGreaterThanOrEqualTo>` operator.
+ * @api
  */
-goog.debug.Console.setConsole = function(console) {
-  goog.debug.Console.console_ = console;
+ol.format.filter.greaterThanOrEqualTo = function(propertyName, expression) {
+  return new ol.format.filter.GreaterThanOrEqualTo(propertyName, expression);
 };
 
 
 /**
- * Install the console and start capturing if "Debug=true" is in the page URL
+ * Creates a `<PropertyIsNull>` comparison operator to test whether a property value
+ * is null.
+ *
+ * @param {!string} propertyName Name of the context property to compare.
+ * @returns {!ol.format.filter.IsNull} `<PropertyIsNull>` operator.
+ * @api
  */
-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);
-  }
+ol.format.filter.isNull = function(propertyName) {
+  return new ol.format.filter.IsNull(propertyName);
 };
 
 
 /**
- * Show an alert with all of the captured debug information.
- * Information is only captured if console is not available
+ * Creates a `<PropertyIsBetween>` comparison operator to test whether an expression
+ * value lies within a range given by a lower and upper bound (inclusive).
+ *
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!number} lowerBoundary The lower bound of the range.
+ * @param {!number} upperBoundary The upper bound of the range.
+ * @returns {!ol.format.filter.IsBetween} `<PropertyIsBetween>` operator.
+ * @api
  */
-goog.debug.Console.show = function() {
-  alert(goog.debug.Console.instance.logBuffer_);
+ol.format.filter.between = function(propertyName, lowerBoundary, upperBoundary) {
+  return new ol.format.filter.IsBetween(propertyName, lowerBoundary, upperBoundary);
 };
 
 
 /**
- * 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
+ * Represents a `<PropertyIsLike>` comparison operator that matches a string property
+ * value against a text pattern.
+ *
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!string} pattern Text pattern.
+ * @param {string=} opt_wildCard Pattern character which matches any sequence of
+ *    zero or more string characters. Default is '*'.
+ * @param {string=} opt_singleChar pattern character which matches any single
+ *    string character. Default is '.'.
+ * @param {string=} opt_escapeChar Escape character which can be used to escape
+ *    the pattern characters. Default is '!'.
+ * @param {boolean=} opt_matchCase Case-sensitive?
+ * @returns {!ol.format.filter.IsLike} `<PropertyIsLike>` operator.
+ * @api
  */
-goog.debug.Console.logToConsole_ = function(console, fnName, record) {
-  if (console[fnName]) {
-    console[fnName](record);
-  } else {
-    console.log(record);
-  }
+ol.format.filter.like = function(propertyName, pattern,
+    opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase) {
+  return new ol.format.filter.IsLike(propertyName, pattern,
+    opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase);
 };
 
-// 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.
+ * Create a `<During>` temporal operator.
  *
- * @author attila@google.com (Attila Bodis)
- * @see ../demos/viewportsizemonitor.html
+ * @param {!string} propertyName Name of the context property to compare.
+ * @param {!string} begin The begin date in ISO-8601 format.
+ * @param {!string} end The end date in ISO-8601 format.
+ * @returns {!ol.format.filter.During} `<During>` operator.
+ * @api
  */
+ol.format.filter.during = function(propertyName, begin, end) {
+  return new ol.format.filter.During(propertyName, begin, end);
+};
 
-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');
+goog.provide('ol.geom.GeometryCollection');
 
+goog.require('ol');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
+goog.require('ol.geom.Geometry');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.obj');
 
 
 /**
- * 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.
+ * @classdesc
+ * An array of {@link ol.geom.Geometry} objects.
  *
- * @param {Window=} opt_window The window to monitor; defaults to the window in
- *    which this code is executing.
  * @constructor
- * @extends {goog.events.EventTarget}
+ * @extends {ol.geom.Geometry}
+ * @param {Array.<ol.geom.Geometry>=} opt_geometries Geometries.
+ * @api
  */
-goog.dom.ViewportSizeMonitor = function(opt_window) {
-  goog.dom.ViewportSizeMonitor.base(this, 'constructor');
+ol.geom.GeometryCollection = function(opt_geometries) {
 
-  /**
-   * The window to monitor. Defaults to the window in which the code is running.
-   * @private {Window}
-   */
-  this.window_ = opt_window || window;
+  ol.geom.Geometry.call(this);
 
   /**
-   * Event listener key for window the window resize handler, as returned by
-   * {@link goog.events.listen}.
-   * @private {goog.events.Key}
+   * @private
+   * @type {Array.<ol.geom.Geometry>}
    */
-  this.listenerKey_ = goog.events.listen(this.window_,
-      goog.events.EventType.RESIZE, this.handleResize_, false, this);
+  this.geometries_ = opt_geometries ? opt_geometries : null;
 
-  /**
-   * The most recently recorded size of the viewport, in pixels.
-   * @private {goog.math.Size}
-   */
-  this.size_ = goog.dom.getViewportSize(this.window_);
+  this.listenGeometriesChange_();
 };
-goog.inherits(goog.dom.ViewportSizeMonitor, goog.events.EventTarget);
+ol.inherits(ol.geom.GeometryCollection, ol.geom.Geometry);
 
 
 /**
- * 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.
+ * @param {Array.<ol.geom.Geometry>} geometries Geometries.
+ * @private
+ * @return {Array.<ol.geom.Geometry>} Cloned geometries.
  */
-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);
+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;
 };
 
 
 /**
- * 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.
+ * @private
  */
-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];
+ol.geom.GeometryCollection.prototype.unlistenGeometriesChange_ = function() {
+  var i, ii;
+  if (!this.geometries_) {
+    return;
+  }
+  for (i = 0, ii = this.geometries_.length; i < ii; ++i) {
+    ol.events.unlisten(
+        this.geometries_[i], ol.events.EventType.CHANGE,
+        this.changed, this);
+  }
 };
 
 
 /**
- * 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_ = {};
+ol.geom.GeometryCollection.prototype.listenGeometriesChange_ = function() {
+  var i, ii;
+  if (!this.geometries_) {
+    return;
+  }
+  for (i = 0, ii = this.geometries_.length; i < ii; ++i) {
+    ol.events.listen(
+        this.geometries_[i], ol.events.EventType.CHANGE,
+        this.changed, this);
+  }
+};
 
 
 /**
- * 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.
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.GeometryCollection} Clone.
+ * @override
+ * @api
  */
-goog.dom.ViewportSizeMonitor.prototype.getSize = function() {
-  // Return a clone instead of the original to preserve encapsulation.
-  return this.size_ ? this.size_.clone() : null;
+ol.geom.GeometryCollection.prototype.clone = function() {
+  var geometryCollection = new ol.geom.GeometryCollection(null);
+  geometryCollection.setGeometries(this.geometries_);
+  return geometryCollection;
 };
 
 
-/** @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;
+/**
+ * @inheritDoc
+ */
+ol.geom.GeometryCollection.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
+  if (minSquaredDistance <
+      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
+    return minSquaredDistance;
   }
-
-  this.window_ = null;
-  this.size_ = null;
+  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;
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-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);
+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;
 };
 
-// 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
+ * @inheritDoc
  */
-
-goog.provide('goog.events.KeyCodes');
-
-goog.require('goog.userAgent');
-
-goog.forwardDeclare('goog.events.BrowserEvent');
+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;
+};
 
 
 /**
- * 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}
+ * Return the geometries that make up this geometry collection.
+ * @return {Array.<ol.geom.Geometry>} Geometries.
+ * @api
  */
-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;
-  }
+ol.geom.GeometryCollection.prototype.getGeometries = function() {
+  return ol.geom.GeometryCollection.cloneGeometries_(this.geometries_);
 };
 
 
 /**
- * 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.
+ * @return {Array.<ol.geom.Geometry>} Geometries.
  */
-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);
-  }
+ol.geom.GeometryCollection.prototype.getGeometriesArray = function() {
+  return this.geometries_;
+};
 
-  // 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);
+/**
+ * @inheritDoc
+ */
+ol.geom.GeometryCollection.prototype.getSimplifiedGeometry = function(squaredTolerance) {
+  if (this.simplifiedGeometryRevision != this.getRevision()) {
+    ol.obj.clear(this.simplifiedGeometryCache);
+    this.simplifiedGeometryMaxMinSquaredTolerance = 0;
+    this.simplifiedGeometryRevision = this.getRevision();
   }
-  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;
+  if (squaredTolerance < 0 ||
+      (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 &&
+       squaredTolerance < this.simplifiedGeometryMaxMinSquaredTolerance)) {
+    return this;
   }
-
-  // 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;
+  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;
     }
   }
-
-  // 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.
+ * @inheritDoc
+ * @api
  */
-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;
-  }
+ol.geom.GeometryCollection.prototype.getType = function() {
+  return ol.geom.GeometryType.GEOMETRY_COLLECTION;
+};
 
-  // 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:
+/**
+ * @inheritDoc
+ * @api
+ */
+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;
-    default:
-      return false;
+    }
   }
+  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.
+ * @return {boolean} Is empty.
  */
-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;
-  }
+ol.geom.GeometryCollection.prototype.isEmpty = function() {
+  return this.geometries_.length === 0;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
+ * @api
  */
-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;
+ol.geom.GeometryCollection.prototype.rotate = function(angle, anchor) {
+  var geometries = this.geometries_;
+  for (var i = 0, ii = geometries.length; i < ii; ++i) {
+    geometries[i].rotate(angle, anchor);
   }
+  this.changed();
 };
 
 
 /**
- * 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.
+ * @inheritDoc
+ * @api
  */
-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;
+ol.geom.GeometryCollection.prototype.scale = function(sx, opt_sy, opt_anchor) {
+  var anchor = opt_anchor;
+  if (!anchor) {
+    anchor = ol.extent.getCenter(this.getExtent());
   }
+  var geometries = this.geometries_;
+  for (var i = 0, ii = geometries.length; i < ii; ++i) {
+    geometries[i].scale(sx, opt_sy, anchor);
+  }
+  this.changed();
 };
 
-// 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
+ * Set the geometries that make up this geometry collection.
+ * @param {Array.<ol.geom.Geometry>} geometries Geometries.
+ * @api
  */
+ol.geom.GeometryCollection.prototype.setGeometries = function(geometries) {
+  this.setGeometriesArray(
+      ol.geom.GeometryCollection.cloneGeometries_(geometries));
+};
 
-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');
 
+/**
+ * @param {Array.<ol.geom.Geometry>} geometries Geometries.
+ */
+ol.geom.GeometryCollection.prototype.setGeometriesArray = function(geometries) {
+  this.unlistenGeometriesChange_();
+  this.geometries_ = geometries;
+  this.listenGeometriesChange_();
+  this.changed();
+};
 
 
 /**
- * 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
+ * @inheritDoc
+ * @api
  */
-goog.events.KeyHandler = function(opt_element, opt_capture) {
-  goog.events.EventTarget.call(this);
-
-  if (opt_element) {
-    this.attach(opt_element, opt_capture);
+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();
 };
-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
+ * Translate the geometry.
+ * @param {number} deltaX Delta X.
+ * @param {number} deltaY Delta Y.
+ * @override
+ * @api
  */
-goog.events.KeyHandler.prototype.element_ = null;
+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();
+};
 
 
 /**
- * The key for the key press listener.
- * @type {goog.events.Key}
- * @private
+ * @inheritDoc
  */
-goog.events.KeyHandler.prototype.keyPressKey_ = null;
+ol.geom.GeometryCollection.prototype.disposeInternal = function() {
+  this.unlistenGeometriesChange_();
+  ol.geom.Geometry.prototype.disposeInternal.call(this);
+};
 
+// TODO: serialize dataProjection as crs member when writing
+// see https://github.com/openlayers/openlayers/issues/2078
 
-/**
- * The key for the key down listener.
- * @type {goog.events.Key}
- * @private
- */
-goog.events.KeyHandler.prototype.keyDownKey_ = null;
+goog.provide('ol.format.GeoJSON');
+
+goog.require('ol');
+goog.require('ol.asserts');
+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.obj');
+goog.require('ol.proj');
 
 
 /**
- * The key for the key up listener.
- * @type {goog.events.Key}
- * @private
+ * @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
  */
-goog.events.KeyHandler.prototype.keyUpKey_ = null;
+ol.format.GeoJSON = function(opt_options) {
 
+  var options = opt_options ? opt_options : {};
 
-/**
- * Used to detect keyboard repeat events.
- * @private
- * @type {number}
- */
-goog.events.KeyHandler.prototype.lastKey_ = -1;
+  ol.format.JSONFeature.call(this);
 
+  /**
+   * @inheritDoc
+   */
+  this.defaultDataProjection = ol.proj.get(
+      options.defaultDataProjection ?
+          options.defaultDataProjection : 'EPSG:4326');
 
-/**
- * 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;
+
+  if (options.featureProjection) {
+    this.defaultFeatureProjection = ol.proj.get(options.featureProjection);
+  }
+
+  /**
+   * Name of the geometry attribute for features.
+   * @type {string|undefined}
+   * @private
+   */
+  this.geometryName_ = options.geometryName;
+
+};
+ol.inherits(ol.format.GeoJSON, ol.format.JSONFeature);
 
 
 /**
- * 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}
+ * @param {GeoJSONGeometry|GeoJSONGeometryCollection} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
  * @private
+ * @return {ol.geom.Geometry} Geometry.
  */
-goog.events.KeyHandler.prototype.altKey_ = false;
+ol.format.GeoJSON.readGeometry_ = function(object, opt_options) {
+  if (!object) {
+    return null;
+  }
+  var geometryReader = ol.format.GeoJSON.GEOMETRY_READERS_[object.type];
+  return /** @type {ol.geom.Geometry} */ (
+      ol.format.Feature.transformWithOptions(
+          geometryReader(object), false, opt_options));
+};
 
 
 /**
- * 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}
+ * @param {GeoJSONGeometryCollection} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
  * @private
+ * @return {ol.geom.GeometryCollection} Geometry collection.
  */
-goog.events.KeyHandler.USES_KEYDOWN_ = goog.userAgent.IE ||
-    goog.userAgent.EDGE ||
-    goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525');
+ol.format.GeoJSON.readGeometryCollectionGeometry_ = function(
+    object, opt_options) {
+  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);
+};
 
 
 /**
- * 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}
+ * @param {GeoJSONGeometry} object Object.
  * @private
+ * @return {ol.geom.Point} Point.
  */
-goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_ = goog.userAgent.MAC &&
-    goog.userAgent.GECKO;
+ol.format.GeoJSON.readPointGeometry_ = function(object) {
+  return new ol.geom.Point(object.coordinates);
+};
 
 
 /**
- * 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.
+ * @param {GeoJSONGeometry} object Object.
  * @private
+ * @return {ol.geom.LineString} LineString.
  */
-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;
-    }
-  }
+ol.format.GeoJSON.readLineStringGeometry_ = function(object) {
+  return new ol.geom.LineString(object.coordinates);
 };
 
 
 /**
- * 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.
+ * @param {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.MultiLineString} MultiLineString.
  */
-goog.events.KeyHandler.prototype.resetState = function() {
-  this.lastKey_ = -1;
-  this.keyCode_ = -1;
+ol.format.GeoJSON.readMultiLineStringGeometry_ = function(object) {
+  return new ol.geom.MultiLineString(object.coordinates);
 };
 
 
 /**
- * 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.
+ * @param {GeoJSONGeometry} object Object.
  * @private
+ * @return {ol.geom.MultiPoint} MultiPoint.
  */
-goog.events.KeyHandler.prototype.handleKeyup_ = function(e) {
-  this.resetState();
-  this.altKey_ = e.altKey;
+ol.format.GeoJSON.readMultiPointGeometry_ = function(object) {
+  return new ol.geom.MultiPoint(object.coordinates);
 };
 
 
 /**
- * Handles the events on the element.
- * @param {goog.events.BrowserEvent} e  The keyboard event sent from the
- *     browser.
+ * @param {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.MultiPolygon} MultiPolygon.
  */
-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);
+ol.format.GeoJSON.readMultiPolygonGeometry_ = function(object) {
+  return new ol.geom.MultiPolygon(object.coordinates);
 };
 
 
 /**
- * Returns the element listened on for the real keyboard events.
- * @return {Element|Document|null} The element listened on for the real
- *     keyboard events.
+ * @param {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Polygon} Polygon.
  */
-goog.events.KeyHandler.prototype.getElement = function() {
-  return this.element_;
+ol.format.GeoJSON.readPolygonGeometry_ = function(object) {
+  return new ol.geom.Polygon(object.coordinates);
 };
 
 
 /**
- * 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).
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometry|GeoJSONGeometryCollection} GeoJSON geometry.
  */
-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);
+ol.format.GeoJSON.writeGeometry_ = function(geometry, opt_options) {
+  var geometryWriter = ol.format.GeoJSON.GEOMETRY_WRITERS_[geometry.getType()];
+  return geometryWriter(/** @type {ol.geom.Geometry} */ (
+      ol.format.Feature.transformWithOptions(geometry, true, opt_options)),
+      opt_options);
 };
 
 
 /**
- * Removes the listeners that may exist.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @private
+ * @return {GeoJSONGeometryCollection} Empty GeoJSON geometry collection.
  */
-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;
+ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ = function(geometry) {
+  return /** @type {GeoJSONGeometryCollection} */ ({
+    type: 'GeometryCollection',
+    geometries: []
+  });
 };
 
 
-/** @override */
-goog.events.KeyHandler.prototype.disposeInternal = function() {
-  goog.events.KeyHandler.superClass_.disposeInternal.call(this);
-  this.detach();
+/**
+ * @param {ol.geom.GeometryCollection} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometryCollection} GeoJSON geometry collection.
+ */
+ol.format.GeoJSON.writeGeometryCollectionGeometry_ = function(
+    geometry, opt_options) {
+  var geometries = geometry.getGeometriesArray().map(function(geometry) {
+    var options = ol.obj.assign({}, opt_options);
+    delete options.featureProjection;
+    return ol.format.GeoJSON.writeGeometry_(geometry, options);
+  });
+  return /** @type {GeoJSONGeometryCollection} */ ({
+    type: 'GeometryCollection',
+    geometries: geometries
+  });
 };
 
 
-
 /**
- * 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
+ * @param {ol.geom.LineString} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
  */
-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;
+ol.format.GeoJSON.writeLineStringGeometry_ = function(geometry, opt_options) {
+  return /** @type {GeoJSONGeometry} */ ({
+    type: 'LineString',
+    coordinates: geometry.getCoordinates()
+  });
 };
-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
+ * @param {ol.geom.MultiLineString} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
  */
-
-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');
-
+ol.format.GeoJSON.writeMultiLineStringGeometry_ = function(geometry, opt_options) {
+  return /** @type {GeoJSONGeometry} */ ({
+    type: 'MultiLineString',
+    coordinates: geometry.getCoordinates()
+  });
+};
 
 
 /**
- * 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}
+ * @param {ol.geom.MultiPoint} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
  */
-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);
+ol.format.GeoJSON.writeMultiPointGeometry_ = function(geometry, opt_options) {
+  return /** @type {GeoJSONGeometry} */ ({
+    type: 'MultiPoint',
+    coordinates: geometry.getCoordinates()
+  });
 };
-goog.inherits(goog.events.MouseWheelHandler, goog.events.EventTarget);
 
 
 /**
- * Enum type for the events fired by the mouse wheel handler.
- * @enum {string}
+ * @param {ol.geom.MultiPolygon} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
  */
-goog.events.MouseWheelHandler.EventType = {
-  MOUSEWHEEL: 'mousewheel'
+ol.format.GeoJSON.writeMultiPolygonGeometry_ = function(geometry, opt_options) {
+  var right;
+  if (opt_options) {
+    right = opt_options.rightHanded;
+  }
+  return /** @type {GeoJSONGeometry} */ ({
+    type: 'MultiPolygon',
+    coordinates: geometry.getCoordinates(right)
+  });
 };
 
 
 /**
- * Optional maximum magnitude for x delta on each mousewheel event.
- * @type {number|undefined}
+ * @param {ol.geom.Point} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
  * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
  */
-goog.events.MouseWheelHandler.prototype.maxDeltaX_;
+ol.format.GeoJSON.writePointGeometry_ = function(geometry, opt_options) {
+  return /** @type {GeoJSONGeometry} */ ({
+    type: 'Point',
+    coordinates: geometry.getCoordinates()
+  });
+};
 
 
 /**
- * Optional maximum magnitude for y delta on each mousewheel event.
- * @type {number|undefined}
+ * @param {ol.geom.Polygon} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
  * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
  */
-goog.events.MouseWheelHandler.prototype.maxDeltaY_;
+ol.format.GeoJSON.writePolygonGeometry_ = function(geometry, opt_options) {
+  var right;
+  if (opt_options) {
+    right = opt_options.rightHanded;
+  }
+  return /** @type {GeoJSONGeometry} */ ({
+    type: 'Polygon',
+    coordinates: geometry.getCoordinates(right)
+  });
+};
 
 
 /**
- * @param {number} maxDeltaX Maximum magnitude for x delta on each mousewheel
- *     event. Should be non-negative.
+ * @const
+ * @private
+ * @type {Object.<string, function(GeoJSONObject): ol.geom.Geometry>}
  */
-goog.events.MouseWheelHandler.prototype.setMaxDeltaX = function(maxDeltaX) {
-  this.maxDeltaX_ = maxDeltaX;
+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_
 };
 
 
 /**
- * @param {number} maxDeltaY Maximum magnitude for y delta on each mousewheel
- *     event. Should be non-negative.
+ * @const
+ * @private
+ * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (GeoJSONGeometry|GeoJSONGeometryCollection)>}
  */
-goog.events.MouseWheelHandler.prototype.setMaxDeltaY = function(maxDeltaY) {
-  this.maxDeltaY_ = maxDeltaY;
+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_
 };
 
 
 /**
- * Handles the events on the element.
- * @param {goog.events.BrowserEvent} e The underlying browser event.
+ * Read a feature from a GeoJSON Feature source.  Only works for Feature or
+ * geometry types.  Use {@link ol.format.GeoJSON#readFeatures} to read
+ * FeatureCollection source. If feature at source has an id, it will be used
+ * as Feature id by calling {@link ol.Feature#setId} internally.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ * @api
  */
-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;
-    }
+ol.format.GeoJSON.prototype.readFeature;
 
-    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;
+/**
+ * Read all features from a GeoJSON source.  Works for all GeoJSON types.
+ * If the source includes only geometries, features will be created with those
+ * geometries.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
+ * @api
+ */
+ol.format.GeoJSON.prototype.readFeatures;
 
-    // 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;
-    }
+/**
+ * @inheritDoc
+ */
+ol.format.GeoJSON.prototype.readFeatureFromObject = function(
+    object, opt_options) {
+  /**
+   * @type {GeoJSONFeature}
+   */
+  var geoJSONFeature = null;
+  if (object.type === 'Feature') {
+    geoJSONFeature = /** @type {GeoJSONFeature} */ (object);
+  } else {
+    geoJSONFeature = /** @type {GeoJSONFeature} */ ({
+      type: 'Feature',
+      geometry: /** @type {GeoJSONGeometry|GeoJSONGeometryCollection} */ (object)
+    });
   }
 
-  if (goog.isNumber(this.maxDeltaX_)) {
-    deltaX = goog.math.clamp(deltaX, -this.maxDeltaX_, this.maxDeltaX_);
+  var geometry = ol.format.GeoJSON.readGeometry_(geoJSONFeature.geometry, opt_options);
+  var feature = new ol.Feature();
+  if (this.geometryName_) {
+    feature.setGeometryName(this.geometryName_);
   }
-  if (goog.isNumber(this.maxDeltaY_)) {
-    deltaY = goog.math.clamp(deltaY, -this.maxDeltaY_, this.maxDeltaY_);
+  feature.setGeometry(geometry);
+  if (geoJSONFeature.id !== undefined) {
+    feature.setId(geoJSONFeature.id);
   }
-  // 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;
+  if (geoJSONFeature.properties) {
+    feature.setProperties(geoJSONFeature.properties);
   }
-  var newEvent = new goog.events.MouseWheelEvent(detail, be, deltaX, deltaY);
-  this.dispatchEvent(newEvent);
+  return feature;
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-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;
+ol.format.GeoJSON.prototype.readFeaturesFromObject = function(
+    object, opt_options) {
+  var geoJSONObject = /** @type {GeoJSONObject} */ (object);
+  /** @type {Array.<ol.Feature>} */
+  var features = null;
+  if (geoJSONObject.type === 'FeatureCollection') {
+    var geoJSONFeatureCollection = /** @type {GeoJSONFeatureCollection} */
+        (object);
+    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));
+    }
   } else {
-    return mouseWheelDelta / scaleFactor;
+    features = [this.readFeatureFromObject(object, opt_options)];
   }
+  return features;
 };
 
 
-/** @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.
+ * Read a geometry from a GeoJSON source.
  *
- * @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
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.geom.Geometry} Geometry.
+ * @api
  */
-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;
+ol.format.GeoJSON.prototype.readGeometry;
 
-  /**
-   * 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;
+/**
+ * @inheritDoc
+ */
+ol.format.GeoJSON.prototype.readGeometryFromObject = function(
+    object, opt_options) {
+  return ol.format.GeoJSON.readGeometry_(
+      /** @type {GeoJSONGeometry} */ (object), opt_options);
 };
-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
+ * Read the projection from a GeoJSON source.
  *
- * @author johnlenz@google.com (John Lenz)
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
  */
-
-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;
-
+ol.format.GeoJSON.prototype.readProjection;
 
 
 /**
- * @constructor
- * @final
+ * @inheritDoc
  */
-goog.log.Logger = goog.debug.Logger;
+ol.format.GeoJSON.prototype.readProjectionFromObject = function(object) {
+  var geoJSONObject = /** @type {GeoJSONObject} */ (object);
+  var crs = geoJSONObject.crs;
+  var projection;
+  if (crs) {
+    if (crs.type == 'name') {
+      projection = 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.
+      projection = ol.proj.get('EPSG:' + crs.properties.code);
+    } else {
+      ol.asserts.assert(false, 36); // Unknown SRS type
+    }
+  } else {
+    projection = this.defaultDataProjection;
+  }
+  return /** @type {ol.proj.Projection} */ (projection);
+};
 
 
+/**
+ * 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.
+ * @override
+ * @api
+ */
+ol.format.GeoJSON.prototype.writeFeature;
+
 
 /**
- * @constructor
- * @final
+ * Encode a feature as a GeoJSON Feature object.
+ *
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {GeoJSONFeature} Object.
+ * @override
+ * @api
  */
-goog.log.Level = goog.debug.Logger.Level;
+ol.format.GeoJSON.prototype.writeFeatureObject = function(feature, opt_options) {
+  opt_options = this.adaptOptions(opt_options);
 
+  var object = /** @type {GeoJSONFeature} */ ({
+    '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 (!ol.obj.isEmpty(properties)) {
+    object.properties = properties;
+  } else {
+    object.properties = null;
+  }
+  return object;
+};
 
 
 /**
- * @constructor
- * @final
+ * 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
  */
-goog.log.LogRecord = goog.debug.LogRecord;
+ol.format.GeoJSON.prototype.writeFeatures;
 
 
 /**
- * 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
+ * Encode an array of features as a GeoJSON object.
  *
- * @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.
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {GeoJSONFeatureCollection} GeoJSON Object.
+ * @override
+ * @api
  */
-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;
+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
+  });
 };
 
 
-// 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.
+ * 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
  */
-goog.log.addHandler = function(logger, handler) {
-  if (goog.log.ENABLED && logger) {
-    logger.addHandler(handler);
-  }
+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.
+ * @override
+ * @api
+ */
+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('ol');
+goog.require('ol.array');
+goog.require('ol.format.Feature');
+goog.require('ol.format.FormatType');
+goog.require('ol.xml');
+
 
 /**
- * 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.
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for XML feature formats.
+ *
+ * @constructor
+ * @abstract
+ * @extends {ol.format.Feature}
  */
-goog.log.removeHandler = function(logger, handler) {
-  if (goog.log.ENABLED && logger) {
-    return logger.removeHandler(handler);
-  } else {
-    return false;
-  }
+ol.format.XMLFeature = function() {
+
+  /**
+   * @type {XMLSerializer}
+   * @private
+   */
+  this.xmlSerializer_ = new XMLSerializer();
+
+  ol.format.Feature.call(this);
 };
+ol.inherits(ol.format.XMLFeature, ol.format.Feature);
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.log.log = function(logger, level, msg, opt_exception) {
-  if (goog.log.ENABLED && logger) {
-    logger.log(level, msg, opt_exception);
-  }
+ol.format.XMLFeature.prototype.getType = function() {
+  return ol.format.FormatType.XML;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.log.error = function(logger, msg, opt_exception) {
-  if (goog.log.ENABLED && logger) {
-    logger.severe(msg, opt_exception);
+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 (typeof source === 'string') {
+    var doc = ol.xml.parse(source);
+    return this.readFeatureFromDocument(doc, opt_options);
+  } else {
+    return null;
   }
 };
 
 
 /**
- * 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.
+ * @param {Document} doc Document.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @return {ol.Feature} Feature.
  */
-goog.log.warning = function(logger, msg, opt_exception) {
-  if (goog.log.ENABLED && logger) {
-    logger.warning(msg, opt_exception);
+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;
   }
 };
 
 
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @return {ol.Feature} Feature.
  */
-goog.log.info = function(logger, msg, opt_exception) {
-  if (goog.log.ENABLED && logger) {
-    logger.info(msg, opt_exception);
-  }
+ol.format.XMLFeature.prototype.readFeatureFromNode = function(node, opt_options) {
+  return null; // not implemented
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.log.fine = function(logger, msg, opt_exception) {
-  if (goog.log.ENABLED && logger) {
-    logger.fine(msg, opt_exception);
+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 (typeof source === 'string') {
+    var doc = ol.xml.parse(source);
+    return this.readFeaturesFromDocument(doc, opt_options);
+  } else {
+    return [];
   }
 };
 
-// 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.
+/**
+ * @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 == Node.ELEMENT_NODE) {
+      ol.array.extend(features, this.readFeaturesFromNode(n, opt_options));
+    }
+  }
+  return features;
+};
 
-goog.provide('ol.pointer.PointerEvent');
 
+/**
+ * @abstract
+ * @param {Node} node Node.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @protected
+ * @return {Array.<ol.Feature>} Features.
+ */
+ol.format.XMLFeature.prototype.readFeaturesFromNode = function(node, opt_options) {};
 
-goog.require('goog.events');
-goog.require('goog.events.Event');
 
+/**
+ * @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 (typeof source === 'string') {
+    var doc = ol.xml.parse(source);
+    return this.readGeometryFromDocument(doc, opt_options);
+  } else {
+    return null;
+  }
+};
 
 
 /**
- * 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.
+ * @param {Document} doc Document.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @protected
+ * @return {ol.geom.Geometry} Geometry.
  */
-ol.pointer.PointerEvent = function(type, browserEvent, opt_eventDict) {
-  goog.base(this, type);
+ol.format.XMLFeature.prototype.readGeometryFromDocument = function(doc, opt_options) {
+  return null; // not implemented
+};
 
-  /**
-   * @const
-   * @type {goog.events.BrowserEvent}
-   */
-  this.browserEvent = browserEvent;
 
-  var eventDict = opt_eventDict ? opt_eventDict : {};
+/**
+ * @param {Node} node Node.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @protected
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.XMLFeature.prototype.readGeometryFromNode = function(node, opt_options) {
+  return null; // not implemented
+};
 
-  /**
-   * @type {number}
-   */
-  this.buttons = this.getButtons_(eventDict);
 
-  /**
-   * @type {number}
-   */
-  this.pressure = this.getPressure_(eventDict, this.buttons);
+/**
+ * @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 (typeof source === 'string') {
+    var doc = ol.xml.parse(source);
+    return this.readProjectionFromDocument(doc);
+  } else {
+    return null;
+  }
+};
 
-  // MouseEvent related properties
 
-  /**
-   * @type {boolean}
-   */
-  this.bubbles = 'bubbles' in eventDict ? eventDict['bubbles'] : false;
+/**
+ * @param {Document} doc Document.
+ * @protected
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.XMLFeature.prototype.readProjectionFromDocument = function(doc) {
+  return this.defaultDataProjection;
+};
 
-  /**
-   * @type {boolean}
-   */
-  this.cancelable = 'cancelable' in eventDict ? eventDict['cancelable'] : false;
 
-  /**
-   * @type {Object}
-   */
-  this.view = 'view' in eventDict ? eventDict['view'] : null;
+/**
+ * @param {Node} node Node.
+ * @protected
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.XMLFeature.prototype.readProjectionFromNode = function(node) {
+  return this.defaultDataProjection;
+};
 
-  /**
-   * @type {number}
-   */
-  this.detail = 'detail' in eventDict ? eventDict['detail'] : null;
 
-  /**
-   * @type {number}
-   */
-  this.screenX = 'screenX' in eventDict ? eventDict['screenX'] : 0;
+/**
+ * @inheritDoc
+ */
+ol.format.XMLFeature.prototype.writeFeature = function(feature, opt_options) {
+  var node = this.writeFeatureNode(feature, opt_options);
+  return this.xmlSerializer_.serializeToString(node);
+};
 
-  /**
-   * @type {number}
-   */
-  this.screenY = 'screenY' in eventDict ? eventDict['screenY'] : 0;
 
-  /**
-   * @type {number}
-   */
-  this.clientX = 'clientX' in eventDict ? eventDict['clientX'] : 0;
+/**
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @protected
+ * @return {Node} Node.
+ */
+ol.format.XMLFeature.prototype.writeFeatureNode = function(feature, opt_options) {
+  return null; // not implemented
+};
 
-  /**
-   * @type {number}
-   */
-  this.clientY = 'clientY' in eventDict ? eventDict['clientY'] : 0;
 
-  /**
-   * @type {boolean}
-   */
-  this.ctrlKey = 'ctrlKey' in eventDict ? eventDict['ctrlKey'] : false;
+/**
+ * @inheritDoc
+ */
+ol.format.XMLFeature.prototype.writeFeatures = function(features, opt_options) {
+  var node = this.writeFeaturesNode(features, opt_options);
+  return this.xmlSerializer_.serializeToString(node);
+};
 
-  /**
-   * @type {boolean}
-   */
-  this.altKey = 'altKey' in eventDict ? eventDict['altKey'] : false;
 
-  /**
-   * @type {boolean}
-   */
-  this.shiftKey = 'shiftKey' in eventDict ? eventDict['shiftKey'] : false;
+/**
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ */
+ol.format.XMLFeature.prototype.writeFeaturesNode = function(features, opt_options) {
+  return null; // not implemented
+};
 
-  /**
-   * @type {boolean}
-   */
-  this.metaKey = 'metaKey' in eventDict ? eventDict['metaKey'] : false;
 
-  /**
-   * @type {number}
-   */
-  this.button = 'button' in eventDict ? eventDict['button'] : 0;
+/**
+ * @inheritDoc
+ */
+ol.format.XMLFeature.prototype.writeGeometry = function(geometry, opt_options) {
+  var node = this.writeGeometryNode(geometry, opt_options);
+  return this.xmlSerializer_.serializeToString(node);
+};
 
-  /**
-   * @type {Node}
-   */
-  this.relatedTarget = 'relatedTarget' in eventDict ?
-      eventDict['relatedTarget'] : null;
 
-  // PointerEvent related properties
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ */
+ol.format.XMLFeature.prototype.writeGeometryNode = function(geometry, opt_options) {
+  return null; // not implemented
+};
 
-  /**
-   * @const
-   * @type {number}
-   */
-  this.pointerId = 'pointerId' in eventDict ? eventDict['pointerId'] : 0;
+// 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');
 
-  /**
-   * @type {number}
-   */
-  this.width = 'width' in eventDict ? eventDict['width'] : 0;
+goog.require('ol');
+goog.require('ol.array');
+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.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.obj');
+goog.require('ol.proj');
+goog.require('ol.xml');
 
-  /**
-   * @type {number}
-   */
-  this.height = 'height' in eventDict ? eventDict['height'] : 0;
+
+/**
+ * @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
+ * @abstract
+ * @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 : {});
 
   /**
-   * @type {number}
+   * @protected
+   * @type {Array.<string>|string|undefined}
    */
-  this.tiltX = 'tiltX' in eventDict ? eventDict['tiltX'] : 0;
+  this.featureType = options.featureType;
 
   /**
-   * @type {number}
+   * @protected
+   * @type {Object.<string, string>|string|undefined}
    */
-  this.tiltY = 'tiltY' in eventDict ? eventDict['tiltY'] : 0;
+  this.featureNS = options.featureNS;
 
   /**
+   * @protected
    * @type {string}
    */
-  this.pointerType = 'pointerType' in eventDict ? eventDict['pointerType'] : '';
+  this.srsName = options.srsName;
 
   /**
-   * @type {number}
+   * @protected
+   * @type {string}
    */
-  this.hwTimestamp = 'hwTimestamp' in eventDict ? eventDict['hwTimestamp'] : 0;
+  this.schemaLocation = '';
 
   /**
-   * @type {boolean}
+   * @type {Object.<string, Object.<string, Object>>}
    */
-  this.isPrimary = 'isPrimary' in eventDict ? eventDict['isPrimary'] : false;
+  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)
+  };
 
-  // keep the semantics of preventDefault
-  if (browserEvent.preventDefault) {
-    this.preventDefault = function() {
-      browserEvent.preventDefault();
-    };
-  }
+  ol.format.XMLFeature.call(this);
 };
-goog.inherits(ol.pointer.PointerEvent, goog.events.Event);
+ol.inherits(ol.format.GMLBase, ol.format.XMLFeature);
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.GMLBase.GMLNS = 'http://www.opengis.net/gml';
 
 
 /**
+ * A regular expression that matches if a string only contains whitespace
+ * characters. It will e.g. match `''`, `' '`, `'\n'` etc. The non-breaking
+ * space (0xa0) is explicitly included as IE doesn't include it in its
+ * definition of `\s`.
+ *
+ * Information from `goog.string.isEmptyOrWhitespace`: https://github.com/google/closure-library/blob/e877b1e/closure/goog/string/string.js#L156-L160
+ *
+ * @const
+ * @type {RegExp}
  * @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;
-};
+ol.format.GMLBase.ONLY_WHITESPACE_RE_ = /^[\s\xa0]*$/;
 
 
 /**
- * @private
- * @param {Object.<string, ?>} eventDict
- * @param {number} buttons
- * @return {number}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<ol.Feature> | undefined} Features.
  */
-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;
+ol.format.GMLBase.prototype.readFeaturesInternal = function(node, objectStack) {
+  var localName = node.localName;
+  var features = null;
+  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];
+    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 = '';
+            var count = 0;
+            var uri = child.namespaceURI;
+            for (var candidate in featureNS) {
+              if (featureNS[candidate] === uri) {
+                key = candidate;
+                break;
+              }
+              ++count;
+            }
+            if (!key) {
+              key = prefix + count;
+              featureNS[key] = uri;
+            }
+            featureType.push(key + ':' + ft);
+          }
+        }
+      }
+      if (localName != 'featureMember') {
+        // recheck featureType for each featureMember
+        context['featureType'] = featureType;
+        context['featureNS'] = featureNS;
+      }
+    }
+    if (typeof featureNS === 'string') {
+      var ns = featureNS;
+      featureNS = {};
+      featureNS[defaultPrefix] = ns;
+    }
+    var parsersNS = {};
+    var featureTypes = Array.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;
+    }
+    if (localName == 'featureMember') {
+      features = ol.xml.pushParseAndPop(undefined, parsersNS, node, objectStack);
+    } else {
+      features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack);
+    }
   }
-  return pressure;
+  if (features === null) {
+    features = [];
+  }
+  return features;
 };
 
 
 /**
- * Is the `buttons` property supported?
- * @type {boolean}
- */
-ol.pointer.PointerEvent.HAS_BUTTONS = false;
-
-
-/**
- * Checks if the `buttons` property is supported.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.Geometry|undefined} Geometry.
  */
-(function() {
-  try {
-    var ev = new MouseEvent('click', {buttons: 1});
-    ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1;
-  } catch (e) {
+ol.format.GMLBase.prototype.readGeometryElement = function(node, objectStack) {
+  var context = /** @type {Object} */ (objectStack[0]);
+  context['srsName'] = node.firstElementChild.getAttribute('srsName');
+  /** @type {ol.geom.Geometry} */
+  var geometry = ol.xml.pushParseAndPop(null,
+      this.GEOMETRY_PARSERS_, node, objectStack, this);
+  if (geometry) {
+    return /** @type {ol.geom.Geometry} */ (
+        ol.format.Feature.transformWithOptions(geometry, false, context));
+  } else {
+    return undefined;
   }
-})();
-
-// FIXME add tests for browser features (Modernizr?)
-
-goog.provide('ol.dom');
-goog.provide('ol.dom.BrowserFeature');
-
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.userAgent');
-goog.require('goog.vec.Mat4');
-goog.require('ol');
+};
 
 
 /**
- * Create an html canvas element and returns its 2d context.
- * @param {number=} opt_width Canvas width.
- * @param {number=} opt_height Canvas height.
- * @return {CanvasRenderingContext2D}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.Feature} Feature.
  */
-ol.dom.createCanvasContext2D = function(opt_width, opt_height) {
-  var canvas = goog.dom.createElement(goog.dom.TagName.CANVAS);
-  if (opt_width) {
-    canvas.width = opt_width;
+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 = n.localName;
+    // 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 (ol.format.GMLBase.ONLY_WHITESPACE_RE_.test(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);
+    }
   }
-  if (opt_height) {
-    canvas.height = opt_height;
+  var feature = new ol.Feature(values);
+  if (geometryName) {
+    feature.setGeometryName(geometryName);
   }
-  return canvas.getContext('2d');
+  if (fid) {
+    feature.setId(fid);
+  }
+  return feature;
 };
 
 
 /**
- * Detect 2d transform.
- * Adapted from http://stackoverflow.com/q/5661671/130442
- * http://caniuse.com/#feat=transforms2d
- * @return {boolean}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.Point|undefined} Point.
  */
-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 = goog.dom.createElement(goog.dom.TagName.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;
-  };
-}());
+ol.format.GMLBase.prototype.readPoint = function(node, objectStack) {
+  var flatCoordinates =
+      this.readFlatCoordinatesFromNode_(node, objectStack);
+  if (flatCoordinates) {
+    var point = new ol.geom.Point(null);
+    point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
+    return point;
+  }
+};
 
 
 /**
- * Detect 3d transform.
- * Adapted from http://stackoverflow.com/q/5661671/130442
- * http://caniuse.com/#feat=transforms3d
- * @return {boolean}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.MultiPoint|undefined} MultiPoint.
  */
-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 = goog.dom.createElement(goog.dom.TagName.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;
-  };
-}());
+ol.format.GMLBase.prototype.readMultiPoint = function(node, objectStack) {
+  /** @type {Array.<Array.<number>>} */
+  var coordinates = ol.xml.pushParseAndPop([],
+      this.MULTIPOINT_PARSERS_, node, objectStack, this);
+  if (coordinates) {
+    return new ol.geom.MultiPoint(coordinates);
+  } else {
+    return undefined;
+  }
+};
 
 
 /**
- * @param {Element} element Element.
- * @param {string} value Value.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.MultiLineString|undefined} MultiLineString.
  */
-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';
+ol.format.GMLBase.prototype.readMultiLineString = function(node, objectStack) {
+  /** @type {Array.<ol.geom.LineString>} */
+  var lineStrings = ol.xml.pushParseAndPop([],
+      this.MULTILINESTRING_PARSERS_, node, objectStack, this);
+  if (lineStrings) {
+    var multiLineString = new ol.geom.MultiLineString(null);
+    multiLineString.setLineStrings(lineStrings);
+    return multiLineString;
+  } else {
+    return undefined;
   }
 };
 
 
 /**
- * @param {!Element} element Element.
- * @param {goog.vec.Mat4.Number} transform Matrix.
- * @param {number=} opt_precision Precision.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.MultiPolygon|undefined} MultiPolygon.
  */
-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 + ')');
+ol.format.GMLBase.prototype.readMultiPolygon = function(node, objectStack) {
+  /** @type {Array.<ol.geom.Polygon>} */
+  var polygons = ol.xml.pushParseAndPop([],
+      this.MULTIPOLYGON_PARSERS_, node, objectStack, this);
+  if (polygons) {
+    var multiPolygon = new ol.geom.MultiPolygon(null);
+    multiPolygon.setPolygons(polygons);
+    return multiPolygon;
   } 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.
+    return undefined;
   }
 };
 
 
 /**
- * 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}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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;
+ol.format.GMLBase.prototype.pointMemberParser_ = function(node, objectStack) {
+  ol.xml.parseNode(this.POINTMEMBER_PARSERS_,
+      node, objectStack, this);
 };
 
 
 /**
- * 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}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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;
+ol.format.GMLBase.prototype.lineStringMemberParser_ = function(node, objectStack) {
+  ol.xml.parseNode(this.LINESTRINGMEMBER_PARSERS_,
+      node, objectStack, this);
 };
 
-goog.provide('ol.webgl');
-goog.provide('ol.webgl.WebGLContextEventType');
-
 
 /**
- * @const
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
  * @private
- * @type {Array.<string>}
  */
-ol.webgl.CONTEXT_IDS_ = [
-  'experimental-webgl',
-  'webgl',
-  'webkit-3d',
-  'moz-webgl'
-];
+ol.format.GMLBase.prototype.polygonMemberParser_ = function(node, objectStack) {
+  ol.xml.parseNode(this.POLYGONMEMBER_PARSERS_, node,
+      objectStack, this);
+};
 
 
 /**
- * @enum {string}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.LineString|undefined} LineString.
  */
-ol.webgl.WebGLContextEventType = {
-  LOST: 'webglcontextlost',
-  RESTORED: 'webglcontextrestored'
+ol.format.GMLBase.prototype.readLineString = function(node, objectStack) {
+  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 {HTMLCanvasElement} canvas Canvas.
- * @param {Object=} opt_attributes Attributes.
- * @return {WebGLRenderingContext} WebGL rendering context.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} LinearRing flat coordinates.
  */
-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) {
-    }
+ol.format.GMLBase.prototype.readFlatLinearRing_ = function(node, objectStack) {
+  var ring = ol.xml.pushParseAndPop(null,
+      this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node,
+      objectStack, this);
+  if (ring) {
+    return ring;
+  } else {
+    return undefined;
   }
-  return null;
 };
 
-goog.provide('ol.has');
-
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('ol');
-goog.require('ol.dom');
-goog.require('ol.webgl');
-
 
 /**
- * The ratio between physical pixels and device-independent pixels
- * (dips) on the device (`window.devicePixelRatio`).
- * @const
- * @type {number}
- * @api stable
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.LinearRing|undefined} LinearRing.
  */
-ol.has.DEVICE_PIXEL_RATIO = goog.global.devicePixelRatio || 1;
+ol.format.GMLBase.prototype.readLinearRing = function(node, objectStack) {
+  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;
+  }
+};
 
 
 /**
- * True if the browser's Canvas implementation implements {get,set}LineDash.
- * @type {boolean}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.Polygon|undefined} Polygon.
  */
-ol.has.CANVAS_LINE_DASH = false;
+ol.format.GMLBase.prototype.readPolygon = function(node, objectStack) {
+  /** @type {Array.<Array.<number>>} */
+  var flatLinearRings = ol.xml.pushParseAndPop([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) {
+      ol.array.extend(flatCoordinates, flatLinearRings[i]);
+      ends.push(flatCoordinates.length);
+    }
+    polygon.setFlatCoordinates(
+        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
+    return polygon;
+  } else {
+    return undefined;
+  }
+};
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>} Flat coordinates.
  */
-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;
-      }
-    })();
+ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(null,
+      this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node,
+      objectStack, this);
+};
 
 
 /**
- * Indicates if DeviceOrientation is supported in the user's browser.
  * @const
- * @type {boolean}
- * @api stable
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.has.DEVICE_ORIENTATION = 'DeviceOrientationEvent' in goog.global;
+ol.format.GMLBase.prototype.MULTIPOINT_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'pointMember': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.pointMemberParser_),
+    'pointMembers': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.pointMemberParser_)
+  }
+};
 
 
 /**
- * True if `ol.ENABLE_DOM` is set to `true` at compile time.
  * @const
- * @type {boolean}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.has.DOM = ol.ENABLE_DOM;
+ol.format.GMLBase.prototype.MULTILINESTRING_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'lineStringMember': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.lineStringMemberParser_),
+    'lineStringMembers': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.lineStringMemberParser_)
+  }
+};
 
 
 /**
- * Is HTML5 geolocation supported in the current browser?
  * @const
- * @type {boolean}
- * @api stable
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.has.GEOLOCATION = 'geolocation' in goog.global.navigator;
+ol.format.GMLBase.prototype.MULTIPOLYGON_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'polygonMember': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.polygonMemberParser_),
+    'polygonMembers': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.polygonMemberParser_)
+  }
+};
 
 
 /**
- * True if browser supports touch events.
  * @const
- * @type {boolean}
- * @api stable
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.has.TOUCH = ol.ASSUME_TOUCH || 'ontouchstart' in goog.global;
+ol.format.GMLBase.prototype.POINTMEMBER_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'Point': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_)
+  }
+};
 
 
 /**
- * True if browser supports pointer events.
  * @const
- * @type {boolean}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.has.POINTER = 'PointerEvent' in goog.global;
+ol.format.GMLBase.prototype.LINESTRINGMEMBER_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'LineString': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.readLineString)
+  }
+};
 
 
 /**
- * True if browser supports ms pointer events (IE 10).
  * @const
- * @type {boolean}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.has.MSPOINTER = !!(goog.global.navigator.msPointerEnabled);
+ol.format.GMLBase.prototype.POLYGONMEMBER_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'Polygon': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.readPolygon)
+  }
+};
 
 
 /**
- * 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
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @protected
  */
-ol.has.WEBGL;
-
-
-(function() {
-  if (ol.ENABLE_WEBGL) {
-    var hasWebGL = false;
-    var textureSize;
-    var /** @type {Array.<string>} */ extensions = [];
-
-    if ('WebGLRenderingContext' in goog.global) {
-      try {
-        var canvas = /** @type {HTMLCanvasElement} */
-            (goog.dom.createElement(goog.dom.TagName.CANVAS));
-        var gl = ol.webgl.getContext(canvas, {
-          failIfMajorPerformanceCaveat: true
-        });
-        if (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;
+ol.format.GMLBase.prototype.RING_PARSERS = {
+  'http://www.opengis.net/gml': {
+    'LinearRing': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readFlatLinearRing_)
   }
-})();
-
-goog.provide('ol.pointer.EventSource');
-
-goog.require('goog.events.BrowserEvent');
-
+};
 
 
 /**
- * @param {ol.pointer.PointerEventHandler} dispatcher
- * @param {!Object.<string, function(goog.events.BrowserEvent)>} mapping
- * @constructor
+ * @inheritDoc
  */
-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;
+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;
 };
 
 
 /**
- * List of events supported by this source.
- * @return {Array.<string>} Event names
+ * 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
  */
-ol.pointer.EventSource.prototype.getEvents = function() {
-  return Object.keys(this.mapping_);
-};
+ol.format.GMLBase.prototype.readFeatures;
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.pointer.EventSource.prototype.getMapping = function() {
-  return this.mapping_;
+ol.format.GMLBase.prototype.readFeaturesFromNode = function(node, opt_options) {
+  var options = {
+    featureType: this.featureType,
+    featureNS: this.featureNS
+  };
+  if (opt_options) {
+    ol.obj.assign(options, this.getReadOptions(node, opt_options));
+  }
+  var features = this.readFeaturesInternal(node, [options]);
+  return features || [];
 };
 
 
 /**
- * Returns the handler that should handle a given event type.
- * @param {string} eventType
- * @return {function(goog.events.BrowserEvent)} Handler
+ * @inheritDoc
  */
-ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) {
-  return this.mapping_[eventType];
+ol.format.GMLBase.prototype.readProjectionFromNode = function(node) {
+  return ol.proj.get(this.srsName ? this.srsName :
+      node.firstElementChild.getAttribute('srsName'));
 };
 
-// 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');
+goog.provide('ol.format.XSD');
 
+goog.require('ol.xml');
+goog.require('ol.string');
 
 
 /**
- * @param {ol.pointer.PointerEventHandler} dispatcher
- * @constructor
- * @extends {ol.pointer.EventSource}
+ * @const
+ * @type {string}
  */
-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);
+ol.format.XSD.NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema';
 
-  /**
-   * @const
-   * @type {Object.<string, goog.events.BrowserEvent|Object>}
-   */
-  this.pointerMap = dispatcher.pointerMap;
 
-  /**
-   * @const
-   * @type {Array.<ol.Pixel>}
-   */
-  this.lastTouches = [];
+/**
+ * @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);
 };
-goog.inherits(ol.pointer.MouseSource, ol.pointer.EventSource);
 
 
 /**
- * @const
- * @type {number}
+ * @param {string} string String.
+ * @return {boolean|undefined} Boolean.
  */
-ol.pointer.MouseSource.POINTER_ID = 1;
+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;
+  }
+};
 
 
 /**
- * @const
- * @type {string}
+ * @param {Node} node Node.
+ * @return {number|undefined} DateTime in seconds.
  */
-ol.pointer.MouseSource.POINTER_TYPE = 'mouse';
+ol.format.XSD.readDateTime = function(node) {
+  var s = ol.xml.getAllTextContent(node, false);
+  var dateTime = Date.parse(s);
+  return isNaN(dateTime) ? undefined : dateTime / 1000;
+};
 
 
 /**
- * Radius around touchend that swallows mouse events.
- *
- * @const
- * @type {number}
+ * @param {Node} node Node.
+ * @return {number|undefined} Decimal.
  */
-ol.pointer.MouseSource.DEDUP_DIST = 25;
+ol.format.XSD.readDecimal = function(node) {
+  var s = ol.xml.getAllTextContent(node, false);
+  return ol.format.XSD.readDecimalString(s);
+};
 
 
 /**
- * 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.
+ * @param {string} string String.
+ * @return {number|undefined} Decimal.
  */
-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;
-    }
+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;
   }
-  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}
+ * @param {Node} node Node.
+ * @return {number|undefined} Non negative integer.
  */
-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;
+ol.format.XSD.readNonNegativeInteger = function(node) {
+  var s = ol.xml.getAllTextContent(node, false);
+  return ol.format.XSD.readNonNegativeIntegerString(s);
 };
 
 
 /**
- * Handler for `mousedown`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {string} string String.
+ * @return {number|undefined} Non negative integer.
  */
-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);
+ol.format.XSD.readNonNegativeIntegerString = function(string) {
+  var m = /^\s*(\d+)\s*$/.exec(string);
+  if (m) {
+    return parseInt(m[1], 10);
+  } else {
+    return undefined;
   }
 };
 
 
 /**
- * Handler for `mousemove`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @return {string|undefined} String.
  */
-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);
-  }
+ol.format.XSD.readString = function(node) {
+  return ol.xml.getAllTextContent(node, false).trim();
 };
 
 
 /**
- * Handler for `mouseup`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node to append a TextNode with the boolean to.
+ * @param {boolean} bool Boolean.
  */
-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();
-    }
-  }
+ol.format.XSD.writeBooleanTextNode = function(node, bool) {
+  ol.format.XSD.writeStringTextNode(node, (bool) ? '1' : '0');
 };
 
 
 /**
- * Handler for `mouseover`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node to append a CDATA Section with the string to.
+ * @param {string} string String.
  */
-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);
-  }
+ol.format.XSD.writeCDATASection = function(node, string) {
+  node.appendChild(ol.xml.DOCUMENT.createCDATASection(string));
 };
 
 
 /**
- * Handler for `mouseout`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node to append a TextNode with the dateTime to.
+ * @param {number} dateTime DateTime in seconds.
  */
-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);
-  }
+ol.format.XSD.writeDateTimeTextNode = function(node, dateTime) {
+  var date = new Date(dateTime * 1000);
+  var string = date.getUTCFullYear() + '-' +
+      ol.string.padNumber(date.getUTCMonth() + 1, 2) + '-' +
+      ol.string.padNumber(date.getUTCDate(), 2) + 'T' +
+      ol.string.padNumber(date.getUTCHours(), 2) + ':' +
+      ol.string.padNumber(date.getUTCMinutes(), 2) + ':' +
+      ol.string.padNumber(date.getUTCSeconds(), 2) + 'Z';
+  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
 };
 
 
 /**
- * Dispatches a `pointercancel` event.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node to append a TextNode with the decimal to.
+ * @param {number} decimal Decimal.
  */
-ol.pointer.MouseSource.prototype.cancel = function(inEvent) {
-  var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
-  this.dispatcher.cancel(e, inEvent);
-  this.cleanupMouse();
+ol.format.XSD.writeDecimalTextNode = function(node, decimal) {
+  var string = decimal.toPrecision();
+  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
 };
 
 
 /**
- * Remove the mouse from the list of active pointers.
+ * @param {Node} node Node to append a TextNode with the decimal to.
+ * @param {number} nonNegativeInteger Non negative integer.
  */
-ol.pointer.MouseSource.prototype.cleanupMouse = function() {
-  delete this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()];
+ol.format.XSD.writeNonNegativeIntegerTextNode = function(node, nonNegativeInteger) {
+  var string = nonNegativeInteger.toString();
+  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
 };
 
-// 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');
+/**
+ * @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.require('ol.pointer.EventSource');
+goog.provide('ol.format.GML3');
 
+goog.require('ol');
+goog.require('ol.array');
+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.MultiLineString');
+goog.require('ol.geom.MultiPolygon');
+goog.require('ol.geom.Polygon');
+goog.require('ol.obj');
+goog.require('ol.proj');
+goog.require('ol.xml');
 
 
 /**
- * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @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
- * @extends {ol.pointer.EventSource}
+ * @param {olx.format.GMLOptions=} opt_options
+ *     Optional configuration object.
+ * @extends {ol.format.GMLBase}
+ * @api
  */
-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);
+ol.format.GML3 = function(opt_options) {
+  var options = /** @type {olx.format.GMLOptions} */
+      (opt_options ? opt_options : {});
+
+  ol.format.GMLBase.call(this, options);
 
   /**
-   * @const
-   * @type {Object.<string, goog.events.BrowserEvent|Object>}
+   * @private
+   * @type {boolean}
    */
-  this.pointerMap = dispatcher.pointerMap;
+  this.surface_ = options.surface !== undefined ? options.surface : false;
 
   /**
-   * @const
-   * @type {Array.<string>}
+   * @private
+   * @type {boolean}
    */
-  this.POINTER_TYPES = [
-    '',
-    'unavailable',
-    'touch',
-    'pen',
-    'mouse'
-  ];
+  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.pointer.MsSource, ol.pointer.EventSource);
+ol.inherits(ol.format.GML3, ol.format.GMLBase);
 
 
 /**
- * Creates a copy of the original event that will be used
- * for the fake pointer event.
- *
+ * @const
+ * @type {string}
  * @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];
-  }
+ol.format.GML3.schemaLocation_ = ol.format.GMLBase.GMLNS +
+    ' http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' +
+    '1.0.0/gmlsf.xsd';
 
-  return e;
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.MultiLineString|undefined} MultiLineString.
+ */
+ol.format.GML3.prototype.readMultiCurve_ = function(node, objectStack) {
+  /** @type {Array.<ol.geom.LineString>} */
+  var lineStrings = ol.xml.pushParseAndPop([],
+      this.MULTICURVE_PARSERS_, node, objectStack, this);
+  if (lineStrings) {
+    var multiLineString = new ol.geom.MultiLineString(null);
+    multiLineString.setLineStrings(lineStrings);
+    return multiLineString;
+  } else {
+    return undefined;
+  }
 };
 
 
 /**
- * Remove this pointer from the list of active pointers.
- * @param {number} pointerId
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.MultiPolygon|undefined} MultiPolygon.
  */
-ol.pointer.MsSource.prototype.cleanup = function(pointerId) {
-  delete this.pointerMap[pointerId.toString()];
+ol.format.GML3.prototype.readMultiSurface_ = function(node, objectStack) {
+  /** @type {Array.<ol.geom.Polygon>} */
+  var polygons = ol.xml.pushParseAndPop([],
+      this.MULTISURFACE_PARSERS_, node, objectStack, this);
+  if (polygons) {
+    var multiPolygon = new ol.geom.MultiPolygon(null);
+    multiPolygon.setPolygons(polygons);
+    return multiPolygon;
+  } else {
+    return undefined;
+  }
 };
 
 
 /**
- * Handler for `msPointerDown`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) {
-  this.pointerMap[inEvent.getBrowserEvent().pointerId.toString()] = inEvent;
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.down(e, inEvent);
+ol.format.GML3.prototype.curveMemberParser_ = function(node, objectStack) {
+  ol.xml.parseNode(this.CURVEMEMBER_PARSERS_, node, objectStack, this);
 };
 
 
 /**
- * Handler for `msPointerMove`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.pointer.MsSource.prototype.msPointerMove = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.move(e, inEvent);
+ol.format.GML3.prototype.surfaceMemberParser_ = function(node, objectStack) {
+  ol.xml.parseNode(this.SURFACEMEMBER_PARSERS_,
+      node, objectStack, this);
 };
 
 
 /**
- * Handler for `msPointerUp`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<(Array.<number>)>|undefined} flat coordinates.
  */
-ol.pointer.MsSource.prototype.msPointerUp = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.up(e, inEvent);
-  this.cleanup(inEvent.getBrowserEvent().pointerId);
+ol.format.GML3.prototype.readPatch_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop([null],
+      this.PATCHES_PARSERS_, node, objectStack, this);
 };
 
 
 /**
- * Handler for `msPointerOut`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} flat coordinates.
  */
-ol.pointer.MsSource.prototype.msPointerOut = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.leaveOut(e, inEvent);
+ol.format.GML3.prototype.readSegment_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop([null],
+      this.SEGMENTS_PARSERS_, node, objectStack, this);
 };
 
 
 /**
- * Handler for `msPointerOver`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<(Array.<number>)>|undefined} flat coordinates.
  */
-ol.pointer.MsSource.prototype.msPointerOver = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.enterOver(e, inEvent);
+ol.format.GML3.prototype.readPolygonPatch_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop([null],
+      this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this);
 };
 
 
 /**
- * Handler for `msPointerCancel`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} flat coordinates.
  */
-ol.pointer.MsSource.prototype.msPointerCancel = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.cancel(e, inEvent);
-  this.cleanup(inEvent.getBrowserEvent().pointerId);
+ol.format.GML3.prototype.readLineStringSegment_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop([null],
+      this.GEOMETRY_FLAT_COORDINATES_PARSERS_,
+      node, objectStack, this);
 };
 
 
 /**
- * Handler for `msLostPointerCapture`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.pointer.MsSource.prototype.msLostPointerCapture = function(inEvent) {
-  var e = this.dispatcher.makeEvent('lostpointercapture',
-      inEvent.getBrowserEvent(), inEvent);
-  this.dispatcher.dispatchEvent(e);
+ol.format.GML3.prototype.interiorParser_ = function(node, objectStack) {
+  /** @type {Array.<number>|undefined} */
+  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
+      this.RING_PARSERS, node, objectStack, this);
+  if (flatLinearRing) {
+    var flatLinearRings = /** @type {Array.<Array.<number>>} */
+        (objectStack[objectStack.length - 1]);
+    flatLinearRings.push(flatLinearRing);
+  }
 };
 
 
 /**
- * Handler for `msGotPointerCapture`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.pointer.MsSource.prototype.msGotPointerCapture = function(inEvent) {
-  var e = this.dispatcher.makeEvent('gotpointercapture',
-      inEvent.getBrowserEvent(), inEvent);
-  this.dispatcher.dispatchEvent(e);
+ol.format.GML3.prototype.exteriorParser_ = function(node, objectStack) {
+  /** @type {Array.<number>|undefined} */
+  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
+      this.RING_PARSERS, node, objectStack, this);
+  if (flatLinearRing) {
+    var flatLinearRings = /** @type {Array.<Array.<number>>} */
+        (objectStack[objectStack.length - 1]);
+    flatLinearRings[0] = flatLinearRing;
+  }
 };
 
-// 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}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Polygon|undefined} Polygon.
  */
-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);
+ol.format.GML3.prototype.readSurface_ = function(node, objectStack) {
+  /** @type {Array.<Array.<number>>} */
+  var flatLinearRings = ol.xml.pushParseAndPop([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) {
+      ol.array.extend(flatCoordinates, flatLinearRings[i]);
+      ends.push(flatCoordinates.length);
+    }
+    polygon.setFlatCoordinates(
+        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
+    return polygon;
+  } else {
+    return undefined;
+  }
 };
-goog.inherits(ol.pointer.NativeSource, ol.pointer.EventSource);
 
 
 /**
- * Handler for `pointerdown`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.LineString|undefined} LineString.
  */
-ol.pointer.NativeSource.prototype.pointerDown = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.format.GML3.prototype.readCurve_ = function(node, objectStack) {
+  /** @type {Array.<number>} */
+  var flatCoordinates = ol.xml.pushParseAndPop([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;
+  }
 };
 
 
 /**
- * Handler for `pointermove`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Extent|undefined} Envelope.
  */
-ol.pointer.NativeSource.prototype.pointerMove = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.format.GML3.prototype.readEnvelope_ = function(node, objectStack) {
+  /** @type {Array.<number>} */
+  var flatCoordinates = ol.xml.pushParseAndPop([null],
+      this.ENVELOPE_PARSERS_, node, objectStack, this);
+  return ol.extent.createOrUpdate(flatCoordinates[1][0],
+      flatCoordinates[1][1], flatCoordinates[2][0],
+      flatCoordinates[2][1]);
 };
 
 
 /**
- * Handler for `pointerup`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} Flat coordinates.
  */
-ol.pointer.NativeSource.prototype.pointerUp = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+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];
+  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;
 };
 
 
 /**
- * Handler for `pointerout`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} Flat coordinates.
  */
-ol.pointer.NativeSource.prototype.pointerOut = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.format.GML3.prototype.readFlatPosList_ = function(node, objectStack) {
+  var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
+  var context = objectStack[0];
+  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;
 };
 
 
 /**
- * Handler for `pointerover`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.pointer.NativeSource.prototype.pointerOver = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.format.GML3.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'pos': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPos_),
+    'posList': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPosList_)
+  }
 };
 
 
 /**
- * Handler for `pointercancel`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.pointer.NativeSource.prototype.pointerCancel = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.format.GML3.prototype.FLAT_LINEAR_RINGS_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'interior': ol.format.GML3.prototype.interiorParser_,
+    'exterior': ol.format.GML3.prototype.exteriorParser_
+  }
 };
 
 
 /**
- * Handler for `lostpointercapture`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.pointer.NativeSource.prototype.lostPointerCapture = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.format.GML3.prototype.GEOMETRY_PARSERS_ = {
+  '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_)
+  }
 };
 
 
 /**
- * Handler for `gotpointercapture`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.pointer.NativeSource.prototype.gotPointerCapture = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.format.GML3.prototype.MULTICURVE_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'curveMember': ol.xml.makeArrayPusher(
+        ol.format.GML3.prototype.curveMemberParser_),
+    'curveMembers': ol.xml.makeArrayPusher(
+        ol.format.GML3.prototype.curveMemberParser_)
+  }
 };
 
-// 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}
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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;
+ol.format.GML3.prototype.MULTISURFACE_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'surfaceMember': ol.xml.makeArrayPusher(
+        ol.format.GML3.prototype.surfaceMemberParser_),
+    'surfaceMembers': ol.xml.makeArrayPusher(
+        ol.format.GML3.prototype.surfaceMemberParser_)
+  }
 };
-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}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.pointer.TouchSource.DEDUP_TIMEOUT = 2500;
+ol.format.GML3.prototype.CURVEMEMBER_PARSERS_ = {
+  '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 {number}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT = 200;
+ol.format.GML3.prototype.SURFACEMEMBER_PARSERS_ = {
+  '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 {string}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.pointer.TouchSource.POINTER_TYPE = 'touch';
+ol.format.GML3.prototype.SURFACE_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'patches': ol.xml.makeReplacer(ol.format.GML3.prototype.readPatch_)
+  }
+};
 
 
 /**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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;
+ol.format.GML3.prototype.CURVE_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'segments': ol.xml.makeReplacer(ol.format.GML3.prototype.readSegment_)
+  }
 };
 
 
 /**
- * Set primary touch if there are no pointers, or the only pointer is the mouse.
- * @param {Touch} inTouch
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.pointer.TouchSource.prototype.setPrimaryTouch_ = function(inTouch) {
-  var count = goog.object.getCount(this.pointerMap);
-  if (count === 0 || (count === 1 && goog.object.containsKey(this.pointerMap,
-      ol.pointer.MouseSource.POINTER_ID.toString()))) {
-    this.firstTouchId_ = inTouch.identifier;
-    this.cancelResetClickCount_();
+ol.format.GML3.prototype.ENVELOPE_PARSERS_ = {
+  '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.XmlParser>>}
  * @private
- * @param {Object} inPointer
  */
-ol.pointer.TouchSource.prototype.removePrimaryPointer_ = function(inPointer) {
-  if (inPointer.isPrimary) {
-    this.firstTouchId_ = undefined;
-    this.resetClickCount_();
+ol.format.GML3.prototype.PATCHES_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'PolygonPatch': ol.xml.makeReplacer(
+        ol.format.GML3.prototype.readPolygonPatch_)
   }
 };
 
 
 /**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.pointer.TouchSource.prototype.resetClickCount_ = function() {
-  this.resetId_ = goog.global.setTimeout(
-      goog.bind(this.resetClickCountHandler_, this),
-      ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT);
+ol.format.GML3.prototype.SEGMENTS_PARSERS_ = {
+  '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.pointer.TouchSource.prototype.resetClickCountHandler_ = function() {
-  this.clickCount_ = 0;
-  this.resetId_ = undefined;
+ol.format.GML3.prototype.writePos_ = function(node, value, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var hasZ = context['hasZ'];
+  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]);
+  }
+  if (hasZ) {
+    // For newly created points, Z can be undefined.
+    var z = point[2] || 0;
+    coords += ' ' + z;
+  }
+  ol.format.XSD.writeStringTextNode(node, coords);
 };
 
 
 /**
+ * @param {Array.<number>} point Point geometry.
+ * @param {string=} opt_srsName Optional srsName
+ * @param {boolean=} opt_hasZ whether the geometry has a Z coordinate (is 3D) or not.
+ * @return {string} The coords string.
  * @private
  */
-ol.pointer.TouchSource.prototype.cancelResetClickCount_ = function() {
-  if (this.resetId_ !== undefined) {
-    goog.global.clearTimeout(this.resetId_);
+ol.format.GML3.prototype.getCoords_ = function(point, opt_srsName, opt_hasZ) {
+  var axisOrientation = 'enu';
+  if (opt_srsName) {
+    axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation();
+  }
+  var coords = ((axisOrientation.substr(0, 2) === 'en') ?
+      point[0] + ' ' + point[1] :
+      point[1] + ' ' + point[0]);
+  if (opt_hasZ) {
+    // For newly created points, Z can be undefined.
+    var z = point[2] || 0;
+    coords += ' ' + z;
   }
+
+  return coords;
 };
 
 
 /**
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString|ol.geom.LinearRing} value Geometry.
+ * @param {Array.<*>} objectStack Node stack.
  * @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;
+ol.format.GML3.prototype.writePosList_ = function(node, value, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var hasZ = context['hasZ'];
+  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, hasZ);
+  }
+  ol.format.XSD.writeStringTextNode(node, parts.join(' '));
 };
 
 
 /**
+ * @param {Node} node Node.
+ * @param {ol.geom.Point} geometry Point geometry.
+ * @param {Array.<*>} objectStack Node stack.
  * @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);
+ol.format.GML3.prototype.writePoint_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  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.XmlSerializer>>}
  * @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;
-    }
+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)
   }
-  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
+ * @param {Node} node Node.
+ * @param {ol.Extent} extent Extent.
+ * @param {Array.<*>} objectStack Node stack.
  */
-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 = goog.object.getKeys(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]);
-    }
+ol.format.GML3.prototype.writeEnvelope = function(node, extent, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  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.XmlNodeStackItem} */
+      ({node: node}), ol.format.GML3.ENVELOPE_SERIALIZERS_,
+      ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
+      values,
+      objectStack, keys, this);
 };
 
 
 /**
- * Handler for `touchstart`, triggers `pointerover`,
- * `pointerenter` and `pointerdown` events.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {ol.geom.LinearRing} geometry LinearRing geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-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_);
+ol.format.GML3.prototype.writeLinearRing_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  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
- * @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);
+ol.format.GML3.prototype.RING_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
+  var context = objectStack[objectStack.length - 1];
+  var parentNode = context.node;
+  var exteriorWritten = context['exteriorWritten'];
+  if (exteriorWritten === undefined) {
+    context['exteriorWritten'] = true;
+  }
+  return ol.xml.createElementNS(parentNode.namespaceURI,
+      exteriorWritten !== undefined ? 'interior' : 'exterior');
 };
 
 
 /**
- * Handler for `touchmove`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {ol.geom.Polygon} geometry Polygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.pointer.TouchSource.prototype.touchmove = function(inEvent) {
-  inEvent.preventDefault();
-  this.processTouches_(inEvent, this.moveOverOut_);
+ol.format.GML3.prototype.writeSurfaceOrPolygon_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var hasZ = context['hasZ'];
+  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, hasZ: hasZ, 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
- * @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;
+ol.format.GML3.prototype.writeCurveOrLineString_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var srsName = context['srsName'];
+  if (node.nodeName !== 'LineStringSegment' && srsName) {
+    node.setAttribute('srsName', srsName);
   }
-  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);
-    }
+  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);
   }
-  pointer.out = event;
-  pointer.outTarget = event.target;
 };
 
 
 /**
- * Handler for `touchend`, triggers `pointerup`,
- * `pointerout` and `pointerleave` events.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.pointer.TouchSource.prototype.touchend = function(inEvent) {
-  this.dedupSynthMouse_(inEvent);
-  this.processTouches_(inEvent, this.upOut_);
+ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var hasZ = context['hasZ'];
+  var srsName = context['srsName'];
+  var surface = context['surface'];
+  if (srsName) {
+    node.setAttribute('srsName', srsName);
+  }
+  var polygons = geometry.getPolygons();
+  ol.xml.pushSerializeAndPop({node: node, hasZ: hasZ, 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
- * @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);
+ol.format.GML3.prototype.writeMultiPoint_ = function(node, geometry,
+    objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var srsName = context['srsName'];
+  var hasZ = context['hasZ'];
+  if (srsName) {
+    node.setAttribute('srsName', srsName);
+  }
+  var points = geometry.getPoints();
+  ol.xml.pushSerializeAndPop({node: node, hasZ: hasZ, srsName: srsName},
+      ol.format.GML3.POINTMEMBER_SERIALIZERS_,
+      ol.xml.makeSimpleNodeFactory('pointMember'), points,
+      objectStack, undefined, this);
 };
 
 
 /**
- * Handler for `touchcancel`, triggers `pointercancel`,
- * `pointerout` and `pointerleave` events.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Node} node Node.
+ * @param {ol.geom.MultiLineString} geometry MultiLineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.pointer.TouchSource.prototype.touchcancel = function(inEvent) {
-  this.processTouches_(inEvent, this.cancelOut_);
+ol.format.GML3.prototype.writeMultiCurveOrLineString_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var hasZ = context['hasZ'];
+  var srsName = context['srsName'];
+  var curve = context['curve'];
+  if (srsName) {
+    node.setAttribute('srsName', srsName);
+  }
+  var lines = geometry.getLineStrings();
+  ol.xml.pushSerializeAndPop({node: node, hasZ: hasZ, 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
- * @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);
+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
- * @param {Object} inPointer
  */
-ol.pointer.TouchSource.prototype.cleanUpPointer_ = function(inPointer) {
-  delete this.pointerMap[inPointer.pointerId];
-  this.removePrimaryPointer_(inPointer);
+ol.format.GML3.prototype.writeSurfaceOrPolygonMember_ = function(node, polygon, objectStack) {
+  var child = this.GEOMETRY_NODE_FACTORY_(
+      polygon, objectStack);
+  if (child) {
+    node.appendChild(child);
+    this.writeSurfaceOrPolygon_(child, polygon, objectStack);
+  }
 };
 
 
 /**
- * Prevent synth mouse events from creating pointer events.
- *
+ * @param {Node} node Node.
+ * @param {ol.geom.Point} point Point geometry.
+ * @param {Array.<*>} objectStack Node stack.
  * @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);
-  }
+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);
 };
 
-// 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.
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString} line LineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.pointer.PointerEventHandler = function(element) {
-  goog.base(this);
-
-  /**
-   * @const
-   * @private
-   * @type {Element|HTMLDocument}
-   */
-  this.element_ = element;
+ol.format.GML3.prototype.writeLineStringOrCurveMember_ = function(node, line, objectStack) {
+  var child = this.GEOMETRY_NODE_FACTORY_(line, objectStack);
+  if (child) {
+    node.appendChild(child);
+    this.writeCurveOrLineString_(child, line, objectStack);
+  }
+};
 
-  /**
-   * @const
-   * @type {Object.<string, goog.events.BrowserEvent|Object>}
-   */
-  this.pointerMap = {};
 
-  /**
-   * @type {Object.<string, function(goog.events.BrowserEvent)>}
-   * @private
-   */
-  this.eventMap_ = {};
+/**
+ * @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);
+};
 
-  /**
-   * @type {Array.<ol.pointer.EventSource>}
-   * @private
-   */
-  this.eventSourceList_ = [];
 
-  this.registerSources();
+/**
+ * @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);
 };
-goog.inherits(ol.pointer.PointerEventHandler, goog.events.EventTarget);
 
 
 /**
- * Set up the event sources (mouse, touch and native pointers)
- * that generate pointer events.
+ * @param {Node} node Node.
+ * @param {ol.geom.Geometry|ol.Extent} geometry Geometry.
+ * @param {Array.<*>} objectStack Node stack.
  */
-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));
+ol.format.GML3.prototype.writeGeometryElement = function(node, geometry, objectStack) {
+  var context = /** @type {olx.format.WriteOptions} */ (objectStack[objectStack.length - 1]);
+  var item = ol.obj.assign({}, context);
+  item.node = node;
+  var value;
+  if (Array.isArray(geometry)) {
+    if (context.dataProjection) {
+      value = ol.proj.transformExtent(
+          geometry, context.featureProjection, context.dataProjection);
+    } else {
+      value = geometry;
     }
+  } else {
+    value =
+        ol.format.Feature.transformWithOptions(/** @type {ol.geom.Geometry} */ (geometry), true, context);
   }
-
-  // register events on the viewport element
-  this.register_();
+  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
+      (item), ol.format.GML3.GEOMETRY_SERIALIZERS_,
+      this.GEOMETRY_NODE_FACTORY_, [value],
+      objectStack, undefined, this);
 };
 
 
 /**
- * Add a new event source that will generate pointer events.
- *
- * @param {string} name A name for the event source
- * @param {ol.pointer.EventSource} source
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
  */
-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);
+ol.format.GML3.prototype.writeFeatureElement = function(node, feature, objectStack) {
+  var fid = feature.getId();
+  if (fid) {
+    node.setAttribute('fid', fid);
+  }
+  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  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);
+        }
       }
-    }, this);
-    this.eventSourceList_.push(s);
+    }
   }
+  var item = ol.obj.assign({}, context);
+  item.node = node;
+  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
+      (item), context.serializers,
+      ol.xml.makeSimpleNodeFactory(undefined, featureNS),
+      values,
+      objectStack, keys);
 };
 
 
 /**
- * Set up the events for all registered event sources.
+ * @param {Node} node Node.
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {Array.<*>} objectStack Node stack.
  * @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());
-  }
+ol.format.GML3.prototype.writeFeatureMembers_ = function(node, features, objectStack) {
+  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  var featureType = context['featureType'];
+  var featureNS = context['featureNS'];
+  var serializers = {};
+  serializers[featureNS] = {};
+  serializers[featureNS][featureType] = ol.xml.makeChildAppender(
+      this.writeFeatureElement, this);
+  var item = ol.obj.assign({}, context);
+  item.node = node;
+  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
+      (item),
+      serializers,
+      ol.xml.makeSimpleNodeFactory(featureType, featureNS), features,
+      objectStack);
 };
 
 
 /**
- * Remove all registered events.
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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());
+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_)
   }
 };
 
 
 /**
- * Calls the right handler for a new event.
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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);
+ol.format.GML3.POINTMEMBER_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'pointMember': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writePointMember_)
   }
 };
 
 
 /**
- * Setup listeners for the given events.
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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);
+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_)
+  }
 };
 
 
 /**
- * Unregister listeners for the given events.
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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);
+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_)
+  }
 };
 
 
 /**
- * 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.
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-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];
+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)
   }
-
-  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
+ * @const
+ * @type {Object.<string, string>}
+ * @private
  */
-ol.pointer.PointerEventHandler.prototype.leaveOut =
-    function(pointerEventData, browserEvent) {
-  this.out(pointerEventData, browserEvent);
-  if (!this.contains_(
-      pointerEventData.target,
-      pointerEventData.relatedTarget)) {
-    this.leave(pointerEventData, browserEvent);
-  }
+ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_ = {
+  'MultiLineString': 'lineStringMember',
+  'MultiCurve': 'curveMember',
+  'MultiPolygon': 'polygonMember',
+  'MultiSurface': 'surfaceMember'
 };
 
 
 /**
- * Triggers a combination of 'pointerover' and 'pointerevents' events.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
  */
-ol.pointer.PointerEventHandler.prototype.enterOver =
-    function(pointerEventData, browserEvent) {
-  this.over(pointerEventData, browserEvent);
-  if (!this.contains_(
-      pointerEventData.target,
-      pointerEventData.relatedTarget)) {
-    this.enter(pointerEventData, browserEvent);
-  }
+ol.format.GML3.prototype.MULTIGEOMETRY_MEMBER_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
+  var parentNode = objectStack[objectStack.length - 1].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
- * @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;
+ol.format.GML3.prototype.GEOMETRY_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
+  var context = objectStack[objectStack.length - 1];
+  var multiSurface = context['multiSurface'];
+  var surface = context['surface'];
+  var curve = context['curve'];
+  var multiCurve = context['multiCurve'];
+  var nodeName;
+  if (!Array.isArray(value)) {
+    nodeName = /** @type {ol.geom.Geometry} */ (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 goog.dom.contains(container, contained);
+  return ol.xml.createElementNS('http://www.opengis.net/gml',
+      nodeName);
 };
 
 
-// EVENT CREATION AND TRACKING
 /**
- * Creates a new Event of type `inType`, based on the information in
- * `pointerEventData`.
+ * Encode a geometry in GML 3.1.1 Simple Features.
  *
- * @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`.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ * @override
+ * @api
  */
-ol.pointer.PointerEventHandler.prototype.makeEvent =
-    function(inType, pointerEventData, browserEvent) {
-  return new ol.pointer.PointerEvent(inType, browserEvent, pointerEventData);
+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, hasZ: this.hasZ, srsName: this.srsName,
+    curve: this.curve_, surface: this.surface_,
+    multiSurface: this.multiSurface_, multiCurve: this.multiCurve_};
+  if (opt_options) {
+    ol.obj.assign(context, opt_options);
+  }
+  this.writeGeometryElement(geom, geometry, [context]);
+  return geom;
 };
 
 
 /**
- * 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
+ * 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
  */
-ol.pointer.PointerEventHandler.prototype.fireEvent =
-    function(inType, pointerEventData, browserEvent) {
-  var e = this.makeEvent(inType, pointerEventData, browserEvent);
-  this.dispatchEvent(e);
-};
+ol.format.GML3.prototype.writeFeatures;
 
 
 /**
- * Creates a pointer event from a native pointer event
- * and dispatches this event.
- * @param {goog.events.BrowserEvent} nativeEvent A platform event with a target.
+ * 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.
+ * @override
+ * @api
  */
-ol.pointer.PointerEventHandler.prototype.fireNativeEvent =
-    function(nativeEvent) {
-  var e = this.makeEvent(nativeEvent.type, nativeEvent.getBrowserEvent(),
-      nativeEvent);
-  this.dispatchEvent(e);
+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,
+    hasZ: this.hasZ,
+    curve: this.curve_,
+    surface: this.surface_,
+    multiSurface: this.multiSurface_,
+    multiCurve: this.multiCurve_,
+    featureNS: this.featureNS,
+    featureType: this.featureType
+  };
+  if (opt_options) {
+    ol.obj.assign(context, opt_options);
+  }
+  this.writeFeatureMembers_(node, features, [context]);
+  return node;
 };
 
+goog.provide('ol.format.GML');
 
-/**
- * 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;
-};
+goog.require('ol.format.GML3');
 
 
 /**
- * @inheritDoc
+ * @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.pointer.PointerEventHandler.prototype.disposeInternal = function() {
-  this.unregister_();
-  goog.base(this, 'disposeInternal');
-};
+ol.format.GML = ol.format.GML3;
 
 
 /**
- * Constants for event names.
- * @enum {string}
+ * 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
  */
-ol.pointer.EventType = {
-  POINTERMOVE: 'pointermove',
-  POINTERDOWN: 'pointerdown',
-  POINTERUP: 'pointerup',
-  POINTEROVER: 'pointerover',
-  POINTEROUT: 'pointerout',
-  POINTERENTER: 'pointerenter',
-  POINTERLEAVE: 'pointerleave',
-  POINTERCANCEL: 'pointercancel'
-};
+ol.format.GML.prototype.writeFeatures;
 
 
 /**
- * Properties to copy when cloning an event, with default values.
- * @type {Array.<Array>}
+ * 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.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]
-];
+ol.format.GML.prototype.writeFeaturesNode;
 
-goog.provide('ol.MapBrowserEvent');
-goog.provide('ol.MapBrowserEvent.EventType');
-goog.provide('ol.MapBrowserEventHandler');
-goog.provide('ol.MapBrowserPointerEvent');
+goog.provide('ol.format.GML2');
 
-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');
-
+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.obj');
+goog.require('ol.proj');
+goog.require('ol.xml');
 
 
 /**
  * @classdesc
- * Events emitted as map browser events are instances of this type.
- * See {@link ol.Map} for which events trigger a map browser event.
+ * Feature format for reading and writing data in the GML format,
+ * version 2.1.2.
  *
  * @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.
+ * @param {olx.format.GMLOptions=} opt_options Optional configuration object.
+ * @extends {ol.format.GMLBase}
+ * @api
  */
-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();
+ol.format.GML2 = function(opt_options) {
+  var options = /** @type {olx.format.GMLOptions} */
+      (opt_options ? opt_options : {});
 
-  /**
-   * The pixel of the original browser event.
-   * @type {ol.Pixel}
-   * @api stable
-   */
-  this.pixel = map.getEventPixel(this.originalEvent);
+  ol.format.GMLBase.call(this, options);
 
-  /**
-   * The coordinate of the original browser event.
-   * @type {ol.Coordinate}
-   * @api stable
-   */
-  this.coordinate = map.getCoordinateFromPixel(this.pixel);
+  this.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS][
+      'featureMember'] =
+      ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readFeaturesInternal);
 
   /**
-   * Indicates if the map is currently being dragged. Only set for
-   * `POINTERDRAG` and `POINTERMOVE` events. Default is `false`.
-   *
-   * @type {boolean}
-   * @api stable
+   * @inheritDoc
    */
-  this.dragging = opt_dragging !== undefined ? opt_dragging : false;
+  this.schemaLocation = options.schemaLocation ?
+      options.schemaLocation : ol.format.GML2.schemaLocation_;
 
 };
-goog.inherits(ol.MapBrowserEvent, ol.MapEvent);
+ol.inherits(ol.format.GML2, ol.format.GMLBase);
 
 
 /**
- * Prevents the default browser action.
- * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault
- * @override
- * @api stable
+ * @const
+ * @type {string}
+ * @private
  */
-ol.MapBrowserEvent.prototype.preventDefault = function() {
-  goog.base(this, 'preventDefault');
-  this.browserEvent.preventDefault();
-};
+ol.format.GML2.schemaLocation_ = ol.format.GMLBase.GMLNS +
+    ' http://schemas.opengis.net/gml/2.1.2/feature.xsd';
 
 
 /**
- * Prevents further propagation of the current event.
- * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation
- * @override
- * @api stable
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} Flat coordinates.
  */
-ol.MapBrowserEvent.prototype.stopPropagation = function() {
-  goog.base(this, 'stopPropagation');
-  this.browserEvent.stopPropagation();
+ol.format.GML2.prototype.readFlatCoordinates_ = function(node, objectStack) {
+  var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
+  var context = /** @type {ol.XmlNodeStackItem} */ (objectStack[0]);
+  var containerSrs = context['srsName'];
+  var axisOrientation = 'enu';
+  if (containerSrs) {
+    var proj = ol.proj.get(containerSrs);
+    if (proj) {
+      axisOrientation = proj.getAxisOrientation();
+    }
+  }
+  var coordsGroups = s.trim().split(/\s+/);
+  var x, y, z;
+  var flatCoordinates = [];
+  for (var i = 0, ii = coordsGroups.length; i < ii; i++) {
+    var coords = coordsGroups[i].split(/,+/);
+    x = parseFloat(coords[0]);
+    y = parseFloat(coords[1]);
+    z = (coords.length === 3) ? parseFloat(coords[2]) : 0;
+    if (axisOrientation.substr(0, 2) === 'en') {
+      flatCoordinates.push(x, y, z);
+    } else {
+      flatCoordinates.push(y, x, z);
+    }
+  }
+  return flatCoordinates;
 };
 
 
-
 /**
- * @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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Extent|undefined} Envelope.
  */
-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;
-
+ol.format.GML2.prototype.readBox_ = function(node, objectStack) {
+  /** @type {Array.<number>} */
+  var flatCoordinates = ol.xml.pushParseAndPop([null],
+      this.BOX_PARSERS_, node, objectStack, this);
+  return ol.extent.createOrUpdate(flatCoordinates[1][0],
+      flatCoordinates[1][1], flatCoordinates[1][3],
+      flatCoordinates[1][4]);
 };
-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}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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);
-
+ol.format.GML2.prototype.innerBoundaryIsParser_ = function(node, objectStack) {
+  /** @type {Array.<number>|undefined} */
+  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
+      this.RING_PARSERS, node, objectStack, this);
+  if (flatLinearRing) {
+    var flatLinearRings = /** @type {Array.<Array.<number>>} */
+        (objectStack[objectStack.length - 1]);
+    flatLinearRings.push(flatLinearRing);
+  }
 };
-goog.inherits(ol.MapBrowserEventHandler, goog.events.EventTarget);
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
  * @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);
+ol.format.GML2.prototype.outerBoundaryIsParser_ = function(node, objectStack) {
+  /** @type {Array.<number>|undefined} */
+  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
+      this.RING_PARSERS, node, objectStack, this);
+  if (flatLinearRing) {
+    var flatLinearRings = /** @type {Array.<Array.<number>>} */
+        (objectStack[objectStack.length - 1]);
+    flatLinearRings[0] = flatLinearRing;
   }
 };
 
 
 /**
- * Keeps track on how many pointers are currently active.
- *
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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;
+ol.format.GML2.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'coordinates': ol.xml.makeReplacer(
+        ol.format.GML2.prototype.readFlatCoordinates_)
   }
-  this.activePointers_ = goog.object.getCount(this.trackedTouches_);
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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;
+ol.format.GML2.prototype.FLAT_LINEAR_RINGS_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'innerBoundaryIs': ol.format.GML2.prototype.innerBoundaryIsParser_,
+    'outerBoundaryIs': ol.format.GML2.prototype.outerBoundaryIsParser_
   }
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @return {boolean} If the left mouse button was pressed.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.MapBrowserEventHandler.prototype.isMouseActionButton_ =
-    function(pointerEvent) {
-  return pointerEvent.button === 0;
+ol.format.GML2.prototype.BOX_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'coordinates': ol.xml.makeArrayPusher(
+        ol.format.GML2.prototype.readFlatCoordinates_)
+  }
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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)
-    ];
+ol.format.GML2.prototype.GEOMETRY_PARSERS_ = {
+  '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_)
   }
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
  * @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);
+ol.format.GML2.prototype.GEOMETRY_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
+  var context = objectStack[objectStack.length - 1];
+  var multiSurface = context['multiSurface'];
+  var surface = context['surface'];
+  var multiCurve = context['multiCurve'];
+  var nodeName;
+  if (!Array.isArray(value)) {
+    nodeName = /** @type {ol.geom.Geometry} */ (value).getType();
+    if (nodeName === 'MultiPolygon' && multiSurface === true) {
+      nodeName = 'MultiSurface';
+    } else if (nodeName === 'Polygon' && surface === true) {
+      nodeName = 'Surface';
+    } else if (nodeName === 'MultiLineString' && multiCurve === true) {
+      nodeName = 'MultiCurve';
+    }
+  } else {
+    nodeName = 'Envelope';
   }
-
-  // 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();
+  return ol.xml.createElementNS('http://www.opengis.net/gml',
+      nodeName);
 };
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
  */
-ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) {
-  var dragging = !!(this.down_ && this.isMoving_(pointerEvent));
-  this.dispatchEvent(new ol.MapBrowserPointerEvent(
-      pointerEvent.type, this.map_, pointerEvent, dragging));
+ol.format.GML2.prototype.writeFeatureElement = function(node, feature, objectStack) {
+  var fid = feature.getId();
+  if (fid) {
+    node.setAttribute('fid', fid);
+  }
+  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  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 = ol.obj.assign({}, context);
+  item.node = node;
+  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
+      (item), context.serializers,
+      ol.xml.makeSimpleNodeFactory(undefined, featureNS),
+      values,
+      objectStack, keys);
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @return {boolean}
- * @private
+ * @param {Node} node Node.
+ * @param {ol.geom.Geometry|ol.Extent} geometry Geometry.
+ * @param {Array.<*>} objectStack Node stack.
  */
-ol.MapBrowserEventHandler.prototype.isMoving_ = function(pointerEvent) {
-  return pointerEvent.clientX != this.down_.clientX ||
-      pointerEvent.clientY != this.down_.clientY;
+ol.format.GML2.prototype.writeGeometryElement = function(node, geometry, objectStack) {
+  var context = /** @type {olx.format.WriteOptions} */ (objectStack[objectStack.length - 1]);
+  var item = ol.obj.assign({}, context);
+  item.node = node;
+  var value;
+  if (Array.isArray(geometry)) {
+    if (context.dataProjection) {
+      value = ol.proj.transformExtent(
+          geometry, context.featureProjection, context.dataProjection);
+    } else {
+      value = geometry;
+    }
+  } else {
+    value =
+        ol.format.Feature.transformWithOptions(/** @type {ol.geom.Geometry} */ (geometry), true, context);
+  }
+  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
+      (item), ol.format.GML2.GEOMETRY_SERIALIZERS_,
+      this.GEOMETRY_NODE_FACTORY_, [value],
+      objectStack, undefined, this);
 };
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString} geometry LineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-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;
+ol.format.GML2.prototype.writeCurveOrLineString_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var srsName = context['srsName'];
+  if (node.nodeName !== 'LineStringSegment' && srsName) {
+    node.setAttribute('srsName', srsName);
   }
-  if (this.pointerEventHandler_) {
-    goog.dispose(this.pointerEventHandler_);
-    this.pointerEventHandler_ = null;
+  if (node.nodeName === 'LineString' ||
+      node.nodeName === 'LineStringSegment') {
+    var coordinates = this.createCoordinatesNode_(node.namespaceURI);
+    node.appendChild(coordinates);
+    this.writeCoordinates_(coordinates, geometry, objectStack);
+  } else if (node.nodeName === 'Curve') {
+    var segments = ol.xml.createElementNS(node.namespaceURI, 'segments');
+    node.appendChild(segments);
+    this.writeCurveSegments_(segments,
+        geometry, objectStack);
   }
-  goog.base(this, 'disposeInternal');
 };
 
 
 /**
- * Constants for event names.
- * @enum {string}
+ * @param {string} namespaceURI XML namespace.
+ * @returns {Node} coordinates node.
+ * @private
  */
-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',
+ol.format.GML2.prototype.createCoordinatesNode_ = function(namespaceURI) {
+  var coordinates = ol.xml.createElementNS(namespaceURI, 'coordinates');
+  coordinates.setAttribute('decimal', '.');
+  coordinates.setAttribute('cs', ',');
+  coordinates.setAttribute('ts', ' ');
 
-  POINTERDOWN: 'pointerdown',
-  POINTERUP: 'pointerup',
-  POINTEROVER: 'pointerover',
-  POINTEROUT: 'pointerout',
-  POINTERENTER: 'pointerenter',
-  POINTERLEAVE: 'pointerleave',
-  POINTERCANCEL: 'pointercancel'
+  return coordinates;
 };
 
-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}
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString|ol.geom.LinearRing} value Geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.LayerProperty = {
-  OPACITY: 'opacity',
-  VISIBLE: 'visible',
-  EXTENT: 'extent',
-  Z_INDEX: 'zIndex',
-  MAX_RESOLUTION: 'maxResolution',
-  MIN_RESOLUTION: 'minResolution',
-  SOURCE: 'source'
+ol.format.GML2.prototype.writeCoordinates_ = function(node, value, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var hasZ = context['hasZ'];
+  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, hasZ);
+  }
+  ol.format.XSD.writeStringTextNode(node, parts.join(' '));
 };
 
 
 /**
- * @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}}
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString} line LineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.LayerState;
-
+ol.format.GML2.prototype.writeCurveSegments_ = function(node, line, objectStack) {
+  var child = ol.xml.createElementNS(node.namespaceURI,
+      'LineStringSegment');
+  node.appendChild(child);
+  this.writeCurveOrLineString_(child, line, objectStack);
+};
 
 
 /**
- * @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
+ * @param {Node} node Node.
+ * @param {ol.geom.Polygon} geometry Polygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-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);
+ol.format.GML2.prototype.writeSurfaceOrPolygon_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var hasZ = context['hasZ'];
+  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, hasZ: hasZ, srsName: srsName},
+        ol.format.GML2.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);
+  }
 };
-goog.inherits(ol.layer.Base, ol.Object);
 
 
 /**
- * @return {ol.layer.LayerState} Layer state.
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node} Node.
+ * @private
  */
-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)
-  };
+ol.format.GML2.prototype.RING_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
+  var context = objectStack[objectStack.length - 1];
+  var parentNode = context.node;
+  var exteriorWritten = context['exteriorWritten'];
+  if (exteriorWritten === undefined) {
+    context['exteriorWritten'] = true;
+  }
+  return ol.xml.createElementNS(parentNode.namespaceURI,
+      exteriorWritten !== undefined ? 'innerBoundaryIs' : 'outerBoundaryIs');
 };
 
 
 /**
- * @param {Array.<ol.layer.Layer>=} opt_array Array of layers (to be
- *     modified in place).
- * @return {Array.<ol.layer.Layer>} Array of layers.
+ * @param {Node} node Node.
+ * @param {ol.geom.Polygon} polygon Polygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.Base.prototype.getLayersArray = goog.abstractMethod;
+ol.format.GML2.prototype.writeSurfacePatches_ = function(node, polygon, objectStack) {
+  var child = ol.xml.createElementNS(node.namespaceURI, 'PolygonPatch');
+  node.appendChild(child);
+  this.writeSurfaceOrPolygon_(child, polygon, objectStack);
+};
 
 
 /**
- * @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.
+ * @param {Node} node Node.
+ * @param {ol.geom.LinearRing} ring LinearRing geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.Base.prototype.getLayerStatesArray = goog.abstractMethod;
+ol.format.GML2.prototype.writeRing_ = function(node, ring, objectStack) {
+  var linearRing = ol.xml.createElementNS(node.namespaceURI, 'LinearRing');
+  node.appendChild(linearRing);
+  this.writeLinearRing_(linearRing, ring, objectStack);
+};
 
 
 /**
- * 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
+ * @param {Array.<number>} point Point geometry.
+ * @param {string=} opt_srsName Optional srsName
+ * @param {boolean=} opt_hasZ whether the geometry has a Z coordinate (is 3D) or not.
+ * @return {string} The coords string.
+ * @private
  */
-ol.layer.Base.prototype.getExtent = function() {
-  return /** @type {ol.Extent|undefined} */ (
-      this.get(ol.layer.LayerProperty.EXTENT));
+ol.format.GML2.prototype.getCoords_ = function(point, opt_srsName, opt_hasZ) {
+  var axisOrientation = 'enu';
+  if (opt_srsName) {
+    axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation();
+  }
+  var coords = ((axisOrientation.substr(0, 2) === 'en') ?
+      point[0] + ',' + point[1] :
+      point[1] + ',' + point[0]);
+  if (opt_hasZ) {
+    // For newly created points, Z can be undefined.
+    var z = point[2] || 0;
+    coords += ',' + z;
+  }
+
+  return coords;
 };
 
 
 /**
- * Return the maximum resolution of the layer.
- * @return {number} The maximum resolution of the layer.
- * @observable
- * @api stable
+ * @param {Node} node Node.
+ * @param {ol.geom.MultiLineString} geometry MultiLineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.Base.prototype.getMaxResolution = function() {
-  return /** @type {number} */ (
-      this.get(ol.layer.LayerProperty.MAX_RESOLUTION));
+ol.format.GML2.prototype.writeMultiCurveOrLineString_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var hasZ = context['hasZ'];
+  var srsName = context['srsName'];
+  var curve = context['curve'];
+  if (srsName) {
+    node.setAttribute('srsName', srsName);
+  }
+  var lines = geometry.getLineStrings();
+  ol.xml.pushSerializeAndPop({node: node, hasZ: hasZ, srsName: srsName, curve: curve},
+      ol.format.GML2.LINESTRINGORCURVEMEMBER_SERIALIZERS_,
+      this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, lines,
+      objectStack, undefined, this);
 };
 
 
 /**
- * Return the minimum resolution of the layer.
- * @return {number} The minimum resolution of the layer.
- * @observable
- * @api stable
+ * @param {Node} node Node.
+ * @param {ol.geom.Point} geometry Point geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.Base.prototype.getMinResolution = function() {
-  return /** @type {number} */ (
-      this.get(ol.layer.LayerProperty.MIN_RESOLUTION));
+ol.format.GML2.prototype.writePoint_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var hasZ = context['hasZ'];
+  var srsName = context['srsName'];
+  if (srsName) {
+    node.setAttribute('srsName', srsName);
+  }
+  var coordinates = this.createCoordinatesNode_(node.namespaceURI);
+  node.appendChild(coordinates);
+  var point = geometry.getCoordinates();
+  var coord = this.getCoords_(point, srsName, hasZ);
+  ol.format.XSD.writeStringTextNode(coordinates, coord);
 };
 
 
 /**
- * Return the opacity of the layer (between 0 and 1).
- * @return {number} The opacity of the layer.
- * @observable
- * @api stable
+ * @param {Node} node Node.
+ * @param {ol.geom.MultiPoint} geometry MultiPoint geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.Base.prototype.getOpacity = function() {
-  return /** @type {number} */ (this.get(ol.layer.LayerProperty.OPACITY));
+ol.format.GML2.prototype.writeMultiPoint_ = function(node, geometry,
+    objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var hasZ = context['hasZ'];
+  var srsName = context['srsName'];
+  if (srsName) {
+    node.setAttribute('srsName', srsName);
+  }
+  var points = geometry.getPoints();
+  ol.xml.pushSerializeAndPop({node: node, hasZ: hasZ, srsName: srsName},
+      ol.format.GML2.POINTMEMBER_SERIALIZERS_,
+      ol.xml.makeSimpleNodeFactory('pointMember'), points,
+      objectStack, undefined, this);
 };
 
 
 /**
- * @return {ol.source.State} Source state.
+ * @param {Node} node Node.
+ * @param {ol.geom.Point} point Point geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.Base.prototype.getSourceState = goog.abstractMethod;
+ol.format.GML2.prototype.writePointMember_ = function(node, point, objectStack) {
+  var child = ol.xml.createElementNS(node.namespaceURI, 'Point');
+  node.appendChild(child);
+  this.writePoint_(child, point, objectStack);
+};
 
 
 /**
- * Return the visibility of the layer (`true` or `false`).
- * @return {boolean} The visibility of the layer.
- * @observable
- * @api stable
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString} line LineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.Base.prototype.getVisible = function() {
-  return /** @type {boolean} */ (this.get(ol.layer.LayerProperty.VISIBLE));
+ol.format.GML2.prototype.writeLineStringOrCurveMember_ = function(node, line, objectStack) {
+  var child = this.GEOMETRY_NODE_FACTORY_(line, objectStack);
+  if (child) {
+    node.appendChild(child);
+    this.writeCurveOrLineString_(child, line, objectStack);
+  }
 };
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @param {ol.geom.LinearRing} geometry LinearRing geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.Base.prototype.getZIndex = function() {
-  return /** @type {number} */ (this.get(ol.layer.LayerProperty.Z_INDEX));
+ol.format.GML2.prototype.writeLinearRing_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var srsName = context['srsName'];
+  if (srsName) {
+    node.setAttribute('srsName', srsName);
+  }
+  var coordinates = this.createCoordinatesNode_(node.namespaceURI);
+  node.appendChild(coordinates);
+  this.writeCoordinates_(coordinates, geometry, objectStack);
 };
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.Base.prototype.setExtent = function(extent) {
-  this.set(ol.layer.LayerProperty.EXTENT, extent);
+ol.format.GML2.prototype.writeMultiSurfaceOrPolygon_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var hasZ = context['hasZ'];
+  var srsName = context['srsName'];
+  var surface = context['surface'];
+  if (srsName) {
+    node.setAttribute('srsName', srsName);
+  }
+  var polygons = geometry.getPolygons();
+  ol.xml.pushSerializeAndPop({node: node, hasZ: hasZ, srsName: srsName, surface: surface},
+      ol.format.GML2.SURFACEORPOLYGONMEMBER_SERIALIZERS_,
+      this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, polygons,
+      objectStack, undefined, this);
 };
 
 
 /**
- * Set the maximum resolution at which the layer is visible.
- * @param {number} maxResolution The maximum resolution of the layer.
- * @observable
- * @api stable
+ * @param {Node} node Node.
+ * @param {ol.geom.Polygon} polygon Polygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.Base.prototype.setMaxResolution = function(maxResolution) {
-  this.set(ol.layer.LayerProperty.MAX_RESOLUTION, maxResolution);
+ol.format.GML2.prototype.writeSurfaceOrPolygonMember_ = function(node, polygon, objectStack) {
+  var child = this.GEOMETRY_NODE_FACTORY_(
+      polygon, objectStack);
+  if (child) {
+    node.appendChild(child);
+    this.writeSurfaceOrPolygon_(child, polygon, objectStack);
+  }
 };
 
 
 /**
- * Set the minimum resolution at which the layer is visible.
- * @param {number} minResolution The minimum resolution of the layer.
- * @observable
- * @api stable
+ * @param {Node} node Node.
+ * @param {ol.Extent} extent Extent.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.layer.Base.prototype.setMinResolution = function(minResolution) {
-  this.set(ol.layer.LayerProperty.MIN_RESOLUTION, minResolution);
+ol.format.GML2.prototype.writeEnvelope = function(node, extent, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  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.XmlNodeStackItem} */
+      ({node: node}), ol.format.GML2.ENVELOPE_SERIALIZERS_,
+      ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
+      values,
+      objectStack, keys, this);
 };
 
 
 /**
- * Set the opacity of the layer, allowed values range from 0 to 1.
- * @param {number} opacity The opacity of the layer.
- * @observable
- * @api stable
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.layer.Base.prototype.setOpacity = function(opacity) {
-  this.set(ol.layer.LayerProperty.OPACITY, opacity);
+ol.format.GML2.GEOMETRY_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'Curve': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeCurveOrLineString_),
+    'MultiCurve': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeMultiCurveOrLineString_),
+    'Point': ol.xml.makeChildAppender(ol.format.GML2.prototype.writePoint_),
+    'MultiPoint': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeMultiPoint_),
+    'LineString': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeCurveOrLineString_),
+    'MultiLineString': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeMultiCurveOrLineString_),
+    'LinearRing': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeLinearRing_),
+    'Polygon': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeSurfaceOrPolygon_),
+    'MultiPolygon': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeMultiSurfaceOrPolygon_),
+    'Surface': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeSurfaceOrPolygon_),
+    'MultiSurface': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeMultiSurfaceOrPolygon_),
+    'Envelope': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeEnvelope)
+  }
 };
 
 
 /**
- * Set the visibility of the layer (`true` or `false`).
- * @param {boolean} visible The visibility of the layer.
- * @observable
- * @api stable
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.layer.Base.prototype.setVisible = function(visible) {
-  this.set(ol.layer.LayerProperty.VISIBLE, visible);
+ol.format.GML2.RING_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'outerBoundaryIs': ol.xml.makeChildAppender(ol.format.GML2.prototype.writeRing_),
+    'innerBoundaryIs': ol.xml.makeChildAppender(ol.format.GML2.prototype.writeRing_)
+  }
 };
 
 
 /**
- * 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
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.layer.Base.prototype.setZIndex = function(zindex) {
-  this.set(ol.layer.LayerProperty.Z_INDEX, zindex);
+ol.format.GML2.POINTMEMBER_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'pointMember': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writePointMember_)
+  }
 };
 
-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
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.render.VectorContext = function() {
+ol.format.GML2.LINESTRINGORCURVEMEMBER_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'lineStringMember': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeLineStringOrCurveMember_),
+    'curveMember': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeLineStringOrCurveMember_)
+  }
 };
 
 
 /**
- * @param {number} zIndex Z index.
- * @param {function(ol.render.VectorContext)} callback Callback.
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
  */
-ol.render.VectorContext.prototype.drawAsync = goog.abstractMethod;
-
+ol.format.GML2.prototype.MULTIGEOMETRY_MEMBER_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
+  var parentNode = objectStack[objectStack.length - 1].node;
+  return ol.xml.createElementNS('http://www.opengis.net/gml',
+      ol.format.GML2.MULTIGEOMETRY_TO_MEMBER_NODENAME_[parentNode.nodeName]);
+};
 
 /**
- * @param {ol.geom.Circle} circleGeometry Circle geometry.
- * @param {ol.Feature} feature Feature,
+ * @const
+ * @type {Object.<string, string>}
+ * @private
  */
-ol.render.VectorContext.prototype.drawCircleGeometry = goog.abstractMethod;
+ol.format.GML2.MULTIGEOMETRY_TO_MEMBER_NODENAME_ = {
+  'MultiLineString': 'lineStringMember',
+  'MultiCurve': 'curveMember',
+  'MultiPolygon': 'polygonMember',
+  'MultiSurface': 'surfaceMember'
+};
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @param {ol.style.Style} style Style.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.render.VectorContext.prototype.drawFeature = goog.abstractMethod;
+ol.format.GML2.SURFACEORPOLYGONMEMBER_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'surfaceMember': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeSurfaceOrPolygonMember_),
+    'polygonMember': ol.xml.makeChildAppender(
+        ol.format.GML2.prototype.writeSurfaceOrPolygonMember_)
+  }
+};
 
 
 /**
- * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry
- *     collection.
- * @param {ol.Feature} feature Feature.
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.render.VectorContext.prototype.drawGeometryCollectionGeometry =
-    goog.abstractMethod;
+ol.format.GML2.ENVELOPE_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'lowerCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+    'upperCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
+  }
+};
 
+goog.provide('ol.format.GPX');
 
-/**
- * @param {ol.geom.LineString} lineStringGeometry Line string geometry.
- * @param {ol.Feature} feature Feature.
- */
-ol.render.VectorContext.prototype.drawLineStringGeometry =
-    goog.abstractMethod;
+goog.require('ol');
+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');
 
 
 /**
- * @param {ol.geom.MultiLineString} multiLineStringGeometry
- *     MultiLineString geometry.
- * @param {ol.Feature} feature Feature.
+ * @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
  */
-ol.render.VectorContext.prototype.drawMultiLineStringGeometry =
-    goog.abstractMethod;
+ol.format.GPX = function(opt_options) {
 
+  var options = opt_options ? opt_options : {};
 
-/**
- * @param {ol.geom.MultiPoint} multiPointGeometry MultiPoint geometry.
- * @param {ol.Feature} feature Feature.
- */
-ol.render.VectorContext.prototype.drawMultiPointGeometry = goog.abstractMethod;
+  ol.format.XMLFeature.call(this);
 
+  /**
+   * @inheritDoc
+   */
+  this.defaultDataProjection = ol.proj.get('EPSG:4326');
 
-/**
- * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry.
- * @param {ol.Feature} feature Feature.
- */
-ol.render.VectorContext.prototype.drawMultiPolygonGeometry =
-    goog.abstractMethod;
+  /**
+   * @type {function(ol.Feature, Node)|undefined}
+   * @private
+   */
+  this.readExtensions_ = options.readExtensions;
+};
+ol.inherits(ol.format.GPX, ol.format.XMLFeature);
 
 
 /**
- * @param {ol.geom.Point} pointGeometry Point geometry.
- * @param {ol.Feature} feature Feature.
+ * @const
+ * @private
+ * @type {Array.<string>}
  */
-ol.render.VectorContext.prototype.drawPointGeometry = goog.abstractMethod;
+ol.format.GPX.NAMESPACE_URIS_ = [
+  null,
+  'http://www.topografix.com/GPX/1/0',
+  'http://www.topografix.com/GPX/1/1'
+];
 
 
 /**
- * @param {ol.geom.Polygon} polygonGeometry Polygon geometry.
- * @param {ol.Feature} feature Feature.
+ * @const
+ * @type {string}
+ * @private
  */
-ol.render.VectorContext.prototype.drawPolygonGeometry = goog.abstractMethod;
+ol.format.GPX.SCHEMA_LOCATION_ = 'http://www.topografix.com/GPX/1/1 ' +
+    'http://www.topografix.com/GPX/1/1/gpx.xsd';
 
 
 /**
  * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {ol.Feature} feature Feature.
- */
-ol.render.VectorContext.prototype.drawText = goog.abstractMethod;
-
-
-/**
- * @param {ol.style.Fill} fillStyle Fill style.
- * @param {ol.style.Stroke} strokeStyle Stroke style.
+ * @param {ol.LayoutOptions} layoutOptions Layout options.
+ * @param {Node} node Node.
+ * @param {Object} values Values.
+ * @private
+ * @return {Array.<number>} Flat coordinates.
  */
-ol.render.VectorContext.prototype.setFillStrokeStyle = goog.abstractMethod;
+ol.format.GPX.appendCoordinate_ = function(flatCoordinates, layoutOptions, node, values) {
+  flatCoordinates.push(
+      parseFloat(node.getAttribute('lon')),
+      parseFloat(node.getAttribute('lat')));
+  if ('ele' in values) {
+    flatCoordinates.push(/** @type {number} */ (values['ele']));
+    delete values['ele'];
+    layoutOptions.hasZ = true;
+  } else {
+    flatCoordinates.push(0);
+  }
+  if ('time' in values) {
+    flatCoordinates.push(/** @type {number} */ (values['time']));
+    delete values['time'];
+    layoutOptions.hasM = true;
+  } else {
+    flatCoordinates.push(0);
+  }
+  return flatCoordinates;
+};
 
 
 /**
- * @param {ol.style.Image} imageStyle Image style.
+ * Choose GeometryLayout based on flags in layoutOptions and adjust flatCoordinates
+ * and ends arrays by shrinking them accordingly (removing unused zero entries).
+ *
+ * @param {ol.LayoutOptions} layoutOptions Layout options.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Array.<number>=} ends Ends.
+ * @return {ol.geom.GeometryLayout} Layout.
  */
-ol.render.VectorContext.prototype.setImageStyle = goog.abstractMethod;
+ol.format.GPX.applyLayoutOptions_ = function(layoutOptions, flatCoordinates, ends) {
+  var layout = ol.geom.GeometryLayout.XY;
+  var stride = 2;
+  if (layoutOptions.hasZ && layoutOptions.hasM) {
+    layout = ol.geom.GeometryLayout.XYZM;
+    stride = 4;
+  } else if (layoutOptions.hasZ) {
+    layout = ol.geom.GeometryLayout.XYZ;
+    stride = 3;
+  } else if (layoutOptions.hasM) {
+    layout = ol.geom.GeometryLayout.XYM;
+    stride = 3;
+  }
+  if (stride !== 4) {
+    var i, ii;
+    for (i = 0, ii = flatCoordinates.length / 4; i < ii; i++) {
+      flatCoordinates[i * stride] = flatCoordinates[i * 4];
+      flatCoordinates[i * stride + 1] = flatCoordinates[i * 4 + 1];
+      if (layoutOptions.hasZ) {
+        flatCoordinates[i * stride + 2] = flatCoordinates[i * 4 + 2];
+      }
+      if (layoutOptions.hasM) {
+        flatCoordinates[i * stride + 2] = flatCoordinates[i * 4 + 3];
+      }
+    }
+    flatCoordinates.length = flatCoordinates.length / 4 * stride;
+    if (ends) {
+      for (i = 0, ii = ends.length; i < ii; i++) {
+        ends[i] = ends[i] / 4 * stride;
+      }
+    }
+  }
+  return layout;
+};
 
 
 /**
- * @param {ol.style.Text} textStyle Text style.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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');
+ol.format.GPX.parseLink_ = function(node, objectStack) {
+  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);
+};
 
 
 /**
- * @enum {string}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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'
+ol.format.GPX.parseExtensions_ = function(node, objectStack) {
+  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  values['extensionsNode_'] = node;
 };
 
 
-
 /**
- * @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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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;
-
+ol.format.GPX.parseRtePt_ = function(node, objectStack) {
+  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']);
+    var layoutOptions = /** @type {ol.LayoutOptions} */
+        (rteValues['layoutOptions']);
+    ol.format.GPX.appendCoordinate_(flatCoordinates, layoutOptions, node, values);
+  }
 };
-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.
- *
- * 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
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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);
+ol.format.GPX.parseTrkPt_ = function(node, objectStack) {
+  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']);
+    var layoutOptions = /** @type {ol.LayoutOptions} */
+        (trkValues['layoutOptions']);
+    ol.format.GPX.appendCoordinate_(flatCoordinates, layoutOptions, node, values);
   }
-
-  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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.layer.Layer.visibleAtResolution = function(layerState, resolution) {
-  return layerState.visible && resolution >= layerState.minResolution &&
-      resolution < layerState.maxResolution;
+ol.format.GPX.parseTrkSeg_ = function(node, objectStack) {
+  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);
 };
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Track.
  */
-ol.layer.Layer.prototype.getLayersArray = function(opt_array) {
-  var array = opt_array ? opt_array : [];
-  array.push(this);
-  return array;
+ol.format.GPX.readRte_ = function(node, objectStack) {
+  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
+  var values = ol.xml.pushParseAndPop({
+    'flatCoordinates': [],
+    'layoutOptions': {}
+  }, ol.format.GPX.RTE_PARSERS_, node, objectStack);
+  if (!values) {
+    return undefined;
+  }
+  var flatCoordinates = /** @type {Array.<number>} */
+      (values['flatCoordinates']);
+  delete values['flatCoordinates'];
+  var layoutOptions = /** @type {ol.LayoutOptions} */ (values['layoutOptions']);
+  delete values['layoutOptions'];
+  var layout = ol.format.GPX.applyLayoutOptions_(layoutOptions, flatCoordinates);
+  var geometry = new ol.geom.LineString(null);
+  geometry.setFlatCoordinates(layout, flatCoordinates);
+  ol.format.Feature.transformWithOptions(geometry, false, options);
+  var feature = new ol.Feature(geometry);
+  feature.setProperties(values);
+  return feature;
 };
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Track.
  */
-ol.layer.Layer.prototype.getLayerStatesArray = function(opt_states) {
-  var states = opt_states ? opt_states : [];
-  states.push(this.getLayerState());
-  return states;
+ol.format.GPX.readTrk_ = function(node, objectStack) {
+  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
+  var values = ol.xml.pushParseAndPop({
+    'flatCoordinates': [],
+    'ends': [],
+    'layoutOptions': {}
+  }, 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 layoutOptions = /** @type {ol.LayoutOptions} */ (values['layoutOptions']);
+  delete values['layoutOptions'];
+  var layout = ol.format.GPX.applyLayoutOptions_(layoutOptions, flatCoordinates, ends);
+  var geometry = new ol.geom.MultiLineString(null);
+  geometry.setFlatCoordinates(layout, flatCoordinates, ends);
+  ol.format.Feature.transformWithOptions(geometry, false, options);
+  var feature = new ol.Feature(geometry);
+  feature.setProperties(values);
+  return feature;
 };
 
 
 /**
- * Get the layer source.
- * @return {ol.source.Source} The layer source (or `null` if not yet set).
- * @observable
- * @api stable
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Waypoint.
  */
-ol.layer.Layer.prototype.getSource = function() {
-  var source = this.get(ol.layer.LayerProperty.SOURCE);
-  return /** @type {ol.source.Source} */ (source) || null;
+ol.format.GPX.readWpt_ = function(node, objectStack) {
+  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 layoutOptions = /** @type {ol.LayoutOptions} */ ({});
+  var coordinates = ol.format.GPX.appendCoordinate_([], layoutOptions, node, values);
+  var layout = ol.format.GPX.applyLayoutOptions_(layoutOptions, coordinates);
+  var geometry = new ol.geom.Point(coordinates, layout);
+  ol.format.Feature.transformWithOptions(geometry, false, options);
+  var feature = new ol.Feature(geometry);
+  feature.setProperties(values);
+  return feature;
 };
 
 
 /**
-  * @inheritDoc
-  */
-ol.layer.Layer.prototype.getSourceState = function() {
-  var source = this.getSource();
-  return !source ? ol.source.State.UNDEFINED : source.getState();
+ * @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.XmlParser>>}
  * @private
  */
-ol.layer.Layer.prototype.handleSourceChange_ = function() {
-  this.changed();
-};
+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.XmlParser>>}
  * @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();
-};
+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')
+    });
 
 
 /**
- * Sets the layer to be rendered on a map. The map will not manage this layer in
- * its layers collection, layer filters in {@link ol.Map#forEachLayerAtPixel}
- * will not filter the layer, and it will be rendered on top. This is useful for
- * temporary layers. To remove an unmanaged layer from the map, use
- * `#setMap(null)`.
- *
- * To add the layer to a map and have it managed by the map, use
- * {@link ol.Map#addLayer} instead.
- * @param {ol.Map} map Map.
- * @api
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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();
-  }
-};
+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_
+    });
 
 
 /**
- * Set the layer source.
- * @param {ol.source.Source} source The layer source.
- * @observable
- * @api stable
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.layer.Layer.prototype.setSource = function(source) {
-  this.set(ol.layer.LayerProperty.SOURCE, source);
-};
+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)
+    });
 
-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');
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @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_
+    });
 
 
 /**
- * @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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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);
+ol.format.GPX.TRKSEG_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'trkpt': ol.format.GPX.parseTrkPt_
+    });
 
 
 /**
- * @protected
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.ImageBase.prototype.changed = function() {
-  this.dispatchEvent(goog.events.EventType.CHANGE);
-};
+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)
+    });
 
 
 /**
- * @return {Array.<ol.Attribution>} Attributions.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.ImageBase.prototype.getAttributions = function() {
-  return this.attributions_;
-};
+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_
+    });
 
 
 /**
- * @return {ol.Extent} Extent.
+ * @param {Array.<ol.Feature>} features List of features.
+ * @private
  */
-ol.ImageBase.prototype.getExtent = function() {
-  return this.extent;
+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);
+  }
 };
 
 
 /**
- * @param {Object=} opt_context Object.
- * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
+ * Read the first feature from a GPX source.
+ * Routes (`<rte>`) are converted into LineString geometries, and tracks (`<trk>`)
+ * into MultiLineString. Any properties on route and track waypoints are ignored.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ * @api
  */
-ol.ImageBase.prototype.getImage = goog.abstractMethod;
+ol.format.GPX.prototype.readFeature;
 
 
 /**
- * @return {number} PixelRatio.
+ * @inheritDoc
  */
-ol.ImageBase.prototype.getPixelRatio = function() {
-  return this.pixelRatio_;
+ol.format.GPX.prototype.readFeatureFromNode = function(node, opt_options) {
+  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;
 };
 
 
 /**
- * @return {number} Resolution.
+ * Read all features from a GPX source.
+ * Routes (`<rte>`) are converted into LineString geometries, and tracks (`<trk>`)
+ * into MultiLineString. Any properties on route and track waypoints are ignored.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
+ * @api
  */
-ol.ImageBase.prototype.getResolution = function() {
-  goog.asserts.assert(this.resolution !== undefined, 'resolution not yet set');
-  return this.resolution;
-};
+ol.format.GPX.prototype.readFeatures;
 
 
 /**
- * @return {ol.ImageState} State.
+ * @inheritDoc
  */
-ol.ImageBase.prototype.getState = function() {
-  return this.state;
+ol.format.GPX.prototype.readFeaturesFromNode = function(node, opt_options) {
+  if (!ol.array.includes(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) {
+    return [];
+  }
+  if (node.localName == 'gpx') {
+    /** @type {Array.<ol.Feature>} */
+    var features = ol.xml.pushParseAndPop([], ol.format.GPX.GPX_PARSERS_,
+        node, [this.getReadOptions(node, opt_options)]);
+    if (features) {
+      this.handleReadExtensions_(features);
+      return features;
+    } else {
+      return [];
+    }
+  }
+  return [];
 };
 
 
 /**
- * Load not yet loaded URI.
+ * Read the projection from a GPX source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
  */
-ol.ImageBase.prototype.load = goog.abstractMethod;
-
-goog.provide('ol.vec.Mat4');
-goog.provide('ol.vec.Mat4.Number');
-
-goog.require('goog.vec.Mat4');
+ol.format.GPX.prototype.readProjection;
 
 
 /**
- * A alias for the goog.vec.Number type.
- * @typedef {goog.vec.Number}
+ * @param {Node} node Node.
+ * @param {string} value Value for the link's `href` attribute.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.vec.Mat4.Number;
+ol.format.GPX.writeLink_ = function(node, value, objectStack) {
+  node.setAttribute('href', value);
+  var context = objectStack[objectStack.length - 1];
+  var properties = context['properties'];
+  var link = [
+    properties['linkText'],
+    properties['linkType']
+  ];
+  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ ({node: node}),
+      ol.format.GPX.LINK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
+      link, objectStack, ol.format.GPX.LINK_SEQUENCE_);
+};
 
 
 /**
- * @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.
+ * @param {Node} node Node.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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);
+ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var parentNode = context.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'];
+  switch (geometryLayout) {
+    case ol.geom.GeometryLayout.XYZM:
+      if (coordinate[3] !== 0) {
+        properties['time'] = coordinate[3];
+      }
+      // fall through
+    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];
+      }
+      break;
+    default:
+      // pass
   }
-  return mat;
+  var orderedKeys = (node.nodeName == 'rtept') ?
+      ol.format.GPX.RTEPT_TYPE_SEQUENCE_[namespaceURI] :
+      ol.format.GPX.WPT_TYPE_SEQUENCE_[namespaceURI];
+  var values = ol.xml.makeSequence(properties, orderedKeys);
+  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
+      ({node: node, 'properties': properties}),
+      ol.format.GPX.WPT_TYPE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
+      values, objectStack, orderedKeys);
 };
 
 
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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;
+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) {
+    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(context,
+      ol.format.GPX.RTE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
+      values, objectStack, orderedKeys);
 };
 
-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
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.renderer.Layer = function(layer) {
-
-  goog.base(this);
-
-  /**
-   * @private
-   * @type {ol.layer.Layer}
-   */
-  this.layer_ = layer;
-
-
+ol.format.GPX.writeTrk_ = function(node, feature, objectStack) {
+  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
+  var properties = feature.getProperties();
+  /** @type {ol.XmlNodeStackItem} */
+  var context = {node: node, 'properties': properties};
+  var geometry = feature.getGeometry();
+  if (geometry) {
+    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(context,
+      ol.format.GPX.TRK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
+      values, objectStack, orderedKeys);
 };
-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.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
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString} lineString LineString.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.renderer.Layer.prototype.forEachFeatureAtCoordinate = ol.nullFunction;
+ol.format.GPX.writeTrkSeg_ = function(node, lineString, objectStack) {
+  /** @type {ol.XmlNodeStackItem} */
+  var context = {node: node, 'geometryLayout': lineString.getLayout(),
+    'properties': {}};
+  ol.xml.pushSerializeAndPop(context,
+      ol.format.GPX.TRKSEG_SERIALIZERS_, ol.format.GPX.TRKSEG_NODE_FACTORY_,
+      lineString.getCoordinates(), objectStack);
+};
 
 
 /**
- * @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
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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;
+ol.format.GPX.writeWpt_ = function(node, feature, objectStack) {
+  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
+  var context = objectStack[objectStack.length - 1];
+  context['properties'] = feature.getProperties();
+  var geometry = feature.getGeometry();
+  if (geometry) {
+    geometry = /** @type {ol.geom.Point} */
+        (ol.format.Feature.transformWithOptions(geometry, true, options));
+    context['geometryLayout'] = geometry.getLayout();
+    ol.format.GPX.writeWptType_(node, geometry.getCoordinates(), objectStack);
   }
 };
 
 
 /**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {olx.FrameState} frameState Frame state.
- * @return {boolean} Is there a feature at the given coordinate?
+ * @const
+ * @type {Array.<string>}
+ * @private
  */
-ol.renderer.Layer.prototype.hasFeatureAtCoordinate = goog.functions.FALSE;
+ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type'];
 
 
 /**
- * Create a function that adds loaded tiles to the tile lookup.
- * @param {ol.source.Tile} source Tile source.
- * @param {Object.<number, Object.<string, ol.Tile>>} tiles Lookup of loaded
- *     tiles by zoom level.
- * @return {function(number, ol.TileRange):boolean} A function that can be
- *     called with a zoom level and a tile range to add loaded tiles to the
- *     lookup.
- * @protected
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.renderer.Layer.prototype.createLoadedTileFinder = function(source, tiles) {
-  return (
-      /**
-       * @param {number} zoom Zoom level.
-       * @param {ol.TileRange} tileRange Tile range.
-       * @return {boolean} The tile range is fully loaded.
-       */
-      function(zoom, tileRange) {
-        return source.forEachLoadedTile(zoom, tileRange, function(tile) {
-          if (!tiles[zoom]) {
-            tiles[zoom] = {};
-          }
-          tiles[zoom][tile.tileCoord.toString()] = tile;
-        });
-      });
-};
+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)
+    });
 
 
 /**
- * @return {ol.layer.Layer} Layer.
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
  */
-ol.renderer.Layer.prototype.getLayer = function() {
-  return this.layer_;
-};
+ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, [
+      'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'rtept'
+    ]);
 
 
 /**
- * Handle changes in image state.
- * @param {goog.events.Event} event Image change event.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @private
  */
-ol.renderer.Layer.prototype.handleImageChange_ = function(event) {
-  var image = /** @type {ol.Image} */ (event.target);
-  if (image.getState() === ol.ImageState.LOADED) {
-    this.renderIfReadyAndVisible();
-  }
-};
+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_))
+    });
 
 
 /**
- * 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
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
  */
-ol.renderer.Layer.prototype.loadImage = function(image) {
-  var imageState = image.getState();
-  if (imageState != ol.ImageState.LOADED &&
-      imageState != ol.ImageState.ERROR) {
-    // the image is either "idle" or "loading", register the change
-    // listener (a noop if the listener was already registered)
-    goog.asserts.assert(imageState == ol.ImageState.IDLE ||
-        imageState == ol.ImageState.LOADING,
-        'imageState is "idle" or "loading"');
-    goog.events.listen(image, goog.events.EventType.CHANGE,
-        this.handleImageChange_, false, this);
-  }
-  if (imageState == ol.ImageState.IDLE) {
-    image.load();
-    imageState = image.getState();
-    goog.asserts.assert(imageState == ol.ImageState.LOADING,
-        'imageState is "loading"');
-  }
-  return imageState == ol.ImageState.LOADED;
-};
+ol.format.GPX.RTEPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, [
+      'ele', 'time'
+    ]);
 
 
 /**
- * @protected
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
  */
-ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() {
-  var layer = this.getLayer();
-  if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) {
-    this.changed();
-  }
-};
+ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, [
+      'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'trkseg'
+    ]);
 
 
 /**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.source.Tile} tileSource Tile source.
- * @protected
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.renderer.Layer.prototype.scheduleExpireCache =
-    function(frameState, tileSource) {
-  if (tileSource.canExpireCache()) {
-    frameState.postRenderFunctions.push(
-        goog.partial(
-            /**
-             * @param {ol.source.Tile} tileSource Tile source.
-             * @param {ol.Map} map Map.
-             * @param {olx.FrameState} frameState Frame state.
-             */
-            function(tileSource, map, frameState) {
-              var tileSourceKey = goog.getUid(tileSource).toString();
-              tileSource.expireCache(frameState.usedTiles[tileSourceKey]);
-            }, tileSource));
-  }
-};
+ol.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_))
+    });
 
 
 /**
- * @param {Object.<string, ol.Attribution>} attributionsSet Attributions
- *     set (target).
- * @param {Array.<ol.Attribution>} attributions Attributions (source).
- * @protected
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
  */
-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;
-    }
-  }
-};
+ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt');
 
 
 /**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.source.Source} source Source.
- * @protected
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-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);
-        }
-      }
-    }
-  }
-};
-
-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} and {@link ol.style.Circle}.
- *
- * @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}
-   */
-  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.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} Image 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} 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}
-   */
-  this.image_ = !image ? new Image() : image;
-
-  if (crossOrigin) {
-    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} 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} Image 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.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} 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 (!layerState.managed ||
-        (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, callback, 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.
- */
-ol.structs.PriorityQueue.prototype.enqueue = function(element) {
-  goog.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_),
-      'key %s is already listed as queued', this.keyFunction_(element));
-  var priority = this.priorityFunction_(element);
-  if (priority != ol.structs.PriorityQueue.DROP) {
-    this.elements_.push(element);
-    this.priorities_.push(priority);
-    this.queuedElements_[this.keyFunction_(element)] = true;
-    this.siftDown_(0, this.elements_.length - 1);
-  }
-};
-
-
-/**
- * @return {number} Count.
- */
-ol.structs.PriorityQueue.prototype.getCount = function() {
-  return this.elements_.length;
-};
-
-
-/**
- * Gets the index of the left child of the node at the given index.
- * @param {number} index The index of the node to get the left child for.
- * @return {number} The index of the left child.
- * @private
- */
-ol.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;
-
-};
-goog.inherits(ol.TileQueue, ol.structs.PriorityQueue);
-
-
-/**
- * @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);
-    --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) {
-      goog.events.listen(tile, goog.events.EventType.CHANGE,
-          this.handleTileChange, false, this);
-      tile.load();
-      ++this.tilesLoading_;
-      ++newLoads;
-    }
-  }
-};
-
-goog.provide('ol.Kinetic');
-
-goog.require('ol.Coordinate');
-goog.require('ol.PreRenderFunction');
-goog.require('ol.animation');
-
-
-
-/**
- * @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.
- */
-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 ? 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.dom.TagName');
-goog.require('goog.functions');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.MapBrowserPointerEvent');
-
-
-/**
- * A function that takes an {@link ol.MapBrowserEvent} and returns a
- * `{boolean}`. If the condition is met, true should be returned.
- *
- * @typedef {function(ol.MapBrowserEvent): boolean}
- * @api stable
- */
-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 (e.g. the windows-key) is
- * pressed, `false` otherwise (e.g. when additionally the shift-key is pressed).
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if only the platform modifier key is pressed.
- * @api stable
- */
-ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) {
-  var browserEvent = mapBrowserEvent.browserEvent;
-  return (
-      !browserEvent.altKey &&
-      browserEvent.platformModifierKey &&
-      !browserEvent.shiftKey);
-};
-
-
-/**
- * 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 !== goog.dom.TagName.INPUT &&
-      tagName !== goog.dom.TagName.SELECT &&
-      tagName !== goog.dom.TagName.TEXTAREA);
-};
-
-
-/**
- * Return `true` if the event originates from a mouse device.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if the event originates from a mouse device.
- * @api stable
- */
-ol.events.condition.mouseOnly = function(mapBrowserEvent) {
-  goog.asserts.assertInstanceof(mapBrowserEvent, ol.MapBrowserPointerEvent,
-      'mapBrowserEvent should be an instance of ol.MapBrowserPointerEvent');
-  /* pointerId must be 1 for mouse devices,
-   * see: http://www.w3.org/Submission/pointer-events/#pointerevent-interface
-   */
-  return mapBrowserEvent.pointerEvent.pointerId == 1;
-};
-
-goog.provide('ol.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 ? 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('goog.events');
-goog.require('ol.geom.Polygon');
-goog.require('ol.render.EventType');
-
-
-
-/**
- * @constructor
- * @extends {goog.Disposable}
- * @param {ol.style.Style} style Style.
- */
-ol.render.Box = function(style) {
-
-  /**
-   * @private
-   * @type {ol.Map}
-   */
-  this.map_ = null;
-
-  /**
-   * @private
-   * @type {goog.events.Key}
-   */
-  this.postComposeListenerKey_ = null;
-
-  /**
-   * @private
-   * @type {ol.Pixel}
-   */
-  this.startPixel_ = null;
-
-  /**
-   * @private
-   * @type {ol.Pixel}
-   */
-  this.endPixel_ = null;
-
-  /**
-   * @private
-   * @type {ol.geom.Polygon}
-   */
-  this.geometry_ = null;
-
-  /**
-   * @private
-   * @type {ol.style.Style}
-   */
-  this.style_ = style;
-
-};
-goog.inherits(ol.render.Box, goog.Disposable);
-
-
-/**
- * @private
- * @return {ol.geom.Polygon} Geometry.
- */
-ol.render.Box.prototype.createGeometry_ = 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();
-  return new ol.geom.Polygon([coordinates]);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.Box.prototype.disposeInternal = function() {
-  this.setMap(null);
-};
-
-
-/**
- * @param {ol.render.Event} event Event.
- * @private
- */
-ol.render.Box.prototype.handleMapPostCompose_ = function(event) {
-  var geometry = this.geometry_;
-  goog.asserts.assert(geometry, 'geometry should be defined');
-  var style = this.style_;
-  goog.asserts.assert(style, 'style must be truthy');
-  // use drawAsync(Infinity) to draw above everything
-  event.vectorContext.drawAsync(Infinity, function(render) {
-    render.setFillStrokeStyle(style.getFill(), style.getStroke());
-    render.setTextStyle(style.getText());
-    render.drawPolygonGeometry(geometry, null);
-  });
-};
-
-
-/**
- * @return {ol.geom.Polygon} Geometry.
- */
-ol.render.Box.prototype.getGeometry = function() {
-  return this.geometry_;
-};
-
-
-/**
- * @private
- */
-ol.render.Box.prototype.requestMapRenderFrame_ = function() {
-  if (this.map_ && this.startPixel_ && this.endPixel_) {
-    this.map_.render();
-  }
-};
-
-
-/**
- * @param {ol.Map} map Map.
- */
-ol.render.Box.prototype.setMap = function(map) {
-  if (this.postComposeListenerKey_) {
-    goog.events.unlistenByKey(this.postComposeListenerKey_);
-    this.postComposeListenerKey_ = null;
-    this.map_.render();
-    this.map_ = null;
-  }
-  this.map_ = map;
-  if (this.map_) {
-    this.postComposeListenerKey_ = goog.events.listen(
-        map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false,
-        this);
-    this.requestMapRenderFrame_();
-  }
-};
-
-
-/**
- * @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.geometry_ = this.createGeometry_();
-  this.requestMapRenderFrame_();
-};
-
-// FIXME draw drag box
-goog.provide('ol.DragBoxEvent');
-goog.provide('ol.interaction.DragBox');
-
-goog.require('goog.events.Event');
-goog.require('ol');
-goog.require('ol.events.ConditionType');
-goog.require('ol.events.condition');
-goog.require('ol.interaction.Pointer');
-goog.require('ol.render.Box');
-
-
-/**
- * @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 : {};
-
-  /**
-   * @private
-   * @type {ol.style.Style}
-   */
-  var style = options.style ? options.style : null;
-
-  /**
-   * @type {ol.render.Box}
-   * @private
-   */
-  this.box_ = new ol.render.Box(style);
-
-  /**
-   * @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;
-  }
-};
-
-// 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.structs.IHasChecksum');
-
-
-
-/**
- * @interface
- */
-ol.structs.IHasChecksum = function() {
-};
-
-
-/**
- * @return {string} The checksum.
- */
-ol.structs.IHasChecksum.prototype.getChecksum = function() {
-};
-
-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.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.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_;
-};
-
-goog.provide('ol.style.Circle');
-
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-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} */
-        (goog.dom.createElement(goog.dom.TagName.CANVAS));
-    this.canvas_.height = size;
-    this.canvas_.width = size;
-
-    // canvas.width and height are rounded to the closest integer
-    size = this.canvas_.width;
-    imageSize = size;
-
-    // draw the circle on the canvas
-    var context = /** @type {CanvasRenderingContext2D} */
-        (this.canvas_.getContext('2d'));
-    this.draw_(renderOptions, context, 0, 0);
-
-    this.createHitDetectionCanvas_(renderOptions);
-  } else {
-    // an atlas manager is used, add the symbol to an atlas
-    size = Math.round(size);
-
-    var hasCustomHitDetectionImage = !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} */
-      (goog.dom.createElement(goog.dom.TagName.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, number): 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} 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.geom.Geometry|undefined)}
- * @api
- */
-ol.style.GeometryFunction;
-
-
-/**
- * Function that is called with a feature and returns its default geometry.
- * @param {ol.Feature} feature Feature to get the geometry for.
- * @return {ol.geom.Geometry|undefined} Geometry to render.
- */
-ol.style.defaultGeometryFunction = function(feature) {
-  goog.asserts.assert(feature, 'feature must not be null');
-  return feature.getGeometry();
-};
-
-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');
-goog.require('ol.style.Stroke');
-goog.require('ol.style.Style');
-
-
-
-/**
- * @classdesc
- * Allows the user to zoom the map by clicking and dragging on the map,
- * normally combined with an {@link ol.events.condition} that limits
- * it to when a key, shift by default, is held down.
- *
- * @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 ? options.duration : 200;
-
-  /**
-   * @private
-   * @type {ol.style.Style}
-   */
-  var style = options.style ?
-      options.style : new ol.style.Style({
-        stroke: new ol.style.Stroke({
-          color: [0, 0, 255, 1]
-        })
-      });
-
-  goog.base(this, {
-    condition: condition,
-    style: style
-  });
-
-};
-goog.inherits(ol.interaction.DragZoom, ol.interaction.DragBox);
-
-
-/**
- * @inheritDoc
- */
-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 ? 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}
- *
- * Note that DragZoom renders a box as a vector polygon, so this interaction
- * should be excluded if you want a build with no vector support.
- *
- * @param {olx.interaction.DefaultsOptions=} opt_options Defaults options.
- * @return {ol.Collection.<ol.interaction.Interaction>} A collection of
- * interactions to be used with the ol.Map constructor's interactions option.
- * @api stable
- */
-ol.interaction.defaults = function(opt_options) {
-
-  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());
-  }
-
-  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.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();
-};
-
-// 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.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.
- * @param {ol.Feature} feature Feature,
- * @api
- */
-ol.render.canvas.Immediate.prototype.drawCircleGeometry =
-    function(circleGeometry, feature) {
-  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} pointGeometry Point geometry.
- * @param {ol.Feature} feature Feature.
- * @api
- */
-ol.render.canvas.Immediate.prototype.drawPointGeometry =
-    function(pointGeometry, feature) {
-  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} multiPointGeometry MultiPoint geometry.
- * @param {ol.Feature} feature Feature.
- * @api
- */
-ol.render.canvas.Immediate.prototype.drawMultiPointGeometry =
-    function(multiPointGeometry, feature) {
-  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} lineStringGeometry Line string geometry.
- * @param {ol.Feature} feature Feature.
- * @api
- */
-ol.render.canvas.Immediate.prototype.drawLineStringGeometry =
-    function(lineStringGeometry, feature) {
-  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} multiLineStringGeometry
- *     MultiLineString geometry.
- * @param {ol.Feature} feature Feature.
- * @api
- */
-ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry =
-    function(multiLineStringGeometry, feature) {
-  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} polygonGeometry Polygon geometry.
- * @param {ol.Feature} feature Feature.
- * @api
- */
-ol.render.canvas.Immediate.prototype.drawPolygonGeometry =
-    function(polygonGeometry, feature) {
-  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.
- * @param {ol.Feature} feature Feature.
- * @api
- */
-ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry =
-    function(multiPolygonGeometry, feature) {
-  if (!ol.extent.intersects(this.extent_, multiPolygonGeometry.getExtent())) {
-    return;
-  }
-  if (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);
-  goog.array.sort(zs);
-  var i, ii, callbacks, j, jj;
-  for (i = 0, ii = zs.length; i < ii; ++i) {
-    callbacks = this.callbacksByZIndex_[zs[i].toString()];
-    for (j = 0, jj = callbacks.length; j < jj; ++j) {
-      callbacks[j](this);
-    }
-  }
-};
-
-
-/**
- * @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,
- *                         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} geometry Geometry.
- * @param {ol.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): 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 i = 0; // instruction index
-  var ii = instructions.length; // end of instructions
-  var d = 0; // data index
-  var dd; // end of per-instruction data
-  var localTransform = this.tmpLocalTransform_;
-  while (i < ii) {
-    var instruction = instructions[i];
-    var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
-    var feature, fill, stroke, text, x, y;
-    switch (type) {
-      case ol.render.canvas.Instruction.BEGIN_GEOMETRY:
-        feature = /** @type {ol.Feature} */ (instruction[1]);
-        var featureUid = goog.getUid(feature).toString();
-        if (skippedFeaturesHash[featureUid] !== undefined ||
-            !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));
-          }
-          if (stroke) {
-            context.strokeText(text, x, y);
-          }
-          if (fill) {
-            context.fillText(text, x, y);
-          }
-          if (scale != 1 || rotation !== 0) {
-            context.setTransform(1, 0, 0, 1, 0, 0);
-          }
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.END_GEOMETRY:
-        if (featureCallback !== undefined) {
-          feature = /** @type {ol.Feature} */ (instruction[1]);
-          var result = featureCallback(feature);
-          if (result) {
-            return result;
-          }
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.FILL:
-        context.fill();
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.MOVE_TO_LINE_TO:
-        goog.asserts.assert(goog.isNumber(instruction[1]),
-            '2nd instruction should be a number');
-        d = /** @type {number} */ (instruction[1]);
-        goog.asserts.assert(goog.isNumber(instruction[2]),
-            '3rd instruction should be a number');
-        dd = /** @type {number} */ (instruction[2]);
-        context.moveTo(pixelCoordinates[d], pixelCoordinates[d + 1]);
-        for (d += 2; d < dd; d += 2) {
-          context.lineTo(pixelCoordinates[d], pixelCoordinates[d + 1]);
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.SET_FILL_STYLE:
-        goog.asserts.assert(goog.isString(instruction[1]),
-            '2nd instruction should be a string');
-        context.fillStyle = /** @type {string} */ (instruction[1]);
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.SET_STROKE_STYLE:
-        goog.asserts.assert(goog.isString(instruction[1]),
-            '2nd instruction should be a string');
-        goog.asserts.assert(goog.isNumber(instruction[2]),
-            '3rd instruction should be a number');
-        goog.asserts.assert(goog.isString(instruction[3]),
-            '4rd instruction should be a string');
-        goog.asserts.assert(goog.isString(instruction[4]),
-            '5th instruction should be a string');
-        goog.asserts.assert(goog.isNumber(instruction[5]),
-            '6th instruction should be a number');
-        goog.asserts.assert(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]));
-        }
-        ++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): 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} geometry Geometry.
- * @param {ol.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): 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} 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);
-  goog.array.sort(zs);
-
-  // 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): 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);
-  goog.array.sort(zs, function(a, b) { return b - a; });
-
-  var i, ii, j, replays, replay, result;
-  for (i = 0, ii = zs.length; i < ii; ++i) {
-    replays = this.replaysByZIndex_[zs[i].toString()];
-    for (j = ol.render.REPLAY_ORDER.length - 1; j >= 0; --j) {
-      replay = replays[ol.render.REPLAY_ORDER[j]];
-      if (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.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.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');
-};
-
-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.renderer.vector');
-
-goog.require('goog.asserts');
-goog.require('ol.geom.Circle');
-goog.require('ol.geom.GeometryCollection');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.render.IReplayGroup');
-goog.require('ol.style.ImageState');
-goog.require('ol.style.Style');
-
-
-/**
- * @param {ol.Feature} feature1 Feature 1.
- * @param {ol.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.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderCircleGeometry_ =
-    function(replayGroup, geometry, style, feature) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.Circle,
-      'geometry should be an ol.geom.Circle');
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  if (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} 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} 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.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderGeometryCollectionGeometry_ =
-    function(replayGroup, geometry, style, feature) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.GeometryCollection,
-      'geometry should be an ol.geom.GeometryCollection');
-  var geometries = geometry.getGeometriesArray();
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    var geometryRenderer =
-        ol.renderer.vector.GEOMETRY_RENDERERS_[geometries[i].getType()];
-    goog.asserts.assert(geometryRenderer !== undefined,
-        'geometryRenderer should be defined');
-    geometryRenderer(replayGroup, geometries[i], style, feature);
-  }
-};
-
-
-/**
- * @param {ol.render.IReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderLineStringGeometry_ =
-    function(replayGroup, geometry, style, feature) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.LineString,
-      'geometry should be an ol.geom.LineString');
-  var strokeStyle = style.getStroke();
-  if (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.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderMultiLineStringGeometry_ =
-    function(replayGroup, geometry, style, feature) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString,
-      'geometry should be an ol.geom.MultiLineString');
-  var strokeStyle = style.getStroke();
-  if (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.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderMultiPolygonGeometry_ =
-    function(replayGroup, geometry, style, feature) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon,
-      'geometry should be an ol.geom.MultiPolygon');
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  if (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.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderPointGeometry_ =
-    function(replayGroup, geometry, style, feature) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.Point,
-      'geometry should be an ol.geom.Point');
-  var imageStyle = style.getImage();
-  if (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.getCoordinates(), 0, 2, 2, geometry, feature);
-  }
-};
-
-
-/**
- * @param {ol.render.IReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderMultiPointGeometry_ =
-    function(replayGroup, geometry, style, feature) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint,
-      'geometry should be an ol.goem.MultiPoint');
-  var imageStyle = style.getImage();
-  if (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.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderPolygonGeometry_ =
-    function(replayGroup, geometry, style, feature) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.Polygon,
-      'geometry should be an ol.geom.Polygon');
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  if (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.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.Extent');
-goog.require('ol.ImageState');
-goog.require('ol.array');
-goog.require('ol.source.Source');
-
-
-/**
- * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
- *            extent: (null|ol.Extent|undefined),
- *            logo: (string|olx.LogoOptions|undefined),
- *            projection: ol.proj.ProjectionLike,
- *            resolutions: (Array.<number>|undefined),
- *            state: (ol.source.State|undefined)}}
- */
-ol.source.ImageOptions;
-
-
-
-/**
- * @classdesc
- * 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');
-
-};
-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 = 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.getImage =
-    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): 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.format.FormatType');
-
-
-/**
- * @enum {string}
- */
-ol.format.FormatType = {
-  JSON: 'json',
-  TEXT: 'text',
-  XML: 'xml'
-};
-
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/**
- * @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.format.FormatType');
-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.source.Vector, Array.<ol.Feature>)} success
- *     Function called with the loaded features. Called with the vector
- *     source as `this`.
- * @return {ol.FeatureLoader} The feature loader.
- */
-ol.featureloader.loadFeaturesXhr = function(url, format, success) {
-  return (
-      /**
-       * @param {ol.Extent} extent Extent.
-       * @param {number} resolution Resolution.
-       * @param {ol.proj.Projection} projection Projection.
-       * @this {ol.source.Vector}
-       */
-      function(extent, resolution, projection) {
-        var xhrIo = new goog.net.XhrIo();
-        xhrIo.setResponseType(goog.net.XhrIo.ResponseType.TEXT);
-        goog.events.listen(xhrIo, goog.net.EventType.COMPLETE,
-            /**
-             * @param {Event} event Event.
-             * @private
-             * @this {ol.source.Vector}
-             */
-            function(event) {
-              var xhrIo = event.target;
-              goog.asserts.assertInstanceof(xhrIo, goog.net.XhrIo,
-                  'event.target/xhrIo is an instance of goog.net.XhrIo');
-              if (xhrIo.isSuccess()) {
-                var type = format.getType();
-                /** @type {Document|Node|Object|string|undefined} */
-                var source;
-                if (type == ol.format.FormatType.JSON) {
-                  source = xhrIo.getResponseText();
-                } else if (type == ol.format.FormatType.TEXT) {
-                  source = xhrIo.getResponseText();
-                } else if (type == ol.format.FormatType.XML) {
-                  if (!goog.userAgent.IE) {
-                    source = xhrIo.getResponseXml();
-                  }
-                  if (!source) {
-                    source = ol.xml.parse(xhrIo.getResponseText());
-                  }
-                } else {
-                  goog.asserts.fail('unexpected format type');
-                }
-                if (source) {
-                  var features = format.readFeatures(source,
-                      {featureProjection: projection});
-                  success.call(this, features);
-                } else {
-                  goog.asserts.fail('undefined or null source');
-                }
-              } else {
-                // FIXME
-              }
-              goog.dispose(xhrIo);
-            }, false, this);
-        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 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);
-      });
-};
-
-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) 2013, 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;
-    },
-
-    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 newNode = {
-            children: node.children.splice(this._chooseSplitIndex(node, m, M)),
-            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);
-    }
-}
-
-// sort array between left and right (inclusive) so that the smallest k elements come first (unordered)
-function select(arr, left, right, k, compare) {
-    var n, i, z, s, sd, newLeft, newRight, t, j;
-
-    while (right > left) {
-        if (right - left > 600) {
-            n = right - left + 1;
-            i = k - left + 1;
-            z = Math.log(n);
-            s = 0.5 * Math.exp(2 * z / 3);
-            sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (i - n / 2 < 0 ? -1 : 1);
-            newLeft = Math.max(left, Math.floor(k - i * s / n + sd));
-            newRight = Math.min(right, Math.floor(k + (n - i) * s / n + sd));
-            select(arr, newLeft, newRight, k, compare);
-        }
-
-        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.object.containsKey(this.items_, goog.getUid(value)),
-      '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.object.containsKey(this.items_, goog.getUid(value)),
-        'uid (%s) of value (%s) already exists', goog.getUid(value), value);
-    this.items_[goog.getUid(value)] = item;
-  }
-  this.rbush_.load(items);
-};
-
-
-/**
- * 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(goog.object.containsKey(this.items_, uid),
-      '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(goog.object.containsKey(this.items_, uid),
-      '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.
- *
- * @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);
-  }
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {function(this: T, ol.Feature): S} f Callback.
- * @param {T=} opt_this The object to use as `this` in `f`.
- * @return {S|undefined}
- * @template T,S
- */
-ol.source.Vector.prototype.forEachFeatureInExtentAtResolution =
-    function(extent, resolution, f, opt_this) {
-  return this.forEachFeatureInExtent(extent, f, 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_.forEachFeatureInExtentAtResolution(extent, resolution,
-      /**
-       * @param {ol.Feature} feature Feature.
-       */
-      function(feature) {
-        loading = loading ||
-            this.renderFeature_(feature, resolution, pixelRatio, replayGroup);
-      }, this);
-  replayGroup.finish();
-
-  if (loading) {
-    return null;
-  }
-
-  if (this.canvasSize_[0] != size[0] || this.canvasSize_[1] != size[1]) {
-    this.canvasContext_.canvas.width = size[0];
-    this.canvasContext_.canvas.height = size[1];
-    this.canvasSize_[0] = size[0];
-    this.canvasSize_[1] = size[1];
-  } else {
-    this.canvasContext_.clearRect(0, 0, size[0], size[1]);
-  }
-
-  var transform = this.getTransform_(ol.extent.getCenter(extent),
-      resolution, pixelRatio, size);
-  replayGroup.replay(this.canvasContext_, pixelRatio, transform, 0, {});
-
-  this.replayGroup_ = replayGroup;
-
-  return this.canvasContext_.canvas;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.ImageVector.prototype.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} 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} 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;
-    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.array');
-goog.require('goog.asserts');
-goog.require('goog.vec.Mat4');
-goog.require('ol.Size');
-goog.require('ol.TileRange');
-goog.require('ol.TileState');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.layer.Tile');
-goog.require('ol.renderer.canvas.Layer');
-goog.require('ol.size');
-goog.require('ol.tilecoord');
-goog.require('ol.vec.Mat4');
-
-
-
-/**
- * @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, tilesToDrawByZ);
-
-  var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
-
-  var tmpExtent = ol.extent.createEmpty();
-  var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
-  var childTileRange, fullyLoaded, tile, tileState, x, y;
-  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
-
-      tile = tileSource.getTile(z, x, y, pixelRatio, projection);
-      tileState = tile.getState();
-      if (tileState == ol.TileState.LOADED ||
-          tileState == ol.TileState.EMPTY ||
-          (tileState == ol.TileState.ERROR && !useInterimTilesOnError)) {
-        tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile;
-        continue;
-      }
-
-      fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
-          tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
-      if (!fullyLoaded) {
-        // FIXME we do not need to clear the tile if it is fully covered by its
-        //       children
-        tilesToClear.push(tile);
-        childTileRange = tileGrid.getTileCoordChildTileRange(
-            tile.tileCoord, tmpTileRange, tmpExtent);
-        if (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);
-  goog.array.sort(zs);
-  var opaque = tileSource.getOpaque();
-  var origin = ol.extent.getTopLeft(tileGrid.getTileCoordExtent(
-      [z, canvasTileRange.minX, canvasTileRange.maxY],
-      tmpExtent));
-  var currentZ, index, scale, tileCoordKey, tileExtent, tilesToDraw;
-  var ix, iy, 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.array');
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('ol.ViewHint');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.layer.Vector');
-goog.require('ol.render.EventType');
-goog.require('ol.render.canvas.ReplayGroup');
-goog.require('ol.renderer.canvas.Layer');
-goog.require('ol.renderer.vector');
-goog.require('ol.source.Vector');
-
-
-
-/**
- * @constructor
- * @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} 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.forEachFeatureInExtentAtResolution(extent, resolution,
-        /**
-         * @param {ol.Feature} feature Feature.
-         */
-        function(feature) {
-          features.push(feature);
-        }, this);
-    goog.array.sort(features, vectorLayerRenderOrder);
-    features.forEach(renderFeature, this);
-  } else {
-    vectorSource.forEachFeatureInExtentAtResolution(
-        extent, resolution, renderFeature, this);
-  }
-  replayGroup.finish();
-
-  this.renderedResolution_ = resolution;
-  this.renderedRevision_ = vectorLayerRevision;
-  this.renderedRenderOrder_ = vectorLayerRenderOrder;
-  this.renderedExtent_ = extent;
-  this.replayGroup_ = replayGroup;
-
-  return true;
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {Array.<ol.style.Style>} styles Array of styles
- * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
- * @return {boolean} `true` if an image is loading.
- */
-ol.renderer.canvas.VectorLayer.prototype.renderFeature =
-    function(feature, resolution, pixelRatio, styles, replayGroup) {
-  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.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.render.Event');
-goog.require('ol.render.EventType');
-goog.require('ol.render.canvas.Immediate');
-goog.require('ol.renderer.Map');
-goog.require('ol.renderer.canvas.ImageLayer');
-goog.require('ol.renderer.canvas.Layer');
-goog.require('ol.renderer.canvas.TileLayer');
-goog.require('ol.renderer.canvas.VectorLayer');
-goog.require('ol.source.State');
-goog.require('ol.vec.Mat4');
-
-
-
-/**
- * @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 && 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.dom.TagName');
-goog.require('goog.vec.Mat4');
-goog.require('ol.ImageBase');
-goog.require('ol.ViewHint');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.layer.Image');
-goog.require('ol.proj');
-goog.require('ol.renderer.dom.Layer');
-goog.require('ol.vec.Mat4');
-
-
-
-/**
- * @constructor
- * @extends {ol.renderer.dom.Layer}
- * @param {ol.layer.Image} imageLayer Image layer.
- */
-ol.renderer.dom.ImageLayer = function(imageLayer) {
-  var target = goog.dom.createElement(goog.dom.TagName.DIV);
-  target.style.position = 'absolute';
-
-  goog.base(this, imageLayer, target);
-
-  /**
-   * The last rendered image.
-   * @private
-   * @type {?ol.ImageBase}
-   */
-  this.image_ = null;
-
-  /**
-   * @private
-   * @type {goog.vec.Mat4.Number}
-   */
-  this.transform_ = goog.vec.Mat4.createNumberIdentity();
-
-};
-goog.inherits(ol.renderer.dom.ImageLayer, ol.renderer.dom.Layer);
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.dom.ImageLayer.prototype.forEachFeatureAtCoordinate =
-    function(coordinate, frameState, callback, thisArg) {
-  var layer = this.getLayer();
-  var source = layer.getSource();
-  var resolution = frameState.viewState.resolution;
-  var rotation = frameState.viewState.rotation;
-  var skippedFeatureUids = frameState.skippedFeatureUids;
-  return source.forEachFeatureAtCoordinate(
-      coordinate, resolution, rotation, skippedFeatureUids,
-      /**
-       * @param {ol.Feature} feature Feature.
-       * @return {?} Callback result.
-       */
-      function(feature) {
-        return callback.call(thisArg, feature, layer);
-      });
-};
-
-
-/**
- * @inheritDoc
- */
-ol.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;
-    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.array');
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.style');
-goog.require('goog.vec.Mat4');
-goog.require('ol');
-goog.require('ol.Coordinate');
-goog.require('ol.TileCoord');
-goog.require('ol.TileRange');
-goog.require('ol.TileState');
-goog.require('ol.ViewHint');
-goog.require('ol.dom');
-goog.require('ol.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 = goog.dom.createElement(goog.dom.TagName.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, tilesToDrawByZ);
-
-  var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
-
-  var tmpExtent = ol.extent.createEmpty();
-  var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
-  var childTileRange, fullyLoaded, tile, tileState, x, y;
-  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
-
-      tile = tileSource.getTile(z, x, y, pixelRatio, projection);
-      tileState = tile.getState();
-      if (tileState == ol.TileState.LOADED) {
-        tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile;
-        continue;
-      } else if (tileState == ol.TileState.EMPTY ||
-                 (tileState == ol.TileState.ERROR &&
-                  !useInterimTilesOnError)) {
-        continue;
-      }
-
-      fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
-          tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
-      if (!fullyLoaded) {
-        childTileRange = tileGrid.getTileCoordChildTileRange(
-            tile.tileCoord, tmpTileRange, tmpExtent);
-        if (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);
-  goog.array.sort(zs);
-
-  /** @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);
-  goog.array.sort(tileLayerZKeys);
-
-  var i, ii, j, origin, resolution;
-  var transform = goog.vec.Mat4.createNumber();
-  for (i = 0, ii = tileLayerZKeys.length; i < ii; ++i) {
-    tileLayerZKey = tileLayerZKeys[i];
-    tileLayerZ = this.tileLayerZs_[tileLayerZKey];
-    if (!(tileLayerZKey in tilesToDrawByZ)) {
-      goog.dom.removeNode(tileLayerZ.target);
-      delete this.tileLayerZs_[tileLayerZKey];
-      continue;
-    }
-    resolution = tileLayerZ.getResolution();
-    origin = tileLayerZ.getOrigin();
-    ol.vec.Mat4.makeTransform2D(transform,
-        frameState.size[0] / 2, frameState.size[1] / 2,
-        resolution / viewState.resolution,
-        resolution / viewState.resolution,
-        viewState.rotation,
-        (origin[0] - center[0]) / resolution,
-        (center[1] - origin[1]) / resolution);
-    tileLayerZ.setTransform(transform);
-    if (tileLayerZKey in newTileLayerZKeys) {
-      for (j = tileLayerZKey - 1; j >= 0; --j) {
-        if (j in this.tileLayerZs_) {
-          goog.dom.insertSiblingAfter(
-              tileLayerZ.target, this.tileLayerZs_[j].target);
-          break;
-        }
-      }
-      if (j < 0) {
-        goog.dom.insertChildAt(this.target, tileLayerZ.target, 0);
-      }
-    } else {
-      if (!frameState.viewHints[ol.ViewHint.ANIMATING] &&
-          !frameState.viewHints[ol.ViewHint.INTERACTING]) {
-        tileLayerZ.removeTilesOutsideExtent(extent, tmpTileRange);
-      }
-    }
-  }
-
-  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 = goog.dom.createElement(goog.dom.TagName.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 = goog.dom.createElement(goog.dom.TagName.DIV);
-    tileElementStyle = tileElement.style;
-    tileElementStyle.overflow = 'hidden';
-    tileElementStyle.width = tileSize[0] + 'px';
-    tileElementStyle.height = tileSize[1] + 'px';
-    imageStyle.position = 'absolute';
-    imageStyle.left = -tileGutter + 'px';
-    imageStyle.top = -tileGutter + 'px';
-    imageStyle.width = (tileSize[0] + 2 * tileGutter) + 'px';
-    imageStyle.height = (tileSize[1] + 2 * tileGutter) + 'px';
-    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.array');
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.vec.Mat4');
-goog.require('ol.ViewHint');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.layer.Vector');
-goog.require('ol.render.Event');
-goog.require('ol.render.EventType');
-goog.require('ol.render.canvas.Immediate');
-goog.require('ol.render.canvas.ReplayGroup');
-goog.require('ol.renderer.dom.Layer');
-goog.require('ol.renderer.vector');
-goog.require('ol.vec.Mat4');
-
-
-
-/**
- * @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} 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.forEachFeatureInExtentAtResolution(extent, resolution,
-        /**
-         * @param {ol.Feature} feature Feature.
-         */
-        function(feature) {
-          features.push(feature);
-        }, this);
-    goog.array.sort(features, vectorLayerRenderOrder);
-    features.forEach(renderFeature, this);
-  } else {
-    vectorSource.forEachFeatureInExtentAtResolution(
-        extent, resolution, renderFeature, this);
-  }
-  replayGroup.finish();
-
-  this.renderedResolution_ = resolution;
-  this.renderedRevision_ = vectorLayerRevision;
-  this.renderedRenderOrder_ = vectorLayerRenderOrder;
-  this.renderedExtent_ = extent;
-  this.replayGroup_ = replayGroup;
-
-  return true;
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {Array.<ol.style.Style>} styles Array of styles
- * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
- * @return {boolean} `true` if an image is loading.
- */
-ol.renderer.dom.VectorLayer.prototype.renderFeature =
-    function(feature, resolution, pixelRatio, styles, replayGroup) {
-  if (!styles) {
-    return false;
-  }
-  var i, ii, loading = false;
-  for (i = 0, ii = styles.length; i < ii; ++i) {
-    loading = ol.renderer.vector.renderFeature(
-        replayGroup, feature, styles[i],
-        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-        this.handleStyleImageChange_, this) || loading;
-  }
-  return loading;
-};
-
-goog.provide('ol.renderer.dom.Map');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.Event');
-goog.require('goog.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_ = goog.dom.createElement(goog.dom.TagName.DIV);
-  this.layersPane_.className = ol.css.CLASS_UNSELECTABLE;
-  var style = this.layersPane_.style;
-  style.position = 'absolute';
-  style.width = '100%';
-  style.height = '100%';
-
-  // 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.
- * @api
- */
-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 (goog.object.containsKey(texturePerImage, uid)) {
-      texture = texturePerImage[uid];
-    } else {
-      texture = ol.webgl.Context.createTexture(
-          gl, image, goog.webgl.CLAMP_TO_EDGE, goog.webgl.CLAMP_TO_EDGE);
-      texturePerImage[uid] = texture;
-    }
-    textures[i] = texture;
-  }
-};
-
-
-/**
- * @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('goog.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);
-  goog.array.sort(zs);
-  var i, ii, callbacks, j, jj;
-  for (i = 0, ii = zs.length; i < ii; ++i) {
-    callbacks = this.callbacksByZIndex_[zs[i].toString()];
-    for (j = 0, jj = callbacks.length; j < jj; ++j) {
-      callbacks[j](this);
-    }
-  }
-};
-
-
-/**
- * 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,
- *                         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} 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;
-    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.array');
-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.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 {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, 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(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, tilesToDrawByZ);
-
-    var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
-    var allTilesLoaded = true;
-    var tmpExtent = ol.extent.createEmpty();
-    var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
-    var childTileRange, fullyLoaded, tile, tileState, x, y, tileExtent;
-    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
-
-        tile = tileSource.getTile(z, x, y, pixelRatio, projection);
-        if (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();
-        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);
-    goog.array.sort(zs);
-    var u_tileOffset = goog.vec.Vec4.createFloat32();
-    var i, ii, sx, sy, tileKey, tilesToDraw, tx, ty;
-    for (i = 0, ii = zs.length; i < ii; ++i) {
-      tilesToDraw = tilesToDrawByZ[zs[i]];
-      for (tileKey in tilesToDraw) {
-        tile = tilesToDraw[tileKey];
-        tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
-        sx = 2 * (tileExtent[2] - tileExtent[0]) /
-            framebufferExtentDimension;
-        sy = 2 * (tileExtent[3] - tileExtent[1]) /
-            framebufferExtentDimension;
-        tx = 2 * (tileExtent[0] - framebufferExtent[0]) /
-            framebufferExtentDimension - 1;
-        ty = 2 * (tileExtent[1] - framebufferExtent[1]) /
-            framebufferExtentDimension - 1;
-        goog.vec.Vec4.setFromValues(u_tileOffset, sx, sy, tx, ty);
-        gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset);
-        mapRenderer.bindTileTexture(tile, tilePixelSize,
-            tileGutter * pixelRatio, goog.webgl.LINEAR, goog.webgl.LINEAR);
-        gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4);
-      }
-    }
-
-    if (allTilesLoaded) {
-      this.renderedTileRange_ = tileRange;
-      this.renderedFramebufferExtent_ = framebufferExtent;
-      this.renderedRevision_ = tileSource.getRevision();
-    } else {
-      this.renderedTileRange_ = null;
-      this.renderedFramebufferExtent_ = null;
-      this.renderedRevision_ = -1;
-      frameState.animate = true;
-    }
-
-  }
-
-  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.array');
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('ol.ViewHint');
-goog.require('ol.extent');
-goog.require('ol.layer.Vector');
-goog.require('ol.render.webgl.ReplayGroup');
-goog.require('ol.renderer.vector');
-goog.require('ol.renderer.webgl.Layer');
-goog.require('ol.vec.Mat4');
-
-
-
-/**
- * @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.forEachFeatureInExtentAtResolution(extent, resolution,
-        /**
-         * @param {ol.Feature} feature Feature.
-         */
-        function(feature) {
-          features.push(feature);
-        }, this);
-    goog.array.sort(features, vectorLayerRenderOrder);
-    features.forEach(renderFeature, this);
-  } else {
-    vectorSource.forEachFeatureInExtentAtResolution(
-        extent, resolution, 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 {Array.<ol.style.Style>} styles 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 i, ii, loading = false;
-  for (i = 0, ii = styles.length; i < ii; ++i) {
-    loading = ol.renderer.vector.renderFeature(
-        replayGroup, feature, styles[i],
-        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-        this.handleStyleImageChange_, this) || loading;
-  }
-  return loading;
-};
-
-// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE)
-
-goog.provide('ol.renderer.webgl.Map');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.Event');
-goog.require('goog.log');
-goog.require('goog.log.Logger');
-goog.require('goog.object');
-goog.require('goog.style');
-goog.require('goog.webgl');
-goog.require('ol');
-goog.require('ol.RendererType');
-goog.require('ol.css');
-goog.require('ol.dom');
-goog.require('ol.layer.Image');
-goog.require('ol.layer.Layer');
-goog.require('ol.layer.Tile');
-goog.require('ol.layer.Vector');
-goog.require('ol.render.Event');
-goog.require('ol.render.EventType');
-goog.require('ol.render.webgl.Immediate');
-goog.require('ol.renderer.Map');
-goog.require('ol.renderer.webgl.ImageLayer');
-goog.require('ol.renderer.webgl.Layer');
-goog.require('ol.renderer.webgl.TileLayer');
-goog.require('ol.renderer.webgl.VectorLayer');
-goog.require('ol.source.State');
-goog.require('ol.structs.LRUCache');
-goog.require('ol.structs.PriorityQueue');
-goog.require('ol.webgl');
-goog.require('ol.webgl.Context');
-goog.require('ol.webgl.WebGLContextEventType');
-
-
-/**
- * @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} */
-      (goog.dom.createElement(goog.dom.TagName.CANVAS));
-  this.canvas_.style.width = '100%';
-  this.canvas_.style.height = '100%';
-  this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
-  goog.dom.insertChildAt(container, this.canvas_, 0);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.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.TagName');
-goog.require('goog.dom.ViewportSizeMonitor');
-goog.require('goog.dom.classlist');
-goog.require('goog.events');
-goog.require('goog.events.BrowserEvent');
-goog.require('goog.events.Event');
-goog.require('goog.events.EventType');
-goog.require('goog.events.KeyHandler');
-goog.require('goog.events.KeyHandler.EventType');
-goog.require('goog.events.MouseWheelHandler');
-goog.require('goog.events.MouseWheelHandler.EventType');
-goog.require('goog.functions');
-goog.require('goog.log');
-goog.require('goog.log.Level');
-goog.require('goog.object');
-goog.require('goog.style');
-goog.require('goog.vec.Mat4');
-goog.require('ol.Collection');
-goog.require('ol.CollectionEventType');
-goog.require('ol.MapBrowserEvent');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.MapBrowserEventHandler');
-goog.require('ol.MapEvent');
-goog.require('ol.MapEventType');
-goog.require('ol.Object');
-goog.require('ol.ObjectEvent');
-goog.require('ol.ObjectEventType');
-goog.require('ol.Pixel');
-goog.require('ol.PostRenderFunction');
-goog.require('ol.PreRenderFunction');
-goog.require('ol.RendererType');
-goog.require('ol.Size');
-goog.require('ol.TileQueue');
-goog.require('ol.View');
-goog.require('ol.ViewHint');
-goog.require('ol.control');
-goog.require('ol.extent');
-goog.require('ol.has');
-goog.require('ol.interaction');
-goog.require('ol.layer.Base');
-goog.require('ol.layer.Group');
-goog.require('ol.proj');
-goog.require('ol.proj.common');
-goog.require('ol.renderer.Map');
-goog.require('ol.renderer.canvas.Map');
-goog.require('ol.renderer.dom.Map');
-goog.require('ol.renderer.webgl.Map');
-goog.require('ol.size');
-goog.require('ol.structs.PriorityQueue');
-goog.require('ol.tilecoord');
-goog.require('ol.vec.Mat4');
-
-
-/**
- * @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_ = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-viewport');
-  this.viewport_.style.position = 'relative';
-  this.viewport_.style.overflow = 'hidden';
-  this.viewport_.style.width = '100%';
-  this.viewport_.style.height = '100%';
-  // prevent page zoom on IE >= 10 browsers
-  this.viewport_.style.msTouchAction = 'none';
-  this.viewport_.style.touchAction = 'none';
-  if (ol.has.TOUCH) {
-    goog.dom.classlist.add(this.viewport_, 'ol-touch');
-  }
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.overlayContainer_ = goog.dom.createDom(goog.dom.TagName.DIV,
-      'ol-overlaycontainer');
-  this.viewport_.appendChild(this.overlayContainer_);
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.overlayContainerStopEvent_ = goog.dom.createDom(goog.dom.TagName.DIV,
-      'ol-overlaycontainer-stopevent');
-  goog.events.listen(this.overlayContainerStopEvent_, [
-    goog.events.EventType.CLICK,
-    goog.events.EventType.DBLCLICK,
-    goog.events.EventType.MOUSEDOWN,
-    goog.events.EventType.TOUCHSTART,
-    goog.events.EventType.MSPOINTERDOWN,
-    ol.MapBrowserEvent.EventType.POINTERDOWN,
-    goog.userAgent.GECKO ? 'DOMMouseScroll' : 'mousewheel'
-  ], goog.events.Event.stopPropagation);
-  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;
-
-  /**
-   * @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(
-      /**
-       * @param {ol.Overlay} overlay Overlay.
-       * @this {ol.Map}
-       */
-      function(overlay) {
-        overlay.setMap(this);
-      }, this);
-
-  goog.events.listen(this.overlays_, ol.CollectionEventType.ADD,
-      /**
-       * @param {ol.CollectionEvent} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(this);
-      }, false, this);
-
-  goog.events.listen(this.overlays_, ol.CollectionEventType.REMOVE,
-      /**
-       * @param {ol.CollectionEvent} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(null);
-      }, false, this);
-
-};
-goog.inherits(ol.Map, ol.Object);
-
-
-/**
- * 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);
-};
+ol.format.GPX.TRKSEG_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'trkpt': ol.xml.makeChildAppender(ol.format.GPX.writeWptType_)
+    });
 
 
 /**
- * Add functions to be called before rendering. This can be used for attaching
- * animations before updating the map's view.  The {@link ol.animation}
- * namespace provides several static methods for creating prerender functions.
- * @param {...ol.PreRenderFunction} var_args Any number of pre-render functions.
- * @api
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
  */
-ol.Map.prototype.beforeRender = function(var_args) {
-  this.render();
-  Array.prototype.push.apply(this.preRenderFunctions_, arguments);
-};
+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'
+    ]);
 
 
 /**
- * @param {ol.PreRenderFunction} preRenderFunction Pre-render function.
- * @return {boolean} Whether the preRenderFunction has been found and removed.
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.Map.prototype.removePreRenderFunction = function(preRenderFunction) {
-  return goog.array.remove(this.preRenderFunctions_, preRenderFunction);
-};
+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)
+    });
 
 
 /**
- *
- * @inheritDoc
+ * @const
+ * @type {Object.<string, string>}
+ * @private
  */
-ol.Map.prototype.disposeInternal = function() {
-  goog.dom.removeNode(this.viewport_);
-  goog.base(this, 'disposeInternal');
+ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = {
+  'Point': 'wpt',
+  'LineString': 'rte',
+  'MultiLineString': 'trk'
 };
 
 
 /**
- * Detect features that intersect a pixel on the viewport, and execute a
- * callback with each intersecting feature. Layers included in the detection can
- * be configured through `opt_layerFilter`.
- * @param {ol.Pixel} pixel Pixel.
- * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature
- *     callback. The callback will be called with two arguments. The first
- *     argument is one {@link ol.Feature feature} at the pixel, the second is
- *     the {@link ol.layer.Layer layer} of the feature. To stop detection,
- *     callback functions can return a truthy value.
- * @param {S=} opt_this Value to use as `this` when executing `callback`.
- * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
- *     filter function. The filter function will receive one argument, the
- *     {@link ol.layer.Layer layer-candidate} and it should return a boolean
- *     value. Only layers which are visible and for which this function returns
- *     `true` will be tested for features. By default, all visible layers will
- *     be tested.
- * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`.
- * @return {T|undefined} Callback result, i.e. the return value of last
- * callback execution, or the first truthy callback return value.
- * @template S,T,U
- * @api stable
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
  */
-ol.Map.prototype.forEachFeatureAtPixel =
-    function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
-  if (!this.frameState_) {
-    return;
+ol.format.GPX.GPX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
+  var geometry = /** @type {ol.Feature} */ (value).getGeometry();
+  if (geometry) {
+    var nodeName = ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_[geometry.getType()];
+    if (nodeName) {
+      var parentNode = objectStack[objectStack.length - 1].node;
+      return ol.xml.createElementNS(parentNode.namespaceURI, nodeName);
+    }
   }
-  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
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-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);
-};
+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_)
+    });
 
 
 /**
- * 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
+ * Encode an array of features in the GPX format.
+ * LineString geometries are output as routes (`<rte>`), and MultiLineString
+ * as tracks (`<trk>`).
+ *
+ * @function
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} Result.
  * @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);
-};
+ol.format.GPX.prototype.writeFeatures;
 
 
 /**
- * Returns the geographical coordinate for a browser event.
- * @param {Event} event Event.
- * @return {ol.Coordinate} Coordinate.
- * @api stable
+ * Encode an array of features in the GPX format as an XML node.
+ * LineString geometries are output as routes (`<rte>`), and MultiLineString
+ * as tracks (`<trk>`).
+ *
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ * @override
+ * @api
  */
-ol.Map.prototype.getEventCoordinate = function(event) {
-  return this.getCoordinateFromPixel(this.getEventPixel(event));
+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');
+  var xmlnsUri = 'http://www.w3.org/2000/xmlns/';
+  var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance';
+  ol.xml.setAttributeNS(gpx, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri);
+  ol.xml.setAttributeNS(gpx, xmlSchemaInstanceUri, 'xsi:schemaLocation',
+      ol.format.GPX.SCHEMA_LOCATION_);
+  gpx.setAttribute('version', '1.1');
+  gpx.setAttribute('creator', 'OpenLayers');
+
+  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
+      ({node: gpx}), ol.format.GPX.GPX_SERIALIZERS_,
+      ol.format.GPX.GPX_NODE_FACTORY_, features, [opt_options]);
+  return gpx;
 };
 
+goog.provide('ol.format.IGCZ');
 
 /**
- * Returns the map pixel position for a browser event relative to the viewport.
- * @param {Event} event Event.
- * @return {ol.Pixel} Pixel.
- * @api stable
+ * IGC altitude/z. One of 'barometric', 'gps', 'none'.
+ * @enum {string}
  */
-ol.Map.prototype.getEventPixel = function(event) {
-  var eventPosition = goog.style.getRelativePosition(event, this.viewport_);
-  return [eventPosition.x, eventPosition.y];
+ol.format.IGCZ = {
+  BAROMETRIC: 'barometric',
+  GPS: 'gps',
+  NONE: 'none'
 };
 
+goog.provide('ol.format.TextFeature');
 
-/**
- * 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));
-};
+goog.require('ol');
+goog.require('ol.format.Feature');
+goog.require('ol.format.FormatType');
 
 
 /**
- * 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
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for text feature formats.
+ *
+ * @constructor
+ * @abstract
+ * @extends {ol.format.Feature}
  */
-ol.Map.prototype.getTargetElement = function() {
-  var target = this.getTarget();
-  return target !== undefined ? goog.dom.getElement(target) : null;
+ol.format.TextFeature = function() {
+  ol.format.Feature.call(this);
 };
+ol.inherits(ol.format.TextFeature, ol.format.Feature);
 
 
 /**
- * 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
+ * @param {Document|Node|Object|string} source Source.
+ * @private
+ * @return {string} Text.
  */
-ol.Map.prototype.getCoordinateFromPixel = function(pixel) {
-  var frameState = this.frameState_;
-  if (!frameState) {
-    return null;
+ol.format.TextFeature.prototype.getText_ = function(source) {
+  if (typeof source === 'string') {
+    return source;
   } else {
-    var vec2 = pixel.slice();
-    return ol.vec.Mat4.multVec2(frameState.pixelToCoordinateMatrix, vec2, vec2);
+    return '';
   }
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.Map.prototype.getOverlays = function() {
-  return this.overlays_;
+ol.format.TextFeature.prototype.getType = function() {
+  return ol.format.FormatType.TEXT;
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.Map.prototype.getInteractions = function() {
-  return this.interactions_;
+ol.format.TextFeature.prototype.readFeature = function(source, opt_options) {
+  return this.readFeatureFromText(
+      this.getText_(source), this.adaptOptions(opt_options));
 };
 
 
 /**
- * Get the layergroup associated with this map.
- * @return {ol.layer.Group} A layer group containing the layers in this map.
- * @observable
- * @api stable
+ * @abstract
+ * @param {string} text Text.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {ol.Feature} Feature.
  */
-ol.Map.prototype.getLayerGroup = function() {
-  return /** @type {ol.layer.Group} */ (this.get(ol.MapProperty.LAYERGROUP));
-};
+ol.format.TextFeature.prototype.readFeatureFromText = function(text, opt_options) {};
 
 
 /**
- * Get the collection of layers associated with this map.
- * @return {!ol.Collection.<ol.layer.Base>} Layers.
- * @api stable
+ * @inheritDoc
  */
-ol.Map.prototype.getLayers = function() {
-  var layers = this.getLayerGroup().getLayers();
-  return layers;
+ol.format.TextFeature.prototype.readFeatures = function(source, opt_options) {
+  return this.readFeaturesFromText(
+      this.getText_(source), this.adaptOptions(opt_options));
 };
 
 
 /**
- * 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
+ * @abstract
+ * @param {string} text Text.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {Array.<ol.Feature>} Features.
  */
-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);
-  }
-};
+ol.format.TextFeature.prototype.readFeaturesFromText = function(text, opt_options) {};
 
 
 /**
- * Get the map renderer.
- * @return {ol.renderer.Map} Renderer
+ * @inheritDoc
  */
-ol.Map.prototype.getRenderer = function() {
-  return this.renderer_;
+ol.format.TextFeature.prototype.readGeometry = function(source, opt_options) {
+  return this.readGeometryFromText(
+      this.getText_(source), this.adaptOptions(opt_options));
 };
 
 
 /**
- * Get the size of this map.
- * @return {ol.Size|undefined} The size in pixels of the map in the DOM.
- * @observable
- * @api stable
+ * @abstract
+ * @param {string} text Text.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {ol.geom.Geometry} Geometry.
  */
-ol.Map.prototype.getSize = function() {
-  return /** @type {ol.Size|undefined} */ (this.get(ol.MapProperty.SIZE));
-};
+ol.format.TextFeature.prototype.readGeometryFromText = function(text, opt_options) {};
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.Map.prototype.getView = function() {
-  return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW));
+ol.format.TextFeature.prototype.readProjection = function(source) {
+  return this.readProjectionFromText(this.getText_(source));
 };
 
 
 /**
- * Get the element that serves as the map viewport.
- * @return {Element} Viewport.
- * @api stable
+ * @param {string} text Text.
+ * @protected
+ * @return {ol.proj.Projection} Projection.
  */
-ol.Map.prototype.getViewport = function() {
-  return this.viewport_;
+ol.format.TextFeature.prototype.readProjectionFromText = function(text) {
+  return this.defaultDataProjection;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-ol.Map.prototype.getOverlayContainer = function() {
-  return this.overlayContainer_;
+ol.format.TextFeature.prototype.writeFeature = function(feature, opt_options) {
+  return this.writeFeatureText(feature, this.adaptOptions(opt_options));
 };
 
 
 /**
- * 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.
+ * @abstract
+ * @param {ol.Feature} feature Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @protected
+ * @return {string} Text.
  */
-ol.Map.prototype.getOverlayContainerStopEvent = function() {
-  return this.overlayContainerStopEvent_;
-};
+ol.format.TextFeature.prototype.writeFeatureText = function(feature, opt_options) {};
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-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;
+ol.format.TextFeature.prototype.writeFeatures = function(
+    features, opt_options) {
+  return this.writeFeaturesText(features, this.adaptOptions(opt_options));
 };
 
 
 /**
- * @param {goog.events.BrowserEvent} browserEvent Browser event.
- * @param {string=} opt_type Type.
+ * @abstract
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @protected
+ * @return {string} Text.
  */
-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);
-};
+ol.format.TextFeature.prototype.writeFeaturesText = function(features, opt_options) {};
 
 
 /**
- * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle.
+ * @inheritDoc
  */
-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;
-      }
-    }
-  }
+ol.format.TextFeature.prototype.writeGeometry = function(
+    geometry, opt_options) {
+  return this.writeGeometryText(geometry, this.adaptOptions(opt_options));
 };
 
 
 /**
+ * @abstract
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
  * @protected
+ * @return {string} Text.
  */
-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;
-};
+ol.format.TextFeature.prototype.writeGeometryText = function(geometry, opt_options) {};
 
+goog.provide('ol.format.IGC');
 
-/**
- * @private
- */
-ol.Map.prototype.handleSizeChanged_ = function() {
-  this.render();
-};
+goog.require('ol');
+goog.require('ol.Feature');
+goog.require('ol.format.Feature');
+goog.require('ol.format.IGCZ');
+goog.require('ol.format.TextFeature');
+goog.require('ol.geom.GeometryLayout');
+goog.require('ol.geom.LineString');
+goog.require('ol.proj');
 
 
 /**
- * @private
+ * @classdesc
+ * Feature format for `*.igc` flight recording files.
+ *
+ * @constructor
+ * @extends {ol.format.TextFeature}
+ * @param {olx.format.IGCOptions=} opt_options Options.
+ * @api
  */
-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();
+ol.format.IGC = function(opt_options) {
 
-  this.keyHandler_.detach();
+  var options = opt_options ? opt_options : {};
 
-  if (!targetElement) {
-    goog.dom.removeNode(this.viewport_);
-    if (this.viewportResizeListenerKey_) {
-      goog.events.unlistenByKey(this.viewportResizeListenerKey_);
-      this.viewportResizeListenerKey_ = null;
-    }
-  } else {
-    targetElement.appendChild(this.viewport_);
+  ol.format.TextFeature.call(this);
 
-    var keyboardEventTarget = !this.keyboardEventTarget_ ?
-        targetElement : this.keyboardEventTarget_;
-    this.keyHandler_.attach(keyboardEventTarget);
+  /**
+   * @inheritDoc
+   */
+  this.defaultDataProjection = ol.proj.get('EPSG:4326');
 
-    if (!this.viewportResizeListenerKey_) {
-      this.viewportResizeListenerKey_ = goog.events.listen(
-          this.viewportSizeMonitor_, goog.events.EventType.RESIZE,
-          this.updateSize, false, this);
-    }
-  }
+  /**
+   * @private
+   * @type {ol.format.IGCZ}
+   */
+  this.altitudeMode_ = options.altitudeMode ?
+      options.altitudeMode : ol.format.IGCZ.NONE;
 
-  this.updateSize();
-  // updateSize calls setSize, so no need to call this.render
-  // ourselves here.
 };
+ol.inherits(ol.format.IGC, ol.format.TextFeature);
 
 
 /**
+ * @const
+ * @type {RegExp}
  * @private
  */
-ol.Map.prototype.handleTileChange_ = function() {
-  this.render();
-};
+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.Map.prototype.handleViewPropertyChanged_ = function() {
-  this.render();
-};
+ol.format.IGC.H_RECORD_RE_ = /^H.([A-Z]{3}).*?:(.*)/;
 
 
 /**
+ * @const
+ * @type {RegExp}
  * @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();
-};
+ol.format.IGC.HFDTE_RECORD_RE_ = /^HFDTE(\d{2})(\d{2})(\d{2})/;
 
 
 /**
- * @param {goog.events.Event} event Event.
+ * A regular expression matching the newline characters `\r\n`, `\r` and `\n`.
+ *
+ * @const
+ * @type {RegExp}
  * @private
  */
-ol.Map.prototype.handleLayerGroupMemberChanged_ = function(event) {
-  goog.asserts.assertInstanceof(event, goog.events.Event,
-      'event should be an Event');
-  this.render();
-};
+ol.format.IGC.NEWLINE_RE_ = /\r\n|\r|\n/;
 
 
 /**
- * @param {ol.ObjectEvent} event Event.
- * @private
+ * Read the feature from the IGC source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ * @api
  */
-ol.Map.prototype.handleLayerGroupPropertyChanged_ = function(event) {
-  goog.asserts.assertInstanceof(event, ol.ObjectEvent,
-      'event should be an ol.ObjectEvent');
-  this.render();
-};
+ol.format.IGC.prototype.readFeature;
 
 
 /**
- * @private
+ * @inheritDoc
  */
-ol.Map.prototype.handleLayerGroupChanged_ = function() {
-  if (this.layerGroupPropertyListenerKeys_) {
-    this.layerGroupPropertyListenerKeys_.forEach(goog.events.unlistenByKey);
-    this.layerGroupPropertyListenerKeys_ = null;
+ol.format.IGC.prototype.readFeatureFromText = function(text, opt_options) {
+  var altitudeMode = this.altitudeMode_;
+  var lines = text.split(ol.format.IGC.NEWLINE_RE_);
+  /** @type {Object.<string, string>} */
+  var properties = {};
+  var flatCoordinates = [];
+  var year = 2000;
+  var month = 0;
+  var day = 1;
+  var lastDateTime = -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 {
+            z = 0;
+          }
+          flatCoordinates.push(z);
+        }
+        var dateTime = Date.UTC(year, month, day, hour, minute, second);
+        // Detect UTC midnight wrap around.
+        if (dateTime < lastDateTime) {
+          dateTime = Date.UTC(year, month, day + 1, hour, minute, second);
+        }
+        flatCoordinates.push(dateTime / 1000);
+        lastDateTime = dateTime;
+      }
+    } 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();
+        }
+      }
+    }
   }
-  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)
-    ];
+  if (flatCoordinates.length === 0) {
+    return null;
   }
-  this.render();
+  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;
 };
 
 
 /**
- * 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.
+ * 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.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;
-};
+ol.format.IGC.prototype.readFeatures;
 
 
 /**
- * @return {boolean} Is rendered.
+ * @inheritDoc
  */
-ol.Map.prototype.isRendered = function() {
-  return !!this.frameState_;
+ol.format.IGC.prototype.readFeaturesFromText = function(text, opt_options) {
+  var feature = this.readFeatureFromText(text, opt_options);
+  if (feature) {
+    return [feature];
+  } else {
+    return [];
+  }
 };
 
 
 /**
- * Requests an immediate render in a synchronous manner.
- * @api stable
+ * Read the projection from the IGC source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
  */
-ol.Map.prototype.renderSync = function() {
-  this.animationDelay_.fire();
-};
+ol.format.IGC.prototype.readProjection;
 
 
 /**
- * Request a map rendering (at the next animation frame).
- * @api stable
+ * Not implemented.
+ * @inheritDoc
  */
-ol.Map.prototype.render = function() {
-  if (!this.animationDelay_.isActive()) {
-    this.animationDelay_.start();
-  }
-};
+ol.format.IGC.prototype.writeFeatureText = function(feature, opt_options) {};
 
 
 /**
- * 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
+ * Not implemented.
+ * @inheritDoc
  */
-ol.Map.prototype.removeControl = function(control) {
-  var controls = this.getControls();
-  goog.asserts.assert(controls !== undefined, 'controls should be defined');
-  return controls.remove(control);
-};
+ol.format.IGC.prototype.writeFeaturesText = function(features, opt_options) {};
 
 
 /**
- * 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
+ * Not implemented.
+ * @inheritDoc
  */
-ol.Map.prototype.removeInteraction = function(interaction) {
-  var interactions = this.getInteractions();
-  goog.asserts.assert(interactions !== undefined,
-      'interactions should be defined');
-  return interactions.remove(interaction);
-};
+ol.format.IGC.prototype.writeGeometryText = function(geometry, opt_options) {};
 
 
 /**
- * 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
+ * Not implemented.
+ * @inheritDoc
  */
-ol.Map.prototype.removeLayer = function(layer) {
-  var layers = this.getLayerGroup().getLayers();
-  return layers.remove(layer);
-};
+ol.format.IGC.prototype.readGeometryFromText = function(text, opt_options) {};
 
+goog.provide('ol.style.IconAnchorUnits');
 
 /**
- * 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
+ * Icon anchor units. One of 'fraction', 'pixels'.
+ * @enum {string}
  */
-ol.Map.prototype.removeOverlay = function(overlay) {
-  var overlays = this.getOverlays();
-  goog.asserts.assert(overlays !== undefined, 'overlays should be defined');
-  return overlays.remove(overlay);
+ol.style.IconAnchorUnits = {
+  FRACTION: 'fraction',
+  PIXELS: 'pixels'
 };
 
+goog.provide('ol.style.IconImage');
+
+goog.require('ol');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.EventTarget');
+goog.require('ol.events.EventType');
+goog.require('ol.ImageState');
+goog.require('ol.style');
+
 
 /**
- * @param {number} time Time.
- * @private
+ * @constructor
+ * @param {Image|HTMLCanvasElement} image Image.
+ * @param {string|undefined} src Src.
+ * @param {ol.Size} size Size.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.ImageState} imageState Image state.
+ * @param {ol.Color} color Color.
+ * @extends {ol.events.EventTarget}
  */
-ol.Map.prototype.renderFrame_ = function(time) {
+ol.style.IconImage = function(image, src, size, crossOrigin, imageState,
+                               color) {
 
-  var i, ii, viewState;
+  ol.events.EventTarget.call(this);
 
-  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: {}
-    });
-  }
+  /**
+   * @private
+   * @type {Image|HTMLCanvasElement}
+   */
+  this.hitDetectionImage_ = null;
 
-  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;
+  /**
+   * @private
+   * @type {Image|HTMLCanvasElement}
+   */
+  this.image_ = !image ? new Image() : image;
 
-    frameState.extent = ol.extent.getForViewAndSize(viewState.center,
-        viewState.resolution, viewState.rotation, frameState.size);
+  if (crossOrigin !== null) {
+    this.image_.crossOrigin = crossOrigin;
   }
 
-  this.frameState_ = frameState;
-  this.renderer_.renderFrame(frameState);
-
-  if (frameState) {
-    if (frameState.animate) {
-      this.render();
-    }
-    Array.prototype.push.apply(
-        this.postRenderFunctions_, frameState.postRenderFunctions);
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.canvas_ = color ?
+      /** @type {HTMLCanvasElement} */ (document.createElement('CANVAS')) :
+      null;
 
-    var idle = this.preRenderFunctions_.length === 0 &&
-        !frameState.viewHints[ol.ViewHint.ANIMATING] &&
-        !frameState.viewHints[ol.ViewHint.INTERACTING] &&
-        !ol.extent.equals(frameState.extent, this.previousExtent_);
+  /**
+   * @private
+   * @type {ol.Color}
+   */
+  this.color_ = color;
 
-    if (idle) {
-      this.dispatchEvent(
-          new ol.MapEvent(ol.MapEventType.MOVEEND, this, frameState));
-      ol.extent.clone(frameState.extent, this.previousExtent_);
-    }
-  }
+  /**
+   * @private
+   * @type {Array.<ol.EventsKey>}
+   */
+  this.imageListenerKeys_ = null;
 
-  this.dispatchEvent(
-      new ol.MapEvent(ol.MapEventType.POSTRENDER, this, frameState));
+  /**
+   * @private
+   * @type {ol.ImageState}
+   */
+  this.imageState_ = imageState;
 
-  goog.async.nextTick(this.handlePostRender, this);
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.size_ = size;
 
-};
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.src_ = src;
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.tainting_ = false;
+  if (this.imageState_ == ol.ImageState.LOADED) {
+    this.determineTainting_();
+  }
 
-/**
- * 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);
 };
+ol.inherits(ol.style.IconImage, ol.events.EventTarget);
 
 
 /**
- * Set the size of this map.
- * @param {ol.Size|undefined} size The size in pixels of the map in the DOM.
- * @observable
- * @api
+ * @param {Image|HTMLCanvasElement} image Image.
+ * @param {string} src Src.
+ * @param {ol.Size} size Size.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.ImageState} imageState Image state.
+ * @param {ol.Color} color Color.
+ * @return {ol.style.IconImage} Icon image.
  */
-ol.Map.prototype.setSize = function(size) {
-  this.set(ol.MapProperty.SIZE, size);
+ol.style.IconImage.get = function(image, src, size, crossOrigin, imageState,
+                                   color) {
+  var iconImageCache = ol.style.iconImageCache;
+  var iconImage = iconImageCache.get(src, crossOrigin, color);
+  if (!iconImage) {
+    iconImage = new ol.style.IconImage(
+        image, src, size, crossOrigin, imageState, color);
+    iconImageCache.set(src, crossOrigin, color, iconImage);
+  }
+  return iconImage;
 };
 
 
 /**
- * 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
+ * @private
  */
-ol.Map.prototype.setTarget = function(target) {
-  this.set(ol.MapProperty.TARGET, target);
+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;
+  }
 };
 
 
 /**
- * Set the view for this map.
- * @param {ol.View} view The view that controls this map.
- * @observable
- * @api stable
+ * @private
  */
-ol.Map.prototype.setView = function(view) {
-  this.set(ol.MapProperty.VIEW, view);
+ol.style.IconImage.prototype.dispatchChangeEvent_ = function() {
+  this.dispatchEvent(ol.events.EventType.CHANGE);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
+ * @private
  */
-ol.Map.prototype.skipFeature = function(feature) {
-  var featureUid = goog.getUid(feature).toString();
-  this.skippedFeatureUids_[featureUid] = true;
-  this.render();
+ol.style.IconImage.prototype.handleImageError_ = function() {
+  this.imageState_ = ol.ImageState.ERROR;
+  this.unlistenImage_();
+  this.dispatchChangeEvent_();
 };
 
 
 /**
- * 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
+ * @private
  */
-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]);
+ol.style.IconImage.prototype.handleImageLoad_ = function() {
+  this.imageState_ = ol.ImageState.LOADED;
+  if (this.size_) {
+    this.image_.width = this.size_[0];
+    this.image_.height = this.size_[1];
   }
+  this.size_ = [this.image_.width, this.image_.height];
+  this.unlistenImage_();
+  this.determineTainting_();
+  this.replaceColor_();
+  this.dispatchChangeEvent_();
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {Image|HTMLCanvasElement} Image or Canvas element.
  */
-ol.Map.prototype.unskipFeature = function(feature) {
-  var featureUid = goog.getUid(feature).toString();
-  delete this.skippedFeatureUids_[featureUid];
-  this.render();
+ol.style.IconImage.prototype.getImage = function(pixelRatio) {
+  return this.canvas_ ? this.canvas_ : this.image_;
 };
 
 
 /**
- * @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, *>}}
+ * @return {ol.ImageState} Image state.
  */
-ol.MapOptionsInternal;
+ol.style.IconImage.prototype.getImageState = function() {
+  return this.imageState_;
+};
 
 
 /**
- * @param {olx.MapOptions} options Map options.
- * @return {ol.MapOptionsInternal} Internal map options.
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {Image|HTMLCanvasElement} Image element.
  */
-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;
+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_;
+};
 
-  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;
-  }
+/**
+ * @return {ol.Size} Image size.
+ */
+ol.style.IconImage.prototype.getSize = function() {
+  return this.size_;
+};
 
-  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();
-  }
+/**
+ * @return {string|undefined} Image src.
+ */
+ol.style.IconImage.prototype.getSrc = function() {
+  return this.src_;
+};
 
-  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;
+/**
+ * Load not yet loaded URI.
+ */
+ol.style.IconImage.prototype.load = function() {
+  if (this.imageState_ == ol.ImageState.IDLE) {
+    this.imageState_ = ol.ImageState.LOADING;
+    this.imageListenerKeys_ = [
+      ol.events.listenOnce(this.image_, ol.events.EventType.ERROR,
+          this.handleImageError_, this),
+      ol.events.listenOnce(this.image_, ol.events.EventType.LOAD,
+          this.handleImageLoad_, this)
+    ];
+    try {
+      this.image_.src = this.src_;
+    } catch (e) {
+      this.handleImageError_();
     }
-  } else {
-    overlays = new ol.Collection();
   }
-
-  return {
-    controls: controls,
-    interactions: interactions,
-    keyboardEventTarget: keyboardEventTarget,
-    logos: logos,
-    overlays: overlays,
-    rendererConstructor: rendererConstructor,
-    values: values
-  };
-
 };
 
 
-ol.proj.common.add();
+/**
+ * @private
+ */
+ol.style.IconImage.prototype.replaceColor_ = function() {
+  if (this.tainting_ || this.color_ === null) {
+    return;
+  }
 
+  this.canvas_.width = this.image_.width;
+  this.canvas_.height = this.image_.height;
 
-if (goog.DEBUG) {
-  (function() {
-    goog.debug.Console.autoInstall();
-    var logger = goog.log.getLogger('ol');
-    logger.setLevel(goog.log.Level.FINEST);
-  })();
-}
+  var ctx = this.canvas_.getContext('2d');
+  ctx.drawImage(this.image_, 0, 0);
 
-goog.provide('ol.Overlay');
-goog.provide('ol.OverlayPositioning');
-goog.provide('ol.OverlayProperty');
-
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.style');
-goog.require('ol.Coordinate');
-goog.require('ol.Map');
-goog.require('ol.MapEventType');
-goog.require('ol.Object');
-goog.require('ol.animation');
-goog.require('ol.dom');
-goog.require('ol.extent');
+  var imgData = ctx.getImageData(0, 0, this.image_.width, this.image_.height);
+  var data = imgData.data;
+  var r = this.color_[0] / 255.0;
+  var g = this.color_[1] / 255.0;
+  var b = this.color_[2] / 255.0;
+
+  for (var i = 0, ii = data.length; i < ii; i += 4) {
+    data[i] *= r;
+    data[i + 1] *= g;
+    data[i + 2] *= b;
+  }
+  ctx.putImageData(imgData, 0, 0);
+};
 
 
 /**
- * @enum {string}
+ * Discards event handlers which listen for load completion or errors.
+ *
+ * @private
  */
-ol.OverlayProperty = {
-  ELEMENT: 'element',
-  MAP: 'map',
-  OFFSET: 'offset',
-  POSITION: 'position',
-  POSITIONING: 'positioning'
+ol.style.IconImage.prototype.unlistenImage_ = function() {
+  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
+  this.imageListenerKeys_ = null;
 };
 
+goog.provide('ol.style.IconOrigin');
 
 /**
- * Overlay position: `'bottom-left'`, `'bottom-center'`,  `'bottom-right'`,
- * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`,
- * `'top-center'`, `'top-right'`
+ * Icon origin. One of 'bottom-left', 'bottom-right', 'top-left', 'top-right'.
  * @enum {string}
- * @api stable
  */
-ol.OverlayPositioning = {
+ol.style.IconOrigin = {
   BOTTOM_LEFT: 'bottom-left',
-  BOTTOM_CENTER: 'bottom-center',
   BOTTOM_RIGHT: 'bottom-right',
-  CENTER_LEFT: 'center-left',
-  CENTER_CENTER: 'center-center',
-  CENTER_RIGHT: 'center-right',
   TOP_LEFT: 'top-left',
-  TOP_CENTER: 'top-center',
   TOP_RIGHT: 'top-right'
 };
 
+goog.provide('ol.style.Icon');
+
+goog.require('ol');
+goog.require('ol.ImageState');
+goog.require('ol.asserts');
+goog.require('ol.color');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.style.IconAnchorUnits');
+goog.require('ol.style.IconImage');
+goog.require('ol.style.IconOrigin');
+goog.require('ol.style.Image');
 
 
 /**
  * @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);
+ * Set icon style for vector features.
  *
  * @constructor
- * @extends {ol.Object}
- * @param {olx.OverlayOptions} options Overlay options.
- * @api stable
+ * @param {olx.style.IconOptions=} opt_options Options.
+ * @extends {ol.style.Image}
+ * @api
  */
-ol.Overlay = function(options) {
+ol.style.Icon = function(opt_options) {
 
-  goog.base(this);
+  var options = opt_options || {};
 
   /**
    * @private
-   * @type {boolean}
+   * @type {Array.<number>}
    */
-  this.insertFirst_ = options.insertFirst !== undefined ?
-      options.insertFirst : true;
+  this.anchor_ = options.anchor !== undefined ? options.anchor : [0.5, 0.5];
 
   /**
    * @private
-   * @type {boolean}
+   * @type {Array.<number>}
    */
-  this.stopEvent_ = options.stopEvent !== undefined ? options.stopEvent : true;
+  this.normalizedAnchor_ = null;
 
   /**
    * @private
-   * @type {Element}
+   * @type {ol.style.IconOrigin}
    */
-  this.element_ = goog.dom.createDom(goog.dom.TagName.DIV, {
-    'class': 'ol-overlay-container'
-  });
-  this.element_.style.position = 'absolute';
+  this.anchorOrigin_ = options.anchorOrigin !== undefined ?
+      options.anchorOrigin : ol.style.IconOrigin.TOP_LEFT;
 
   /**
-   * @protected
-   * @type {boolean}
+   * @private
+   * @type {ol.style.IconAnchorUnits}
    */
-  this.autoPan = options.autoPan !== undefined ? options.autoPan : false;
+  this.anchorXUnits_ = options.anchorXUnits !== undefined ?
+      options.anchorXUnits : ol.style.IconAnchorUnits.FRACTION;
 
   /**
    * @private
-   * @type {olx.animation.PanOptions}
+   * @type {ol.style.IconAnchorUnits}
    */
-  this.autoPanAnimation_ = options.autoPanAnimation !== undefined ?
-      options.autoPanAnimation : /** @type {olx.animation.PanOptions} */ ({});
+  this.anchorYUnits_ = options.anchorYUnits !== undefined ?
+      options.anchorYUnits : ol.style.IconAnchorUnits.FRACTION;
 
   /**
    * @private
-   * @type {number}
+   * @type {?string}
    */
-  this.autoPanMargin_ = options.autoPanMargin !== undefined ?
-      options.autoPanMargin : 20;
+  this.crossOrigin_ =
+      options.crossOrigin !== undefined ? options.crossOrigin : null;
 
   /**
-   * @private
-   * @type {{bottom_: string,
-   *         left_: string,
-   *         right_: string,
-   *         top_: string,
-   *         visible: boolean}}
+   * @type {Image|HTMLCanvasElement}
    */
-  this.rendered_ = {
-    bottom_: '',
-    left_: '',
-    right_: '',
-    top_: '',
-    visible: true
-  };
+  var image = options.img !== undefined ? options.img : null;
 
   /**
-   * @private
-   * @type {goog.events.Key}
+   * @type {ol.Size}
    */
-  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);
+  var imgSize = options.imgSize !== undefined ? options.imgSize : null;
 
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(ol.OverlayProperty.POSITION),
-      this.handlePositionChanged, false, this);
+  /**
+   * @type {string|undefined}
+   */
+  var src = options.src;
 
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(ol.OverlayProperty.POSITIONING),
-      this.handlePositioningChanged, false, this);
+  ol.asserts.assert(!(src !== undefined && image),
+      4); // `image` and `src` cannot be provided at the same time
+  ol.asserts.assert(!image || (image && imgSize),
+      5); // `imgSize` must be set when `image` is provided
 
-  if (options.element !== undefined) {
-    this.setElement(options.element);
+  if ((src === undefined || src.length === 0) && image) {
+    src = image.src || ol.getUid(image).toString();
   }
+  ol.asserts.assert(src !== undefined && src.length > 0,
+      6); // A defined and non-empty `src` or `image` must be provided
 
-  this.setOffset(options.offset !== undefined ? options.offset : [0, 0]);
+  /**
+   * @type {ol.ImageState}
+   */
+  var imageState = options.src !== undefined ?
+      ol.ImageState.IDLE : ol.ImageState.LOADED;
 
-  this.setPositioning(options.positioning !== undefined ?
-      /** @type {ol.OverlayPositioning} */ (options.positioning) :
-      ol.OverlayPositioning.TOP_LEFT);
+  /**
+   * @private
+   * @type {ol.Color}
+   */
+  this.color_ = options.color !== undefined ? ol.color.asArray(options.color) :
+      null;
 
-  if (options.position !== undefined) {
-    this.setPosition(options.position);
-  }
+  /**
+   * @private
+   * @type {ol.style.IconImage}
+   */
+  this.iconImage_ = ol.style.IconImage.get(
+      image, /** @type {string} */ (src), imgSize, this.crossOrigin_, imageState, this.color_);
 
-};
-goog.inherits(ol.Overlay, ol.Object);
+  /**
+   * @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;
 
-/**
- * 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));
-};
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.origin_ = null;
 
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.size_ = options.size !== undefined ? options.size : null;
 
-/**
- * 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));
-};
+  /**
+   * @type {number}
+   */
+  var opacity = options.opacity !== undefined ? options.opacity : 1;
 
+  /**
+   * @type {boolean}
+   */
+  var rotateWithView = options.rotateWithView !== undefined ?
+      options.rotateWithView : false;
 
-/**
- * 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));
-};
+  /**
+   * @type {number}
+   */
+  var rotation = options.rotation !== undefined ? options.rotation : 0;
 
+  /**
+   * @type {number}
+   */
+  var scale = options.scale !== undefined ? options.scale : 1;
 
-/**
- * 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));
-};
+  /**
+   * @type {boolean}
+   */
+  var snapToPixel = options.snapToPixel !== undefined ?
+      options.snapToPixel : true;
 
+  ol.style.Image.call(this, {
+    opacity: opacity,
+    rotation: rotation,
+    scale: scale,
+    snapToPixel: snapToPixel,
+    rotateWithView: rotateWithView
+  });
 
-/**
- * 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));
 };
+ol.inherits(ol.style.Icon, ol.style.Image);
 
 
 /**
- * @protected
+ * Clones the style.
+ * @return {ol.style.Icon} The cloned style.
+ * @api
  */
-ol.Overlay.prototype.handleElementChanged = function() {
-  goog.dom.removeChildren(this.element_);
-  var element = this.getElement();
-  if (element) {
-    goog.dom.append(/** @type {!Node} */ (this.element_), element);
-  }
+ol.style.Icon.prototype.clone = function() {
+  var oldImage = this.getImage(1);
+  var newImage;
+  if (this.iconImage_.getImageState() === ol.ImageState.LOADED) {
+    if (oldImage.tagName.toUpperCase() === 'IMG') {
+      newImage = /** @type {Image} */ (oldImage.cloneNode(true));
+    } else {
+      newImage = /** @type {HTMLCanvasElement} */ (document.createElement('canvas'));
+      var context = newImage.getContext('2d');
+      newImage.width = oldImage.width;
+      newImage.height = oldImage.height;
+      context.drawImage(oldImage, 0, 0);
+    }
+  }
+  return new ol.style.Icon({
+    anchor: this.anchor_.slice(),
+    anchorOrigin: this.anchorOrigin_,
+    anchorXUnits: this.anchorXUnits_,
+    anchorYUnits: this.anchorYUnits_,
+    crossOrigin: this.crossOrigin_,
+    color: (this.color_ && this.color_.slice) ? this.color_.slice() : this.color_ || undefined,
+    img: newImage ? newImage : undefined,
+    imgSize: newImage ? this.iconImage_.getSize().slice() : undefined,
+    src: newImage ? undefined : this.getSrc(),
+    offset: this.offset_.slice(),
+    offsetOrigin: this.offsetOrigin_,
+    size: this.size_ !== null ? this.size_.slice() : undefined,
+    opacity: this.getOpacity(),
+    scale: this.getScale(),
+    snapToPixel: this.getSnapToPixel(),
+    rotation: this.getRotation(),
+    rotateWithView: this.getRotateWithView()
+  });
 };
 
 
 /**
- * @protected
+ * @inheritDoc
+ * @api
  */
-ol.Overlay.prototype.handleMapChanged = function() {
-  if (this.mapPostrenderListenerKey_) {
-    goog.dom.removeNode(this.element_);
-    goog.events.unlistenByKey(this.mapPostrenderListenerKey_);
-    this.mapPostrenderListenerKey_ = null;
+ol.style.Icon.prototype.getAnchor = function() {
+  if (this.normalizedAnchor_) {
+    return this.normalizedAnchor_;
   }
-  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 {
-      goog.dom.append(/** @type {!Node} */ (container), this.element_);
+  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];
     }
   }
-};
-
-
-/**
- * @protected
- */
-ol.Overlay.prototype.render = function() {
-  this.updatePixelPosition();
-};
-
 
-/**
- * @protected
- */
-ol.Overlay.prototype.handleOffsetChanged = function() {
-  this.updatePixelPosition();
+  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_;
 };
 
 
 /**
- * @protected
+ * Get the icon color.
+ * @return {ol.Color} Color.
+ * @api
  */
-ol.Overlay.prototype.handlePositionChanged = function() {
-  this.updatePixelPosition();
-  if (this.get(ol.OverlayProperty.POSITION) !== undefined && this.autoPan) {
-    this.panIntoView_();
-  }
+ol.style.Icon.prototype.getColor = function() {
+  return this.color_;
 };
 
 
 /**
- * @protected
+ * Get the image icon.
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {Image|HTMLCanvasElement} Image or Canvas element.
+ * @override
+ * @api
  */
-ol.Overlay.prototype.handlePositioningChanged = function() {
-  this.updatePixelPosition();
+ol.style.Icon.prototype.getImage = function(pixelRatio) {
+  return this.iconImage_.getImage(pixelRatio);
 };
 
 
 /**
- * Set the DOM element to be associated with this overlay.
- * @param {Element|undefined} element The Element containing the overlay.
- * @observable
- * @api stable
+ * @override
  */
-ol.Overlay.prototype.setElement = function(element) {
-  this.set(ol.OverlayProperty.ELEMENT, element);
+ol.style.Icon.prototype.getImageSize = function() {
+  return this.iconImage_.getSize();
 };
 
 
 /**
- * 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
+ * @override
  */
-ol.Overlay.prototype.setMap = function(map) {
-  this.set(ol.OverlayProperty.MAP, map);
+ol.style.Icon.prototype.getHitDetectionImageSize = function() {
+  return this.getImageSize();
 };
 
 
 /**
- * Set the offset for this overlay.
- * @param {Array.<number>} offset Offset.
- * @observable
- * @api stable
+ * @override
  */
-ol.Overlay.prototype.setOffset = function(offset) {
-  this.set(ol.OverlayProperty.OFFSET, offset);
+ol.style.Icon.prototype.getImageState = function() {
+  return this.iconImage_.getImageState();
 };
 
 
 /**
- * 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
+ * @override
  */
-ol.Overlay.prototype.setPosition = function(position) {
-  this.set(ol.OverlayProperty.POSITION, position);
+ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) {
+  return this.iconImage_.getHitDetectionImage(pixelRatio);
 };
 
 
 /**
- * Pan the map so that the overlay is entirely visible in the current viewport
- * (if necessary).
- * @private
+ * @inheritDoc
+ * @api
  */
-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;
+ol.style.Icon.prototype.getOrigin = function() {
+  if (this.origin_) {
+    return this.origin_;
   }
+  var offset = this.offset_;
 
-  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 (this.offsetOrigin_ != ol.style.IconOrigin.TOP_LEFT) {
+    var size = this.getSize();
+    var iconImageSize = this.iconImage_.getSize();
+    if (!size || !iconImageSize) {
+      return null;
     }
-    if (offsetTop < 0) {
-      // move map up
-      delta[1] = offsetTop - margin;
-    } else if (offsetBottom < 0) {
-      // move map down
-      delta[1] = Math.abs(offsetBottom) + margin;
+    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 (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));
+    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 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;
-    }
-  }
+ * Get the image URL.
+ * @return {string|undefined} Image src.
+ * @api
+ */
+ol.style.Icon.prototype.getSrc = function() {
+  return this.iconImage_.getSrc();
+};
 
-  this.setVisible(true);
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.Icon.prototype.getSize = function() {
+  return !this.size_ ? this.iconImage_.getSize() : this.size_;
 };
 
-goog.provide('ol.control.OverviewMap');
 
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.dom.classlist');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.math.Size');
-goog.require('goog.style');
-goog.require('ol');
-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');
+/**
+ * @override
+ */
+ol.style.Icon.prototype.listenImageChange = function(listener, thisArg) {
+  return ol.events.listen(this.iconImage_, ol.events.EventType.CHANGE,
+      listener, 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.
+ * @override
+ * @api
+ */
+ol.style.Icon.prototype.load = function() {
+  this.iconImage_.load();
+};
+
 
 /**
- * Create a new control with a map acting as an overview map for an other
- * defined map.
+ * @override
+ */
+ol.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) {
+  ol.events.unlisten(this.iconImage_, ol.events.EventType.CHANGE,
+      listener, thisArg);
+};
+
+goog.provide('ol.style.Text');
+
+
+goog.require('ol.style.Fill');
+
+
+/**
+ * @classdesc
+ * Set text style for vector features.
+ *
  * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.OverviewMapOptions=} opt_options OverviewMap options.
+ * @param {olx.style.TextOptions=} opt_options Options.
  * @api
  */
-ol.control.OverviewMap = function(opt_options) {
+ol.style.Text = function(opt_options) {
 
-  var options = opt_options ? opt_options : {};
+  var options = opt_options || {};
 
   /**
-   * @type {boolean}
    * @private
+   * @type {string|undefined}
    */
-  this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;
+  this.font_ = options.font;
 
   /**
    * @private
-   * @type {boolean}
+   * @type {number|undefined}
    */
-  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';
+  this.rotation_ = options.rotation;
 
   /**
    * @private
-   * @type {Node}
+   * @type {boolean|undefined}
    */
-  this.collapseLabel_ = goog.isString(collapseLabel) ?
-      goog.dom.createDom(goog.dom.TagName.SPAN, {}, collapseLabel) :
-      collapseLabel;
-
-  var label = options.label ? options.label : '\u00BB';
+  this.rotateWithView_ = options.rotateWithView;
 
   /**
    * @private
-   * @type {Node}
+   * @type {number|undefined}
    */
-  this.label_ = goog.isString(label) ?
-      goog.dom.createDom(goog.dom.TagName.SPAN, {}, label) :
-      label;
+  this.scale_ = options.scale;
 
-  var activeLabel = (this.collapsible_ && !this.collapsed_) ?
-      this.collapseLabel_ : this.label_;
-  var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
-    'type': 'button',
-    'title': tipLabel
-  }, activeLabel);
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.text_ = options.text;
 
-  goog.events.listen(button, goog.events.EventType.CLICK,
-      this.handleClick_, false, this);
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.textAlign_ = options.textAlign;
 
-  var ovmapDiv = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-overviewmap-map');
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.textBaseline_ = options.textBaseline;
 
   /**
-   * @type {ol.Map}
    * @private
+   * @type {ol.style.Fill}
    */
-  this.ovmap_ = new ol.Map({
-    controls: new ol.Collection(),
-    interactions: new ol.Collection(),
-    target: ovmapDiv,
-    view: options.view
-  });
-  var ovmap = this.ovmap_;
+  this.fill_ = options.fill !== undefined ? options.fill :
+      new ol.style.Fill({color: ol.style.Text.DEFAULT_FILL_COLOR_});
 
-  if (options.layers) {
-    options.layers.forEach(
-        /**
-       * @param {ol.layer.Layer} layer Layer.
-       */
-        function(layer) {
-          ovmap.addLayer(layer);
-        }, this);
-  }
+  /**
+   * @private
+   * @type {ol.style.Stroke}
+   */
+  this.stroke_ = options.stroke !== undefined ? options.stroke : null;
 
-  var box = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-overviewmap-box');
+  /**
+   * @private
+   * @type {number}
+   */
+  this.offsetX_ = options.offsetX !== undefined ? options.offsetX : 0;
 
   /**
-   * @type {ol.Overlay}
    * @private
+   * @type {number}
    */
-  this.boxOverlay_ = new ol.Overlay({
-    position: [0, 0],
-    positioning: ol.OverlayPositioning.BOTTOM_LEFT,
-    element: box
-  });
-  this.ovmap_.addOverlay(this.boxOverlay_);
+  this.offsetY_ = options.offsetY !== undefined ? options.offsetY : 0;
+};
 
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL +
-      (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') +
-      (this.collapsible_ ? '' : ' ol-uncollapsible');
-  var element = goog.dom.createDom(goog.dom.TagName.DIV,
-      cssClasses, ovmapDiv, button);
 
-  var render = options.render ? options.render : ol.control.OverviewMap.render;
+/**
+ * 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';
 
-  goog.base(this, {
-    element: element,
-    render: render,
-    target: options.target
+
+/**
+ * Clones the style.
+ * @return {ol.style.Text} The cloned style.
+ * @api
+ */
+ol.style.Text.prototype.clone = function() {
+  return new ol.style.Text({
+    font: this.getFont(),
+    rotation: this.getRotation(),
+    rotateWithView: this.getRotateWithView(),
+    scale: this.getScale(),
+    text: this.getText(),
+    textAlign: this.getTextAlign(),
+    textBaseline: this.getTextBaseline(),
+    fill: this.getFill() ? this.getFill().clone() : undefined,
+    stroke: this.getStroke() ? this.getStroke().clone() : undefined,
+    offsetX: this.getOffsetX(),
+    offsetY: this.getOffsetY()
   });
 };
-goog.inherits(ol.control.OverviewMap, ol.control.Control);
 
 
 /**
- * @inheritDoc
+ * Get the font name.
+ * @return {string|undefined} Font.
  * @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_();
-      }
-    }
-  }
+ol.style.Text.prototype.getFont = function() {
+  return this.font_;
 };
 
 
 /**
- * Handle map property changes.  This only deals with changes to the map's view.
- * @param {ol.ObjectEvent} event The propertychange event.
- * @private
+ * Get the x-offset for the text.
+ * @return {number} Horizontal text offset.
+ * @api
  */
-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);
-  }
+ol.style.Text.prototype.getOffsetX = function() {
+  return this.offsetX_;
 };
 
 
 /**
- * Register listeners for view property changes.
- * @param {ol.View} view The view.
- * @private
+ * Get the y-offset for the text.
+ * @return {number} Vertical text offset.
+ * @api
  */
-ol.control.OverviewMap.prototype.bindView_ = function(view) {
-  goog.events.listen(view,
-      ol.Object.getChangeEventType(ol.ViewProperty.ROTATION),
-      this.handleRotationChanged_, false, this);
+ol.style.Text.prototype.getOffsetY = function() {
+  return this.offsetY_;
 };
 
 
 /**
- * Unregister listeners for view property changes.
- * @param {ol.View} view The view.
- * @private
+ * Get the fill style for the text.
+ * @return {ol.style.Fill} Fill style.
+ * @api
  */
-ol.control.OverviewMap.prototype.unbindView_ = function(view) {
-  goog.events.unlisten(view,
-      ol.Object.getChangeEventType(ol.ViewProperty.ROTATION),
-      this.handleRotationChanged_, false, this);
+ol.style.Text.prototype.getFill = function() {
+  return this.fill_;
 };
 
 
 /**
- * Handle rotation changes to the main map.
- * TODO: This should rotate the extent rectrangle instead of the
- * overview map's view.
- * @private
+ * Determine whether the text rotates with the map.
+ * @return {boolean|undefined} Rotate with map.
+ * @api
  */
-ol.control.OverviewMap.prototype.handleRotationChanged_ = function() {
-  this.ovmap_.getView().setRotation(this.getMap().getView().getRotation());
+ol.style.Text.prototype.getRotateWithView = function() {
+  return this.rotateWithView_;
 };
 
 
 /**
- * Update the overview map element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.OverviewMap}
+ * Get the text rotation.
+ * @return {number|undefined} Rotation.
  * @api
  */
-ol.control.OverviewMap.render = function(mapEvent) {
-  this.validateExtent_();
-  this.updateBox_();
+ol.style.Text.prototype.getRotation = function() {
+  return this.rotation_;
 };
 
 
 /**
- * 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
+ * Get the text scale.
+ * @return {number|undefined} Scale.
+ * @api
  */
-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');
+ol.style.Text.prototype.getScale = function() {
+  return this.scale_;
+};
 
-  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]));
+/**
+ * Get the stroke style for the text.
+ * @return {ol.style.Stroke} Stroke style.
+ * @api
+ */
+ol.style.Text.prototype.getStroke = function() {
+  return this.stroke_;
+};
 
-  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_();
-  }
+/**
+ * Get the text to be rendered.
+ * @return {string|undefined} Text.
+ * @api
+ */
+ol.style.Text.prototype.getText = function() {
+  return this.text_;
 };
 
 
 /**
- * Reset the overview map extent to half calculated min and max ratio times
- * the extent of the main map.
- * @private
+ * Get the text alignment.
+ * @return {string|undefined} Text align.
+ * @api
  */
-ol.control.OverviewMap.prototype.resetExtent_ = function() {
-  if (ol.OVERVIEWMAP_MAX_RATIO === 0 || ol.OVERVIEWMAP_MIN_RATIO === 0) {
-    return;
-  }
+ol.style.Text.prototype.getTextAlign = function() {
+  return this.textAlign_;
+};
 
-  var map = this.getMap();
-  var ovmap = this.ovmap_;
 
-  var mapSize = map.getSize();
-  goog.asserts.assertArray(mapSize, 'mapSize should be an array');
+/**
+ * Get the text baseline.
+ * @return {string|undefined} Text baseline.
+ * @api
+ */
+ol.style.Text.prototype.getTextBaseline = function() {
+  return this.textBaseline_;
+};
 
-  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');
+/**
+ * Set the font.
+ *
+ * @param {string|undefined} font Font.
+ * @api
+ */
+ol.style.Text.prototype.setFont = function(font) {
+  this.font_ = font;
+};
 
-  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 x offset.
+ *
+ * @param {number} offsetX Horizontal text offset.
+ * @api
+ */
+ol.style.Text.prototype.setOffsetX = function(offsetX) {
+  this.offsetX_ = offsetX;
 };
 
 
 /**
- * Set the center of the overview map to the map center without changing its
- * resolution.
- * @private
+ * Set the y offset.
+ *
+ * @param {number} offsetY Vertical text offset.
+ * @api
  */
-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');
+ol.style.Text.prototype.setOffsetY = function(offsetY) {
+  this.offsetY_ = offsetY;
+};
 
-  var ovview = ovmap.getView();
-  goog.asserts.assert(ovview, 'ovview should be defined');
 
-  ovview.setCenter(view.getCenter());
+/**
+ * Set the fill.
+ *
+ * @param {ol.style.Fill} fill Fill style.
+ * @api
+ */
+ol.style.Text.prototype.setFill = function(fill) {
+  this.fill_ = fill;
 };
 
 
 /**
- * Update the box using the main map extent
- * @private
+ * Set the rotation.
+ *
+ * @param {number|undefined} rotation Rotation.
+ * @api
  */
-ol.control.OverviewMap.prototype.updateBox_ = function() {
-  var map = this.getMap();
-  var ovmap = this.ovmap_;
-
-  if (!map.isRendered() || !ovmap.isRendered()) {
-    return;
-  }
+ol.style.Text.prototype.setRotation = function(rotation) {
+  this.rotation_ = rotation;
+};
 
-  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');
+/**
+ * Set the scale.
+ *
+ * @param {number|undefined} scale Scale.
+ * @api
+ */
+ol.style.Text.prototype.setScale = function(scale) {
+  this.scale_ = scale;
+};
 
-  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');
+/**
+ * Set the stroke.
+ *
+ * @param {ol.style.Stroke} stroke Stroke style.
+ * @api
+ */
+ol.style.Text.prototype.setStroke = function(stroke) {
+  this.stroke_ = stroke;
+};
 
-  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 the text.
+ *
+ * @param {string|undefined} text Text.
+ * @api
+ */
+ol.style.Text.prototype.setText = function(text) {
+  this.text_ = text;
+};
 
-  // 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));
-  }
+/**
+ * Set the text alignment.
+ *
+ * @param {string|undefined} textAlign Text align.
+ * @api
+ */
+ol.style.Text.prototype.setTextAlign = function(textAlign) {
+  this.textAlign_ = textAlign;
 };
 
 
 /**
- * @param {number} rotation Target rotation.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {ol.Coordinate|undefined} Coordinate for rotation and center anchor.
- * @private
+ * Set the text baseline.
+ *
+ * @param {string|undefined} textBaseline Text baseline.
+ * @api
  */
-ol.control.OverviewMap.prototype.calculateCoordinateRotate_ = function(
-    rotation, coordinate) {
-  var coordinateRotate;
+ol.style.Text.prototype.setTextBaseline = function(textBaseline) {
+  this.textBaseline_ = textBaseline;
+};
 
-  var map = this.getMap();
-  var view = map.getView();
-  goog.asserts.assert(view, 'view should be defined');
+// FIXME http://earth.google.com/kml/1.0 namespace?
+// FIXME why does node.getAttribute return an unknown type?
+// FIXME serialize arbitrary feature properties
+// FIXME don't parse style if extractStyles is false
 
-  var currentCenter = view.getCenter();
+goog.provide('ol.format.KML');
 
-  if (currentCenter) {
-    coordinateRotate = [
-      coordinate[0] - currentCenter[0],
-      coordinate[1] - currentCenter[1]
-    ];
-    ol.coordinate.rotate(coordinateRotate, rotation);
-    ol.coordinate.add(coordinateRotate, currentCenter);
-  }
-  return coordinateRotate;
-};
+goog.require('ol');
+goog.require('ol.Feature');
+goog.require('ol.array');
+goog.require('ol.asserts');
+goog.require('ol.color');
+goog.require('ol.format.Feature');
+goog.require('ol.format.XMLFeature');
+goog.require('ol.format.XSD');
+goog.require('ol.geom.GeometryCollection');
+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.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.Stroke');
+goog.require('ol.style.Style');
+goog.require('ol.style.Text');
+goog.require('ol.xml');
 
 
 /**
- * @param {goog.events.BrowserEvent} event The event to handle
- * @private
+ * @classdesc
+ * Feature format for reading and writing data in the KML format.
+ *
+ * Note that the KML format uses the URL() constructor. Older browsers such as IE
+ * which do not support this will need a URL polyfill to be loaded before use.
+ *
+ * @constructor
+ * @extends {ol.format.XMLFeature}
+ * @param {olx.format.KMLOptions=} opt_options Options.
+ * @api
  */
-ol.control.OverviewMap.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  this.handleToggle_();
-};
+ol.format.KML = function(opt_options) {
 
+  var options = opt_options ? opt_options : {};
 
-/**
- * @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_;
+  ol.format.XMLFeature.call(this);
 
-  // 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);
+  if (!ol.format.KML.DEFAULT_STYLE_ARRAY_) {
+    ol.format.KML.createStyleDefaults_();
   }
-};
 
+  /**
+   * @inheritDoc
+   */
+  this.defaultDataProjection = ol.proj.get('EPSG:4326');
 
-/**
- * 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_;
-};
+  /**
+   * @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;
 
-/**
- * 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_();
-  }
-};
+  /**
+   * @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;
 
-/**
- * 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_();
 };
+ol.inherits(ol.format.KML, ol.format.XMLFeature);
 
 
 /**
- * Determine if the overview map is collapsed.
- * @return {boolean} The overview map is collapsed.
- * @api stable
+ * @const
+ * @type {Array.<string>}
+ * @private
  */
-ol.control.OverviewMap.prototype.getCollapsed = function() {
-  return this.collapsed_;
-};
-
-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.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.math');
-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.proj');
-goog.require('ol.proj.METERS_PER_UNIT');
-goog.require('ol.proj.Units');
-goog.require('ol.sphere.NORMAL');
+ol.format.KML.GX_NAMESPACE_URIS_ = [
+  'http://www.google.com/kml/ext/2.2'
+];
 
 
 /**
- * @enum {string}
+ * @const
+ * @type {Array.<string>}
+ * @private
  */
-ol.control.ScaleLineProperty = {
-  UNITS: 'units'
-};
+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'
+];
 
 
 /**
- * Units for the scale line. Supported values are `'degrees'`, `'imperial'`,
- * `'nautical'`, `'metric'`, `'us'`.
- * @enum {string}
- * @api stable
+ * @const
+ * @type {string}
+ * @private
  */
-ol.control.ScaleLineUnits = {
-  DEGREES: 'degrees',
-  IMPERIAL: 'imperial',
-  NAUTICAL: 'nautical',
-  METRIC: 'metric',
-  US: 'us'
-};
-
+ol.format.KML.SCHEMA_LOCATION_ = 'http://www.opengis.net/kml/2.2 ' +
+    'https://developers.google.com/kml/schema/kml22gx.xsd';
 
 
 /**
- * @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
+ * @return {Array.<ol.style.Style>} Default style.
+ * @private
  */
-ol.control.ScaleLine = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
+ol.format.KML.createStyleDefaults_ = function() {
+  /**
+   * @const
+   * @type {ol.Color}
+   * @private
+   */
+  ol.format.KML.DEFAULT_COLOR_ = [255, 255, 255, 1];
 
-  var className = options.className ? options.className : 'ol-scale-line';
+  /**
+   * @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
-   * @type {Element}
    */
-  this.innerElement_ = goog.dom.createDom(goog.dom.TagName.DIV,
-      className + '-inner');
+  ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_ = [20, 2]; // FIXME maybe [8, 32] ?
 
   /**
+   * @const
+   * @type {ol.style.IconAnchorUnits}
    * @private
-   * @type {Element}
    */
-  this.element_ = goog.dom.createDom(goog.dom.TagName.DIV,
-      className + ' ' + ol.css.CLASS_UNSELECTABLE, this.innerElement_);
+  ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_ =
+      ol.style.IconAnchorUnits.PIXELS;
 
   /**
+   * @const
+   * @type {ol.style.IconAnchorUnits}
    * @private
-   * @type {?olx.ViewState}
    */
-  this.viewState_ = null;
+  ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_ =
+      ol.style.IconAnchorUnits.PIXELS;
 
   /**
+   * @const
+   * @type {ol.Size}
    * @private
-   * @type {number}
    */
-  this.minWidth_ = options.minWidth !== undefined ? options.minWidth : 64;
+  ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_ = [64, 64];
 
   /**
+   * @const
+   * @type {string}
    * @private
-   * @type {boolean}
    */
-  this.renderedVisible_ = false;
+  ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ =
+      'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png';
 
   /**
+   * @const
+   * @type {number}
    * @private
-   * @type {number|undefined}
    */
-  this.renderedWidth_ = undefined;
+  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 {string}
+   * @private
    */
-  this.renderedHTML_ = '';
+  ol.format.KML.DEFAULT_NO_IMAGE_STYLE_ = 'NO_IMAGE';
 
   /**
+   * @const
+   * @type {ol.style.Stroke}
    * @private
-   * @type {?ol.TransformFunction}
    */
-  this.toEPSG4326_ = null;
+  ol.format.KML.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
+    color: ol.format.KML.DEFAULT_COLOR_,
+    width: 1
+  });
 
-  var render = options.render ? options.render : ol.control.ScaleLine.render;
+  /**
+   * @const
+   * @type {ol.style.Stroke}
+   * @private
+   */
+  ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_ = new ol.style.Stroke({
+    color: [51, 51, 51, 1],
+    width: 2
+  });
 
-  goog.base(this, {
-    element: this.element_,
-    render: render,
-    target: options.target
+  /**
+   * @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
   });
 
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(ol.control.ScaleLineProperty.UNITS),
-      this.handleUnitsChanged_, false, this);
+  /**
+   * @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
+  });
 
-  this.setUnits(/** @type {ol.control.ScaleLineUnits} */ (options.units) ||
-      ol.control.ScaleLineUnits.METRIC);
+  /**
+   * @const
+   * @type {Array.<ol.style.Style>}
+   * @private
+   */
+  ol.format.KML.DEFAULT_STYLE_ARRAY_ = [ol.format.KML.DEFAULT_STYLE_];
 
+  return ol.format.KML.DEFAULT_STYLE_ARRAY_;
 };
-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
+ * @type {Object.<string, ol.style.IconAnchorUnits>}
+ * @private
  */
-ol.control.ScaleLine.prototype.getUnits = function() {
-  return /** @type {ol.control.ScaleLineUnits|undefined} */ (
-      this.get(ol.control.ScaleLineProperty.UNITS));
+ol.format.KML.ICON_ANCHOR_UNITS_MAP_ = {
+  'fraction': ol.style.IconAnchorUnits.FRACTION,
+  'pixels': ol.style.IconAnchorUnits.PIXELS,
+  'insetPixels': ol.style.IconAnchorUnits.PIXELS
 };
 
 
 /**
- * Update the scale line element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.ScaleLine}
- * @api
+ * @param {ol.style.Style|undefined} foundStyle Style.
+ * @param {string} name Name.
+ * @return {ol.style.Style} style Style.
+ * @private
  */
-ol.control.ScaleLine.render = function(mapEvent) {
-  var frameState = mapEvent.frameState;
-  if (!frameState) {
-    this.viewState_ = null;
+ol.format.KML.createNameStyleFunction_ = function(foundStyle, name) {
+  var textStyle = null;
+  var textOffset = [0, 0];
+  var textAlign = 'start';
+  if (foundStyle.getImage()) {
+    var imageSize = foundStyle.getImage().getImageSize();
+    if (imageSize === null) {
+      imageSize = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_;
+    }
+    if (imageSize.length == 2) {
+      var imageScale = foundStyle.getImage().getScale();
+      // Offset the label to be centered to the right of the icon, if there is
+      // one.
+      textOffset[0] = imageScale * imageSize[0] / 2;
+      textOffset[1] = -imageScale * imageSize[1] / 2;
+      textAlign = 'left';
+    }
+  }
+  if (foundStyle.getText() !== null) {
+    // clone the text style, customizing it with name, alignments and offset.
+    // Note that kml does not support many text options that OpenLayers does (rotation, textBaseline).
+    var foundText = foundStyle.getText();
+    textStyle = foundText.clone();
+    textStyle.setFont(foundText.getFont() || ol.format.KML.DEFAULT_TEXT_STYLE_.getFont());
+    textStyle.setScale(foundText.getScale() || ol.format.KML.DEFAULT_TEXT_STYLE_.getScale());
+    textStyle.setFill(foundText.getFill() || ol.format.KML.DEFAULT_TEXT_STYLE_.getFill());
+    textStyle.setStroke(foundText.getStroke() || ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_);
   } else {
-    this.viewState_ = frameState.viewState;
+    textStyle = ol.format.KML.DEFAULT_TEXT_STYLE_.clone();
   }
-  this.updateElement_();
+  textStyle.setText(name);
+  textStyle.setOffsetX(textOffset[0]);
+  textStyle.setOffsetY(textOffset[1]);
+  textStyle.setTextAlign(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.control.ScaleLine.prototype.handleUnitsChanged_ = function() {
-  this.updateElement_();
+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;
+        var name = '';
+        if (drawName) {
+          if (this.getGeometry()) {
+            drawName = (this.getGeometry().getType() ===
+                        ol.geom.GeometryType.POINT);
+          }
+        }
+
+        if (drawName) {
+          name = /** @type {string} */ (this.get('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;
+      });
 };
 
 
 /**
- * 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
+ * @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.control.ScaleLine.prototype.setUnits = function(units) {
-  this.set(ol.control.ScaleLineProperty.UNITS, units);
+ol.format.KML.findStyle_ = function(styleValue, defaultStyle, sharedStyles) {
+  if (Array.isArray(styleValue)) {
+    return styleValue;
+  } else if (typeof styleValue === 'string') {
+    // 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.control.ScaleLine.prototype.updateElement_ = function() {
-  var viewState = this.viewState_;
+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
+    ];
 
-  if (!viewState) {
-    if (this.renderedVisible_) {
-      goog.style.setElementShown(this.element_, false);
-      this.renderedVisible_ = false;
-    }
-    return;
+  } else {
+    return undefined;
   }
+};
 
-  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(goog.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(goog.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;
+/**
+ * @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} URI.
+ */
+ol.format.KML.readURI_ = function(node) {
+  var s = ol.xml.getAllTextContent(node, false).trim();
+  if (node.baseURI && node.baseURI !== 'about:blank') {
+    var url = new URL(s, node.baseURI);
+    return url.href;
   } else {
-    this.toEPSG4326_ = null;
+    return s;
   }
+};
 
-  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;
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {ol.KMLVec2_} Vec2.
+ */
+ol.format.KML.readVec2_ = function(node) {
+  var xunits = node.getAttribute('xunits');
+  var yunits = node.getAttribute('yunits');
+  var origin;
+  if (xunits !== 'insetPixels') {
+    if (yunits !== 'insetPixels') {
+      origin = ol.style.IconOrigin.BOTTOM_LEFT;
     } else {
-      suffix = 'mi';
-      pointResolution /= 1609.3472;
+      origin = ol.style.IconOrigin.TOP_LEFT;
     }
   } 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;
+    if (yunits !== 'insetPixels') {
+      origin = ol.style.IconOrigin.BOTTOM_RIGHT;
+    } else {
+      origin = ol.style.IconOrigin.TOP_RIGHT;
     }
-    ++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;
   }
-
+  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],
+    origin: origin
+  };
 };
 
-// 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>
- *
+ * @param {Node} node Node.
+ * @private
+ * @return {number|undefined} Scale.
  */
-
-goog.provide('goog.events.EventHandler');
-
-goog.require('goog.Disposable');
-goog.require('goog.events');
-goog.require('goog.object');
-
-goog.forwardDeclare('goog.events.EventWrapper');
-
+ol.format.KML.readScale_ = function(node) {
+  return ol.format.XSD.readDecimal(node);
+};
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<ol.style.Style>|string|undefined} StyleMap.
  */
-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_ = {};
+ol.format.KML.readStyleMapValue_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(undefined,
+      ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack);
 };
-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
+ /**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
  * @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;
-    }
+ol.format.KML.IconStyleParser_ = function(node, objectStack) {
+  // 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]);
+  var IconObject = 'Icon' in object ? object['Icon'] : {};
+  var drawIcon = (!('Icon' in object) || Object.keys(IconObject).length > 0);
+  var src;
+  var href = /** @type {string|undefined} */
+      (IconObject['href']);
+  if (href) {
+    src = href;
+  } else if (drawIcon) {
+    src = ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_;
+  }
+  var anchor, anchorXUnits, anchorYUnits;
+  var anchorOrigin = ol.style.IconOrigin.BOTTOM_LEFT;
+  var hotSpot = /** @type {ol.KMLVec2_|undefined} */
+      (object['hotSpot']);
+  if (hotSpot) {
+    anchor = [hotSpot.x, hotSpot.y];
+    anchorXUnits = hotSpot.xunits;
+    anchorYUnits = hotSpot.yunits;
+    anchorOrigin = hotSpot.origin;
+  } 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 key = listenerObj.key;
-    this.keys_[key] = listenerObj;
+  var offset;
+  var x = /** @type {number|undefined} */
+      (IconObject['x']);
+  var y = /** @type {number|undefined} */
+      (IconObject['y']);
+  if (x !== undefined && y !== undefined) {
+    offset = [x, y];
   }
 
-  return this;
-};
+  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);
+  }
 
-/**
- * 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 scale = /** @type {number|undefined} */
+      (object['scale']);
+
+  if (drawIcon) {
+    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 key = listenerObj.key;
-    this.keys_[key] = listenerObj;
+    var imageStyle = new ol.style.Icon({
+      anchor: anchor,
+      anchorOrigin: anchorOrigin,
+      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;
+  } else {
+    // handle the case when we explicitly want to draw no icon.
+    styleObject['imageStyle'] = ol.format.KML.DEFAULT_NO_IMAGE_STYLE_;
   }
-
-  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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-goog.events.EventHandler.prototype.listenWithWrapper = function(
-    src, wrapper, listener, opt_capt) {
-  // TODO(mknichel): Remove the opt_scope from this function and then
-  // templatize it.
-  return this.listenWithWrapper_(src, wrapper, listener, opt_capt);
+ol.format.KML.LabelStyleParser_ = function(node, objectStack) {
+  // FIXME colorMode
+  var object = ol.xml.pushParseAndPop(
+      {}, ol.format.KML.LABEL_STYLE_PARSERS_, node, objectStack);
+  if (!object) {
+    return;
+  }
+  var styleObject = objectStack[objectStack.length - 1];
+  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;
 };
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-goog.events.EventHandler.prototype.listenWithWrapperAndScope = function(
-    src, wrapper, listener, capture, scope) {
-  // TODO(mknichel): Deprecate this function.
-  return this.listenWithWrapper_(src, wrapper, listener, capture, scope);
+ol.format.KML.LineStyleParser_ = function(node, objectStack) {
+  // 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];
+  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;
 };
 
 
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
  * @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;
+ol.format.KML.PolyStyleParser_ = function(node, objectStack) {
+  // FIXME colorMode
+  var object = ol.xml.pushParseAndPop(
+      {}, ol.format.KML.POLY_STYLE_PARSERS_, node, objectStack);
+  if (!object) {
+    return;
+  }
+  var styleObject = objectStack[objectStack.length - 1];
+  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;
+  }
 };
 
 
 /**
- * @return {number} Number of listeners registered by this handler.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>} LinearRing flat coordinates.
  */
-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;
+ol.format.KML.readFlatLinearRing_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(null,
+      ol.format.KML.FLAT_LINEAR_RING_PARSERS_, node, objectStack);
 };
 
 
 /**
- * Unlistens on an event.
- * @param {goog.events.ListenableType} src Event source.
- * @param {string|Array<string>|
- *     !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
- *     type Event type or array of event types to unlisten to.
- * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
- *     Optional callback function to be used as the listener or an object with
- *     handleEvent function.
- * @param {boolean=} opt_capture Optional whether to use capture phase.
- * @param {Object=} opt_scope Object in whose scope to call the listener.
- * @return {!goog.events.EventHandler} This object, allowing for chaining of
- *     calls.
- * @template EVENTOBJ
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-goog.events.EventHandler.prototype.unlisten = function(src, type, opt_fn,
-                                                       opt_capture,
-                                                       opt_scope) {
-  if (goog.isArray(type)) {
-    for (var i = 0; i < type.length; i++) {
-      this.unlisten(src, type[i], opt_fn, opt_capture, opt_scope);
-    }
+ol.format.KML.gxCoordParser_ = function(node, objectStack) {
+  var gxTrackObject = /** @type {ol.KMLGxTrackObject_} */
+      (objectStack[objectStack.length - 1]);
+  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 {
-    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];
-    }
+    flatCoordinates.push(0, 0, 0, 0);
   }
-
-  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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.MultiLineString|undefined} MultiLineString.
  */
-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;
+ol.format.KML.readGxMultiTrack_ = function(node, objectStack) {
+  var lineStrings = ol.xml.pushParseAndPop([],
+      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;
 };
 
 
 /**
- * Unlistens to all events.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.LineString|undefined} LineString.
  */
-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_ = {};
+ol.format.KML.readGxTrack_ = function(node, objectStack) {
+  var gxTrackObject = ol.xml.pushParseAndPop(
+      /** @type {ol.KMLGxTrackObject_} */ ({
+        flatCoordinates: [],
+        whens: []
+      }), ol.format.KML.GX_TRACK_PARSERS_, node, objectStack);
+  if (!gxTrackObject) {
+    return undefined;
+  }
+  var flatCoordinates = gxTrackObject.flatCoordinates;
+  var whens = gxTrackObject.whens;
+  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;
 };
 
 
 /**
- * Disposes of this EventHandler and removes all listeners that it registered.
- * @override
- * @protected
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object} Icon object.
  */
-goog.events.EventHandler.prototype.disposeInternal = function() {
-  goog.events.EventHandler.superClass_.disposeInternal.call(this);
-  this.removeAll();
+ol.format.KML.readIcon_ = function(node, objectStack) {
+  var iconObject = ol.xml.pushParseAndPop(
+      {}, ol.format.KML.ICON_PARSERS_, node, objectStack);
+  if (iconObject) {
+    return iconObject;
+  } else {
+    return null;
+  }
 };
 
 
 /**
- * Default event handler
- * @param {goog.events.Event} e Event object.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>} Flat coordinates.
  */
-goog.events.EventHandler.prototype.handleEvent = function(e) {
-  throw Error('EventHandler.handleEvent not implemented');
+ol.format.KML.readFlatCoordinatesFromNode_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(null,
+      ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack);
 };
 
-// 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.
- *
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.LineString|undefined} LineString.
  */
-
-goog.provide('goog.style.bidi');
-
-goog.require('goog.dom');
-goog.require('goog.style');
-goog.require('goog.userAgent');
+ol.format.KML.readLineString_ = function(node, objectStack) {
+  var properties = ol.xml.pushParseAndPop({},
+      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;
+  }
+};
 
 
 /**
- * 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).
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Polygon|undefined} Polygon.
  */
-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;
-    }
+ol.format.KML.readLinearRing_ = function(node, objectStack) {
+  var properties = ol.xml.pushParseAndPop({},
+      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;
   }
-  // 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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Geometry} Geometry.
  */
-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;
+ol.format.KML.readMultiGeometry_ = function(node, objectStack) {
+  var geometries = ol.xml.pushParseAndPop([],
+      ol.format.KML.MULTI_GEOMETRY_PARSERS_, node, objectStack);
+  if (!geometries) {
+    return null;
   }
-
-  // Just give up in this case.
-  if (!bestParent) {
-    return offsetLeftForReal;
+  if (geometries.length === 0) {
+    return new ol.geom.GeometryCollection(geometries);
   }
-
-  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;
+  /** @type {ol.geom.Geometry} */
+  var multiGeometry;
+  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 (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;
+  if (homogeneous) {
+    var layout;
+    var flatCoordinates;
+    if (type == ol.geom.GeometryType.POINT) {
+      var point = geometries[0];
+      layout = point.getLayout();
+      flatCoordinates = point.getFlatCoordinates();
+      for (i = 1, ii = geometries.length; i < ii; ++i) {
+        geometry = geometries[i];
+        ol.array.extend(flatCoordinates, geometry.getFlatCoordinates());
+      }
+      multiGeometry = new ol.geom.MultiPoint(null);
+      multiGeometry.setFlatCoordinates(layout, flatCoordinates);
+      ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries);
+    } else if (type == ol.geom.GeometryType.LINE_STRING) {
+      multiGeometry = new ol.geom.MultiLineString(null);
+      multiGeometry.setLineStrings(geometries);
+      ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries);
+    } else if (type == ol.geom.GeometryType.POLYGON) {
+      multiGeometry = new ol.geom.MultiPolygon(null);
+      multiGeometry.setPolygons(geometries);
+      ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries);
+    } else if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
+      multiGeometry = new ol.geom.GeometryCollection(geometries);
+    } else {
+      ol.asserts.assert(false, 37); // Unknown geometry type found
+    }
+  } else {
+    multiGeometry = new ol.geom.GeometryCollection(geometries);
   }
-
-  return offsetLeftForReal;
+  return /** @type {ol.geom.Geometry} */ (multiGeometry);
 };
 
 
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Point|undefined} Point.
  */
-goog.style.bidi.setScrollOffset = function(element, offsetStart) {
-  offsetStart = Math.max(offsetStart, 0);
-  // In LTR and in "mirrored" browser RTL (such as IE), we set scrollLeft to
-  // the number of pixels to scroll.
-  // Otherwise, in RTL, we need to account for different browser behavior.
-  if (!goog.style.isRightToLeft(element)) {
-    element.scrollLeft = offsetStart;
-  } else if (goog.userAgent.GECKO) {
-    // Negative scroll-left positions in RTL.
-    element.scrollLeft = -offsetStart;
-  } else if (!(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) {
-    // Take the current scrollLeft value and move to the right by the
-    // offsetStart to get to the left edge of the element, and then by
-    // the clientWidth of the element to get to the right edge.
-    element.scrollLeft =
-        element.scrollWidth - offsetStart - element.clientWidth;
+ol.format.KML.readPoint_ = function(node, objectStack) {
+  var properties = ol.xml.pushParseAndPop({},
+      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);
+    point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
+    point.setProperties(properties);
+    return point;
   } else {
-    element.scrollLeft = offsetStart;
+    return undefined;
   }
 };
 
 
 /**
- * Sets the element's left style attribute in LTR or right style attribute in
- * RTL.  Also clears the left attribute in RTL and the right attribute in LTR.
- * @param {Element} elem The element to position.
- * @param {number} left The left position in LTR; will be set as right in RTL.
- * @param {?number} top The top position.  If null only the left/right is set.
- * @param {boolean} isRtl Whether we are in RTL mode.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Polygon|undefined} Polygon.
  */
-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 = '';
+ol.format.KML.readPolygon_ = function(node, objectStack) {
+  var properties = ol.xml.pushParseAndPop(/** @type {Object<string,*>} */ ({}),
+      ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node,
+      objectStack);
+  var flatLinearRings = ol.xml.pushParseAndPop([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) {
+      ol.array.extend(flatCoordinates, flatLinearRings[i]);
+      ends.push(flatCoordinates.length);
+    }
+    polygon.setFlatCoordinates(
+        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
+    polygon.setProperties(properties);
+    return polygon;
   } else {
-    elem.style.left = left + 'px';
-    elem.style.right = '';
+    return undefined;
   }
 };
 
-// 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
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<ol.style.Style>} Style.
  */
-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);
+ol.format.KML.readStyle_ = function(node, objectStack) {
+  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_);
+  if (imageStyle == ol.format.KML.DEFAULT_NO_IMAGE_STYLE_) {
+    imageStyle = undefined;
+  }
+  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
+  })];
 };
-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}
+ * 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 A multi-geometry.
+ * @param {Array.<ol.geom.Geometry>} geometries List of geometries.
  * @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;
+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);
   }
 };
 
 
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-goog.fx.Dragger.prototype.enableRightPositioningForRtl =
-    function(useRightPositioningForRtl) {
-  this.useRightPositioningForRtl_ = useRightPositioningForRtl;
+ol.format.KML.DataParser_ = function(node, objectStack) {
+  var name = node.getAttribute('name');
+  ol.xml.parseNode(ol.format.KML.DATA_PARSERS_, node, objectStack);
+  var featureObject =
+    /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  if (name !== null) {
+    featureObject[name] = featureObject.value;
+  } else if (featureObject.displayName !== null) {
+    featureObject[featureObject.displayName] = featureObject.value;
+  }
 };
 
 
 /**
- * Returns the event handler, intended for subclass use.
- * @return {!goog.events.EventHandler<T>} The event handler.
- * @this {T}
- * @template T
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-goog.fx.Dragger.prototype.getHandler = function() {
-  // TODO(user): templated "this" values currently result in "this" being
-  // "unknown" in the body of the function.
-  var self = /** @type {goog.fx.Dragger} */ (this);
-  return self.eventHandler_;
+ol.format.KML.ExtendedDataParser_ = function(node, objectStack) {
+  ol.xml.parseNode(ol.format.KML.EXTENDED_DATA_PARSERS_, node, objectStack);
 };
 
-
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-goog.fx.Dragger.prototype.setLimits = function(limits) {
-  this.limits = limits || new goog.math.Rect(NaN, NaN, NaN, NaN);
+ol.format.KML.RegionParser_ = function(node, objectStack) {
+  ol.xml.parseNode(ol.format.KML.REGION_PARSERS_, node, objectStack);
 };
 
-
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-goog.fx.Dragger.prototype.setHysteresis = function(distance) {
-  this.hysteresisDistanceSquared_ = Math.pow(distance, 2);
+ol.format.KML.PairDataParser_ = function(node, objectStack) {
+  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;
+    }
+  }
 };
 
 
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-goog.fx.Dragger.prototype.getHysteresis = function() {
-  return Math.sqrt(this.hysteresisDistanceSquared_);
+ol.format.KML.PlacemarkStyleMapParser_ = function(node, objectStack) {
+  var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack);
+  if (!styleMapValue) {
+    return;
+  }
+  var placemarkObject = objectStack[objectStack.length - 1];
+  if (Array.isArray(styleMapValue)) {
+    placemarkObject['Style'] = styleMapValue;
+  } else if (typeof styleMapValue === 'string') {
+    placemarkObject['styleUrl'] = styleMapValue;
+  } else {
+    ol.asserts.assert(false, 38); // `styleMapValue` has an unknown type
+  }
 };
 
 
 /**
- * Sets the SCROLL event target to make drag element follow scrolling.
- *
- * @param {EventTarget} scrollTarget The event target that dispatches SCROLL
- *     events.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-goog.fx.Dragger.prototype.setScrollTarget = function(scrollTarget) {
-  this.scrollTarget_ = scrollTarget;
+ol.format.KML.SchemaDataParser_ = function(node, objectStack) {
+  ol.xml.parseNode(ol.format.KML.SCHEMA_DATA_PARSERS_, node, objectStack);
 };
 
 
 /**
- * Enables cancelling of built-in IE drag events.
- * @param {boolean} cancelIeDragStart Whether to enable cancelling of IE
- *     dragstart event.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-goog.fx.Dragger.prototype.setCancelIeDragStart = function(cancelIeDragStart) {
-  this.ieDragStartCancellingOn_ = cancelIeDragStart;
+ol.format.KML.SimpleDataParser_ = function(node, objectStack) {
+  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;
+  }
 };
 
 
 /**
- * @return {boolean} Whether the dragger is enabled.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-goog.fx.Dragger.prototype.getEnabled = function() {
-  return this.enabled_;
+ol.format.KML.LatLonAltBoxParser_ = function(node, objectStack) {
+  var object = ol.xml.pushParseAndPop({}, ol.format.KML.LAT_LON_ALT_BOX_PARSERS_, node, objectStack);
+  if (!object) {
+    return;
+  }
+  var regionObject = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  var extent = [
+    parseFloat(object['west']),
+    parseFloat(object['south']),
+    parseFloat(object['east']),
+    parseFloat(object['north'])
+  ];
+  regionObject['extent'] = extent;
+  regionObject['altitudeMode'] = object['altitudeMode'];
+  regionObject['minAltitude'] = parseFloat(object['minAltitude']);
+  regionObject['maxAltitude'] = parseFloat(object['maxAltitude']);
 };
 
 
 /**
- * Set whether dragger is enabled
- * @param {boolean} enabled Whether dragger is enabled.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-goog.fx.Dragger.prototype.setEnabled = function(enabled) {
-  this.enabled_ = enabled;
+ol.format.KML.LodParser_ = function(node, objectStack) {
+  var object = ol.xml.pushParseAndPop({}, ol.format.KML.LOD_PARSERS_, node, objectStack);
+  if (!object) {
+    return;
+  }
+  var lodObject = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  lodObject['minLodPixels'] = parseFloat(object['minLodPixels']);
+  lodObject['maxLodPixels'] = parseFloat(object['maxLodPixels']);
+  lodObject['minFadeExtent'] = parseFloat(object['minFadeExtent']);
+  lodObject['maxFadeExtent'] = parseFloat(object['maxFadeExtent']);
 };
 
 
 /**
- * Set whether mousedown should be default prevented.
- * @param {boolean} preventMouseDown Whether mousedown should be default
- *     prevented.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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;
+ol.format.KML.innerBoundaryIsParser_ = function(node, objectStack) {
+  /** @type {Array.<number>|undefined} */
+  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
+      ol.format.KML.INNER_BOUNDARY_IS_PARSERS_, node, objectStack);
+  if (flatLinearRing) {
+    var flatLinearRings = /** @type {Array.<Array.<number>>} */
+        (objectStack[objectStack.length - 1]);
+    flatLinearRings.push(flatLinearRing);
+  }
 };
 
 
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
  * @private
  */
-goog.fx.Dragger.prototype.isRightToLeft_ = function() {
-  if (!goog.isDef(this.rightToLeft_)) {
-    this.rightToLeft_ = goog.style.isRightToLeft(this.target);
+ol.format.KML.outerBoundaryIsParser_ = function(node, objectStack) {
+  /** @type {Array.<number>|undefined} */
+  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
+      ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_, node, objectStack);
+  if (flatLinearRing) {
+    var flatLinearRings = /** @type {Array.<Array.<number>>} */
+        (objectStack[objectStack.length - 1]);
+    flatLinearRings[0] = flatLinearRing;
   }
-  return this.rightToLeft_;
 };
 
 
 /**
- * Event handler that is used to start the drag
- * @param {goog.events.BrowserEvent} e Event object.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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);
-  }
+ol.format.KML.LinkParser_ = function(node, objectStack) {
+  ol.xml.parseNode(ol.format.KML.LINK_PARSERS_, node, objectStack);
 };
 
 
 /**
- * Sets up event handlers when dragging starts.
- * @protected
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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);
-  }
+ol.format.KML.whenParser_ = function(node, objectStack) {
+  var gxTrackObject = /** @type {ol.KMLGxTrackObject_} */
+      (objectStack[objectStack.length - 1]);
+  var whens = gxTrackObject.whens;
+  var s = ol.xml.getAllTextContent(node, false);
+  var when = Date.parse(s);
+  whens.push(isNaN(when) ? 0 : when);
 };
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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));
-};
+ol.format.KML.DATA_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'displayName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'value': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
+    });
 
 
 /**
- * Unregisters the event handlers that are only active during dragging, and
- * releases mouse capture.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-goog.fx.Dragger.prototype.cleanUpAfterDragging_ = function() {
-  this.eventHandler_.removeAll();
-  if (goog.fx.Dragger.HAS_SET_CAPTURE_) {
-    this.document_.releaseCapture();
-  }
-};
+ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'Data': ol.format.KML.DataParser_,
+      'SchemaData': ol.format.KML.SchemaDataParser_
+    });
 
 
 /**
- * Event handler that is used to end the drag.
- * @param {goog.events.BrowserEvent} e Event object.
- * @param {boolean=} opt_dragCanceled Whether the drag has been canceled.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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);
-  }
-};
+ol.format.KML.REGION_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'LatLonAltBox': ol.format.KML.LatLonAltBoxParser_,
+      'Lod': ol.format.KML.LodParser_
+    });
 
 
 /**
- * Event handler that is used to end the drag by cancelling it.
- * @param {goog.events.BrowserEvent} e Event object.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.fx.Dragger.prototype.endDragCancel = function(e) {
-  this.endDrag(e, true);
-};
+ol.format.KML.LAT_LON_ALT_BOX_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'altitudeMode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'minAltitude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'maxAltitude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'north': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'south': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'east': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'west': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal)
+    });
 
 
 /**
- * Event handler that is used on mouse / touch move to update the drag
- * @param {goog.events.BrowserEvent} e Event object.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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();
-      }
-    }
-  }
-};
+ol.format.KML.LOD_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'minLodPixels': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'maxLodPixels': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'minFadeExtent': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'maxFadeExtent': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal)
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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);
-};
+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)
+    });
 
 
 /**
- * Event handler for scroll target scrolling.
- * @param {goog.events.BrowserEvent} e The event.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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);
-};
+ol.format.KML.FLAT_LINEAR_RING_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_)
+    });
 
 
 /**
- * @param {goog.events.BrowserEvent} e The closure object
- *     representing the browser event that caused a drag event.
- * @param {number} x The new horizontal position for the drag element.
- * @param {number} y The new vertical position for the drag element.
- * @param {boolean} dragFromScroll Whether dragging was caused by scrolling
- *     the associated scroll target.
- * @protected
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.fx.Dragger.prototype.doDrag = function(e, x, y, dragFromScroll) {
-  this.defaultAction(x, y);
-  this.dispatchEvent(new goog.fx.DragEvent(
-      goog.fx.Dragger.EventType.DRAG, this, e.clientX, e.clientY, e, x, y));
-};
+ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'innerBoundaryIs': ol.format.KML.innerBoundaryIsParser_,
+      'outerBoundaryIs': ol.format.KML.outerBoundaryIsParser_
+    });
 
 
 /**
- * Returns the 'real' x after limits are applied (allows for some
- * limits to be undefined).
- * @param {number} x X-coordinate to limit.
- * @return {number} The 'real' X-coordinate after limits are applied.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.fx.Dragger.prototype.limitX = function(x) {
-  var rect = this.limits;
-  var left = !isNaN(rect.left) ? rect.left : null;
-  var width = !isNaN(rect.width) ? rect.width : 0;
-  var maxX = left != null ? left + width : Infinity;
-  var minX = left != null ? left : -Infinity;
-  return Math.min(maxX, Math.max(minX, x));
-};
+ol.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_
+        }));
 
 
 /**
- * Returns the 'real' y after limits are applied (allows for some
- * limits to be undefined).
- * @param {number} y Y-coordinate to limit.
- * @return {number} The 'real' Y-coordinate after limits are applied.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.fx.Dragger.prototype.limitY = function(y) {
-  var rect = this.limits;
-  var top = !isNaN(rect.top) ? rect.top : null;
-  var height = !isNaN(rect.height) ? rect.height : 0;
-  var maxY = top != null ? top + height : Infinity;
-  var minY = top != null ? top : -Infinity;
-  return Math.min(maxY, Math.max(minY, y));
-};
+ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_)
+    });
 
 
 /**
- * Overridable function for computing the initial position of the target
- * before dragging begins.
- * @protected
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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;
-};
+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)
+        }));
 
 
 /**
- * Overridable function for handling the default action of the drag behaviour.
- * Normally this is simply moving the element to x,y though in some cases it
- * might be used to resize the layer.  This is basically a shortcut to
- * implementing a default ondrag event handler.
- * @param {number} x X-coordinate for target element. In right-to-left, x this
- *     is the number of pixels the target should be moved to from the right.
- * @param {number} y Y-coordinate for target element.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.fx.Dragger.prototype.defaultAction = function(x, y) {
-  if (this.useRightPositioningForRtl_ && this.isRightToLeft_()) {
-    this.target.style.right = x + 'px';
-  } else {
-    this.target.style.left = x + 'px';
-  }
-  this.target.style.top = y + 'px';
-};
+ol.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_)
+    });
 
 
 /**
- * @return {boolean} Whether the dragger is currently in the midst of a drag.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.fx.Dragger.prototype.isDragging = function() {
-  return this.dragging_;
-};
-
+ol.format.KML.INNER_BOUNDARY_IS_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
+    });
 
 
 /**
- * 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}
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.Event');
-goog.require('goog.events.EventType');
-goog.require('goog.fx.DragEvent');
-goog.require('goog.fx.Dragger');
-goog.require('goog.fx.Dragger.EventType');
-goog.require('goog.math.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');
-
+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_)
+    });
 
 
 /**
- * @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
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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 ? options.duration : 200;
-
-  var className = options.className ? options.className : 'ol-zoomslider';
-  var thumbElement = goog.dom.createDom(goog.dom.TagName.DIV,
-      [className + '-thumb', ol.css.CLASS_UNSELECTABLE]);
-  var containerElement = goog.dom.createDom(goog.dom.TagName.DIV,
-      [className, ol.css.CLASS_UNSELECTABLE, ol.css.CLASS_CONTROL],
-      thumbElement);
-
-  /**
-   * @type {goog.fx.Dragger}
-   * @private
-   */
-  this.dragger_ = new goog.fx.Dragger(thumbElement);
-  this.registerDisposable(this.dragger_);
-
-  goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.START,
-      this.handleDraggerStart_, false, this);
-  goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.DRAG,
-      this.handleDraggerDrag_, false, this);
-  goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.END,
-      this.handleDraggerEnd_, false, this);
-
-  goog.events.listen(containerElement, goog.events.EventType.CLICK,
-      this.handleContainerClick_, false, this);
-  goog.events.listen(thumbElement, goog.events.EventType.CLICK,
-      goog.events.Event.stopPropagation);
-
-  var render = options.render ? options.render : ol.control.ZoomSlider.render;
-
-  goog.base(this, {
-    element: containerElement,
-    render: render
-  });
-};
-goog.inherits(ol.control.ZoomSlider, ol.control.Control);
+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)
+    });
 
 
 /**
- * The enum for available directions.
- *
- * @enum {number}
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.control.ZoomSlider.direction = {
-  VERTICAL: 0,
-  HORIZONTAL: 1
-};
+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_)
+    });
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.control.ZoomSlider.prototype.setMap = function(map) {
-  goog.base(this, 'setMap', map);
-  if (map) {
-    map.render();
-  }
-};
+ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.GX_NAMESPACE_URIS_, {
+      'Track': ol.xml.makeArrayPusher(ol.format.KML.readGxTrack_)
+    });
 
 
 /**
- * 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.
- *
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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;
-};
+ol.format.KML.NETWORK_LINK_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'ExtendedData': ol.format.KML.ExtendedDataParser_,
+      'Region': ol.format.KML.RegionParser_,
+      '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)
+    });
 
 
 /**
- * Update the zoomslider element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.ZoomSlider}
- * @api
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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);
-  }
-};
+ol.format.KML.LINK_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_)
+    });
 
 
 /**
- * @param {goog.events.BrowserEvent} browserEvent The browser event to handle.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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));
-};
+ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
+    });
 
 
 /**
- * Handle dragger start events.
- * @param {goog.fx.DragEvent} event The drag event.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.control.ZoomSlider.prototype.handleDraggerStart_ = function(event) {
-  this.getMap().getView().setHint(ol.ViewHint.INTERACTING, 1);
-};
+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.readURI_)
+    });
 
 
 /**
- * Handle dragger drag events.
- *
- * @param {goog.fx.DragEvent} event The drag event.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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_);
-};
+ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'ExtendedData': ol.format.KML.ExtendedDataParser_,
+      'Region': ol.format.KML.RegionParser_,
+      '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')
+        }
+    ));
 
 
 /**
- * Handle dragger end events.
- * @param {goog.fx.DragEvent} event The drag event.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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);
-};
+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)
+    });
 
 
 /**
- * Positions the thumb inside its container according to the given resolution.
- *
- * @param {number} res The res.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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);
-  }
-};
+ol.format.KML.SCHEMA_DATA_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'SimpleData': ol.format.KML.SimpleDataParser_
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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);
-};
+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_
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.control.ZoomSlider.prototype.getResolutionForPosition_ = function(position) {
-  var fn = this.getMap().getView().getResolutionForValueFunction();
-  return fn(1 - position);
-};
+ol.format.KML.STYLE_MAP_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'Pair': ol.format.KML.PairDataParser_
+    });
 
 
 /**
- * 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).
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
  * @private
+ * @return {Array.<ol.Feature>|undefined} Features.
  */
-ol.control.ZoomSlider.prototype.getPositionForResolution_ = function(res) {
-  var fn = this.getMap().getView().getValueForResolutionFunction();
-  return 1 - fn(res);
+ol.format.KML.prototype.readDocumentOrFolder_ = function(node, objectStack) {
+  // 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': this.readSharedStyle_.bind(this),
+        'StyleMap': this.readSharedStyleMap_.bind(this)
+      });
+  /** @type {Array.<ol.Feature>} */
+  var features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack, this);
+  if (features) {
+    return features;
+  } else {
+    return undefined;
+  }
 };
 
-goog.provide('ol.control.ZoomToExtent');
-
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-
-
 
 /**
- * @classdesc
- * A button control which, when pressed, changes the map view to a specific
- * extent. To style this control use the css selector `.ol-zoom-extent`.
- *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.ZoomToExtentOptions=} opt_options Options.
- * @api stable
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Feature.
  */
-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';
+ol.format.KML.prototype.readPlacemark_ = function(node, objectStack) {
+  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 label = options.label ? options.label : 'E';
-  var tipLabel = options.tipLabel ?
-      options.tipLabel : 'Fit to extent';
-  var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
-    'type': 'button',
-    'title': tipLabel
-  }, label);
+  var geometry = object['geometry'];
+  if (geometry) {
+    ol.format.Feature.transformWithOptions(geometry, false, options);
+  }
+  feature.setGeometry(geometry);
+  delete object['geometry'];
 
-  goog.events.listen(button, goog.events.EventType.CLICK,
-      this.handleClick_, false, this);
+  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
 
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL;
-  var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button);
+  feature.setProperties(object);
 
-  goog.base(this, {
-    element: element,
-    target: options.target
-  });
+  return feature;
 };
-goog.inherits(ol.control.ZoomToExtent, ol.control.Control);
 
 
 /**
- * @param {goog.events.BrowserEvent} event The event to handle
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
  * @private
  */
-ol.control.ZoomToExtent.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  this.handleZoomToExtent_();
+ol.format.KML.prototype.readSharedStyle_ = function(node, objectStack) {
+  var id = node.getAttribute('id');
+  if (id !== null) {
+    var style = ol.format.KML.readStyle_(node, objectStack);
+    if (style) {
+      var styleUri;
+      if (node.baseURI && node.baseURI !== 'about:blank') {
+        var url = new URL('#' + id, node.baseURI);
+        styleUri = url.href;
+      } else {
+        styleUri = '#' + id;
+      }
+      this.sharedStyles_[styleUri] = style;
+    }
+  }
 };
 
 
 /**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
  * @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);
+ol.format.KML.prototype.readSharedStyleMap_ = function(node, objectStack) {
+  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 && node.baseURI !== 'about:blank') {
+    var url = new URL('#' + id, node.baseURI);
+    styleUri = url.href;
+  } else {
+    styleUri = '#' + id;
+  }
+  this.sharedStyles_[styleUri] = styleMapValue;
 };
 
-goog.provide('ol.DeviceOrientation');
-goog.provide('ol.DeviceOrientationProperty');
 
-goog.require('goog.events');
-goog.require('goog.math');
-goog.require('ol');
-goog.require('ol.Object');
-goog.require('ol.has');
+/**
+ * Read the first feature from a KML source. MultiGeometries are converted into
+ * GeometryCollections if they are a mix of geometry types, and into MultiPoint/
+ * MultiLineString/MultiPolygon if they are all of the same type.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ * @api
+ */
+ol.format.KML.prototype.readFeature;
 
 
 /**
- * @enum {string}
+ * @inheritDoc
  */
-ol.DeviceOrientationProperty = {
-  ALPHA: 'alpha',
-  BETA: 'beta',
-  GAMMA: 'gamma',
-  HEADING: 'heading',
-  TRACKING: 'tracking'
+ol.format.KML.prototype.readFeatureFromNode = function(node, opt_options) {
+  if (!ol.array.includes(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) {
+    return null;
+  }
+  var feature = this.readPlacemark_(
+      node, [this.getReadOptions(node, opt_options)]);
+  if (feature) {
+    return feature;
+  } else {
+    return null;
+  }
 };
 
 
-
 /**
- * @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.
+ * Read all features from a KML source. MultiGeometries are converted into
+ * GeometryCollections if they are a mix of geometry types, and into MultiPoint/
+ * MultiLineString/MultiPolygon if they are all of the same type.
  *
- * @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.
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
  * @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);
+ol.format.KML.prototype.readFeatures;
 
 
 /**
  * @inheritDoc
  */
-ol.DeviceOrientation.prototype.disposeInternal = function() {
-  this.setTracking(false);
-  goog.base(this, 'disposeInternal');
+ol.format.KML.prototype.readFeaturesFromNode = function(node, opt_options) {
+  if (!ol.array.includes(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) {
+    return [];
+  }
+  var features;
+  var localName = node.localName;
+  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) {
+        ol.array.extend(features, fs);
+      }
+    }
+    return features;
+  } else {
+    return [];
+  }
 };
 
 
 /**
- * @private
- * @param {goog.events.BrowserEvent} browserEvent Event.
+ * Read the name of the KML.
+ *
+ * @param {Document|Node|string} source Souce.
+ * @return {string|undefined} Name.
+ * @api
  */
-ol.DeviceOrientation.prototype.orientationChange_ = function(browserEvent) {
-  var event = /** @type {DeviceOrientationEvent} */
-      (browserEvent.getBrowserEvent());
-  if (event.alpha !== null) {
-    var alpha = goog.math.toRadians(event.alpha);
-    this.set(ol.DeviceOrientationProperty.ALPHA, alpha);
-    // event.absolute is undefined in iOS.
-    if (goog.isBoolean(event.absolute) && event.absolute) {
-      this.set(ol.DeviceOrientationProperty.HEADING, alpha);
-    } else if (goog.isNumber(event.webkitCompassHeading) &&
-               event.webkitCompassAccuracy != -1) {
-      var heading = goog.math.toRadians(event.webkitCompassHeading);
-      this.set(ol.DeviceOrientationProperty.HEADING, heading);
-    }
-  }
-  if (event.beta !== null) {
-    this.set(ol.DeviceOrientationProperty.BETA,
-        goog.math.toRadians(event.beta));
-  }
-  if (event.gamma !== null) {
-    this.set(ol.DeviceOrientationProperty.GAMMA,
-        goog.math.toRadians(event.gamma));
+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 (typeof source === 'string') {
+    var doc = ol.xml.parse(source);
+    return this.readNameFromDocument(doc);
+  } else {
+    return undefined;
   }
-  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
+ * @param {Document} doc Document.
+ * @return {string|undefined} Name.
  */
-ol.DeviceOrientation.prototype.getAlpha = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientationProperty.ALPHA));
+ol.format.KML.prototype.readNameFromDocument = function(doc) {
+  var n;
+  for (n = doc.firstChild; n; n = n.nextSibling) {
+    if (n.nodeType == Node.ELEMENT_NODE) {
+      var name = this.readNameFromNode(n);
+      if (name) {
+        return name;
+      }
+    }
+  }
+  return undefined;
 };
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @return {string|undefined} Name.
  */
-ol.DeviceOrientation.prototype.getBeta = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientationProperty.BETA));
+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 = n.localName;
+    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;
 };
 
 
 /**
- * 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
+ * Read the network links of the KML.
+ *
+ * @param {Document|Node|string} source Source.
+ * @return {Array.<Object>} Network links.
  * @api
  */
-ol.DeviceOrientation.prototype.getGamma = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientationProperty.GAMMA));
+ol.format.KML.prototype.readNetworkLinks = function(source) {
+  var networkLinks = [];
+  if (ol.xml.isDocument(source)) {
+    ol.array.extend(networkLinks, this.readNetworkLinksFromDocument(
+        /** @type {Document} */ (source)));
+  } else if (ol.xml.isNode(source)) {
+    ol.array.extend(networkLinks, this.readNetworkLinksFromNode(
+        /** @type {Node} */ (source)));
+  } else if (typeof source === 'string') {
+    var doc = ol.xml.parse(source);
+    ol.array.extend(networkLinks, this.readNetworkLinksFromDocument(doc));
+  }
+  return networkLinks;
 };
 
 
 /**
- * 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
+ * @param {Document} doc Document.
+ * @return {Array.<Object>} Network links.
  */
-ol.DeviceOrientation.prototype.getHeading = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientationProperty.HEADING));
+ol.format.KML.prototype.readNetworkLinksFromDocument = function(doc) {
+  var n, networkLinks = [];
+  for (n = doc.firstChild; n; n = n.nextSibling) {
+    if (n.nodeType == Node.ELEMENT_NODE) {
+      ol.array.extend(networkLinks, this.readNetworkLinksFromNode(n));
+    }
+  }
+  return networkLinks;
 };
 
 
 /**
- * Determine if orientation is being tracked.
- * @return {boolean} Changes in device orientation are being tracked.
- * @observable
- * @api
+ * @param {Node} node Node.
+ * @return {Array.<Object>} Network links.
  */
-ol.DeviceOrientation.prototype.getTracking = function() {
-  return /** @type {boolean} */ (
-      this.get(ol.DeviceOrientationProperty.TRACKING));
+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 = n.localName;
+    if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
+        (localName == 'Document' ||
+         localName == 'Folder' ||
+         localName == 'kml')) {
+      ol.array.extend(networkLinks, this.readNetworkLinksFromNode(n));
+    }
+  }
+  return networkLinks;
 };
 
 
 /**
- * @private
+ * Read the regions of the KML.
+ *
+ * @param {Document|Node|string} source Source.
+ * @return {Array.<Object>} Regions.
+ * @api
  */
-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;
-    }
+ol.format.KML.prototype.readRegion = function(source) {
+  var regions = [];
+  if (ol.xml.isDocument(source)) {
+    ol.array.extend(regions, this.readRegionFromDocument(
+        /** @type {Document} */ (source)));
+  } else if (ol.xml.isNode(source)) {
+    ol.array.extend(regions, this.readRegionFromNode(
+        /** @type {Node} */ (source)));
+  } else if (typeof source === 'string') {
+    var doc = ol.xml.parse(source);
+    ol.array.extend(regions, this.readRegionFromDocument(doc));
   }
+  return regions;
 };
 
 
 /**
- * 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
+ * @param {Document} doc Document.
+ * @return {Array.<Object>} Region.
  */
-ol.DeviceOrientation.prototype.setTracking = function(tracking) {
-  this.set(ol.DeviceOrientationProperty.TRACKING, tracking);
+ol.format.KML.prototype.readRegionFromDocument = function(doc) {
+  var n, regions = [];
+  for (n = doc.firstChild; n; n = n.nextSibling) {
+    if (n.nodeType == Node.ELEMENT_NODE) {
+      ol.array.extend(regions, this.readRegionFromNode(n));
+    }
+  }
+  return regions;
 };
 
-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
+ * @param {Node} node Node.
+ * @return {Array.<Object>} Region.
+ * @api
  */
-ol.format.Feature = function() {
-
-  /**
-   * @protected
-   * @type {ol.proj.Projection}
-   */
-  this.defaultDataProjection = null;
+ol.format.KML.prototype.readRegionFromNode = function(node) {
+  var n, regions = [];
+  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
+    if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
+        n.localName == 'Region') {
+      var obj = ol.xml.pushParseAndPop({}, ol.format.KML.REGION_PARSERS_,
+          n, []);
+      regions.push(obj);
+    }
+  }
+  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
+    var localName = n.localName;
+    if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
+        (localName == 'Document' ||
+         localName == 'Folder' ||
+         localName == 'kml')) {
+      ol.array.extend(regions, this.readRegionFromNode(n));
+    }
+  }
+  return regions;
 };
 
 
 /**
- * @return {Array.<string>} Extensions.
+ * Read the projection from a KML source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
  */
-ol.format.Feature.prototype.getExtensions = goog.abstractMethod;
+ol.format.KML.prototype.readProjection;
 
 
 /**
- * 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
+ * @param {Node} node Node to append a TextNode with the color to.
+ * @param {ol.Color|string} color Color.
+ * @private
  */
-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
-    };
+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;
   }
-  return this.adaptOptions(options);
+  ol.format.XSD.writeStringTextNode(node, abgr.join(''));
 };
 
 
 /**
- * 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.
+ * @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.Feature.prototype.adaptOptions = function(options) {
-  var updatedOptions;
-  if (options) {
-    updatedOptions = {
-      featureProjection: options.featureProjection,
-      dataProjection: options.dataProjection ?
-          options.dataProjection : this.defaultDataProjection,
-      rightHanded: options.rightHanded
-    };
+ol.format.KML.writeCoordinatesTextNode_ = function(node, coordinates, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+
+  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 {
+    ol.asserts.assert(false, 34); // Invalid 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];
+      }
+    }
   }
-  return updatedOptions;
+  ol.format.XSD.writeStringTextNode(node, text);
 };
 
 
 /**
- * @return {ol.format.FormatType} Format.
+ * @param {Node} node Node.
+ * @param {{name: *, value: *}} pair Name value pair.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.Feature.prototype.getType = goog.abstractMethod;
+ol.format.KML.writeDataNode_ = function(node, pair, objectStack) {
+  node.setAttribute('name', pair.name);
+  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
+  var value = pair.value;
 
+  if (typeof value == 'object') {
+    if (value !== null && value.displayName) {
+      ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_,
+        ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value.displayName], objectStack, ['displayName']);
+    }
 
-/**
- * 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;
+    if (value !== null && value.value) {
+      ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_,
+        ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value.value], objectStack, ['value']);
+    }
+  } else {
+    ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_,
+     ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value], objectStack, ['value']);
+  }
+};
 
 
 /**
- * Read all features from a source.
- *
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
+ * @param {Node} node Node to append a TextNode with the name to.
+ * @param {string} name DisplayName.
+ * @private
  */
-ol.format.Feature.prototype.readFeatures = goog.abstractMethod;
+ol.format.KML.writeDataNodeName_ = function(node, name) {
+  ol.format.XSD.writeCDATASection(node, name);
+};
 
 
 /**
- * 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.
+ * @param {Node} node Node to append a CDATA Section with the value to.
+ * @param {string} value Value.
+ * @private
  */
-ol.format.Feature.prototype.readGeometry = goog.abstractMethod;
+ol.format.KML.writeDataNodeValue_ = function(node, value) {
+  ol.format.XSD.writeStringTextNode(node, value);
+};
 
 
 /**
- * Read the projection from a source.
- *
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
+ * @param {Node} node Node.
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {Array.<*>} objectStack Object stack.
+ * @this {ol.format.KML}
+ * @private
  */
-ol.format.Feature.prototype.readProjection = goog.abstractMethod;
+ol.format.KML.writeDocument_ = function(node, features, objectStack) {
+  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
+  ol.xml.pushSerializeAndPop(context, ol.format.KML.DOCUMENT_SERIALIZERS_,
+      ol.format.KML.DOCUMENT_NODE_FACTORY_, features, objectStack, undefined,
+      this);
+};
 
 
 /**
- * Encode a feature in this format.
- *
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} Result.
+ * @param {Node} node Node.
+ * @param {{names: Array<string>, values: (Array<*>)}} namesAndValues Names and values.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.Feature.prototype.writeFeature = goog.abstractMethod;
+ol.format.KML.writeExtendedData_ = function(node, namesAndValues, objectStack) {
+  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
+  var names = namesAndValues.names, values = namesAndValues.values;
+  var length = names.length;
+
+  for (var i = 0; i < length; i++) {
+    ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_,
+      ol.format.KML.DATA_NODE_FACTORY_, [{name: names[i], value: values[i]}], objectStack);
+  }
+};
 
 
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {Object} icon Icon object.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.Feature.prototype.writeFeatures = goog.abstractMethod;
+ol.format.KML.writeIcon_ = function(node, icon, objectStack) {
+  var /** @type {ol.XmlNodeStackItem} */ 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);
+};
 
 
 /**
- * Write a single geometry in this format.
- *
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} Result.
+ * @param {Node} node Node.
+ * @param {ol.style.Icon} style Icon style.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.Feature.prototype.writeGeometry = goog.abstractMethod;
+ol.format.KML.writeIconStyle_ = function(node, style, objectStack) {
+  var /** @type {ol.XmlNodeStackItem} */ 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
 
-/**
- * @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);
+    if (origin && iconImageSize && origin[0] !== 0 && origin[1] !== size[1]) {
+      iconProperties['x'] = origin[0];
+      iconProperties['y'] = iconImageSize[1] - (origin[1] + size[1]);
     }
-  } else {
-    return geometry;
-  }
-};
 
-goog.provide('ol.format.JSONFeature');
+    if (anchor && anchor[0] !== 0 && anchor[1] !== size[1]) {
+      var /** @type {ol.KMLVec2_} */ hotSpot = {
+        x: anchor[0],
+        xunits: ol.style.IconAnchorUnits.PIXELS,
+        y: size[1] - anchor[1],
+        yunits: ol.style.IconAnchorUnits.PIXELS
+      };
+      properties['hotSpot'] = hotSpot;
+    }
+  }
 
-goog.require('goog.asserts');
-goog.require('goog.json');
-goog.require('ol.format.Feature');
-goog.require('ol.format.FormatType');
+  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
+  }
 
-/**
- * @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);
+  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);
 };
-goog.inherits(ol.format.JSONFeature, ol.format.Feature);
 
 
 /**
- * @param {Document|Node|Object|string} source Source.
+ * @param {Node} node Node.
+ * @param {ol.style.Text} style style.
+ * @param {Array.<*>} objectStack Object stack.
  * @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;
+ol.format.KML.writeLabelStyle_ = function(node, style, objectStack) {
+  var /** @type {ol.XmlNodeStackItem} */ 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);
 };
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node.
+ * @param {ol.style.Stroke} style style.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.JSONFeature.prototype.getType = function() {
-  return ol.format.FormatType.JSON;
+ol.format.KML.writeLineStyle_ = function(node, style, objectStack) {
+  var /** @type {ol.XmlNodeStackItem} */ 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);
 };
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.JSONFeature.prototype.readFeature = function(source, opt_options) {
-  return this.readFeatureFromObject(
-      this.getObject_(source), this.getReadOptions(source, opt_options));
+ol.format.KML.writeMultiGeometry_ = function(node, geometry, objectStack) {
+  /** @type {ol.XmlNodeStackItem} */
+  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.GEOMETRY_COLLECTION) {
+    geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries();
+    factory = ol.format.KML.GEOMETRY_NODE_FACTORY_;
+  } else 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 {
+    ol.asserts.assert(false, 39); // Unknown geometry type
+  }
+  ol.xml.pushSerializeAndPop(context,
+      ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_, factory,
+      geometries, objectStack);
 };
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node.
+ * @param {ol.geom.LinearRing} linearRing Linear ring.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.JSONFeature.prototype.readFeatures = function(source, opt_options) {
-  return this.readFeaturesFromObject(
-      this.getObject_(source), this.getReadOptions(source, opt_options));
+ol.format.KML.writeBoundaryIs_ = function(node, linearRing, objectStack) {
+  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
+  ol.xml.pushSerializeAndPop(context,
+      ol.format.KML.BOUNDARY_IS_SERIALIZERS_,
+      ol.format.KML.LINEAR_RING_NODE_FACTORY_, [linearRing], objectStack);
 };
 
 
 /**
- * @param {Object} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {ol.Feature} Feature.
+ * 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.JSONFeature.prototype.readFeatureFromObject = goog.abstractMethod;
+ol.format.KML.writePlacemark_ = function(node, feature, objectStack) {
+  var /** @type {ol.XmlNodeStackItem} */ 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();
 
-/**
- * @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;
+  // don't export these to ExtendedData
+  var filter = {'address': 1, 'description': 1, 'name': 1, 'open': 1,
+    'phoneNumber': 1, 'styleUrl': 1, 'visibility': 1};
+  filter[feature.getGeometryName()] = 1;
+  var keys = Object.keys(properties || {}).sort().filter(function(v) {
+    return !filter[v];
+  });
 
+  if (keys.length > 0) {
+    var sequence = ol.xml.makeSequence(properties, keys);
+    var namesAndValues = {names: keys, values: sequence};
+    ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_,
+      ol.format.KML.EXTENDEDDATA_NODE_FACTORY_, [namesAndValues], objectStack);
+  }
 
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.readGeometry = function(source, opt_options) {
-  return this.readGeometryFromObject(
-      this.getObject_(source), this.getReadOptions(source, opt_options));
+  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) {
+      var style = Array.isArray(styles) ? styles[0] : styles;
+      if (this.writeStyles_) {
+        properties['Style'] = style;
+      }
+      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 {Object} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {ol.geom.Geometry} Geometry.
+ * @param {Node} node Node.
+ * @param {ol.geom.SimpleGeometry} geometry Geometry.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.JSONFeature.prototype.readGeometryFromObject = goog.abstractMethod;
+ol.format.KML.writePrimitiveGeometry_ = function(node, geometry, objectStack) {
+  var flatCoordinates = geometry.getFlatCoordinates();
+  var /** @type {ol.XmlNodeStackItem} */ 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);
+};
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node.
+ * @param {ol.geom.Polygon} polygon Polygon.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.JSONFeature.prototype.readProjection = function(source) {
-  return this.readProjectionFromObject(this.getObject_(source));
+ol.format.KML.writePolygon_ = function(node, polygon, objectStack) {
+  var linearRings = polygon.getLinearRings();
+  var outerRing = linearRings.shift();
+  var /** @type {ol.XmlNodeStackItem} */ 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 {Object} object Object.
- * @protected
- * @return {ol.proj.Projection} Projection.
+ * @param {Node} node Node.
+ * @param {ol.style.Fill} style Style.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.JSONFeature.prototype.readProjectionFromObject = goog.abstractMethod;
+ol.format.KML.writePolyStyle_ = function(node, style, objectStack) {
+  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
+  ol.xml.pushSerializeAndPop(context, ol.format.KML.POLY_STYLE_SERIALIZERS_,
+      ol.format.KML.COLOR_NODE_FACTORY_, [style.getColor()], objectStack);
+};
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node to append a TextNode with the scale to.
+ * @param {number|undefined} scale Scale.
+ * @private
  */
-ol.format.JSONFeature.prototype.writeFeature = function(feature, opt_options) {
-  return goog.json.serialize(this.writeFeatureObject(feature, opt_options));
+ol.format.KML.writeScaleTextNode_ = function(node, scale) {
+  // the Math is to remove any excess decimals created by float arithmetic
+  ol.format.XSD.writeDecimalTextNode(node,
+      Math.round(scale * 1e6) / 1e6);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {Object} Object.
+ * @param {Node} node Node.
+ * @param {ol.style.Style} style Style.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.JSONFeature.prototype.writeFeatureObject = goog.abstractMethod;
+ol.format.KML.writeStyle_ = function(node, style, objectStack) {
+  var /** @type {ol.XmlNodeStackItem} */ 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);
+};
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node to append a TextNode with the Vec2 to.
+ * @param {ol.KMLVec2_} vec2 Vec2.
+ * @private
  */
-ol.format.JSONFeature.prototype.writeFeatures = function(
-    features, opt_options) {
-  return goog.json.serialize(this.writeFeaturesObject(features, opt_options));
+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);
 };
 
 
 /**
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {Object} Object.
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
  */
-ol.format.JSONFeature.prototype.writeFeaturesObject = goog.abstractMethod;
+ol.format.KML.KML_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'Document', 'Placemark'
+    ]);
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.format.JSONFeature.prototype.writeGeometry = function(
-    geometry, opt_options) {
-  return goog.json.serialize(this.writeGeometryObject(geometry, opt_options));
-};
+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_)
+    });
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {Object} Object.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.format.JSONFeature.prototype.writeGeometryObject = goog.abstractMethod;
-
-goog.provide('ol.format.EsriJSON');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.object');
-goog.require('ol.Feature');
-goog.require('ol.extent');
-goog.require('ol.format.Feature');
-goog.require('ol.format.JSONFeature');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.LinearRing');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.geom.flat.orient');
-goog.require('ol.proj');
-
+ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_)
+    });
 
 
 /**
- * @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
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.format.EsriJSON = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  goog.base(this);
+ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'Data': ol.xml.makeChildAppender(ol.format.KML.writeDataNode_),
+      'value': ol.xml.makeChildAppender(ol.format.KML.writeDataNodeValue_),
+      'displayName': ol.xml.makeChildAppender(ol.format.KML.writeDataNodeName_)
+    });
 
-  /**
-   * Name of the geometry attribute for features.
-   * @type {string|undefined}
-   * @private
-   */
-  this.geometryName_ = options.geometryName;
 
+/**
+ * @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',
+  'GeometryCollection': 'MultiGeometry'
 };
-goog.inherits(ol.format.EsriJSON, ol.format.JSONFeature);
 
 
 /**
- * @param {EsriJSONGeometry} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @const
+ * @type {Object.<string, Array.<string>>}
  * @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));
-};
+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'
+    ]));
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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;
-};
+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)
+        }));
 
 
 /**
- * @param {EsriJSONGeometry} object Object.
+ * @const
+ * @type {Object.<string, Array.<string>>}
  * @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;
-};
+ol.format.KML.ICON_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'scale', 'heading', 'Icon', 'hotSpot'
+    ]);
 
 
 /**
- * @param {EsriJSONGeometry} object Object.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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);
-};
+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_)
+    });
 
 
 /**
- * @param {EsriJSONGeometry} object Object.
+ * @const
+ * @type {Object.<string, Array.<string>>}
  * @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);
-};
+ol.format.KML.LABEL_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'color', 'scale'
+    ]);
 
 
 /**
- * @param {EsriJSONGeometry} object Object.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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;
-};
+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_)
+    });
 
 
 /**
- * @param {EsriJSONGeometry} object Object.
+ * @const
+ * @type {Object.<string, Array.<string>>}
  * @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);
-};
+ol.format.KML.LINE_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'color', 'width'
+    ]);
 
 
 /**
- * @param {EsriJSONGeometry} object Object.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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);
-};
+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)
+    });
 
 
 /**
- * @param {EsriJSONGeometry} object Object.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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);
-};
+ol.format.KML.BOUNDARY_IS_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'LinearRing': ol.xml.makeChildAppender(
+          ol.format.KML.writePrimitiveGeometry_)
+    });
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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]
+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_),
+      'GeometryCollection': ol.xml.makeChildAppender(
+          ol.format.KML.writeMultiGeometry_)
     });
-  } else {
-    goog.asserts.fail('Unknown geometry layout');
-  }
-};
 
 
 /**
- * @param {ol.geom.SimpleGeometry} geometry Geometry.
+ * @const
+ * @type {Object.<string, Array.<string>>}
  * @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)
-  };
-};
+ol.format.KML.PLACEMARK_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'name', 'open', 'visibility', 'address', 'phoneNumber', 'description',
+      'styleUrl', 'Style'
+    ]);
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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()]
-  });
-};
+ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'ExtendedData': ol.xml.makeChildAppender(
+          ol.format.KML.writeExtendedData_),
+      '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)
+    });
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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)
-  });
-};
+ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'coordinates': ol.xml.makeChildAppender(
+          ol.format.KML.writeCoordinatesTextNode_)
+    });
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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()
-  });
-};
+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_)
+    });
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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()
-  });
-};
+ol.format.KML.POLY_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_)
+    });
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @const
+ * @type {Object.<string, Array.<string>>}
  * @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
-  });
-};
+ol.format.KML.STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'IconStyle', 'LabelStyle', 'LineStyle', 'PolyStyle'
+    ]);
 
 
 /**
  * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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_;
+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
- * @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_;
+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);
+};
 
 
 /**
- * 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
+ * @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) {
+  var parentNode = objectStack[objectStack.length - 1].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.EsriJSON.prototype.readFeature;
+ol.format.KML.GEOMETRY_NODE_FACTORY_ = function(value, objectStack,
+    opt_nodeName) {
+  if (value) {
+    var parentNode = objectStack[objectStack.length - 1].node;
+    return ol.xml.createElementNS(parentNode.namespaceURI,
+        ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_[/** @type {ol.geom.Geometry} */ (value).getType()]);
+  }
+};
 
 
 /**
- * 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
+ * A factory for creating coordinates nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
  */
-ol.format.EsriJSON.prototype.readFeatures;
+ol.format.KML.COLOR_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('color');
 
 
 /**
- * @inheritDoc
+ * A factory for creating coordinates nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
  */
-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;
-};
+ol.format.KML.COORDINATES_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('coordinates');
 
 
 /**
- * @inheritDoc
+ * A factory for creating Data nodes.
+ * @const
+ * @type {function(*, Array.<*>): (Node|undefined)}
+ * @private
  */
-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)];
-  }
-};
+ol.format.KML.DATA_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('Data');
 
 
 /**
- * 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
+ * A factory for creating ExtendedData nodes.
+ * @const
+ * @type {function(*, Array.<*>): (Node|undefined)}
+ * @private
  */
-ol.format.EsriJSON.prototype.readGeometry;
+ol.format.KML.EXTENDEDDATA_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('ExtendedData');
 
 
 /**
- * @inheritDoc
+ * A factory for creating innerBoundaryIs nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
  */
-ol.format.EsriJSON.prototype.readGeometryFromObject = function(
-    object, opt_options) {
-  return ol.format.EsriJSON.readGeometry_(
-      /** @type {EsriJSONGeometry} */ (object), opt_options);
-};
+ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('innerBoundaryIs');
 
 
 /**
- * Read the projection from a EsriJSON source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api
+ * A factory for creating Point nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
  */
-ol.format.EsriJSON.prototype.readProjection;
+ol.format.KML.POINT_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('Point');
 
 
 /**
- * @inheritDoc
+ * A factory for creating LineString nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
  */
-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;
-  }
-};
+ol.format.KML.LINE_STRING_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('LineString');
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
+ * A factory for creating LinearRing nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
  * @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);
-};
+ol.format.KML.LINEAR_RING_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('LinearRing');
 
 
 /**
- * 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
+ * A factory for creating Polygon nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
  */
-ol.format.EsriJSON.prototype.writeGeometry;
+ol.format.KML.POLYGON_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('Polygon');
 
 
 /**
- * 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
+ * A factory for creating outerBoundaryIs nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
  */
-ol.format.EsriJSON.prototype.writeGeometryObject = function(geometry,
-    opt_options) {
-  return ol.format.EsriJSON.writeGeometry_(geometry,
-      this.adaptOptions(opt_options));
-};
+ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('outerBoundaryIs');
 
 
 /**
- * Encode a feature as a EsriJSON Feature string.
+ * Encode an array of features in the KML format. GeometryCollections, MultiPoints,
+ * MultiLineStrings, and MultiPolygons are output as MultiGeometries.
  *
  * @function
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} EsriJSON.
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {string} Result.
  * @api
  */
-ol.format.EsriJSON.prototype.writeFeature;
+ol.format.KML.prototype.writeFeatures;
 
 
 /**
- * Encode a feature as a esriJSON Feature object.
+ * Encode an array of features in the KML format as an XML node. GeometryCollections,
+ * MultiPoints, MultiLineStrings, and MultiPolygons are output as MultiGeometries.
  *
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {Object} Object.
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ * @override
  * @api
  */
-ol.format.EsriJSON.prototype.writeFeatureObject = function(
-    feature, opt_options) {
+ol.format.KML.prototype.writeFeaturesNode = function(features, 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()
-    });
+  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.XmlNodeStackItem} */ context = {node: kml};
+  var properties = {};
+  if (features.length > 1) {
+    properties['Document'] = features;
+  } else if (features.length == 1) {
+    properties['Placemark'] = features[0];
   }
-  return object;
+  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;
 };
 
 
 /**
- * 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
+ * @fileoverview
+ * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, unusedLocalVariables, uselessCode, visibility}
  */
-ol.format.EsriJSON.prototype.writeFeatures;
+goog.provide('ol.ext.PBF');
+
+/** @typedef {function(*)} */
+ol.ext.PBF = function() {};
+
+(function() {(function (exports) {
+'use strict';
+
+var 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)
+};
+var 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;
+};
+var index$2 = {
+	read: read,
+	write: write
+};
+
+var index = Pbf;
+function Pbf(buf) {
+    this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0);
+    this.pos = 0;
+    this.type = 0;
+    this.length = this.buf.length;
+}
+Pbf.Varint  = 0;
+Pbf.Fixed64 = 1;
+Pbf.Bytes   = 2;
+Pbf.Fixed32 = 5;
+var SHIFT_LEFT_32 = (1 << 16) * (1 << 16);
+var SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32;
+Pbf.prototype = {
+    destroy: function() {
+        this.buf = null;
+    },
+    readFields: function(readField, result, end) {
+        end = end || this.length;
+        while (this.pos < end) {
+            var val = this.readVarint(),
+                tag = val >> 3,
+                startPos = this.pos;
+            this.type = val & 0x7;
+            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 = readUInt32(this.buf, this.pos);
+        this.pos += 4;
+        return val;
+    },
+    readSFixed32: function() {
+        var val = readInt32(this.buf, this.pos);
+        this.pos += 4;
+        return val;
+    },
+    readFixed64: function() {
+        var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
+        this.pos += 8;
+        return val;
+    },
+    readSFixed64: function() {
+        var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
+        this.pos += 8;
+        return val;
+    },
+    readFloat: function() {
+        var val = index$2.read(this.buf, this.pos, true, 23, 4);
+        this.pos += 4;
+        return val;
+    },
+    readDouble: function() {
+        var val = index$2.read(this.buf, this.pos, true, 52, 8);
+        this.pos += 8;
+        return val;
+    },
+    readVarint: function(isSigned) {
+        var buf = this.buf,
+            val, b;
+        b = buf[this.pos++]; val  =  b & 0x7f;        if (b < 0x80) return val;
+        b = buf[this.pos++]; val |= (b & 0x7f) << 7;  if (b < 0x80) return val;
+        b = buf[this.pos++]; val |= (b & 0x7f) << 14; if (b < 0x80) return val;
+        b = buf[this.pos++]; val |= (b & 0x7f) << 21; if (b < 0x80) return val;
+        b = buf[this.pos];   val |= (b & 0x0f) << 28;
+        return readVarintRemainder(val, isSigned, this);
+    },
+    readVarint64: function() {
+        return this.readVarint(true);
+    },
+    readSVarint: function() {
+        var num = this.readVarint();
+        return num % 2 === 1 ? (num + 1) / -2 : num / 2;
+    },
+    readBoolean: function() {
+        return Boolean(this.readVarint());
+    },
+    readString: function() {
+        var end = this.readVarint() + this.pos,
+            str = readUtf8(this.buf, this.pos, end);
+        this.pos = end;
+        return str;
+    },
+    readBytes: function() {
+        var end = this.readVarint() + this.pos,
+            buffer = this.buf.subarray(this.pos, end);
+        this.pos = end;
+        return buffer;
+    },
+    readPackedVarint: function(arr, isSigned) {
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readVarint(isSigned));
+        return arr;
+    },
+    readPackedSVarint: function(arr) {
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readSVarint());
+        return arr;
+    },
+    readPackedBoolean: function(arr) {
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readBoolean());
+        return arr;
+    },
+    readPackedFloat: function(arr) {
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readFloat());
+        return arr;
+    },
+    readPackedDouble: function(arr) {
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readDouble());
+        return arr;
+    },
+    readPackedFixed32: function(arr) {
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readFixed32());
+        return arr;
+    },
+    readPackedSFixed32: function(arr) {
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readSFixed32());
+        return arr;
+    },
+    readPackedFixed64: function(arr) {
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readFixed64());
+        return arr;
+    },
+    readPackedSFixed64: function(arr) {
+        var end = readPackedEnd(this);
+        arr = 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);
+    },
+    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 Uint8Array(length);
+            buf.set(this.buf);
+            this.buf = buf;
+            this.length = length;
+        }
+    },
+    finish: function() {
+        this.length = this.pos;
+        this.pos = 0;
+        return this.buf.subarray(0, this.length);
+    },
+    writeFixed32: function(val) {
+        this.realloc(4);
+        writeInt32(this.buf, val, this.pos);
+        this.pos += 4;
+    },
+    writeSFixed32: function(val) {
+        this.realloc(4);
+        writeInt32(this.buf, val, this.pos);
+        this.pos += 4;
+    },
+    writeFixed64: function(val) {
+        this.realloc(8);
+        writeInt32(this.buf, val & -1, this.pos);
+        writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
+        this.pos += 8;
+    },
+    writeSFixed64: function(val) {
+        this.realloc(8);
+        writeInt32(this.buf, val & -1, this.pos);
+        writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
+        this.pos += 8;
+    },
+    writeVarint: function(val) {
+        val = +val || 0;
+        if (val > 0xfffffff || val < 0) {
+            writeBigVarint(val, this);
+            return;
+        }
+        this.realloc(4);
+        this.buf[this.pos++] =           val & 0x7f  | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
+        this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
+        this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
+        this.buf[this.pos++] =   (val >>> 7) & 0x7f;
+    },
+    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);
+        this.realloc(str.length * 4);
+        this.pos++;
+        var startPos = this.pos;
+        this.pos = writeUtf8(this.buf, str, this.pos);
+        var len = this.pos - startPos;
+        if (len >= 0x80) makeRoomForExtraLength(startPos, len, this);
+        this.pos = startPos - 1;
+        this.writeVarint(len);
+        this.pos += len;
+    },
+    writeFloat: function(val) {
+        this.realloc(4);
+        index$2.write(this.buf, val, this.pos, true, 23, 4);
+        this.pos += 4;
+    },
+    writeDouble: function(val) {
+        this.realloc(8);
+        index$2.write(this.buf, val, this.pos, true, 52, 8);
+        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++;
+        var startPos = this.pos;
+        fn(obj, this);
+        var len = this.pos - startPos;
+        if (len >= 0x80) makeRoomForExtraLength(startPos, len, this);
+        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 readVarintRemainder(l, s, p) {
+    var buf = p.buf,
+        h, b;
+    b = buf[p.pos++]; h  = (b & 0x70) >> 4;  if (b < 0x80) return toNum(l, h, s);
+    b = buf[p.pos++]; h |= (b & 0x7f) << 3;  if (b < 0x80) return toNum(l, h, s);
+    b = buf[p.pos++]; h |= (b & 0x7f) << 10; if (b < 0x80) return toNum(l, h, s);
+    b = buf[p.pos++]; h |= (b & 0x7f) << 17; if (b < 0x80) return toNum(l, h, s);
+    b = buf[p.pos++]; h |= (b & 0x7f) << 24; if (b < 0x80) return toNum(l, h, s);
+    b = buf[p.pos++]; h |= (b & 0x01) << 31; if (b < 0x80) return toNum(l, h, s);
+    throw new Error('Expected varint not more than 10 bytes');
+}
+function readPackedEnd(pbf) {
+    return pbf.type === Pbf.Bytes ?
+        pbf.readVarint() + pbf.pos : pbf.pos + 1;
+}
+function toNum(low, high, isSigned) {
+    if (isSigned) {
+        return high * 0x100000000 + (low >>> 0);
+    }
+    return ((high >>> 0) * 0x100000000) + (low >>> 0);
+}
+function writeBigVarint(val, pbf) {
+    var low, high;
+    if (val >= 0) {
+        low  = (val % 0x100000000) | 0;
+        high = (val / 0x100000000) | 0;
+    } else {
+        low  = ~(-val % 0x100000000);
+        high = ~(-val / 0x100000000);
+        if (low ^ 0xffffffff) {
+            low = (low + 1) | 0;
+        } else {
+            low = 0;
+            high = (high + 1) | 0;
+        }
+    }
+    if (val >= 0x10000000000000000 || val < -0x10000000000000000) {
+        throw new Error('Given varint doesn\'t fit into 10 bytes');
+    }
+    pbf.realloc(10);
+    writeBigVarintLow(low, high, pbf);
+    writeBigVarintHigh(high, pbf);
+}
+function writeBigVarintLow(low, high, pbf) {
+    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
+    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
+    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
+    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
+    pbf.buf[pbf.pos]   = low & 0x7f;
+}
+function writeBigVarintHigh(high, pbf) {
+    var lsb = (high & 0x07) << 4;
+    pbf.buf[pbf.pos++] |= lsb         | ((high >>>= 3) ? 0x80 : 0); if (!high) return;
+    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
+    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
+    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
+    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
+    pbf.buf[pbf.pos++]  = high & 0x7f;
+}
+function makeRoomForExtraLength(startPos, len, pbf) {
+    var extraLen =
+        len <= 0x3fff ? 1 :
+        len <= 0x1fffff ? 2 :
+        len <= 0xfffffff ? 3 : Math.ceil(Math.log(len) / (Math.LN2 * 7));
+    pbf.realloc(extraLen);
+    for (var i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i];
+}
+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]); }
+function readUInt32(buf, pos) {
+    return ((buf[pos]) |
+        (buf[pos + 1] << 8) |
+        (buf[pos + 2] << 16)) +
+        (buf[pos + 3] * 0x1000000);
+}
+function writeInt32(buf, val, pos) {
+    buf[pos] = val;
+    buf[pos + 1] = (val >>> 8);
+    buf[pos + 2] = (val >>> 16);
+    buf[pos + 3] = (val >>> 24);
+}
+function readInt32(buf, pos) {
+    return ((buf[pos]) |
+        (buf[pos + 1] << 8) |
+        (buf[pos + 2] << 16)) +
+        (buf[pos + 3] << 24);
+}
+function readUtf8(buf, pos, end) {
+    var str = '';
+    var i = pos;
+    while (i < end) {
+        var b0 = buf[i];
+        var c = null;
+        var bytesPerSequence =
+            b0 > 0xEF ? 4 :
+            b0 > 0xDF ? 3 :
+            b0 > 0xBF ? 2 : 1;
+        if (i + bytesPerSequence > end) break;
+        var b1, b2, b3;
+        if (bytesPerSequence === 1) {
+            if (b0 < 0x80) {
+                c = b0;
+            }
+        } else if (bytesPerSequence === 2) {
+            b1 = buf[i + 1];
+            if ((b1 & 0xC0) === 0x80) {
+                c = (b0 & 0x1F) << 0x6 | (b1 & 0x3F);
+                if (c <= 0x7F) {
+                    c = null;
+                }
+            }
+        } else if (bytesPerSequence === 3) {
+            b1 = buf[i + 1];
+            b2 = buf[i + 2];
+            if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) {
+                c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | (b2 & 0x3F);
+                if (c <= 0x7FF || (c >= 0xD800 && c <= 0xDFFF)) {
+                    c = null;
+                }
+            }
+        } else if (bytesPerSequence === 4) {
+            b1 = buf[i + 1];
+            b2 = buf[i + 2];
+            b3 = buf[i + 3];
+            if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) {
+                c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | (b3 & 0x3F);
+                if (c <= 0xFFFF || c >= 0x110000) {
+                    c = null;
+                }
+            }
+        }
+        if (c === null) {
+            c = 0xFFFD;
+            bytesPerSequence = 1;
+        } else if (c > 0xFFFF) {
+            c -= 0x10000;
+            str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800);
+            c = 0xDC00 | c & 0x3FF;
+        }
+        str += String.fromCharCode(c);
+        i += bytesPerSequence;
+    }
+    return str;
+}
+function writeUtf8(buf, str, pos) {
+    for (var i = 0, c, lead; i < str.length; i++) {
+        c = str.charCodeAt(i);
+        if (c > 0xD7FF && c < 0xE000) {
+            if (lead) {
+                if (c < 0xDC00) {
+                    buf[pos++] = 0xEF;
+                    buf[pos++] = 0xBF;
+                    buf[pos++] = 0xBD;
+                    lead = c;
+                    continue;
+                } else {
+                    c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000;
+                    lead = null;
+                }
+            } else {
+                if (c > 0xDBFF || (i + 1 === str.length)) {
+                    buf[pos++] = 0xEF;
+                    buf[pos++] = 0xBF;
+                    buf[pos++] = 0xBD;
+                } else {
+                    lead = c;
+                }
+                continue;
+            }
+        } else if (lead) {
+            buf[pos++] = 0xEF;
+            buf[pos++] = 0xBF;
+            buf[pos++] = 0xBD;
+            lead = null;
+        }
+        if (c < 0x80) {
+            buf[pos++] = c;
+        } else {
+            if (c < 0x800) {
+                buf[pos++] = c >> 0x6 | 0xC0;
+            } else {
+                if (c < 0x10000) {
+                    buf[pos++] = c >> 0xC | 0xE0;
+                } else {
+                    buf[pos++] = c >> 0x12 | 0xF0;
+                    buf[pos++] = c >> 0xC & 0x3F | 0x80;
+                }
+                buf[pos++] = c >> 0x6 & 0x3F | 0x80;
+            }
+            buf[pos++] = c & 0x3F | 0x80;
+        }
+    }
+    return pos;
+}
+
+exports['default'] = index;
+
+}((this.PBF = this.PBF || {})));}).call(ol.ext);
+ol.ext.PBF = ol.ext.PBF.default;
 
 
 /**
- * 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
+ * @fileoverview
+ * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, unusedLocalVariables, uselessCode, visibility}
  */
-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.ext.vectortile.VectorTile');
+
+/** @typedef {function(*)} */
+ol.ext.vectortile.VectorTile = function() {};
+
+(function() {(function (exports) {
+'use strict';
+
+var index$2 = 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); },
+    multByPoint:    function(p) { return this.clone()._multByPoint(p); },
+    divByPoint:     function(p) { return this.clone()._divByPoint(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); },
+    rotateAround:  function(a,p) { return this.clone()._rotateAround(a,p); },
+    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(other) {
+        return this.x === other.x &&
+               this.y === other.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);
+    },
+    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;
+    },
+    _multByPoint: function(p) {
+        this.x *= p.x;
+        this.y *= p.y;
+        return this;
+    },
+    _divByPoint: function(p) {
+        this.x /= p.x;
+        this.y /= p.y;
+        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;
+    },
+    _rotateAround: function(angle, p) {
+        var cos = Math.cos(angle),
+            sin = Math.sin(angle),
+            x = p.x + cos * (this.x - p.x) - sin * (this.y - p.y),
+            y = p.y + sin * (this.x - p.x) + cos * (this.y - p.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;
+    }
+};
+Point.convert = function (a) {
+    if (a instanceof Point) {
+        return a;
+    }
+    if (Array.isArray(a)) {
+        return new Point(a[0], a[1]);
+    }
+    return a;
 };
 
-// TODO: serialize dataProjection as crs member when writing
-// see https://github.com/openlayers/ol3/issues/2078
+var vectortilefeature = VectorTileFeature$1;
+function VectorTileFeature$1(pbf, end, extent, keys, values) {
+    this.properties = {};
+    this.extent = extent;
+    this.type = 0;
+    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$1.types = ['Unknown', 'Point', 'LineString', 'Polygon'];
+VectorTileFeature$1.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) {
+                if (line) lines.push(line);
+                line = [];
+            }
+            line.push(new index$2(x, y));
+        } else if (cmd === 7) {
+            if (line) {
+                line.push(line[0].clone());
+            }
+        } else {
+            throw new Error('unknown command ' + cmd);
+        }
+    }
+    if (line) lines.push(line);
+    return lines;
+};
+VectorTileFeature$1.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$1.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$1.types[this.type],
+        i, j;
+    function project(line) {
+        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
+            ];
+        }
+    }
+    switch (this.type) {
+    case 1:
+        var points = [];
+        for (i = 0; i < coords.length; i++) {
+            points[i] = coords[i][0];
+        }
+        coords = points;
+        project(coords);
+        break;
+    case 2:
+        for (i = 0; i < coords.length; i++) {
+            project(coords[i]);
+        }
+        break;
+    case 3:
+        coords = classifyRings(coords);
+        for (i = 0; i < coords.length; i++) {
+            for (j = 0; j < coords[i].length; j++) {
+                project(coords[i][j]);
+            }
+        }
+        break;
+    }
+    if (coords.length === 1) {
+        coords = coords[0];
+    } else {
+        type = 'Multi' + type;
+    }
+    var result = {
+        type: "Feature",
+        geometry: {
+            type: type,
+            coordinates: coords
+        },
+        properties: this.properties
+    };
+    if ('id' in this) {
+        result.id = this.id;
+    }
+    return result;
+};
+function classifyRings(rings) {
+    var len = rings.length;
+    if (len <= 1) return [rings];
+    var polygons = [],
+        polygon,
+        ccw;
+    for (var i = 0; i < len; i++) {
+        var area = signedArea(rings[i]);
+        if (area === 0) continue;
+        if (ccw === undefined) ccw = area < 0;
+        if (ccw === area < 0) {
+            if (polygon) polygons.push(polygon);
+            polygon = [rings[i]];
+        } else {
+            polygon.push(rings[i]);
+        }
+    }
+    if (polygon) polygons.push(polygon);
+    return polygons;
+}
+function signedArea(ring) {
+    var sum = 0;
+    for (var i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) {
+        p1 = ring[i];
+        p2 = ring[j];
+        sum += (p2.x - p1.x) * (p1.y + p2.y);
+    }
+    return sum;
+}
 
-goog.provide('ol.format.GeoJSON');
+var vectortilelayer = VectorTileLayer$1;
+function VectorTileLayer$1(pbf, end) {
+    this.version = 1;
+    this.name = null;
+    this.extent = 4096;
+    this.length = 0;
+    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;
+}
+VectorTileLayer$1.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);
+};
 
-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');
+var vectortile = VectorTile$1;
+function VectorTile$1(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;
+    }
+}
+
+var VectorTile = vectortile;
+var VectorTileFeature = vectortilefeature;
+var VectorTileLayer = vectortilelayer;
+var index = {
+	VectorTile: VectorTile,
+	VectorTileFeature: VectorTileFeature,
+	VectorTileLayer: VectorTileLayer
+};
+
+exports['default'] = index;
+exports.VectorTile = VectorTile;
+exports.VectorTileFeature = VectorTileFeature;
+exports.VectorTileLayer = VectorTileLayer;
 
+}((this.vectortile = this.vectortile || {})));}).call(ol.ext);
+
+goog.provide('ol.render.Feature');
+
+goog.require('ol');
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryType');
 
 
 /**
- * @classdesc
- * Feature format for reading and writing data in the GeoJSON format.
+ * 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
- * @extends {ol.format.JSONFeature}
- * @param {olx.format.GeoJSONOptions=} opt_options Options.
- * @api stable
+ * @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.
+ * @param {number|string|undefined} id Feature id.
  */
-ol.format.GeoJSON = function(opt_options) {
+ol.render.Feature = function(type, flatCoordinates, ends, properties, id) {
+  /**
+   * @private
+   * @type {ol.Extent|undefined}
+   */
+  this.extent_;
 
-  var options = opt_options ? opt_options : {};
+  /**
+   * @private
+   * @type {number|string|undefined}
+   */
+  this.id_ = id;
 
-  goog.base(this);
+  /**
+   * @private
+   * @type {ol.geom.GeometryType}
+   */
+  this.type_ = type;
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {Array.<number>}
    */
-  this.defaultDataProjection = ol.proj.get(
-      options.defaultDataProjection ?
-          options.defaultDataProjection : 'EPSG:4326');
+  this.flatCoordinates_ = flatCoordinates;
 
+  /**
+   * @private
+   * @type {Array.<number>|Array.<Array.<number>>}
+   */
+  this.ends_ = ends;
 
   /**
-   * Name of the geometry attribute for features.
-   * @type {string|undefined}
    * @private
+   * @type {Object.<string, *>}
    */
-  this.geometryName_ = options.geometryName;
-
+  this.properties_ = properties;
 };
-goog.inherits(ol.format.GeoJSON, ol.format.JSONFeature);
 
 
 /**
- * @const
- * @type {Array.<string>}
- * @private
+ * Get a feature property by its key.
+ * @param {string} key Key
+ * @return {*} Value for the requested key.
+ * @api
  */
-ol.format.GeoJSON.EXTENSIONS_ = ['.geojson'];
+ol.render.Feature.prototype.get = function(key) {
+  return this.properties_[key];
+};
 
 
 /**
- * @param {GeoJSONGeometry|GeoJSONGeometryCollection} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @private
- * @return {ol.geom.Geometry} Geometry.
+ * @return {Array.<number>|Array.<Array.<number>>} Ends or endss.
  */
-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));
+ol.render.Feature.prototype.getEnds = function() {
+  return this.ends_;
 };
 
 
 /**
- * @param {GeoJSONGeometryCollection} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @private
- * @return {ol.geom.GeometryCollection} Geometry collection.
+ * Get the extent of this feature's geometry.
+ * @return {ol.Extent} Extent.
+ * @api
  */
-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);
-};
+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_;
+};
 
 /**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.Point} Point.
+ * Get the feature identifier.  This is a stable identifier for the feature and
+ * is set when reading data from a remote source.
+ * @return {number|string|undefined} Id.
+ * @api
  */
-ol.format.GeoJSON.readPointGeometry_ = function(object) {
-  goog.asserts.assert(object.type == 'Point',
-      'object.type should be Point');
-  return new ol.geom.Point(object.coordinates);
+ol.render.Feature.prototype.getId = function() {
+  return this.id_;
 };
 
 
 /**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.LineString} LineString.
+ * @return {Array.<number>} Flat coordinates.
  */
-ol.format.GeoJSON.readLineStringGeometry_ = function(object) {
-  goog.asserts.assert(object.type == 'LineString',
-      'object.type should be LineString');
-  return new ol.geom.LineString(object.coordinates);
+ol.render.Feature.prototype.getOrientedFlatCoordinates = function() {
+  return this.flatCoordinates_;
 };
 
 
 /**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.MultiLineString} MultiLineString.
+ * @return {Array.<number>} Flat coordinates.
  */
-ol.format.GeoJSON.readMultiLineStringGeometry_ = function(object) {
-  goog.asserts.assert(object.type == 'MultiLineString',
-      'object.type should be MultiLineString');
-  return new ol.geom.MultiLineString(object.coordinates);
-};
+ol.render.Feature.prototype.getFlatCoordinates =
+    ol.render.Feature.prototype.getOrientedFlatCoordinates;
 
 
 /**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.MultiPoint} MultiPoint.
+ * For API compatibility with {@link ol.Feature}, this method is useful when
+ * determining the geometry type in style function (see {@link #getType}).
+ * @return {ol.render.Feature} Feature.
+ * @api
  */
-ol.format.GeoJSON.readMultiPointGeometry_ = function(object) {
-  goog.asserts.assert(object.type == 'MultiPoint',
-      'object.type should be MultiPoint');
-  return new ol.geom.MultiPoint(object.coordinates);
+ol.render.Feature.prototype.getGeometry = function() {
+  return this;
 };
 
 
 /**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.MultiPolygon} MultiPolygon.
+ * Get the feature properties.
+ * @return {Object.<string, *>} Feature properties.
+ * @api
  */
-ol.format.GeoJSON.readMultiPolygonGeometry_ = function(object) {
-  goog.asserts.assert(object.type == 'MultiPolygon',
-      'object.type should be MultiPolygon');
-  return new ol.geom.MultiPolygon(object.coordinates);
+ol.render.Feature.prototype.getProperties = function() {
+  return this.properties_;
 };
 
 
 /**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.Polygon} Polygon.
+ * Get the feature for working with its geometry.
+ * @return {ol.render.Feature} Feature.
  */
-ol.format.GeoJSON.readPolygonGeometry_ = function(object) {
-  goog.asserts.assert(object.type == 'Polygon',
-      'object.type should be Polygon');
-  return new ol.geom.Polygon(object.coordinates);
-};
+ol.render.Feature.prototype.getSimplifiedGeometry =
+    ol.render.Feature.prototype.getGeometry;
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometry|GeoJSONGeometryCollection} GeoJSON geometry.
+ * @return {number} Stride.
  */
-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);
+ol.render.Feature.prototype.getStride = function() {
+  return 2;
 };
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @private
- * @return {GeoJSONGeometryCollection} Empty GeoJSON geometry collection.
+ * @return {undefined}
  */
-ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ = function(geometry) {
-  return /** @type {GeoJSONGeometryCollection} */ ({
-    type: 'GeometryCollection',
-    geometries: []
-  });
-};
+ol.render.Feature.prototype.getStyleFunction = ol.nullFunction;
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometryCollection} GeoJSON geometry collection.
+ * Get the type of this feature's geometry.
+ * @return {ol.geom.GeometryType} Geometry type.
+ * @api
  */
-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
-  });
+ol.render.Feature.prototype.getType = function() {
+  return this.type_;
 };
 
+//FIXME Implement projection handling
 
-/**
- * @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()
-  });
-};
+goog.provide('ol.format.MVT');
+
+goog.require('ol');
+goog.require('ol.ext.PBF');
+goog.require('ol.ext.vectortile.VectorTile');
+goog.require('ol.format.Feature');
+goog.require('ol.format.FormatType');
+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.Projection');
+goog.require('ol.proj.Units');
+goog.require('ol.render.Feature');
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
+ * @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.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()
-  });
-};
+ol.format.MVT = function(opt_options) {
 
+  ol.format.Feature.call(this);
 
-/**
- * @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()
+  var options = opt_options ? opt_options : {};
+
+  /**
+   * @type {ol.proj.Projection}
+   */
+  this.defaultDataProjection = new ol.proj.Projection({
+    code: '',
+    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,*>,number)}
+   */
+  this.featureClass_ = options.featureClass ?
+      options.featureClass : ol.render.Feature;
+
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.geometryName_ = options.geometryName;
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.layerName_ = options.layerName ? options.layerName : 'layer';
+
+  /**
+   * @private
+   * @type {Array.<string>}
+   */
+  this.layers_ = options.layers ? options.layers : null;
 
-/**
- * @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)
-  });
 };
+ol.inherits(ol.format.MVT, ol.format.Feature);
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
+ * @inheritDoc
  */
-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()
-  });
+ol.format.MVT.prototype.getType = function() {
+  return ol.format.FormatType.ARRAY_BUFFER;
 };
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
  * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
+ * @param {Object} rawFeature Raw Mapbox feature.
+ * @param {string} layer Layer.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
  */
-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;
+ol.format.MVT.prototype.readFeature_ = function(
+    rawFeature, layer, opt_options) {
+  var feature = new this.featureClass_();
+  var id = rawFeature.id;
+  var values = rawFeature.properties;
+  values[this.layerName_] = layer;
+  if (this.geometryName_) {
+    feature.setGeometryName(this.geometryName_);
   }
-  return /** @type {GeoJSONGeometry} */ ({
-    type: 'Polygon',
-    coordinates: geometry.getCoordinates(right)
-  });
+  var geometry = ol.format.Feature.transformWithOptions(
+      ol.format.MVT.readGeometry_(rawFeature), false,
+      this.adaptOptions(opt_options));
+  feature.setGeometry(geometry);
+  feature.setId(id);
+  feature.setProperties(values);
+  return feature;
 };
 
 
 /**
- * @const
  * @private
- * @type {Object.<string, function(GeoJSONObject): ol.geom.Geometry>}
+ * @param {Object} rawFeature Raw Mapbox feature.
+ * @param {string} layer Layer.
+ * @return {ol.render.Feature} Feature.
  */
-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_
-};
+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;
+  var id = rawFeature.id;
 
-/**
- * @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_
+  return new this.featureClass_(geometryType, flatCoordinates, ends, values, id);
 };
 
 
 /**
  * @inheritDoc
+ * @api
  */
-ol.format.GeoJSON.prototype.getExtensions = function() {
-  return ol.format.GeoJSON.EXTENSIONS_;
-};
+ol.format.MVT.prototype.readFeatures = function(source, opt_options) {
+  var layers = this.layers_;
+
+  var pbf = new ol.ext.PBF(/** @type {ArrayBuffer} */ (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);
+    }
+  }
 
-/**
- * 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;
+  return features;
+};
 
 
 /**
- * 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
+ * @inheritDoc
+ * @api
  */
-ol.format.GeoJSON.prototype.readFeatures;
+ol.format.MVT.prototype.readProjection = function(source) {
+  return this.defaultDataProjection;
+};
 
 
 /**
- * @inheritDoc
+ * Sets the layers that features will be read from.
+ * @param {Array.<string>} layers Layers.
+ * @api
  */
-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) {
-    feature.setId(geoJSONFeature.id);
-  }
-  if (geoJSONFeature.properties) {
-    feature.setProperties(geoJSONFeature.properties);
-  }
-  return feature;
+ol.format.MVT.prototype.setLayers = function(layers) {
+  this.layers_ = layers;
 };
 
 
 /**
- * @inheritDoc
+ * @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.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));
+ol.format.MVT.calculateFlatCoordinates_ = function(
+    coords, flatCoordinates, ends) {
+  var end = 0;
+  for (var i = 0, ii = coords.length; i < ii; ++i) {
+    var line = coords[i];
+    var j, jj;
+    for (j = 0, jj = line.length; j < jj; ++j) {
+      var coord = line[j];
+      // Non-tilespace coords can be calculated here when a TileGrid and
+      // TileCoord are known.
+      flatCoordinates.push(coord.x, coord.y);
     }
-    return features;
-  } else {
-    goog.asserts.fail('Unknown geoJSONObject.type: ' + geoJSONObject.type);
-    return [];
+    end += 2 * j;
+    ends.push(end);
   }
 };
 
 
 /**
- * Read a geometry from a GeoJSON source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @private
+ * @param {Object} rawFeature Raw Mapbox feature.
  * @return {ol.geom.Geometry} Geometry.
- * @api stable
  */
-ol.format.GeoJSON.prototype.readGeometry;
+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);
+  }
 
-/**
- * @inheritDoc
- */
-ol.format.GeoJSON.prototype.readGeometryFromObject = function(
-    object, opt_options) {
-  return ol.format.GeoJSON.readGeometry_(
-      /** @type {GeoJSONGeometry} */ (object), opt_options);
+  geom.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates,
+      ends);
+
+  return geom;
 };
 
 
 /**
- * Read the projection from a GeoJSON source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
+ * Not implemented.
+ * @override
  */
-ol.format.GeoJSON.prototype.readProjection;
+ol.format.MVT.prototype.readFeature = function() {};
 
 
 /**
- * @inheritDoc
+ * Not implemented.
+ * @override
  */
-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;
-  }
-};
+ol.format.MVT.prototype.readGeometry = function() {};
 
 
 /**
- * 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
+ * Not implemented.
+ * @override
  */
-ol.format.GeoJSON.prototype.writeFeature;
+ol.format.MVT.prototype.writeFeature = function() {};
 
 
 /**
- * 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
+ * Not implemented.
+ * @override
  */
-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) {
-    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;
-};
+ol.format.MVT.prototype.writeGeometry = function() {};
 
 
 /**
- * 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
+ * Not implemented.
+ * @override
  */
-ol.format.GeoJSON.prototype.writeFeatures;
+ol.format.MVT.prototype.writeFeatures = function() {};
 
+// FIXME add typedef for stack state objects
+goog.provide('ol.format.OSMXML');
 
-/**
- * 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
-  });
-};
+goog.require('ol');
+goog.require('ol.array');
+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.obj');
+goog.require('ol.proj');
+goog.require('ol.xml');
 
 
 /**
- * Encode a geometry as a GeoJSON string.
+ * @classdesc
+ * Feature format for reading data in the
+ * [OSMXML format](http://wiki.openstreetmap.org/wiki/OSM_XML).
  *
- * @function
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} GeoJSON.
- * @api stable
+ * @constructor
+ * @extends {ol.format.XMLFeature}
+ * @api
  */
-ol.format.GeoJSON.prototype.writeGeometry;
-
+ol.format.OSMXML = function() {
+  ol.format.XMLFeature.call(this);
 
-/**
- * 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));
+  /**
+   * @inheritDoc
+   */
+  this.defaultDataProjection = ol.proj.get('EPSG:4326');
 };
+ol.inherits(ol.format.OSMXML, ol.format.XMLFeature);
 
-goog.provide('ol.format.XMLFeature');
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom.NodeType');
-goog.require('goog.dom.xml');
-goog.require('ol.format.Feature');
-goog.require('ol.format.FormatType');
-goog.require('ol.proj');
-goog.require('ol.xml');
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.OSMXML.readNode_ = function(node, objectStack) {
+  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
+  var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  var id = node.getAttribute('id');
+  /** @type {ol.Coordinate} */
+  var coordinates = [
+    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 (!ol.obj.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);
+  }
+};
 
 
 /**
- * @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}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.XMLFeature = function() {
-  goog.base(this);
+ol.format.OSMXML.readWay_ = function(node, objectStack) {
+  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]);
+  /** @type {Array.<number>} */
+  var flatCoordinates = [];
+  for (var i = 0, ii = values.ndrefs.length; i < ii; i++) {
+    var point = state.nodes[values.ndrefs[i]];
+    ol.array.extend(flatCoordinates, point);
+  }
+  var geometry;
+  if (values.ndrefs[0] == values.ndrefs[values.ndrefs.length - 1]) {
+    // closed way
+    geometry = new ol.geom.Polygon(null);
+    geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates,
+        [flatCoordinates.length]);
+  } else {
+    geometry = new ol.geom.LineString(null);
+    geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
+  }
+  ol.format.Feature.transformWithOptions(geometry, false, options);
+  var feature = new ol.Feature(geometry);
+  feature.setId(id);
+  feature.setProperties(values.tags);
+  state.features.push(feature);
 };
-goog.inherits(ol.format.XMLFeature, ol.format.Feature);
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-ol.format.XMLFeature.prototype.getType = function() {
-  return ol.format.FormatType.XML;
+ol.format.OSMXML.readNd_ = function(node, objectStack) {
+  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  values.ndrefs.push(node.getAttribute('ref'));
 };
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
  */
-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;
-  }
+ol.format.OSMXML.readTag_ = function(node, objectStack) {
+  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  values.tags[node.getAttribute('k')] = node.getAttribute('v');
 };
 
 
 /**
- * @param {Document} doc Document.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @return {ol.Feature} Feature.
+ * @const
+ * @private
+ * @type {Array.<string>}
  */
-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;
-  }
-};
+ol.format.OSMXML.NAMESPACE_URIS_ = [
+  null
+];
 
 
 /**
- * @param {Node} node Node.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @return {ol.Feature} Feature.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.format.XMLFeature.prototype.readFeatureFromNode = goog.abstractMethod;
+ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OSMXML.NAMESPACE_URIS_, {
+      'nd': ol.format.OSMXML.readNd_,
+      'tag': ol.format.OSMXML.readTag_
+    });
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.format.XMLFeature.prototype.readFeatures = function(source, opt_options) {
-  if (ol.xml.isDocument(source)) {
-    return this.readFeaturesFromDocument(
-        /** @type {Document} */ (source), opt_options);
-  } else if (ol.xml.isNode(source)) {
-    return this.readFeaturesFromNode(/** @type {Node} */ (source), opt_options);
-  } else if (goog.isString(source)) {
-    var doc = ol.xml.parse(source);
-    return this.readFeaturesFromDocument(doc, opt_options);
-  } else {
-    goog.asserts.fail('Unknown source type');
-    return [];
-  }
-};
+ol.format.OSMXML.PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OSMXML.NAMESPACE_URIS_, {
+      'node': ol.format.OSMXML.readNode_,
+      'way': ol.format.OSMXML.readWay_
+    });
 
 
 /**
- * @param {Document} doc Document.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @protected
- * @return {Array.<ol.Feature>} Features.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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;
-};
+ol.format.OSMXML.NODE_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OSMXML.NAMESPACE_URIS_, {
+      'tag': ol.format.OSMXML.readTag_
+    });
 
 
 /**
- * @param {Node} node Node.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @protected
+ * 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
  */
-ol.format.XMLFeature.prototype.readFeaturesFromNode = goog.abstractMethod;
+ol.format.OSMXML.prototype.readFeatures;
 
 
 /**
  * @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;
+ol.format.OSMXML.prototype.readFeaturesFromNode = function(node, opt_options) {
+  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 [];
 };
 
 
 /**
- * @param {Document} doc Document.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @protected
- * @return {ol.geom.Geometry} Geometry.
+ * Read the projection from an OSM source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
  */
-ol.format.XMLFeature.prototype.readGeometryFromDocument = goog.abstractMethod;
+ol.format.OSMXML.prototype.readProjection;
 
 
 /**
- * @param {Node} node Node.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @protected
- * @return {ol.geom.Geometry} Geometry.
+ * Not implemented.
+ * @inheritDoc
  */
-ol.format.XMLFeature.prototype.readGeometryFromNode = goog.abstractMethod;
+ol.format.OSMXML.prototype.writeFeatureNode = function(feature, opt_options) {};
 
 
 /**
+ * Not implemented.
  * @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;
-  }
-};
+ol.format.OSMXML.prototype.writeFeaturesNode = function(features, opt_options) {};
 
 
 /**
- * @param {Document} doc Document.
- * @protected
- * @return {ol.proj.Projection} Projection.
+ * Not implemented.
+ * @inheritDoc
  */
-ol.format.XMLFeature.prototype.readProjectionFromDocument = function(doc) {
-  return this.defaultDataProjection;
-};
+ol.format.OSMXML.prototype.writeGeometryNode = function(geometry, opt_options) {};
+
+goog.provide('ol.format.XLink');
 
 
 /**
- * @param {Node} node Node.
- * @protected
- * @return {ol.proj.Projection} Projection.
+ * @const
+ * @type {string}
  */
-ol.format.XMLFeature.prototype.readProjectionFromNode = function(node) {
-  return this.defaultDataProjection;
-};
+ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink';
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node.
+ * @return {boolean|undefined} Boolean.
  */
-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));
+ol.format.XLink.readHref = function(node) {
+  return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href');
 };
 
+goog.provide('ol.format.XML');
 
-/**
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @protected
- * @return {Node} Node.
- */
-ol.format.XMLFeature.prototype.writeFeatureNode = goog.abstractMethod;
+goog.require('ol.xml');
 
 
 /**
- * @inheritDoc
+ * @classdesc
+ * Generic format for reading non-feature XML data
+ *
+ * @constructor
+ * @abstract
+ * @struct
  */
-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));
+ol.format.XML = function() {
 };
 
 
 /**
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {Node} Node.
+ * @param {Document|Node|string} source Source.
+ * @return {Object} The parsed result.
  */
-ol.format.XMLFeature.prototype.writeFeaturesNode = goog.abstractMethod;
+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 (typeof source === 'string') {
+    var doc = ol.xml.parse(source);
+    return this.readFromDocument(doc);
+  } else {
+    return null;
+  }
+};
 
 
 /**
- * @inheritDoc
+ * @abstract
+ * @param {Document} doc Document.
+ * @return {Object} Object
  */
-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));
-};
+ol.format.XML.prototype.readFromDocument = function(doc) {};
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {Node} Node.
+ * @abstract
+ * @param {Node} node Node.
+ * @return {Object} Object
  */
-ol.format.XMLFeature.prototype.writeGeometryNode = goog.abstractMethod;
+ol.format.XML.prototype.readFromNode = function(node) {};
 
-// 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.provide('ol.format.OWS');
 
-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');
+goog.require('ol.format.XLink');
+goog.require('ol.format.XML');
+goog.require('ol.format.XSD');
 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}
+ * @extends {ol.format.XML}
  */
-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);
+ol.format.OWS = function() {
+  ol.format.XML.call(this);
 };
-goog.inherits(ol.format.GMLBase, ol.format.XMLFeature);
+ol.inherits(ol.format.OWS, ol.format.XML);
 
 
 /**
- * @const
- * @type {string}
+ * @inheritDoc
  */
-ol.format.GMLBase.GMLNS = 'http://www.opengis.net/gml';
+ol.format.OWS.prototype.readFromDocument = function(doc) {
+  for (var n = doc.firstChild; n; n = n.nextSibling) {
+    if (n.nodeType == Node.ELEMENT_NODE) {
+      return this.readFromNode(n);
+    }
+  }
+  return null;
+};
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<ol.Feature>} Features.
+ * @inheritDoc
  */
-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;
+ol.format.OWS.prototype.readFromNode = function(node) {
+  var owsObject = ol.xml.pushParseAndPop({},
+      ol.format.OWS.PARSERS_, node, []);
+  return owsObject ? owsObject : null;
 };
 
 
 /**
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.Geometry|undefined} Geometry.
+ * @private
+ * @return {Object|undefined} The address.
  */
-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;
-  }
+ol.format.OWS.readAddress_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.ADDRESS_PARSERS_, node, objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
- * @return {ol.Feature} Feature.
+ * @private
+ * @return {Object|undefined} The values.
  */
-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;
+ol.format.OWS.readAllowedValues_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.ALLOWED_VALUES_PARSERS_, node, objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.Point|undefined} Point.
+ * @private
+ * @return {Object|undefined} The constraint.
  */
-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;
+ol.format.OWS.readConstraint_ = function(node, objectStack) {
+  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.
- * @return {ol.geom.MultiPoint|undefined} MultiPoint.
+ * @private
+ * @return {Object|undefined} The contact info.
  */
-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;
-  }
+ol.format.OWS.readContactInfo_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.CONTACT_INFO_PARSERS_, node, objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.MultiLineString|undefined} MultiLineString.
+ * @private
+ * @return {Object|undefined} The DCP.
  */
-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;
-  }
+ol.format.OWS.readDcp_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.DCP_PARSERS_, node, objectStack);
 };
 
 
 /**
  * @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 {
+ * @private
+ * @return {Object|undefined} The GET object.
+ */
+ol.format.OWS.readGet_ = function(node, objectStack) {
+  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);
 };
 
 
@@ -89795,15 +51359,11 @@ ol.format.GMLBase.prototype.readMultiPolygon = function(node, objectStack) {
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
  * @private
+ * @return {Object|undefined} The HTTP object.
  */
-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);
+ol.format.OWS.readHttp_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({}, ol.format.OWS.HTTP_PARSERS_,
+      node, objectStack);
 };
 
 
@@ -89811,16 +51371,18 @@ ol.format.GMLBase.prototype.pointMemberParser_ = function(node, objectStack) {
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
  * @private
+ * @return {Object|undefined} The operation.
  */
-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);
+ol.format.OWS.readOperation_ = function(node, objectStack) {
+  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]);
+  object[name] = value;
 };
 
 
@@ -89828,38 +51390,25 @@ ol.format.GMLBase.prototype.lineStringMemberParser_ =
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
  * @private
+ * @return {Object|undefined} The operations metadata.
  */
-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);
+ol.format.OWS.readOperationsMetadata_ = function(node,
+    objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.OPERATIONS_METADATA_PARSERS_, node,
+      objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.LineString|undefined} LineString.
+ * @private
+ * @return {Object|undefined} The phone.
  */
-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;
-  }
+ol.format.OWS.readPhone_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.PHONE_PARSERS_, node, objectStack);
 };
 
 
@@ -89867,74 +51416,39 @@ ol.format.GMLBase.prototype.readLineString = function(node, objectStack) {
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
  * @private
- * @return {Array.<number>|undefined} LinearRing flat coordinates.
+ * @return {Object|undefined} The service identification.
  */
-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;
-  }
+ol.format.OWS.readServiceIdentification_ = function(node,
+    objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_, node,
+      objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.LinearRing|undefined} LinearRing.
+ * @private
+ * @return {Object|undefined} The service contact.
  */
-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;
-  }
+ol.format.OWS.readServiceContact_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.OWS.SERVICE_CONTACT_PARSERS_, node,
+      objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.Polygon|undefined} Polygon.
+ * @private
+ * @return {Object|undefined} The service provider.
  */
-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;
-  }
+ol.format.OWS.readServiceProvider_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.OWS.SERVICE_PROVIDER_PARSERS_, node,
+      objectStack);
 };
 
 
@@ -89942,21115 +51456,21539 @@ ol.format.GMLBase.prototype.readPolygon = function(node, objectStack) {
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
  * @private
- * @return {Array.<number>} Flat coordinates.
+ * @return {string|undefined} The value.
  */
-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));
+ol.format.OWS.readValue_ = function(node, objectStack) {
+  return ol.format.XSD.readString(node);
 };
 
 
 /**
  * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @type {Array.<string>}
  * @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_)
-  }
-});
+ol.format.OWS.NAMESPACE_URIS_ = [
+  null,
+  'http://www.opengis.net/ows/1.1'
+];
 
 
 /**
  * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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_)
-  }
-});
+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>>}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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_)
-  }
-});
+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>>}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.GMLBase.prototype.POINTMEMBER_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'Point': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_)
-  }
-});
+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>>}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.GMLBase.prototype.LINESTRINGMEMBER_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'LineString': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.readLineString)
-  }
-});
+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>>}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.GMLBase.prototype.POLYGONMEMBER_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'Polygon': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.readPolygon)
-  }
-});
+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>>}
- * @protected
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.format.GMLBase.prototype.RING_PARSERS = Object({
-  'http://www.opengis.net/gml' : {
-    'LinearRing': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readFlatLinearRing_)
-  }
-});
+ol.format.OWS.DCP_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'HTTP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readHttp_)
+    });
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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;
-};
+ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Get': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readGet_),
+      'Post': undefined // TODO
+    });
 
 
 /**
- * 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
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.format.GMLBase.prototype.readFeatures;
+ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'DCP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readDcp_)
+    });
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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]);
-};
+ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Operation': ol.format.OWS.readOperation_
+    });
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.format.GMLBase.prototype.readProjectionFromNode = function(node) {
-  return ol.proj.get(this.srsName_ ? this.srsName_ :
-      node.firstElementChild.getAttribute('srsName'));
-};
+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)
+    });
 
-goog.provide('ol.format.XSD');
 
-goog.require('goog.asserts');
-goog.require('goog.string');
-goog.require('ol');
-goog.require('ol.xml');
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @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 {string}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.format.XSD.NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema';
+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_)
+    });
 
 
 /**
- * @param {Node} node Node.
- * @return {boolean|undefined} Boolean.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.format.XSD.readBoolean = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  return ol.format.XSD.readBooleanString(s);
-};
+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)
+    });
 
 
 /**
- * @param {string} string String.
- * @return {boolean|undefined} Boolean.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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;
-  }
-};
+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');
 
 
 /**
- * @param {Node} node Node.
- * @return {number|undefined} DateTime in seconds.
+ * @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.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;
+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 {
-    return undefined;
+    dest = [];
+    destOffset = 0;
+  }
+  var j = offset;
+  while (j < end) {
+    var x = flatCoordinates[j++];
+    dest[destOffset++] = flatCoordinates[j++];
+    dest[destOffset++] = x;
+    for (var k = 2; k < stride; ++k) {
+      dest[destOffset++] = flatCoordinates[j++];
+    }
   }
+  dest.length = destOffset;
+  return dest;
 };
 
+goog.provide('ol.format.Polyline');
+
+goog.require('ol');
+goog.require('ol.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');
+
 
 /**
- * @param {Node} node Node.
- * @return {number|undefined} Decimal.
+ * @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
  */
-ol.format.XSD.readDecimal = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  return ol.format.XSD.readDecimalString(s);
+ol.format.Polyline = function(opt_options) {
+
+  var options = opt_options ? opt_options : {};
+
+  ol.format.TextFeature.call(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;
 };
+ol.inherits(ol.format.Polyline, ol.format.TextFeature);
 
 
 /**
- * @param {string} string String.
- * @return {number|undefined} Decimal.
+ * 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.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;
+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);
 };
 
 
 /**
- * @param {Node} node Node.
- * @return {number|undefined} Non negative integer.
+ * 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.XSD.readNonNegativeInteger = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  return ol.format.XSD.readNonNegativeIntegerString(s);
+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;
 };
 
 
 /**
- * @param {string} string String.
- * @return {number|undefined} Non negative integer.
+ * 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.XSD.readNonNegativeIntegerString = function(string) {
-  var m = /^\s*(\d+)\s*$/.exec(string);
-  if (m) {
-    return parseInt(m[1], 10);
-  } else {
-    return undefined;
+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);
 };
 
 
 /**
- * @param {Node} node Node.
- * @return {string|undefined} String.
+ * 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.XSD.readString = function(node) {
-  return ol.xml.getAllTextContent(node, false).trim();
+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;
 };
 
 
 /**
- * @param {Node} node Node to append a TextNode with the boolean to.
- * @param {boolean} bool Boolean.
+ * 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.XSD.writeBooleanTextNode = function(node, bool) {
-  ol.format.XSD.writeStringTextNode(node, (bool) ? '1' : '0');
+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);
 };
 
 
 /**
- * @param {Node} node Node to append a TextNode with the dateTime to.
- * @param {number} dateTime DateTime in seconds.
+ * 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.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));
+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;
 };
 
 
 /**
- * @param {Node} node Node to append a TextNode with the decimal to.
- * @param {number} decimal Decimal.
+ * 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.XSD.writeDecimalTextNode = function(node, decimal) {
-  var string = decimal.toPrecision();
-  node.appendChild(ol.xml.DOCUMENT.createTextNode(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;
 };
 
 
 /**
- * @param {Node} node Node to append a TextNode with the decimal to.
- * @param {number} nonNegativeInteger Non negative integer.
+ * 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.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));
+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;
 };
 
 
 /**
- * @param {Node} node Node to append a TextNode with the string to.
- * @param {string} string String.
+ * 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.XSD.writeStringTextNode = function(node, string) {
-  node.appendChild(ol.xml.DOCUMENT.createTextNode(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;
 };
 
-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.
+ * Read the feature from the Polyline source. The coordinates are assumed to be
+ * in two dimensions and in latitude, longitude order.
  *
- * @constructor
- * @param {olx.format.GMLOptions=} opt_options Optional configuration object.
- * @extends {ol.format.GMLBase}
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
  * @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);
+ol.format.Polyline.prototype.readFeature;
 
-  /**
-   * @inheritDoc
-   */
-  this.schemaLocation = options.schemaLocation ?
-      options.schemaLocation : ol.format.GML2.schemaLocation_;
 
+/**
+ * @inheritDoc
+ */
+ol.format.Polyline.prototype.readFeatureFromText = function(text, opt_options) {
+  var geometry = this.readGeometryFromText(text, opt_options);
+  return new ol.Feature(geometry);
 };
-goog.inherits(ol.format.GML2, ol.format.GMLBase);
 
 
 /**
- * @const
- * @type {string}
- * @private
+ * 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
  */
-ol.format.GML2.schemaLocation_ = ol.format.GMLBase.GMLNS +
-    ' http://schemas.opengis.net/gml/2.1.2/feature.xsd';
+ol.format.Polyline.prototype.readFeatures;
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>|undefined} Flat coordinates.
+ * @inheritDoc
  */
-ol.format.GML2.prototype.readFlatCoordinates_ = function(node, objectStack) {
-  var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
-  var context = objectStack[0];
-  goog.asserts.assert(goog.isObject(context), '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;
+ol.format.Polyline.prototype.readFeaturesFromText = function(text, opt_options) {
+  var feature = this.readFeatureFromText(text, opt_options);
+  return [feature];
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Extent|undefined} Envelope.
+ * 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
  */
-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]);
-};
+ol.format.Polyline.prototype.readGeometry;
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @inheritDoc
  */
-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);
-  }
+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)));
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Read the projection from a Polyline source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
  */
-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;
-  }
-};
+ol.format.Polyline.prototype.readProjection;
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @inheritDoc
  */
-ol.format.GML2.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'coordinates': ol.xml.makeReplacer(
-        ol.format.GML2.prototype.readFlatCoordinates_)
+ol.format.Polyline.prototype.writeFeatureText = function(feature, opt_options) {
+  var geometry = feature.getGeometry();
+  if (geometry) {
+    return this.writeGeometryText(geometry, opt_options);
+  } else {
+    ol.asserts.assert(false, 40); // Expected `feature` to have a geometry
+    return '';
   }
-});
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @inheritDoc
  */
-ol.format.GML2.prototype.FLAT_LINEAR_RINGS_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'innerBoundaryIs': ol.format.GML2.prototype.innerBoundaryIsParser_,
-    'outerBoundaryIs': ol.format.GML2.prototype.outerBoundaryIsParser_
-  }
-});
+ol.format.Polyline.prototype.writeFeaturesText = function(features, opt_options) {
+  return this.writeFeatureText(features[0], opt_options);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * 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
  */
-ol.format.GML2.prototype.BOX_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'coordinates': ol.xml.makeArrayPusher(
-        ol.format.GML2.prototype.readFlatCoordinates_)
-  }
-});
+ol.format.Polyline.prototype.writeGeometry;
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @inheritDoc
  */
-ol.format.GML2.prototype.GEOMETRY_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint),
-    'MultiPoint': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readMultiPoint),
-    'LineString': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readLineString),
-    'MultiLineString': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readMultiLineString),
-    'LinearRing' : ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readLinearRing),
-    'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon),
-    'MultiPolygon': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readMultiPolygon),
-    'Box': ol.xml.makeReplacer(ol.format.GML2.prototype.readBox_)
-  }
-});
+ol.format.Polyline.prototype.writeGeometryText = function(geometry, opt_options) {
+  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.GML');
-goog.provide('ol.format.GML3');
+goog.provide('ol.format.TopoJSON');
 
-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.format.JSONFeature');
 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
- * 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.
+ * Feature format for reading data in the TopoJSON format.
  *
  * @constructor
- * @param {olx.format.GMLOptions=} opt_options
- *     Optional configuration object.
- * @extends {ol.format.GMLBase}
+ * @extends {ol.format.JSONFeature}
+ * @param {olx.format.TopoJSONOptions=} opt_options Options.
  * @api
  */
-ol.format.GML3 = function(opt_options) {
-  var options = /** @type {olx.format.GMLOptions} */
-      (opt_options ? opt_options : {});
-
-  goog.base(this, options);
+ol.format.TopoJSON = function(opt_options) {
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.surface_ = options.surface !== undefined ? options.surface : false;
+  var options = opt_options ? opt_options : {};
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.curve_ = options.curve !== undefined ? options.curve : false;
+  ol.format.JSONFeature.call(this);
 
   /**
    * @private
-   * @type {boolean}
+   * @type {string|undefined}
    */
-  this.multiCurve_ = options.multiCurve !== undefined ?
-      options.multiCurve : true;
+  this.layerName_ = options.layerName;
 
   /**
    * @private
-   * @type {boolean}
+   * @type {Array.<string>}
    */
-  this.multiSurface_ = options.multiSurface !== undefined ?
-      options.multiSurface : true;
+  this.layers_ = options.layers ? options.layers : null;
 
   /**
    * @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';
-
+  this.defaultDataProjection = ol.proj.get(
+      options.defaultDataProjection ?
+          options.defaultDataProjection : 'EPSG:4326');
 
-/**
- * @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;
-  }
 };
+ol.inherits(ol.format.TopoJSON, ol.format.JSONFeature);
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * 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
- * @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;
+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);
   }
-};
-
-
-/**
- * @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);
+  // provide fresh copies of coordinate arrays
+  for (j = 0, jj = coordinates.length; j < jj; ++j) {
+    coordinates[j] = coordinates[j].slice();
   }
+  return coordinates;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * 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.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;
+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);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * 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
- * @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);
+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);
     }
-    polygon.setFlatCoordinates(
-        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
-    return polygon;
-  } else {
-    return undefined;
   }
+  return new ol.geom.MultiPoint(coordinates);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * 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
- * @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;
-  }
+ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) {
+  var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs);
+  return new ol.geom.LineString(coordinates);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * 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
- * @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]);
+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);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * 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
- * @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;
+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 flatCoordinates;
+  return new ol.geom.Polygon(coordinates);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * 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
- * @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);
+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 flatCoordinates;
+  return new ol.geom.MultiPolygon(coordinates);
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * 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 {string|undefined} property Property to set the `GeometryCollection`'s parent
+ *     object to.
+ * @param {string} name Name of the `Topology`'s child object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Array of features.
  * @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_)
+ol.format.TopoJSON.readFeaturesFromGeometryCollection_ = function(
+    collection, arcs, scale, translate, property, name, 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, property, name, opt_options);
   }
-});
+  return features;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * 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 {string|undefined} property Property to set the `GeometryCollection`'s parent
+ *     object to.
+ * @param {string} name Name of the `Topology`'s child object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
  * @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_
+ol.format.TopoJSON.readFeatureFromGeometry_ = function(object, arcs,
+    scale, translate, property, name, opt_options) {
+  var geometry;
+  var type = object.type;
+  var geometryReader = ol.format.TopoJSON.GEOMETRY_READERS_[type];
+  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);
+  }
+  var properties = object.properties;
+  if (property) {
+    if (!properties) {
+      properties = {};
+    }
+    properties[property] = name;
+  }
+  if (properties) {
+    feature.setProperties(properties);
+  }
+  return feature;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Read all features from a TopoJSON source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {Array.<ol.Feature>} Features.
+ * @api
  */
-ol.format.GML3.prototype.GEOMETRY_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint),
-    'MultiPoint': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readMultiPoint),
-    'LineString': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readLineString),
-    'MultiLineString': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readMultiLineString),
-    'LinearRing' : ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readLinearRing),
-    'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon),
-    'MultiPolygon': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readMultiPolygon),
-    'Surface': ol.xml.makeReplacer(ol.format.GML3.prototype.readSurface_),
-    'MultiSurface': ol.xml.makeReplacer(
-        ol.format.GML3.prototype.readMultiSurface_),
-    'Curve': ol.xml.makeReplacer(ol.format.GML3.prototype.readCurve_),
-    'MultiCurve': ol.xml.makeReplacer(
-        ol.format.GML3.prototype.readMultiCurve_),
-    'Envelope': ol.xml.makeReplacer(ol.format.GML3.prototype.readEnvelope_)
-  }
-});
+ol.format.TopoJSON.prototype.readFeatures;
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @inheritDoc
  */
-ol.format.GML3.prototype.MULTICURVE_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'curveMember': ol.xml.makeArrayPusher(
-        ol.format.GML3.prototype.curveMemberParser_),
-    'curveMembers': ol.xml.makeArrayPusher(
-        ol.format.GML3.prototype.curveMemberParser_)
+ol.format.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 = topoJSONTopology.objects;
+    var property = this.layerName_;
+    var objectName, feature;
+    for (objectName in topoJSONFeatures) {
+      if (this.layers_ && this.layers_.indexOf(objectName) == -1) {
+        continue;
+      }
+      if (topoJSONFeatures[objectName].type === 'GeometryCollection') {
+        feature = /** @type {TopoJSONGeometryCollection} */
+            (topoJSONFeatures[objectName]);
+        features.push.apply(features,
+            ol.format.TopoJSON.readFeaturesFromGeometryCollection_(
+                feature, arcs, scale, translate, property, objectName, opt_options));
+      } else {
+        feature = /** @type {TopoJSONGeometry} */
+            (topoJSONFeatures[objectName]);
+        features.push(ol.format.TopoJSON.readFeatureFromGeometry_(
+            feature, arcs, scale, translate, property, objectName, opt_options));
+      }
+    }
+    return features;
+  } else {
+    return [];
   }
-});
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * 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.GML3.prototype.MULTISURFACE_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'surfaceMember': ol.xml.makeArrayPusher(
-        ol.format.GML3.prototype.surfaceMemberParser_),
-    'surfaceMembers': ol.xml.makeArrayPusher(
-        ol.format.GML3.prototype.surfaceMemberParser_)
+ol.format.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);
   }
-});
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * 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.GML3.prototype.CURVEMEMBER_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'LineString': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.readLineString),
-    'Curve': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readCurve_)
+ol.format.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);
   }
-});
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * 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.GML3.prototype.SURFACEMEMBER_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'Polygon': ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readPolygon),
-    'Surface': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readSurface_)
-  }
-});
+ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) {
+  vertex[0] = vertex[0] * scale[0] + translate[0];
+  vertex[1] = vertex[1] * scale[1] + translate[1];
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Read the projection from a TopoJSON source.
+ *
+ * @param {Document|Node|Object|string} object Source.
+ * @return {ol.proj.Projection} Projection.
+ * @override
+ * @api
  */
-ol.format.GML3.prototype.SURFACE_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'patches': ol.xml.makeReplacer(ol.format.GML3.prototype.readPatch_)
-  }
-});
+ol.format.TopoJSON.prototype.readProjection;
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @inheritDoc
  */
-ol.format.GML3.prototype.CURVE_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'segments': ol.xml.makeReplacer(ol.format.GML3.prototype.readSegment_)
-  }
-});
+ol.format.TopoJSON.prototype.readProjectionFromObject = function(object) {
+  return this.defaultDataProjection;
+};
 
 
 /**
  * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
  * @private
+ * @type {Object.<string, function(TopoJSONGeometry, Array, ...Array): ol.geom.Geometry>}
  */
-ol.format.GML3.prototype.ENVELOPE_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'lowerCorner': ol.xml.makeArrayPusher(
-        ol.format.GML3.prototype.readFlatPosList_),
-    'upperCorner': ol.xml.makeArrayPusher(
-        ol.format.GML3.prototype.readFlatPosList_)
-  }
-});
+ol.format.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_
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Not implemented.
+ * @inheritDoc
  */
-ol.format.GML3.prototype.PATCHES_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'PolygonPatch': ol.xml.makeReplacer(
-        ol.format.GML3.prototype.readPolygonPatch_)
-  }
-});
+ol.format.TopoJSON.prototype.writeFeatureObject = function(feature, opt_options) {};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Not implemented.
+ * @inheritDoc
  */
-ol.format.GML3.prototype.SEGMENTS_PARSERS_ = Object({
-  'http://www.opengis.net/gml' : {
-    'LineStringSegment': ol.xml.makeReplacer(
-        ol.format.GML3.prototype.readLineStringSegment_)
-  }
-});
+ol.format.TopoJSON.prototype.writeFeaturesObject = function(features, opt_options) {};
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Point} value Point geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * Not implemented.
+ * @inheritDoc
  */
-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);
-};
+ol.format.TopoJSON.prototype.writeGeometryObject = function(geometry, opt_options) {};
 
 
 /**
- * @param {Array.<number>} point Point geometry.
- * @param {string=} opt_srsName Optional srsName
- * @return {string}
- * @private
+ * Not implemented.
+ * @override
  */
-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]);
-};
+ol.format.TopoJSON.prototype.readGeometryFromObject = function() {};
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LineString|ol.geom.LinearRing} value Geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * Not implemented.
+ * @override
  */
-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(' '));
-};
+ol.format.TopoJSON.prototype.readFeatureFromObject = function() {};
 
+goog.provide('ol.format.WFS');
 
-/**
- * @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);
-};
+goog.require('ol');
+goog.require('ol.asserts');
+goog.require('ol.format.GML2');
+goog.require('ol.format.GML3');
+goog.require('ol.format.GMLBase');
+goog.require('ol.format.filter');
+goog.require('ol.format.XMLFeature');
+goog.require('ol.format.XSD');
+goog.require('ol.geom.Geometry');
+goog.require('ol.obj');
+goog.require('ol.proj');
+goog.require('ol.xml');
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @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
  */
-ol.format.GML3.ENVELOPE_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'lowerCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-    'upperCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
-  }
-};
+ol.format.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_LOCATIONS[ol.format.WFS.DEFAULT_VERSION];
 
-/**
- * @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);
+  ol.format.XMLFeature.call(this);
 };
+ol.inherits(ol.format.WFS, ol.format.XMLFeature);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LinearRing} geometry LinearRing geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @const
+ * @type {string}
  */
-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);
-};
+ol.format.WFS.FEATURE_PREFIX = 'feature';
 
 
 /**
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node} Node.
- * @private
+ * @const
+ * @type {string}
  */
-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');
-};
+ol.format.WFS.XMLNS = 'http://www.w3.org/2000/xmlns/';
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Polygon} geometry Polygon geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @const
+ * @type {string}
  */
-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);
-  }
-};
+ol.format.WFS.OGCNS = 'http://www.opengis.net/ogc';
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LineString} geometry LineString geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @const
+ * @type {string}
  */
-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);
-  }
-};
+ol.format.WFS.WFSNS = 'http://www.opengis.net/wfs';
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @const
+ * @type {string}
  */
-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);
-};
+ol.format.WFS.FESNS = 'http://www.opengis.net/fes';
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.MultiPoint} geometry MultiPoint geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @const
+ * @type {Object.<string, string>}
  */
-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);
+ol.format.WFS.SCHEMA_LOCATIONS = {
+  '1.1.0': 'http://www.opengis.net/wfs ' +
+      'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd',
+  '1.0.0': 'http://www.opengis.net/wfs ' +
+      'http://schemas.opengis.net/wfs/1.0.0/wfs.xsd'
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.MultiLineString} geometry MultiLineString geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @const
+ * @type {string}
  */
-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);
-};
+ol.format.WFS.DEFAULT_VERSION = '1.1.0';
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LinearRing} ring LinearRing geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * 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
  */
-ol.format.GML3.prototype.writeRing_ = function(node, ring, objectStack) {
-  var linearRing = ol.xml.createElementNS(node.namespaceURI, 'LinearRing');
-  node.appendChild(linearRing);
-  this.writeLinearRing_(linearRing, ring, objectStack);
-};
+ol.format.WFS.prototype.readFeatures;
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Polygon} polygon Polygon geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @inheritDoc
  */
-ol.format.GML3.prototype.writeSurfaceOrPolygonMember_ =
-    function(node, polygon, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context), 'context should be an Object');
-  var child = this.GEOMETRY_NODE_FACTORY_(
-      polygon, objectStack);
-  if (child) {
-    node.appendChild(child);
-    this.writeSurfaceOrPolygon_(child, polygon, objectStack);
+ol.format.WFS.prototype.readFeaturesFromNode = function(node, opt_options) {
+  var context = /** @type {ol.XmlNodeStackItem} */ ({
+    'featureType': this.featureType_,
+    'featureNS': this.featureNS_
+  });
+  ol.obj.assign(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;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Point} point Point geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * Read transaction response of the source.
+ *
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.WFSTransactionResponse|undefined} Transaction response.
+ * @api
  */
-ol.format.GML3.prototype.writePointMember_ =
-    function(node, point, objectStack) {
-  var child = ol.xml.createElementNS(node.namespaceURI, 'Point');
-  node.appendChild(child);
-  this.writePoint_(child, point, objectStack);
+ol.format.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 (typeof source === 'string') {
+    var doc = ol.xml.parse(source);
+    return this.readTransactionResponseFromDocument(doc);
+  } else {
+    return undefined;
+  }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LineString} line LineString geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * Read feature collection metadata of the source.
+ *
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.WFSFeatureCollectionMetadata|undefined}
+ *     FeatureCollection metadata.
+ * @api
  */
-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);
+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 (typeof source === 'string') {
+    var doc = ol.xml.parse(source);
+    return this.readFeatureCollectionMetadataFromDocument(doc);
+  } else {
+    return undefined;
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Polygon} polygon Polygon geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @param {Document} doc Document.
+ * @return {ol.WFSFeatureCollectionMetadata|undefined}
+ *     FeatureCollection metadata.
  */
-ol.format.GML3.prototype.writeSurfacePatches_ =
-    function(node, polygon, objectStack) {
-  var child = ol.xml.createElementNS(node.namespaceURI, 'PolygonPatch');
-  node.appendChild(child);
-  this.writeSurfaceOrPolygon_(child, polygon, objectStack);
+ol.format.WFS.prototype.readFeatureCollectionMetadataFromDocument = function(doc) {
+  for (var n = doc.firstChild; n; n = n.nextSibling) {
+    if (n.nodeType == Node.ELEMENT_NODE) {
+      return this.readFeatureCollectionMetadataFromNode(n);
+    }
+  }
+  return undefined;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LineString} line LineString geometry.
- * @param {Array.<*>} objectStack Node stack.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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);
+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.
- * @param {ol.geom.Geometry|ol.Extent} geometry Geometry.
- * @param {Array.<*>} objectStack Node stack.
+ * @return {ol.WFSFeatureCollectionMetadata|undefined}
+ *     FeatureCollection metadata.
  */
-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);
+ol.format.WFS.prototype.readFeatureCollectionMetadataFromNode = function(node) {
+  var result = {};
+  var value = ol.format.XSD.readNonNegativeIntegerString(
+      node.getAttribute('numberOfFeatures'));
+  result['numberOfFeatures'] = value;
+  return ol.xml.pushParseAndPop(
+      /** @type {ol.WFSFeatureCollectionMetadata} */ (result),
+      ol.format.WFS.FEATURE_COLLECTION_PARSERS_, node, [], this.gmlFormat_);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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);
-        }
-      }
-    }
+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)
   }
-  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.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Transaction Summary.
  * @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);
+ol.format.WFS.readTransactionSummary_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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_)
+ol.format.WFS.OGC_FID_PARSERS_ = {
+  'http://www.opengis.net/ogc': {
+    'FeatureId': ol.xml.makeArrayPusher(function(node, objectStack) {
+      return node.getAttribute('fid');
+    })
   }
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
  * @private
  */
-ol.format.GML3.POINTMEMBER_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'pointMember': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writePointMember_)
-  }
+ol.format.WFS.fidParser_ = function(node, objectStack) {
+  ol.xml.parseNode(ol.format.WFS.OGC_FID_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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_)
+ol.format.WFS.INSERT_RESULTS_PARSERS_ = {
+  'http://www.opengis.net/wfs': {
+    'Feature': ol.format.WFS.fidParser_
   }
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<string>|undefined} Insert results.
  * @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_)
-  }
+ol.format.WFS.readInsertResults_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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)
+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')
   }
 };
 
 
 /**
- * @const
- * @type {Object.<string, string>}
- * @private
+ * @param {Document} doc Document.
+ * @return {ol.WFSTransactionResponse|undefined} Transaction response.
  */
-ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_ = {
-  'MultiLineString': 'lineStringMember',
-  'MultiCurve': 'curveMember',
-  'MultiPolygon': 'polygonMember',
-  'MultiSurface': 'surfaceMember'
+ol.format.WFS.prototype.readTransactionResponseFromDocument = function(doc) {
+  for (var n = doc.firstChild; n; n = n.nextSibling) {
+    if (n.nodeType == Node.ELEMENT_NODE) {
+      return this.readTransactionResponseFromNode(n);
+    }
+  }
+  return undefined;
 };
 
 
 /**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
- * @private
+ * @param {Node} node Node.
+ * @return {ol.WFSTransactionResponse|undefined} Transaction response.
  */
-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]);
+ol.format.WFS.prototype.readTransactionResponseFromNode = function(node) {
+  return ol.xml.pushParseAndPop(
+      /** @type {ol.WFSTransactionResponse} */({}),
+      ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_, node, []);
 };
 
 
 /**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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';
+ol.format.WFS.QUERY_SERIALIZERS_ = {
+  'http://www.opengis.net/wfs': {
+    'PropertyName': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
   }
-  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
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-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);
+ol.format.WFS.writeFeature_ = function(node, feature, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  var featureType = context['featureType'];
+  var featureNS = context['featureNS'];
+  var gmlVersion = context['gmlVersion'];
+  var child = ol.xml.createElementNS(featureNS, featureType);
+  node.appendChild(child);
+  if (gmlVersion === 2) {
+    ol.format.GML2.prototype.writeFeatureElement(child, feature, objectStack);
+  } else {
+    ol.format.GML3.prototype.writeFeatureElement(child, feature, objectStack);
   }
-  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
+ * @param {Node} node Node.
+ * @param {number|string} fid Feature identifier.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.format.GML3.prototype.writeFeatures;
+ol.format.WFS.writeOgcFidFilter_ = function(node, fid, objectStack) {
+  var filter = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'Filter');
+  var child = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'FeatureId');
+  filter.appendChild(child);
+  child.setAttribute('fid', fid);
+  node.appendChild(filter);
+};
 
 
 /**
- * Encode an array of features in the GML 3.1.1 format as an XML node.
- *
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {Node} Node.
- * @api
+ * @param {string|undefined} featurePrefix The prefix of the feature.
+ * @param {string} featureType The type of the feature.
+ * @returns {string} The value of the typeName property.
+ * @private
  */
-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);
+ol.format.WFS.getTypeName_ = function(featurePrefix, featureType) {
+  featurePrefix = featurePrefix ? featurePrefix :
+      ol.format.WFS.FEATURE_PREFIX;
+  var prefix = featurePrefix + ':';
+  // The featureType already contains the prefix.
+  if (featureType.indexOf(prefix) === 0) {
+    return featureType;
+  } else {
+    return prefix + featureType;
   }
-  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
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.format.GML = ol.format.GML3;
+ol.format.WFS.writeDelete_ = function(node, feature, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  ol.asserts.assert(feature.getId() !== undefined, 26); // Features must have an id set
+  var featureType = context['featureType'];
+  var featurePrefix = context['featurePrefix'];
+  var featureNS = context['featureNS'];
+  var typeName = ol.format.WFS.getTypeName_(featurePrefix, featureType);
+  node.setAttribute('typeName', typeName);
+  ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
+      featureNS);
+  var fid = feature.getId();
+  if (fid !== undefined) {
+    ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack);
+  }
+};
 
 
 /**
- * Encode an array of features in GML 3.1.1 Simple Features.
- *
- * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {string} Result.
- * @api stable
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.format.GML.prototype.writeFeatures;
+ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  ol.asserts.assert(feature.getId() !== undefined, 27); // Features must have an id set
+  var featureType = context['featureType'];
+  var featurePrefix = context['featurePrefix'];
+  var featureNS = context['featureNS'];
+  var typeName = ol.format.WFS.getTypeName_(featurePrefix, featureType);
+  node.setAttribute('typeName', typeName);
+  ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
+      featureNS);
+  var fid = feature.getId();
+  if (fid !== undefined) {
+    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(/** @type {ol.XmlNodeStackItem} */ (
+      {'gmlVersion': context['gmlVersion'], node: node,
+        'hasZ': context['hasZ'], 'srsName': context['srsName']}),
+        ol.format.WFS.TRANSACTION_SERIALIZERS_,
+        ol.xml.makeSimpleNodeFactory('Property'), values,
+        objectStack);
+    ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack);
+  }
+};
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @param {Object} pair Property name and value.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-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');
-
+ol.format.WFS.writeProperty_ = function(node, pair, objectStack) {
+  var name = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'Name');
+  var context = objectStack[objectStack.length - 1];
+  var gmlVersion = context['gmlVersion'];
+  node.appendChild(name);
+  ol.format.XSD.writeStringTextNode(name, pair.name);
+  if (pair.value !== undefined && pair.value !== null) {
+    var value = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'Value');
+    node.appendChild(value);
+    if (pair.value instanceof ol.geom.Geometry) {
+      if (gmlVersion === 2) {
+        ol.format.GML2.prototype.writeGeometryElement(value,
+            pair.value, objectStack);
+      } else {
+        ol.format.GML3.prototype.writeGeometryElement(value,
+            pair.value, objectStack);
+      }
+    } else {
+      ol.format.XSD.writeStringTextNode(value, pair.value);
+    }
+  }
+};
 
 
 /**
- * @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
+ * @param {Node} node Node.
+ * @param {{vendorId: string, safeToIgnore: boolean, value: string}}
+ *     nativeElement The native element.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-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;
+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);
+  }
 };
-goog.inherits(ol.format.GPX, ol.format.XMLFeature);
 
 
 /**
- * @const
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @private
- * @type {Array.<string>}
  */
-ol.format.GPX.NAMESPACE_URIS_ = [
-  null,
-  'http://www.topografix.com/GPX/1/0',
-  'http://www.topografix.com/GPX/1/1'
-];
+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 {Array.<number>} flatCoordinates Flat coordinates.
  * @param {Node} node Node.
- * @param {Object} values Values.
+ * @param {string} featureType Feature type.
+ * @param {Array.<*>} objectStack Node stack.
  * @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'];
+ol.format.WFS.writeQuery_ = function(node, featureType, objectStack) {
+  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  var featurePrefix = context['featurePrefix'];
+  var featureNS = context['featureNS'];
+  var propertyNames = context['propertyNames'];
+  var srsName = context['srsName'];
+  var typeName;
+  // If feature prefix is not defined, we must not use the default prefix.
+  if (featurePrefix) {
+    typeName = ol.format.WFS.getTypeName_(featurePrefix, featureType);
   } else {
-    flatCoordinates.push(0);
+    typeName = featureType;
   }
-  if ('time' in values) {
-    flatCoordinates.push(/** @type {number} */ (values['time']));
-    delete values['time'];
-  } else {
-    flatCoordinates.push(0);
+  node.setAttribute('typeName', typeName);
+  if (srsName) {
+    node.setAttribute('srsName', srsName);
+  }
+  if (featureNS) {
+    ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
+        featureNS);
+  }
+  var item = /** @type {ol.XmlNodeStackItem} */ (ol.obj.assign({}, context));
+  item.node = node;
+  ol.xml.pushSerializeAndPop(item,
+      ol.format.WFS.QUERY_SERIALIZERS_,
+      ol.xml.makeSimpleNodeFactory('PropertyName'), propertyNames,
+      objectStack);
+  var filter = context['filter'];
+  if (filter) {
+    var child = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'Filter');
+    node.appendChild(child);
+    ol.format.WFS.writeFilterCondition_(child, filter, objectStack);
   }
-  return flatCoordinates;
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.format.filter.Filter} filter Filter.
+ * @param {Array.<*>} objectStack Node 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);
+ol.format.WFS.writeFilterCondition_ = function(node, filter, objectStack) {
+  /** @type {ol.XmlNodeStackItem} */
+  var item = {node: node};
+  ol.xml.pushSerializeAndPop(item,
+      ol.format.WFS.GETFEATURE_SERIALIZERS_,
+      ol.xml.makeSimpleNodeFactory(filter.getTagName()),
+      [filter], objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.format.filter.Bbox} filter Filter.
+ * @param {Array.<*>} objectStack Node 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;
+ol.format.WFS.writeBboxFilter_ = function(node, filter, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  context['srsName'] = filter.srsName;
+
+  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
+  ol.format.GML3.prototype.writeGeometryElement(node, filter.extent, objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.format.filter.Intersects} filter Filter.
+ * @param {Array.<*>} objectStack Node 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);
-  }
+ol.format.WFS.writeIntersectsFilter_ = function(node, filter, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  context['srsName'] = filter.srsName;
+
+  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
+  ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.format.filter.Within} filter Filter.
+ * @param {Array.<*>} objectStack Node 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);
-  }
+ol.format.WFS.writeWithinFilter_ = function(node, filter, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  context['srsName'] = filter.srsName;
+
+  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
+  ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.format.filter.During} filter Filter.
+ * @param {Array.<*>} objectStack Node 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);
+ol.format.WFS.writeDuringFilter_ = function(node, filter, objectStack) {
+
+  var valueReference = ol.xml.createElementNS(ol.format.WFS.FESNS, 'ValueReference');
+  ol.format.XSD.writeStringTextNode(valueReference, filter.propertyName);
+  node.appendChild(valueReference);
+
+  var timePeriod = ol.xml.createElementNS(ol.format.GMLBase.GMLNS, 'TimePeriod');
+
+  node.appendChild(timePeriod);
+
+  var begin = ol.xml.createElementNS(ol.format.GMLBase.GMLNS, 'begin');
+  timePeriod.appendChild(begin);
+  ol.format.WFS.writeTimeInstant_(begin, filter.begin);
+
+  var end = ol.xml.createElementNS(ol.format.GMLBase.GMLNS, 'end');
+  timePeriod.appendChild(end);
+  ol.format.WFS.writeTimeInstant_(end, filter.end);
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.format.filter.LogicalNary} filter Filter.
+ * @param {Array.<*>} objectStack Node 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;
+ol.format.WFS.writeLogicalFilter_ = function(node, filter, objectStack) {
+  /** @type {ol.XmlNodeStackItem} */
+  var item = {node: node};
+  filter.conditions.forEach(function(condition) {
+    ol.xml.pushSerializeAndPop(item,
+        ol.format.WFS.GETFEATURE_SERIALIZERS_,
+        ol.xml.makeSimpleNodeFactory(condition.getTagName()),
+        [condition], objectStack);
+  });
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.format.filter.Not} filter Filter.
+ * @param {Array.<*>} objectStack Node 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;
+ol.format.WFS.writeNotFilter_ = function(node, filter, objectStack) {
+  /** @type {ol.XmlNodeStackItem} */
+  var item = {node: node};
+  var condition = filter.condition;
+  ol.xml.pushSerializeAndPop(item,
+      ol.format.WFS.GETFEATURE_SERIALIZERS_,
+      ol.xml.makeSimpleNodeFactory(condition.getTagName()),
+      [condition], objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.format.filter.ComparisonBinary} filter Filter.
+ * @param {Array.<*>} objectStack Node 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;
+ol.format.WFS.writeComparisonFilter_ = function(node, filter, objectStack) {
+  if (filter.matchCase !== undefined) {
+    node.setAttribute('matchCase', filter.matchCase.toString());
   }
-  var coordinates = ol.format.GPX.appendCoordinate_([], node, values);
-  var geometry = new ol.geom.Point(
-      coordinates, ol.geom.GeometryLayout.XYZM);
-  ol.format.Feature.transformWithOptions(geometry, false, options);
-  var feature = new ol.Feature(geometry);
-  feature.setProperties(values);
-  return feature;
+  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
+  ol.format.WFS.writeOgcLiteral_(node, '' + filter.expression);
 };
 
 
 /**
- * @const
- * @type {Object.<string, function(Node, Array.<*>): (ol.Feature|undefined)>}
+ * @param {Node} node Node.
+ * @param {ol.format.filter.IsNull} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
  * @private
  */
-ol.format.GPX.FEATURE_READER_ = {
-  'rte': ol.format.GPX.readRte_,
-  'trk': ol.format.GPX.readTrk_,
-  'wpt': ol.format.GPX.readWpt_
+ol.format.WFS.writeIsNullFilter_ = function(node, filter, objectStack) {
+  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {Node} node Node.
+ * @param {ol.format.filter.IsBetween} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
  * @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_)
-    });
+ol.format.WFS.writeIsBetweenFilter_ = function(node, filter, objectStack) {
+  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
+
+  var lowerBoundary = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'LowerBoundary');
+  node.appendChild(lowerBoundary);
+  ol.format.WFS.writeOgcLiteral_(lowerBoundary, '' + filter.lowerBoundary);
+
+  var upperBoundary = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'UpperBoundary');
+  node.appendChild(upperBoundary);
+  ol.format.WFS.writeOgcLiteral_(upperBoundary, '' + filter.upperBoundary);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {Node} node Node.
+ * @param {ol.format.filter.IsLike} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
  * @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')
-    });
+ol.format.WFS.writeIsLikeFilter_ = function(node, filter, objectStack) {
+  node.setAttribute('wildCard', filter.wildCard);
+  node.setAttribute('singleChar', filter.singleChar);
+  node.setAttribute('escapeChar', filter.escapeChar);
+  if (filter.matchCase !== undefined) {
+    node.setAttribute('matchCase', filter.matchCase.toString());
+  }
+  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
+  ol.format.WFS.writeOgcLiteral_(node, '' + filter.pattern);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {string} tagName Tag name.
+ * @param {Node} node Node.
+ * @param {string} value Value.
  * @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_
-    });
+ol.format.WFS.writeOgcExpression_ = function(tagName, node, value) {
+  var property = ol.xml.createElementNS(ol.format.WFS.OGCNS, tagName);
+  ol.format.XSD.writeStringTextNode(property, value);
+  node.appendChild(property);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {Node} node Node.
+ * @param {string} value PropertyName value.
  * @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)
-    });
+ol.format.WFS.writeOgcPropertyName_ = function(node, value) {
+  ol.format.WFS.writeOgcExpression_('PropertyName', node, value);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {Node} node Node.
+ * @param {string} value PropertyName value.
  * @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_
-    });
+ol.format.WFS.writeOgcLiteral_ = function(node, value) {
+  ol.format.WFS.writeOgcExpression_('Literal', node, value);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {Node} node Node.
+ * @param {string} time PropertyName value.
  * @private
  */
-ol.format.GPX.TRKSEG_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'trkpt': ol.format.GPX.parseTrkPt_
-    });
+ol.format.WFS.writeTimeInstant_ = function(node, time) {
+  var timeInstant = ol.xml.createElementNS(ol.format.GMLBase.GMLNS, 'TimeInstant');
+  node.appendChild(timeInstant);
+
+  var timePosition = ol.xml.createElementNS(ol.format.GMLBase.GMLNS, 'timePosition');
+  timeInstant.appendChild(timePosition);
+  ol.format.XSD.writeStringTextNode(timePosition, time);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @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)
-    });
+ol.format.WFS.GETFEATURE_SERIALIZERS_ = {
+  'http://www.opengis.net/wfs': {
+    'Query': ol.xml.makeChildAppender(ol.format.WFS.writeQuery_)
+  },
+  'http://www.opengis.net/ogc': {
+    'During': ol.xml.makeChildAppender(ol.format.WFS.writeDuringFilter_),
+    'And': ol.xml.makeChildAppender(ol.format.WFS.writeLogicalFilter_),
+    'Or': ol.xml.makeChildAppender(ol.format.WFS.writeLogicalFilter_),
+    'Not': ol.xml.makeChildAppender(ol.format.WFS.writeNotFilter_),
+    'BBOX': ol.xml.makeChildAppender(ol.format.WFS.writeBboxFilter_),
+    'Intersects': ol.xml.makeChildAppender(ol.format.WFS.writeIntersectsFilter_),
+    'Within': ol.xml.makeChildAppender(ol.format.WFS.writeWithinFilter_),
+    'PropertyIsEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
+    'PropertyIsNotEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
+    'PropertyIsLessThan': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
+    'PropertyIsLessThanOrEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
+    'PropertyIsGreaterThan': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
+    'PropertyIsGreaterThanOrEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
+    'PropertyIsNull': ol.xml.makeChildAppender(ol.format.WFS.writeIsNullFilter_),
+    'PropertyIsBetween': ol.xml.makeChildAppender(ol.format.WFS.writeIsBetweenFilter_),
+    'PropertyIsLike': ol.xml.makeChildAppender(ol.format.WFS.writeIsLikeFilter_)
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * Encode filter as WFS `Filter` and return the Node.
+ *
+ * @param {ol.format.filter.Filter} filter Filter.
+ * @return {Node} Result.
+ * @api
+ */
+ol.format.WFS.writeFilter = function(filter) {
+  var child = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'Filter');
+  ol.format.WFS.writeFilterCondition_(child, filter, []);
+  return child;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<string>} featureTypes Feature types.
+ * @param {Array.<*>} objectStack Node stack.
  * @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_
-    });
+ol.format.WFS.writeGetFeature_ = function(node, featureTypes, objectStack) {
+  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  var item = /** @type {ol.XmlNodeStackItem} */ (ol.obj.assign({}, 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
+ */
+ol.format.WFS.prototype.writeGetFeature = function(options) {
+  var node = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'GetFeature');
+  node.setAttribute('service', 'WFS');
+  node.setAttribute('version', '1.1.0');
+  var filter;
+  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);
+    }
+    filter = options.filter;
+    if (options.bbox) {
+      ol.asserts.assert(options.geometryName,
+          12); // `options.geometryName` must also be provided when `options.bbox` is set
+      var bbox = ol.format.filter.bbox(
+          /** @type {string} */ (options.geometryName), options.bbox, options.srsName);
+      if (filter) {
+        // if bbox and filter are both set, combine the two into a single filter
+        filter = ol.format.filter.and(filter, bbox);
+      } else {
+        filter = bbox;
+      }
+    }
+  }
+  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
+      'xsi:schemaLocation', this.schemaLocation_);
+  /** @type {ol.XmlNodeStackItem} */
+  var context = {
+    node: node,
+    'srsName': options.srsName,
+    'featureNS': options.featureNS ? options.featureNS : this.featureNS_,
+    'featurePrefix': options.featurePrefix,
+    'geometryName': options.geometryName,
+    'filter': filter,
+    'propertyNames': options.propertyNames ? options.propertyNames : []
+  };
+  ol.asserts.assert(Array.isArray(options.featureTypes),
+      11); // `options.featureTypes` should be an Array
+  ol.format.WFS.writeGetFeature_(node, /** @type {!Array.<string>} */ (options.featureTypes), [context]);
+  return node;
+};
 
 
 /**
- * @param {Array.<ol.Feature>} features
- * @private
+ * 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
  */
-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);
+ol.format.WFS.prototype.writeTransaction = function(inserts, updates, deletes,
+    options) {
+  var objectStack = [];
+  var node = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'Transaction');
+  var version = options.version ?
+        options.version : ol.format.WFS.DEFAULT_VERSION;
+  var gmlVersion = version === '1.0.0' ? 2 : 3;
+  node.setAttribute('service', 'WFS');
+  node.setAttribute('version', version);
+  var baseObj;
+  /** @type {ol.XmlNodeStackItem} */
+  var obj;
+  if (options) {
+    baseObj = options.gmlOptions ? options.gmlOptions : {};
+    if (options.handle) {
+      node.setAttribute('handle', options.handle);
     }
-    feature.set('extensionsNode_', undefined);
   }
+  var schemaLocation = ol.format.WFS.SCHEMA_LOCATIONS[version];
+  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
+      'xsi:schemaLocation', schemaLocation);
+  if (inserts) {
+    obj = {node: node, 'featureNS': options.featureNS,
+      'featureType': options.featureType, 'featurePrefix': options.featurePrefix,
+      'gmlVersion': gmlVersion, 'hasZ': options.hasZ, 'srsName': options.srsName};
+    ol.obj.assign(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,
+      'gmlVersion': gmlVersion, 'hasZ': options.hasZ, 'srsName': options.srsName};
+    ol.obj.assign(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,
+      'gmlVersion': gmlVersion, 'srsName': options.srsName},
+    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,
+      'gmlVersion': gmlVersion, 'srsName': options.srsName},
+    ol.format.WFS.TRANSACTION_SERIALIZERS_,
+    ol.xml.makeSimpleNodeFactory('Native'), options.nativeElements,
+    objectStack);
+  }
+  return node;
 };
 
 
 /**
- * Read the first feature from a GPX source.
+ * Read the projection from a WFS source.
  *
  * @function
  * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @api stable
+ * @return {?ol.proj.Projection} Projection.
+ * @api
  */
-ol.format.GPX.prototype.readFeature;
+ol.format.WFS.prototype.readProjection;
 
 
 /**
  * @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;
+ol.format.WFS.prototype.readProjectionFromDocument = function(doc) {
+  for (var n = doc.firstChild; n; n = n.nextSibling) {
+    if (n.nodeType == Node.ELEMENT_NODE) {
+      return this.readProjectionFromNode(n);
+    }
   }
-  var feature = featureReader(node, [this.getReadOptions(node, opt_options)]);
-  if (!feature) {
-    return null;
+  return null;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.WFS.prototype.readProjectionFromNode = function(node) {
+  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);
+      }
+    }
   }
-  this.handleReadExtensions_([feature]);
-  return feature;
+
+  return null;
 };
 
+goog.provide('ol.format.WKT');
+
+goog.require('ol');
+goog.require('ol.Feature');
+goog.require('ol.format.Feature');
+goog.require('ol.format.TextFeature');
+goog.require('ol.geom.GeometryCollection');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.GeometryLayout');
+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');
+
 
 /**
- * Read all features from a GPX source.
+ * @classdesc
+ * Geometry format for reading and writing data in the `WellKnownText` (WKT)
+ * format.
  *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
+ * @constructor
+ * @extends {ol.format.TextFeature}
+ * @param {olx.format.WKTOptions=} opt_options Options.
+ * @api
  */
-ol.format.GPX.prototype.readFeatures;
+ol.format.WKT = function(opt_options) {
+
+  var options = opt_options ? opt_options : {};
+
+  ol.format.TextFeature.call(this);
+
+  /**
+   * Split GeometryCollection into multiple features.
+   * @type {boolean}
+   * @private
+   */
+  this.splitCollection_ = options.splitCollection !== undefined ?
+      options.splitCollection : false;
+
+};
+ol.inherits(ol.format.WKT, ol.format.TextFeature);
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {string}
  */
-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 [];
-};
+ol.format.WKT.EMPTY = 'EMPTY';
 
 
 /**
- * Read the projection from a GPX source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
+ * @const
+ * @type {string}
  */
-ol.format.GPX.prototype.readProjection;
+ol.format.WKT.Z = 'Z';
 
 
 /**
- * @param {Node} node Node.
- * @param {string} value Value for the link's `href` attribute.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @const
+ * @type {string}
  */
-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_);
-};
+ol.format.WKT.M = 'M';
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @const
+ * @type {string}
  */
-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);
-};
+ol.format.WKT.ZM = 'ZM';
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.geom.Point} geom Point geometry.
+ * @return {string} Coordinates part of Point as WKT.
  * @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();
+ol.format.WKT.encodePointGeometry_ = function(geom) {
+  var coordinates = geom.getCoordinates();
+  if (coordinates.length === 0) {
+    return '';
   }
-  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);
+  return coordinates.join(' ');
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.geom.MultiPoint} geom MultiPoint geometry.
+ * @return {string} Coordinates part of MultiPoint as WKT.
  * @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();
+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]) + ')');
   }
-  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);
+  return array.join(',');
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LineString} lineString LineString.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry.
+ * @return {string} Coordinates part of GeometryCollection as WKT.
  * @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);
+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 {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry.
+ * @return {string} Coordinates part of LineString as WKT.
  * @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);
+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].join(' '));
   }
+  return array.join(',');
 };
 
 
 /**
- * @const
- * @type {Array.<string>}
+ * @param {ol.geom.MultiLineString} geom MultiLineString geometry.
+ * @return {string} Coordinates part of MultiLineString as WKT.
  * @private
  */
-ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type'];
+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(',');
+};
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @param {ol.geom.Polygon} geom Polygon geometry.
+ * @return {string} Coordinates part of Polygon as WKT.
  * @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)
-    });
+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(',');
+};
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
+ * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry.
+ * @return {string} Coordinates part of MultiPolygon as WKT.
  * @private
  */
-ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, [
-      'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'rtept'
-    ]);
-
+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(',');
+};
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @param {ol.geom.SimpleGeometry} geom SimpleGeometry geometry.
+ * @return {string} Potential dimensional information for WKT type.
  * @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_))
-    });
+ol.format.WKT.encodeGeometryLayout_ = function(geom) {
+  var layout = geom.getLayout();
+  var dimInfo = '';
+  if (layout === ol.geom.GeometryLayout.XYZ || layout === ol.geom.GeometryLayout.XYZM) {
+    dimInfo += ol.format.WKT.Z;
+  }
+  if (layout === ol.geom.GeometryLayout.XYM || layout === ol.geom.GeometryLayout.XYZM) {
+    dimInfo += ol.format.WKT.M;
+  }
+  return dimInfo;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
+ * Encode a geometry as WKT.
+ * @param {ol.geom.Geometry} geom The geometry to encode.
+ * @return {string} WKT string for the geometry.
  * @private
  */
-ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, [
-      'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'trkseg'
-    ]);
+ol.format.WKT.encode_ = function(geom) {
+  var type = geom.getType();
+  var geometryEncoder = ol.format.WKT.GeometryEncoder_[type];
+  var enc = geometryEncoder(geom);
+  type = type.toUpperCase();
+  if (geom instanceof ol.geom.SimpleGeometry) {
+    var dimInfo = ol.format.WKT.encodeGeometryLayout_(geom);
+    if (dimInfo.length > 0) {
+      type += ' ' + dimInfo;
+    }
+  }
+  if (enc.length === 0) {
+    return type + ' ' + ol.format.WKT.EMPTY;
+  }
+  return type + '(' + enc + ')';
+};
 
 
 /**
  * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @type {Object.<string, function(ol.geom.Geometry): string>}
  * @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_))
-    });
+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_
+};
 
 
 /**
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * Parse a WKT string.
+ * @param {string} wkt WKT string.
+ * @return {ol.geom.Geometry|undefined}
+ *     The geometry created.
  * @private
  */
-ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt');
+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();
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * 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
  */
-ol.format.GPX.TRKSEG_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'trkpt': ol.xml.makeChildAppender(ol.format.GPX.writeWptType_)
-    });
+ol.format.WKT.prototype.readFeature;
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
+ * @inheritDoc
  */
-ol.format.GPX.WPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, [
-      'ele', 'time', 'magvar', 'geoidheight', 'name', 'cmt', 'desc', 'src',
-      'link', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop', 'pdop',
-      'ageofdgpsdata', 'dgpsid'
-    ]);
+ol.format.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;
+};
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * 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
  */
-ol.format.GPX.WPT_TYPE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'ele': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'time': ol.xml.makeChildAppender(ol.format.XSD.writeDateTimeTextNode),
-      'magvar': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'geoidheight': ol.xml.makeChildAppender(
-          ol.format.XSD.writeDecimalTextNode),
-      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
-      'sym': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'fix': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'sat': ol.xml.makeChildAppender(
-          ol.format.XSD.writeNonNegativeIntegerTextNode),
-      'hdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'vdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'pdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'ageofdgpsdata': ol.xml.makeChildAppender(
-          ol.format.XSD.writeDecimalTextNode),
-      'dgpsid': ol.xml.makeChildAppender(
-          ol.format.XSD.writeNonNegativeIntegerTextNode)
-    });
+ol.format.WKT.prototype.readFeatures;
 
 
 /**
- * @const
- * @type {Object.<string, string>}
- * @private
+ * @inheritDoc
  */
-ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = {
-  'Point': 'wpt',
-  'LineString': 'rte',
-  'MultiLineString': 'trk'
+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;
 };
 
 
 /**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
- * @private
+ * 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
  */
-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 parentNode = objectStack[objectStack.length - 1].node;
-    goog.asserts.assert(ol.xml.isNode(parentNode),
-        'parentNode should be an XML node');
-    return ol.xml.createElementNS(parentNode.namespaceURI,
-        ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_[geometry.getType()]);
-  }
-};
+ol.format.WKT.prototype.readGeometry;
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @inheritDoc
  */
-ol.format.GPX.GPX_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'rte': ol.xml.makeChildAppender(ol.format.GPX.writeRte_),
-      'trk': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_),
-      'wpt': ol.xml.makeChildAppender(ol.format.GPX.writeWpt_)
-    });
+ol.format.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 an array of features in the GPX format.
+ * Encode a feature as a WKT string.
  *
  * @function
- * @param {Array.<ol.Feature>} features Features.
+ * @param {ol.Feature} feature Feature.
  * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} Result.
- * @api stable
+ * @return {string} WKT string.
+ * @api
  */
-ol.format.GPX.prototype.writeFeatures;
+ol.format.WKT.prototype.writeFeature;
 
 
 /**
- * Encode an array of features in the GPX format as an XML node.
+ * @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 Options.
- * @return {Node} Node.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} WKT string.
  * @api
  */
-ol.format.GPX.prototype.writeFeaturesNode = function(features, opt_options) {
-  opt_options = this.adaptOptions(opt_options);
-  //FIXME Serialize metadata
-  var gpx = ol.xml.createElementNS('http://www.topografix.com/GPX/1/1', 'gpx');
+ol.format.WKT.prototype.writeFeatures;
 
-  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
-      ({node: gpx}), ol.format.GPX.GPX_SERIALIZERS_,
-      ol.format.GPX.GPX_NODE_FACTORY_, features, [opt_options]);
-  return gpx;
+
+/**
+ * @inheritDoc
+ */
+ol.format.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);
 };
 
-// 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)
+ * Write a single geometry as a WKT string.
+ *
+ * @function
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @return {string} WKT string.
+ * @api
  */
+ol.format.WKT.prototype.writeGeometry;
 
 
 /**
- * Namespace for string utilities
+ * @inheritDoc
  */
-goog.provide('goog.string.newlines');
-goog.provide('goog.string.newlines.Line');
-
-goog.require('goog.array');
+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)));
+};
 
 
 /**
- * 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.
+ * @const
+ * @enum {number}
+ * @private
  */
-goog.string.newlines.splitLines = function(str, opt_keepNewlines) {
-  var lines = goog.string.newlines.getLines(str);
-  return goog.array.map(lines, function(line) {
-    return opt_keepNewlines ? line.getFullLine() : line.getContent();
-  });
+ol.format.WKT.TokenType_ = {
+  TEXT: 1,
+  LEFT_PAREN: 2,
+  RIGHT_PAREN: 3,
+  NUMBER: 4,
+  COMMA: 5,
+  EOF: 6
 };
 
 
-
 /**
- * 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.
+ * Class to tokenize a WKT string.
+ * @param {string} wkt WKT string.
  * @constructor
- * @struct
- * @final
+ * @protected
  */
-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;
+ol.format.WKT.Lexer = function(wkt) {
 
   /**
-   * 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}
+   * @type {string}
    */
-  this.endContentIndex = endContentIndex;
+  this.wkt = wkt;
 
   /**
-   * 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}
+   * @private
    */
-
-  this.endLineIndex = endLineIndex;
+  this.index_ = -1;
 };
 
 
 /**
- * @return {string} The content of the line, excluding any newline characters.
+ * @param {string} c Character.
+ * @return {boolean} Whether the character is alphabetic.
+ * @private
  */
-goog.string.newlines.Line.prototype.getContent = function() {
-  return this.string.substring(this.startLineIndex, this.endContentIndex);
+ol.format.WKT.Lexer.prototype.isAlpha_ = function(c) {
+  return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
 };
 
 
 /**
- * @return {string} The full line, including any newline characters.
+ * @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
  */
-goog.string.newlines.Line.prototype.getFullLine = function() {
-  return this.string.substring(this.startLineIndex, this.endLineIndex);
+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;
 };
 
 
 /**
- * @return {string} The newline characters, if any ('\n', \r', '\r\n', '', etc).
+ * @param {string} c Character.
+ * @return {boolean} Whether the character is whitespace.
+ * @private
  */
-goog.string.newlines.Line.prototype.getNewline = function() {
-  return this.string.substring(this.endContentIndex, this.endLineIndex);
+ol.format.WKT.Lexer.prototype.isWhiteSpace_ = function(c) {
+  return c == ' ' || c == '\t' || c == '\r' || c == '\n';
 };
 
 
 /**
- * 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.
+ * @return {string} Next string character.
+ * @private
  */
-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;
+ol.format.WKT.Lexer.prototype.nextChar_ = function() {
+  return this.wkt.charAt(++this.index_);
 };
 
-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}
+ * Fetch and return the next token.
+ * @return {!ol.WKTToken} Next string token.
  */
-ol.format.TextFeature = function() {
-  goog.base(this);
-};
-goog.inherits(ol.format.TextFeature, ol.format.Feature);
-
+ol.format.WKT.Lexer.prototype.nextToken = function() {
+  var c = this.nextChar_();
+  var token = {position: this.index_, value: c};
 
-/**
- * @param {Document|Node|Object|string} source Source.
- * @private
- * @return {string} Text.
- */
-ol.format.TextFeature.prototype.getText_ = function(source) {
-  if (goog.isString(source)) {
-    return source;
+  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 {
-    goog.asserts.fail();
-    return '';
+    throw new Error('Unexpected character: ' + c);
   }
+
+  return token;
 };
 
 
 /**
- * @inheritDoc
+ * @return {number} Numeric token value.
+ * @private
  */
-ol.format.TextFeature.prototype.getType = function() {
-  return ol.format.FormatType.TEXT;
+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_--));
 };
 
 
 /**
- * @inheritDoc
+ * @return {string} String token value.
+ * @private
  */
-ol.format.TextFeature.prototype.readFeature = function(source, opt_options) {
-  return this.readFeatureFromText(
-      this.getText_(source), this.adaptOptions(opt_options));
+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();
 };
 
 
 /**
- * @param {string} text Text.
- * @param {olx.format.ReadOptions=} opt_options Read options.
+ * Class to parse the tokens from the WKT string.
+ * @param {ol.format.WKT.Lexer} lexer The lexer.
+ * @constructor
  * @protected
- * @return {ol.Feature} Feature.
  */
-ol.format.TextFeature.prototype.readFeatureFromText = goog.abstractMethod;
+ol.format.WKT.Parser = function(lexer) {
+
+  /**
+   * @type {ol.format.WKT.Lexer}
+   * @private
+   */
+  this.lexer_ = lexer;
 
+  /**
+   * @type {ol.WKTToken}
+   * @private
+   */
+  this.token_;
 
-/**
- * @inheritDoc
- */
-ol.format.TextFeature.prototype.readFeatures = function(source, opt_options) {
-  return this.readFeaturesFromText(
-      this.getText_(source), this.adaptOptions(opt_options));
+  /**
+   * @type {ol.geom.GeometryLayout}
+   * @private
+   */
+  this.layout_ = ol.geom.GeometryLayout.XY;
 };
 
 
 /**
- * @param {string} text Text.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {Array.<ol.Feature>} Features.
+ * Fetch the next token form the lexer and replace the active token.
+ * @private
  */
-ol.format.TextFeature.prototype.readFeaturesFromText = goog.abstractMethod;
-
+ol.format.WKT.Parser.prototype.consume_ = function() {
+  this.token_ = this.lexer_.nextToken();
+};
 
 /**
- * @inheritDoc
+ * Tests if the given type matches the type of the current token.
+ * @param {ol.format.WKT.TokenType_} type Token type.
+ * @return {boolean} Whether the token matches the given type.
  */
-ol.format.TextFeature.prototype.readGeometry = function(source, opt_options) {
-  return this.readGeometryFromText(
-      this.getText_(source), this.adaptOptions(opt_options));
+ol.format.WKT.Parser.prototype.isTokenType = function(type) {
+  var isMatch = this.token_.type == type;
+  return isMatch;
 };
 
 
 /**
- * @param {string} text Text.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {ol.geom.Geometry} Geometry.
+ * If the given type matches the current token, consume it.
+ * @param {ol.format.WKT.TokenType_} type Token type.
+ * @return {boolean} Whether the token matches the given type.
  */
-ol.format.TextFeature.prototype.readGeometryFromText = goog.abstractMethod;
+ol.format.WKT.Parser.prototype.match = function(type) {
+  var isMatch = this.isTokenType(type);
+  if (isMatch) {
+    this.consume_();
+  }
+  return isMatch;
+};
 
 
 /**
- * @inheritDoc
+ * Try to parse the tokens provided by the lexer.
+ * @return {ol.geom.Geometry} The geometry.
  */
-ol.format.TextFeature.prototype.readProjection = function(source) {
-  return this.readProjectionFromText(this.getText_(source));
+ol.format.WKT.Parser.prototype.parse = function() {
+  this.consume_();
+  var geometry = this.parseGeometry_();
+  return geometry;
 };
 
 
 /**
- * @param {string} text Text.
- * @protected
- * @return {ol.proj.Projection} Projection.
+ * Try to parse the dimensional info.
+ * @return {ol.geom.GeometryLayout} The layout.
+ * @private
  */
-ol.format.TextFeature.prototype.readProjectionFromText = function(text) {
-  return this.defaultDataProjection;
+ol.format.WKT.Parser.prototype.parseGeometryLayout_ = function() {
+  var layout = ol.geom.GeometryLayout.XY;
+  var dimToken = this.token_;
+  if (this.isTokenType(ol.format.WKT.TokenType_.TEXT)) {
+    var dimInfo = dimToken.value;
+    if (dimInfo === ol.format.WKT.Z) {
+      layout = ol.geom.GeometryLayout.XYZ;
+    } else if (dimInfo === ol.format.WKT.M) {
+      layout = ol.geom.GeometryLayout.XYM;
+    } else if (dimInfo === ol.format.WKT.ZM) {
+      layout = ol.geom.GeometryLayout.XYZM;
+    }
+    if (layout !== ol.geom.GeometryLayout.XY) {
+      this.consume_();
+    }
+  }
+  return layout;
 };
 
 
 /**
- * @inheritDoc
+ * @return {!ol.geom.Geometry} The geometry.
+ * @private
  */
-ol.format.TextFeature.prototype.writeFeature = function(feature, opt_options) {
-  return this.writeFeatureText(feature, this.adaptOptions(opt_options));
+ol.format.WKT.Parser.prototype.parseGeometry_ = function() {
+  var token = this.token_;
+  if (this.match(ol.format.WKT.TokenType_.TEXT)) {
+    var geomType = token.value;
+    this.layout_ = this.parseGeometryLayout_();
+    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, this.layout_);
+    }
+  }
+  throw new Error(this.formatErrorMessage_());
 };
 
 
 /**
- * @param {ol.Feature} feature Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @protected
- * @return {string} Text.
+ * @return {!Array.<ol.geom.Geometry>} A collection of geometries.
+ * @private
  */
-ol.format.TextFeature.prototype.writeFeatureText = goog.abstractMethod;
+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_());
+};
 
 
 /**
- * @inheritDoc
+ * @return {Array.<number>} All values in a point.
+ * @private
  */
-ol.format.TextFeature.prototype.writeFeatures = function(
-    features, opt_options) {
-  return this.writeFeaturesText(features, this.adaptOptions(opt_options));
+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_());
 };
 
 
 /**
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @protected
- * @return {string} Text.
+ * @return {!Array.<!Array.<number>>} All points in a linestring.
+ * @private
  */
-ol.format.TextFeature.prototype.writeFeaturesText = goog.abstractMethod;
+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_());
+};
 
 
 /**
- * @inheritDoc
+ * @return {!Array.<!Array.<number>>} All points in a polygon.
+ * @private
  */
-ol.format.TextFeature.prototype.writeGeometry = function(
-    geometry, opt_options) {
-  return this.writeGeometryText(geometry, this.adaptOptions(opt_options));
+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_());
 };
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @protected
- * @return {string} Text.
+ * @return {!Array.<!Array.<number>>} All points in a multipoint.
+ * @private
  */
-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');
-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');
+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_());
+};
 
 
 /**
- * IGC altitude/z. One of 'barometric', 'gps', 'none'.
- * @enum {string}
- * @api
+ * @return {!Array.<!Array.<number>>} All linestring points
+ *                                        in a multilinestring.
+ * @private
  */
-ol.format.IGCZ = {
-  BAROMETRIC: 'barometric',
-  GPS: 'gps',
-  NONE: 'none'
+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_());
 };
 
 
-
 /**
- * @classdesc
- * Feature format for `*.igc` flight recording files.
- *
- * @constructor
- * @extends {ol.format.TextFeature}
- * @param {olx.format.IGCOptions=} opt_options Options.
- * @api
+ * @return {!Array.<!Array.<number>>} All polygon points in a multipolygon.
+ * @private
  */
-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;
-
+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_());
 };
-goog.inherits(ol.format.IGC, ol.format.TextFeature);
 
 
 /**
- * @const
- * @type {Array.<string>}
+ * @return {!Array.<number>} A point.
  * @private
  */
-ol.format.IGC.EXTENSIONS_ = ['.igc'];
+ol.format.WKT.Parser.prototype.parsePoint_ = function() {
+  var coordinates = [];
+  var dimensions = this.layout_.length;
+  for (var i = 0; i < dimensions; ++i) {
+    var token = this.token_;
+    if (this.match(ol.format.WKT.TokenType_.NUMBER)) {
+      coordinates.push(token.value);
+    } else {
+      break;
+    }
+  }
+  if (coordinates.length == dimensions) {
+    return coordinates;
+  }
+  throw new Error(this.formatErrorMessage_());
+};
 
 
 /**
- * @const
- * @type {RegExp}
+ * @return {!Array.<!Array.<number>>} An array of points.
  * @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})/;
+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;
+};
 
 
 /**
- * @const
- * @type {RegExp}
+ * @return {!Array.<!Array.<number>>} An array of points.
  * @private
  */
-ol.format.IGC.H_RECORD_RE_ = /^H.([A-Z]{3}).*?:(.*)/;
+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;
+};
 
 
 /**
- * @const
- * @type {RegExp}
+ * @return {!Array.<!Array.<number>>} An array of points.
  * @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_;
+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;
 };
 
 
 /**
- * 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
+ * @return {!Array.<!Array.<number>>} An array of points.
+ * @private
  */
-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;
+ol.format.WKT.Parser.prototype.parsePolygonTextList_ = function() {
+  var coordinates = [this.parsePolygonText_()];
+  while (this.match(ol.format.WKT.TokenType_.COMMA)) {
+    coordinates.push(this.parsePolygonText_());
   }
-  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;
+  return coordinates;
 };
 
 
 /**
- * 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
+ * @return {boolean} Whether the token implies an empty geometry.
+ * @private
  */
-ol.format.IGC.prototype.readFeatures;
+ol.format.WKT.Parser.prototype.isEmptyGeometry_ = function() {
+  var isEmpty = this.isTokenType(ol.format.WKT.TokenType_.TEXT) &&
+      this.token_.value == ol.format.WKT.EMPTY;
+  if (isEmpty) {
+    this.consume_();
+  }
+  return isEmpty;
+};
 
 
 /**
- * @inheritDoc
+ * Create an error message for an unexpected token error.
+ * @return {string} Error message.
+ * @private
  */
-ol.format.IGC.prototype.readFeaturesFromText = function(text, opt_options) {
-  var feature = this.readFeatureFromText(text, opt_options);
-  if (feature) {
-    return [feature];
-  } else {
-    return [];
-  }
+ol.format.WKT.Parser.prototype.formatErrorMessage_ = function() {
+  return 'Unexpected `' + this.token_.value + '` at position ' +
+      this.token_.position + ' in `' + this.lexer_.wkt + '`';
 };
 
 
 /**
- * Read the projection from the IGC source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api
+ * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout)}
+ * @private
  */
-ol.format.IGC.prototype.readProjection;
+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
+};
 
-// 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.
- *
+ * @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('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');
+goog.provide('ol.format.WMSCapabilities');
 
+goog.require('ol');
+goog.require('ol.format.XLink');
+goog.require('ol.format.XML');
+goog.require('ol.format.XSD');
+goog.require('ol.xml');
 
 
 /**
- * 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.
+ * @classdesc
+ * Format for reading WMS capabilities data
  *
- * @throws URIError If opt_uri is provided and URI is malformed (that is,
- *     if decodeURIComponent fails on any of the URI components).
  * @constructor
- * @struct
+ * @extends {ol.format.XML}
+ * @api
  */
-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;
+ol.format.WMSCapabilities = function() {
 
-  /**
-   * Whether or not to ignore case when comparing query params.
-   * @private {boolean}
-   */
-  this.ignoreCase_ = false;
+  ol.format.XML.call(this);
 
   /**
-   * Object representing query data.
-   * @private {!goog.Uri.QueryData}
+   * @type {string|undefined}
    */
-  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_);
-  }
+  this.version = undefined;
 };
+ol.inherits(ol.format.WMSCapabilities, ol.format.XML);
 
 
 /**
- * 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.
+ * Read a WMS capabilities document.
  *
- * @type {boolean}
- */
-goog.Uri.preserveParameterTypesCompatibilityFlag = false;
-
-
-/**
- * Parameter name added to stop caching.
- * @type {string}
+ * @function
+ * @param {Document|Node|string} source The XML source.
+ * @return {Object} An object representing the WMS capabilities.
+ * @api
  */
-goog.Uri.RANDOM_PARAM = goog.uri.utils.StandardQueryParam.RANDOM;
+ol.format.WMSCapabilities.prototype.read;
 
 
 /**
- * @return {string} The string form of the url.
- * @override
+ * @inheritDoc
  */
-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('/');
+ol.format.WMSCapabilities.prototype.readFromDocument = function(doc) {
+  for (var n = doc.firstChild; n; n = n.nextSibling) {
+    if (n.nodeType == Node.ELEMENT_NODE) {
+      return this.readFromNode(n);
     }
-    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('');
+  return null;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.Uri.prototype.resolve = function(relativeUri) {
-
-  var absoluteUri = this.clone();
+ol.format.WMSCapabilities.prototype.readFromNode = function(node) {
+  this.version = node.getAttribute('version').trim();
+  var wmsCapabilityObject = ol.xml.pushParseAndPop({
+    'version': this.version
+  }, ol.format.WMSCapabilities.PARSERS_, node, []);
+  return wmsCapabilityObject ? wmsCapabilityObject : null;
+};
 
-  // 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();
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Attribution object.
+ */
+ol.format.WMSCapabilities.readAttribution_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_, node, objectStack);
+};
 
-  if (overridden) {
-    absoluteUri.setScheme(relativeUri.getScheme());
-  } else {
-    overridden = relativeUri.hasUserInfo();
-  }
 
-  if (overridden) {
-    absoluteUri.setUserInfo(relativeUri.getUserInfo());
-  } else {
-    overridden = relativeUri.hasDomain();
-  }
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object} Bounding box object.
+ */
+ol.format.WMSCapabilities.readBoundingBox_ = function(node, objectStack) {
+  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'))
+  ];
 
-  if (overridden) {
-    absoluteUri.setDomain(relativeUri.getDomain());
-  } else {
-    overridden = relativeUri.hasPort();
-  }
+  var resolutions = [
+    ol.format.XSD.readDecimalString(node.getAttribute('resx')),
+    ol.format.XSD.readDecimalString(node.getAttribute('resy'))
+  ];
 
-  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);
-    }
-  }
+  return {
+    'crs': node.getAttribute('CRS'),
+    'extent': extent,
+    'res': resolutions
+  };
+};
 
-  if (overridden) {
-    absoluteUri.setPath(path);
-  } else {
-    overridden = relativeUri.hasQuery();
-  }
 
-  if (overridden) {
-    absoluteUri.setQueryData(relativeUri.getDecodedQuery());
-  } else {
-    overridden = relativeUri.hasFragment();
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.Extent|undefined} Bounding box object.
+ */
+ol.format.WMSCapabilities.readEXGeographicBoundingBox_ = function(node, objectStack) {
+  var geographicBoundingBox = ol.xml.pushParseAndPop(
+        {},
+        ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_,
+        node, objectStack);
+  if (!geographicBoundingBox) {
+    return undefined;
   }
-
-  if (overridden) {
-    absoluteUri.setFragment(relativeUri.getFragment());
+  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 absoluteUri;
+  return [
+    westBoundLongitude, southBoundLatitude,
+    eastBoundLongitude, northBoundLatitude
+  ];
 };
 
 
 /**
- * Clones the URI instance.
- * @return {!goog.Uri} New instance of the URI object.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Capability object.
  */
-goog.Uri.prototype.clone = function() {
-  return new goog.Uri(this);
+ol.format.WMSCapabilities.readCapability_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.CAPABILITY_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @return {string} The encoded scheme/protocol for the URI.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Service object.
  */
-goog.Uri.prototype.getScheme = function() {
-  return this.scheme_;
+ol.format.WMSCapabilities.readService_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.SERVICE_PARSERS_, node, objectStack);
 };
 
 
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Contact information 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;
+ol.format.WMSCapabilities.readContactInformation_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_,
+        node, objectStack);
 };
 
 
 /**
- * @return {boolean} Whether the scheme has been set.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Contact person object.
  */
-goog.Uri.prototype.hasScheme = function() {
-  return !!this.scheme_;
+ol.format.WMSCapabilities.readContactPersonPrimary_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_,
+        node, objectStack);
 };
 
 
 /**
- * @return {string} The decoded user info.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Contact address object.
  */
-goog.Uri.prototype.getUserInfo = function() {
-  return this.userInfo_;
+ol.format.WMSCapabilities.readContactAddress_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_,
+        node, objectStack);
 };
 
 
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<string>|undefined} Format array.
  */
-goog.Uri.prototype.setUserInfo = function(newUserInfo, opt_decode) {
-  this.enforceReadOnly();
-  this.userInfo_ = opt_decode ? goog.Uri.decodeOrEmpty_(newUserInfo) :
-                   newUserInfo;
-  return this;
+ol.format.WMSCapabilities.readException_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        [], ol.format.WMSCapabilities.EXCEPTION_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @return {boolean} Whether the user info has been set.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Layer object.
  */
-goog.Uri.prototype.hasUserInfo = function() {
-  return !!this.userInfo_;
+ol.format.WMSCapabilities.readCapabilityLayer_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @return {string} The decoded domain.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Layer object.
  */
-goog.Uri.prototype.getDomain = function() {
-  return this.domain_;
-};
+ol.format.WMSCapabilities.readLayer_ = function(node, objectStack) {
+  var parentLayerObject = /**  @type {Object.<string,*>} */
+        (objectStack[objectStack.length - 1]);
 
+  var layerObject = ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack);
 
-/**
- * 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;
-};
+  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;
 
-/**
- * @return {boolean} Whether the domain has been set.
- */
-goog.Uri.prototype.hasDomain = function() {
-  return !!this.domain_;
+  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 = layerObject[key] || [];
+      layerObject[key] = childValue.concat(parentLayerObject[key]);
+    }
+  });
+
+  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;
 };
 
 
 /**
- * @return {?number} The port number.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object} Dimension object.
  */
-goog.Uri.prototype.getPort = function() {
-  return this.port_;
+ol.format.WMSCapabilities.readDimension_ = function(node, objectStack) {
+  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;
 };
 
 
 /**
- * Sets the port number.
- * @param {*} newPort Port number. Will be explicitly casted to a number.
- * @return {!goog.Uri} Reference to this URI object.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Online resource 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;
+ol.format.WMSCapabilities.readFormatOnlineresource_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_,
+        node, objectStack);
 };
 
 
 /**
- * @return {boolean} Whether the port has been set.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Request object.
  */
-goog.Uri.prototype.hasPort = function() {
-  return this.port_ != null;
+ol.format.WMSCapabilities.readRequest_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.REQUEST_PARSERS_, node, objectStack);
 };
 
 
 /**
-  * @return {string} The decoded path.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} DCP type object.
  */
-goog.Uri.prototype.getPath = function() {
-  return this.path_;
+ol.format.WMSCapabilities.readDCPType_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.DCPTYPE_PARSERS_, node, objectStack);
 };
 
 
 /**
- * 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.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} HTTP object.
  */
-goog.Uri.prototype.setPath = function(newPath, opt_decode) {
-  this.enforceReadOnly();
-  this.path_ = opt_decode ? goog.Uri.decodeOrEmpty_(newPath, true) : newPath;
-  return this;
+ol.format.WMSCapabilities.readHTTP_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.HTTP_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @return {boolean} Whether the path has been set.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Operation type object.
  */
-goog.Uri.prototype.hasPath = function() {
-  return !!this.path_;
+ol.format.WMSCapabilities.readOperationType_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @return {boolean} Whether the query string has been set.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Online resource object.
  */
-goog.Uri.prototype.hasQuery = function() {
-  return this.queryData_.toString() !== '';
+ol.format.WMSCapabilities.readSizedFormatOnlineresource_ = function(node, objectStack) {
+  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;
 };
 
 
 /**
- * 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.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Authority URL 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_);
+ol.format.WMSCapabilities.readAuthorityURL_ = function(node, objectStack) {
+  var authorityObject =
+        ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
+  if (authorityObject) {
+    authorityObject['name'] = node.getAttribute('name');
+    return authorityObject;
   }
-
-  return this;
+  return undefined;
 };
 
 
 /**
- * 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.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Metadata URL object.
  */
-goog.Uri.prototype.setQuery = function(newQuery, opt_decode) {
-  return this.setQueryData(newQuery, opt_decode);
+ol.format.WMSCapabilities.readMetadataURL_ = function(node, objectStack) {
+  var metadataObject =
+        ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
+  if (metadataObject) {
+    metadataObject['type'] = node.getAttribute('type');
+    return metadataObject;
+  }
+  return undefined;
 };
 
 
 /**
- * @return {string} The encoded URI query, not including the ?.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Style object.
  */
-goog.Uri.prototype.getEncodedQuery = function() {
-  return this.queryData_.toString();
+ol.format.WMSCapabilities.readStyle_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        {}, ol.format.WMSCapabilities.STYLE_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @return {string} The decoded URI query, not including the ?.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<string>|undefined} Keyword list.
  */
-goog.Uri.prototype.getDecodedQuery = function() {
-  return this.queryData_.toDecodedString();
+ol.format.WMSCapabilities.readKeywordList_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+        [], ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_, node, objectStack);
 };
 
 
 /**
- * Returns the query data.
- * @return {!goog.Uri.QueryData} QueryData object.
+ * @const
+ * @private
+ * @type {Array.<string>}
  */
-goog.Uri.prototype.getQueryData = function() {
-  return this.queryData_;
-};
+ol.format.WMSCapabilities.NAMESPACE_URIS_ = [
+  null,
+  'http://www.opengis.net/wms'
+];
 
 
 /**
- * @return {string} The encoded URI query, not including the ?.
- *
- * Warning: This method, unlike other getter methods, returns encoded
- * value, instead of decoded one.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.getQuery = function() {
-  return this.getEncodedQuery();
-};
+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_)
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.setParameterValue = function(key, value) {
-  this.enforceReadOnly();
-  this.queryData_.set(key, value);
-  return this;
-};
+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_)
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.setParameterValues = function(key, values) {
-  this.enforceReadOnly();
-
-  if (!goog.isArray(values)) {
-    values = [String(values)];
-  }
+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)
+    });
 
-  this.queryData_.setValues(key, values);
 
-  return this;
-};
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @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)
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.getParameterValues = function(name) {
-  return this.queryData_.getValues(name);
-};
+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)
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.getParameterValue = function(paramName) {
-  // NOTE(nicksantos): This type-cast is a lie when
-  // preserveParameterTypesCompatibilityFlag is set to true.
-  // But this should only be set to true in tests.
-  return /** @type {string|undefined} */ (this.queryData_.get(paramName));
-};
+ol.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)
+    });
 
 
 /**
- * @return {string} The URI fragment, not including the #.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.getFragment = function() {
-  return this.fragment_;
-};
+ol.format.WMSCapabilities.EXCEPTION_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'Format': ol.xml.makeArrayPusher(ol.format.XSD.readString)
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.setFragment = function(newFragment, opt_decode) {
-  this.enforceReadOnly();
-  this.fragment_ = opt_decode ? goog.Uri.decodeOrEmpty_(newFragment) :
-                   newFragment;
-  return this;
-};
+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_)
+    });
 
 
 /**
- * @return {boolean} Whether the URI has a fragment set.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.hasFragment = function() {
-  return !!this.fragment_;
-};
+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_)
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.hasSameDomainAs = function(uri2) {
-  return ((!this.hasDomain() && !uri2.hasDomain()) ||
-          this.getDomain() == uri2.getDomain()) &&
-      ((!this.hasPort() && !uri2.hasPort()) ||
-          this.getPort() == uri2.getPort());
-};
+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)
+    });
 
 
 /**
- * Adds a random parameter to the Uri.
- * @return {!goog.Uri} Reference to this Uri object.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.makeUnique = function() {
-  this.enforceReadOnly();
-  this.setParameterValue(goog.Uri.RANDOM_PARAM, goog.string.getRandomString());
-
-  return this;
-};
+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_)
+    });
 
 
 /**
- * Removes the named query parameter.
- *
- * @param {string} key The parameter to remove.
- * @return {!goog.Uri} Reference to this URI object.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.removeParameter = function(key) {
-  this.enforceReadOnly();
-  this.queryData_.remove(key);
-  return this;
-};
+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_)
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.setReadOnly = function(isReadOnly) {
-  this.isReadOnly_ = isReadOnly;
-  return this;
-};
+ol.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'HTTP': ol.xml.makeObjectPropertySetter(
+          ol.format.WMSCapabilities.readHTTP_)
+    });
 
 
 /**
- * @return {boolean} Whether the URI is read only.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.isReadOnly = function() {
-  return this.isReadOnly_;
-};
+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_)
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.enforceReadOnly = function() {
-  if (this.isReadOnly_) {
-    throw Error('Tried to modify a read-only Uri');
-  }
-};
+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_)
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.setIgnoreCase = function(ignoreCase) {
-  this.ignoreCase_ = ignoreCase;
-  if (this.queryData_) {
-    this.queryData_.setIgnoreCase(ignoreCase);
-  }
-  return this;
-};
+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)
+    });
 
 
 /**
- * @return {boolean} Whether to ignore case.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.prototype.getIgnoreCase = function() {
-  return this.ignoreCase_;
-};
+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');
 
-//==============================================================================
-// Static members
-//==============================================================================
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.format.GML2');
+goog.require('ol.format.XMLFeature');
+goog.require('ol.obj');
+goog.require('ol.xml');
 
 
 /**
- * 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.
+ * @classdesc
+ * Format for reading WMSGetFeatureInfo format. It uses
+ * {@link ol.format.GML2} to read features.
  *
- * @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.
+ * @constructor
+ * @extends {ol.format.XMLFeature}
+ * @param {olx.format.WMSGetFeatureInfoOptions=} opt_options Options.
+ * @api
  */
-goog.Uri.parse = function(uri, opt_ignoreCase) {
-  return uri instanceof goog.Uri ?
-         uri.clone() : new goog.Uri(uri, opt_ignoreCase);
-};
+ol.format.WMSGetFeatureInfo = function(opt_options) {
 
+  var options = opt_options ? opt_options : {};
 
-/**
- * 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) {
+  /**
+   * @private
+   * @type {string}
+   */
+  this.featureNS_ = 'http://mapserver.gis.umn.edu/mapserver';
+
+
+  /**
+   * @private
+   * @type {ol.format.GML2}
+   */
+  this.gmlFormat_ = new ol.format.GML2();
 
-  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);
+  /**
+   * @private
+   * @type {Array.<string>}
+   */
+  this.layers_ = options.layers ? options.layers : null;
 
-  return uri;
+  ol.format.XMLFeature.call(this);
 };
+ol.inherits(ol.format.WMSGetFeatureInfo, ol.format.XMLFeature);
 
 
 /**
- * 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.
+ * @const
+ * @type {string}
+ * @private
  */
-goog.Uri.resolve = function(base, rel) {
-  if (!(base instanceof goog.Uri)) {
-    base = goog.Uri.parse(base);
-  }
+ol.format.WMSGetFeatureInfo.featureIdentifier_ = '_feature';
 
-  if (!(rel instanceof goog.Uri)) {
-    rel = goog.Uri.parse(rel);
-  }
 
-  return base.resolve(rel);
-};
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+ol.format.WMSGetFeatureInfo.layerIdentifier_ = '_layer';
 
 
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<ol.Feature>} Features.
+ * @private
  */
-goog.Uri.removeDotSegments = function(path) {
-  if (path == '..' || path == '.') {
-    return '';
+ol.format.WMSGetFeatureInfo.prototype.readFeatures_ = function(node, objectStack) {
+  node.setAttribute('namespaceURI', this.featureNS_);
+  var localName = node.localName;
+  /** @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 !== Node.ELEMENT_NODE) {
+        continue;
+      }
+      var context = objectStack[0];
 
-  } 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;
+      var toRemove = ol.format.WMSGetFeatureInfo.layerIdentifier_;
+      var layerName = layer.localName.replace(toRemove, '');
 
-  } else {
-    var leadingSlash = goog.string.startsWith(path, '/');
-    var segments = path.split('/');
-    var out = [];
+      if (this.layers_ && !ol.array.includes(this.layers_, layerName)) {
+        continue;
+      }
 
-    for (var pos = 0; pos < segments.length; ) {
-      var segment = segments[pos++];
+      var featureType = layerName +
+          ol.format.WMSGetFeatureInfo.featureIdentifier_;
 
-      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;
+      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.setAttribute('namespaceURI', this.featureNS_);
+      var layerFeatures = ol.xml.pushParseAndPop(
+          [], parsersNS, layer, objectStack, this.gmlFormat_);
+      if (layerFeatures) {
+        ol.array.extend(features, layerFeatures);
       }
     }
-
-    return out.join('/');
   }
+  if (localName == 'FeatureCollection') {
+    var gmlFeatures = ol.xml.pushParseAndPop([],
+        this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node,
+        [{}], this.gmlFormat_);
+    if (gmlFeatures) {
+      features = gmlFeatures;
+    }
+  }
+  return features;
 };
 
 
 /**
- * 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
+ * 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
  */
-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);
-};
+ol.format.WMSGetFeatureInfo.prototype.readFeatures;
 
 
 /**
- * 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
+ * @inheritDoc
  */
-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;
+ol.format.WMSGetFeatureInfo.prototype.readFeaturesFromNode = function(node, opt_options) {
+  var options = {};
+  if (opt_options) {
+    ol.obj.assign(options, this.getReadOptions(node, opt_options));
   }
-  return null;
+  return this.readFeatures_(node, [options]);
 };
 
 
 /**
- * Converts a character in [\01-\177] to its unicode character equivalent.
- * @param {string} ch One character string.
- * @return {string} Encoded string.
- * @private
+ * Not implemented.
+ * @inheritDoc
  */
-goog.Uri.encodeChar_ = function(ch) {
-  var n = ch.charCodeAt(0);
-  return '%' + ((n >> 4) & 0xf).toString(16) + (n & 0xf).toString(16);
-};
+ol.format.WMSGetFeatureInfo.prototype.writeFeatureNode = function(feature, opt_options) {};
 
 
 /**
- * Removes double percent-encoding from a string.
- * @param  {string} doubleEncodedString String
- * @return {string} String with double encoding removed.
- * @private
+ * Not implemented.
+ * @inheritDoc
  */
-goog.Uri.removeDoubleEncoding_ = function(doubleEncodedString) {
-  return doubleEncodedString.replace(/%25([0-9a-fA-F]{2})/g, '%$1');
-};
+ol.format.WMSGetFeatureInfo.prototype.writeFeaturesNode = function(features, opt_options) {};
 
 
 /**
- * Regular expression for characters that are disallowed in the scheme or
- * userInfo part of the URI.
- * @type {RegExp}
- * @private
+ * Not implemented.
+ * @inheritDoc
  */
-goog.Uri.reDisallowedInSchemeOrUserInfo_ = /[#\/\?@]/g;
+ol.format.WMSGetFeatureInfo.prototype.writeGeometryNode = function(geometry, opt_options) {};
+
+goog.provide('ol.format.WMTSCapabilities');
+
+goog.require('ol');
+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');
 
 
 /**
- * Regular expression for characters that are disallowed in a relative path.
- * Colon is included due to RFC 3986 3.3.
- * @type {RegExp}
- * @private
+ * @classdesc
+ * Format for reading WMTS capabilities data.
+ *
+ * @constructor
+ * @extends {ol.format.XML}
+ * @api
  */
-goog.Uri.reDisallowedInRelativePath_ = /[\#\?:]/g;
+ol.format.WMTSCapabilities = function() {
+  ol.format.XML.call(this);
+
+  /**
+   * @type {ol.format.OWS}
+   * @private
+   */
+  this.owsParser_ = new ol.format.OWS();
+};
+ol.inherits(ol.format.WMTSCapabilities, ol.format.XML);
 
 
 /**
- * Regular expression for characters that are disallowed in an absolute path.
- * @type {RegExp}
- * @private
+ * Read a WMTS capabilities document.
+ *
+ * @function
+ * @param {Document|Node|string} source The XML source.
+ * @return {Object} An object representing the WMTS capabilities.
+ * @api
  */
-goog.Uri.reDisallowedInAbsolutePath_ = /[\#\?]/g;
+ol.format.WMTSCapabilities.prototype.read;
 
 
 /**
- * Regular expression for characters that are disallowed in the query.
- * @type {RegExp}
- * @private
+ * @inheritDoc
  */
-goog.Uri.reDisallowedInQuery_ = /[\#\?@]/g;
+ol.format.WMTSCapabilities.prototype.readFromDocument = function(doc) {
+  for (var n = doc.firstChild; n; n = n.nextSibling) {
+    if (n.nodeType == Node.ELEMENT_NODE) {
+      return this.readFromNode(n);
+    }
+  }
+  return null;
+};
 
 
 /**
- * Regular expression for characters that are disallowed in the fragment.
- * @type {RegExp}
- * @private
+ * @inheritDoc
  */
-goog.Uri.reDisallowedInFragment_ = /#/g;
+ol.format.WMTSCapabilities.prototype.readFromNode = function(node) {
+  var version = node.getAttribute('version').trim();
+  var WMTSCapabilityObject = this.owsParser_.readFromNode(node);
+  if (!WMTSCapabilityObject) {
+    return null;
+  }
+  WMTSCapabilityObject['version'] = version;
+  WMTSCapabilityObject = ol.xml.pushParseAndPop(WMTSCapabilityObject,
+        ol.format.WMTSCapabilities.PARSERS_, node, []);
+  return WMTSCapabilityObject ? WMTSCapabilityObject : null;
+};
 
 
 /**
- * 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.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Attribution object.
  */
-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];
+ol.format.WMTSCapabilities.readContents_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+        ol.format.WMTSCapabilities.CONTENTS_PARSERS_, node, objectStack);
 };
 
 
-
 /**
- * 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
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Layers object.
  */
-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;
+ol.format.WMTSCapabilities.readLayer_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+        ol.format.WMTSCapabilities.LAYER_PARSERS_, node, objectStack);
 };
 
 
 /**
- * If the underlying key map is not yet initialized, it parses the
- * query string and fills the map with parsed data.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Tile Matrix Set object.
  */
-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);
-      });
-    }
-  }
+ol.format.WMTSCapabilities.readTileMatrixSet_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.TMS_PARSERS_, node, objectStack);
 };
 
 
 /**
- * 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.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Style object.
  */
-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');
+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;
 
-  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.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Tile Matrix Set Link object.
  */
-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;
+ol.format.WMTSCapabilities.readTileMatrixSetLink_ = function(node,
+    objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @return {?number} The number of parameters.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Dimension object.
  */
-goog.Uri.QueryData.prototype.getCount = function() {
-  this.ensureKeyMapInitialized_();
-  return this.count_;
+ol.format.WMTSCapabilities.readDimensions_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.DIMENSION_PARSERS_, node, objectStack);
 };
 
 
 /**
- * Adds a key value pair.
- * @param {string} key Name.
- * @param {*} value Value.
- * @return {!goog.Uri.QueryData} Instance of this object.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Resource URL 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 = []));
+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;
   }
-  values.push(value);
-  this.count_++;
-  return this;
+  if (template) {
+    resource['template'] = template;
+  }
+  if (resourceType) {
+    resource['resourceType'] = resourceType;
+  }
+  return resource;
 };
 
 
 /**
- * Removes all the params with the given key.
- * @param {string} key Name.
- * @return {boolean} Whether any parameter was removed.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} WGS84 BBox object.
  */
-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);
+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 false;
+  return ol.extent.boundingExtent(coordinates);
 };
 
 
 /**
- * Clears the parameters.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Legend object.
  */
-goog.Uri.QueryData.prototype.clear = function() {
-  this.invalidateCache_();
-  this.keyMap_ = null;
-  this.count_ = 0;
+ol.format.WMTSCapabilities.readLegendUrl_ = function(node, objectStack) {
+  var legend = {};
+  legend['format'] = node.getAttribute('format');
+  legend['href'] = ol.format.XLink.readHref(node);
+  return legend;
 };
 
 
 /**
- * @return {boolean} Whether we have any parameters.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Coordinates object.
  */
-goog.Uri.QueryData.prototype.isEmpty = function() {
-  this.ensureKeyMapInitialized_();
-  return this.count_ == 0;
+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];
 };
 
 
 /**
- * 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.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} TileMatrix object.
  */
-goog.Uri.QueryData.prototype.containsKey = function(key) {
-  this.ensureKeyMapInitialized_();
-  key = this.getKeyName_(key);
-  return this.keyMap_.containsKey(key);
+ol.format.WMTSCapabilities.readTileMatrix_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.TM_PARSERS_, node, objectStack);
 };
 
 
 /**
- * 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.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} TileMatrixSetLimits Object.
  */
-goog.Uri.QueryData.prototype.containsValue = function(value) {
-  // NOTE(arv): This solution goes through all the params even if it was the
-  // first param. We can get around this by not reusing code or by switching to
-  // iterators.
-  var vals = this.getValues();
-  return goog.array.contains(vals, value);
+ol.format.WMTSCapabilities.readTileMatrixLimitsList_ = function(node,
+    objectStack) {
+  return ol.xml.pushParseAndPop([],
+      ol.format.WMTSCapabilities.TMS_LIMITS_LIST_PARSERS_, node,
+      objectStack);
 };
 
 
 /**
- * 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.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} TileMatrixLimits Array.
  */
-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;
+ol.format.WMTSCapabilities.readTileMatrixLimits_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.TMS_LIMITS_PARSERS_, node, objectStack);
 };
 
 
 /**
- * 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.
+ * @const
+ * @private
+ * @type {Array.<string>}
  */
-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;
-};
+ol.format.WMTSCapabilities.NAMESPACE_URIS_ = [
+  null,
+  'http://www.opengis.net/wmts/1.0'
+];
 
 
 /**
- * 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;
-};
+ * @const
+ * @private
+ * @type {Array.<string>}
+ */
+ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_ = [
+  null,
+  'http://www.opengis.net/ows/1.1'
+];
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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;
-  }
-};
+ol.format.WMTSCapabilities.PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'Contents': ol.xml.makeObjectPropertySetter(
+          ol.format.WMTSCapabilities.readContents_)
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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;
-  }
-};
+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_)
+    });
 
 
 /**
- * @return {string} Encoded query string.
- * @override
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.QueryData.prototype.toString = function() {
-  if (this.encodedQuery_) {
-    return this.encodedQuery_;
-  }
+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)
+    }));
 
-  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);
-    }
-  }
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @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)
+    }));
 
-  return this.encodedQuery_ = sb.join('&');
-};
 
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'TileMatrixSet': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'TileMatrixSetLimits': ol.xml.makeObjectPropertySetter(
+          ol.format.WMTSCapabilities.readTileMatrixLimitsList_)
+    });
 
 /**
- * @throws URIError If URI is malformed (that is, if decodeURIComponent fails on
- *     any of the URI components).
- * @return {string} Decoded query string.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-goog.Uri.QueryData.prototype.toDecodedString = function() {
-  return goog.Uri.decodeOrEmpty_(this.toString());
-};
+ol.format.WMTSCapabilities.TMS_LIMITS_LIST_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'TileMatrixLimits': ol.xml.makeArrayPusher(
+          ol.format.WMTSCapabilities.readTileMatrixLimits_)
+    });
 
 
 /**
- * Invalidate the cache.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-goog.Uri.QueryData.prototype.invalidateCache_ = function() {
-  this.encodedQuery_ = null;
-};
+ol.format.WMTSCapabilities.TMS_LIMITS_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'TileMatrix': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'MinTileRow': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readNonNegativeInteger),
+      'MaxTileRow': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readNonNegativeInteger),
+      'MinTileCol': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readNonNegativeInteger),
+      'MaxTileCol': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readNonNegativeInteger)
+    });
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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;
-};
+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)
+    }));
 
 
 /**
- * Clone the query data instance.
- * @return {!goog.Uri.QueryData} New instance of the QueryData object.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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;
-};
+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_)
+    });
 
 
 /**
- * Helper function to get the key name from a JavaScript object. Converts
- * the object to a string, and to lower case if necessary.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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;
-};
+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)
+    }));
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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;
-};
+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.GeolocationProperty');
 
 
 /**
- * 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.
+ * @enum {string}
  */
-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);
-  }
+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'
 };
 
-goog.provide('ol.style.Text');
-
+// FIXME handle geolocation not supported
 
-goog.require('ol.style.Fill');
+goog.provide('ol.Geolocation');
 
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.GeolocationProperty');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.geom.Polygon');
+goog.require('ol.has');
+goog.require('ol.math');
+goog.require('ol.proj');
+goog.require('ol.sphere.WGS84');
 
 
 /**
  * @classdesc
- * Set text style for vector features.
+ * 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());
+ *     });
  *
+ * @fires error
  * @constructor
- * @param {olx.style.TextOptions=} opt_options Options.
+ * @extends {ol.Object}
+ * @param {olx.GeolocationOptions=} opt_options Options.
  * @api
  */
-ol.style.Text = function(opt_options) {
+ol.Geolocation = function(opt_options) {
+
+  ol.Object.call(this);
 
   var options = opt_options || {};
 
   /**
+   * The unprojected (EPSG:4326) device position.
    * @private
-   * @type {string|undefined}
+   * @type {ol.Coordinate}
    */
-  this.font_ = options.font;
+  this.position_ = null;
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {ol.TransformFunction}
    */
-  this.rotation_ = options.rotation;
+  this.transform_ = ol.proj.identityTransform;
 
   /**
    * @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;
+  this.watchId_ = undefined;
 
-  /**
-   * @private
-   * @type {ol.style.Fill}
-   */
-  this.fill_ = options.fill !== undefined ? options.fill :
-      new ol.style.Fill({color: ol.style.Text.DEFAULT_FILL_COLOR_});
+  ol.events.listen(
+      this, ol.Object.getChangeEventType(ol.GeolocationProperty.PROJECTION),
+      this.handleProjectionChanged_, this);
+  ol.events.listen(
+      this, ol.Object.getChangeEventType(ol.GeolocationProperty.TRACKING),
+      this.handleTrackingChanged_, this);
 
-  /**
-   * @private
-   * @type {ol.style.Stroke}
-   */
-  this.stroke_ = options.stroke !== undefined ? options.stroke : null;
+  if (options.projection !== undefined) {
+    this.setProjection(options.projection);
+  }
+  if (options.trackingOptions !== undefined) {
+    this.setTrackingOptions(options.trackingOptions);
+  }
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.offsetX_ = options.offsetX !== undefined ? options.offsetX : 0;
+  this.setTracking(options.tracking !== undefined ? options.tracking : false);
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.offsetY_ = options.offsetY !== undefined ? options.offsetY : 0;
 };
+ol.inherits(ol.Geolocation, ol.Object);
 
 
 /**
- * The default fill color to use if no fill was set at construction time; a
- * blackish `#333`.
- *
- * @const {string}
- * @private
+ * @inheritDoc
  */
-ol.style.Text.DEFAULT_FILL_COLOR_ = '#333';
+ol.Geolocation.prototype.disposeInternal = function() {
+  this.setTracking(false);
+  ol.Object.prototype.disposeInternal.call(this);
+};
 
 
 /**
- * Get the font name.
- * @return {string|undefined} Font.
- * @api
+ * @private
  */
-ol.style.Text.prototype.getFont = function() {
-  return this.font_;
+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_));
+    }
+  }
 };
 
 
 /**
- * Get the x-offset for the text.
- * @return {number} Horizontal text offset.
- * @api
+ * @private
  */
-ol.style.Text.prototype.getOffsetX = function() {
-  return this.offsetX_;
+ol.Geolocation.prototype.handleTrackingChanged_ = function() {
+  if (ol.has.GEOLOCATION) {
+    var tracking = this.getTracking();
+    if (tracking && this.watchId_ === undefined) {
+      this.watchId_ = navigator.geolocation.watchPosition(
+          this.positionChange_.bind(this),
+          this.positionError_.bind(this),
+          this.getTrackingOptions());
+    } else if (!tracking && this.watchId_ !== undefined) {
+      navigator.geolocation.clearWatch(this.watchId_);
+      this.watchId_ = undefined;
+    }
+  }
 };
 
 
 /**
- * Get the y-offset for the text.
- * @return {number} Vertical text offset.
- * @api
+ * @private
+ * @param {GeolocationPosition} position position event.
  */
-ol.style.Text.prototype.getOffsetY = function() {
-  return this.offsetY_;
+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();
 };
 
-
 /**
- * Get the fill style for the text.
- * @return {ol.style.Fill} Fill style.
+ * Triggered when the Geolocation returns an error.
+ * @event error
  * @api
  */
-ol.style.Text.prototype.getFill = function() {
-  return this.fill_;
-};
-
 
 /**
- * Get the text rotation.
- * @return {number|undefined} Rotation.
- * @api
+ * @private
+ * @param {GeolocationPositionError} error error object.
  */
-ol.style.Text.prototype.getRotation = function() {
-  return this.rotation_;
+ol.Geolocation.prototype.positionError_ = function(error) {
+  error.type = ol.events.EventType.ERROR;
+  this.setTracking(false);
+  this.dispatchEvent(/** @type {{type: string, target: undefined}} */ (error));
 };
 
 
 /**
- * Get the text scale.
- * @return {number|undefined} Scale.
+ * Get the accuracy of the position in meters.
+ * @return {number|undefined} The accuracy of the position measurement in
+ *     meters.
+ * @observable
  * @api
  */
-ol.style.Text.prototype.getScale = function() {
-  return this.scale_;
+ol.Geolocation.prototype.getAccuracy = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.GeolocationProperty.ACCURACY));
 };
 
 
 /**
- * Get the stroke style for the text.
- * @return {ol.style.Stroke} Stroke style.
+ * Get a geometry of the position accuracy.
+ * @return {?ol.geom.Polygon} A geometry of the position accuracy.
+ * @observable
  * @api
  */
-ol.style.Text.prototype.getStroke = function() {
-  return this.stroke_;
+ol.Geolocation.prototype.getAccuracyGeometry = function() {
+  return /** @type {?ol.geom.Polygon} */ (
+      this.get(ol.GeolocationProperty.ACCURACY_GEOMETRY) || null);
 };
 
 
 /**
- * Get the text to be rendered.
- * @return {string|undefined} Text.
+ * Get the altitude associated with the position.
+ * @return {number|undefined} The altitude of the position in meters above mean
+ *     sea level.
+ * @observable
  * @api
  */
-ol.style.Text.prototype.getText = function() {
-  return this.text_;
+ol.Geolocation.prototype.getAltitude = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.GeolocationProperty.ALTITUDE));
 };
 
 
 /**
- * Get the text alignment.
- * @return {string|undefined} Text align.
+ * Get the altitude accuracy of the position.
+ * @return {number|undefined} The accuracy of the altitude measurement in
+ *     meters.
+ * @observable
  * @api
  */
-ol.style.Text.prototype.getTextAlign = function() {
-  return this.textAlign_;
+ol.Geolocation.prototype.getAltitudeAccuracy = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.GeolocationProperty.ALTITUDE_ACCURACY));
 };
 
 
 /**
- * Get the text baseline.
- * @return {string|undefined} Text baseline.
+ * Get the heading as radians clockwise from North.
+ * @return {number|undefined} The heading of the device in radians from north.
+ * @observable
  * @api
  */
-ol.style.Text.prototype.getTextBaseline = function() {
-  return this.textBaseline_;
+ol.Geolocation.prototype.getHeading = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.GeolocationProperty.HEADING));
 };
 
 
 /**
- * Set the font.
- *
- * @param {string|undefined} font Font.
+ * Get the position of the device.
+ * @return {ol.Coordinate|undefined} The current position of the device reported
+ *     in the current projection.
+ * @observable
  * @api
  */
-ol.style.Text.prototype.setFont = function(font) {
-  this.font_ = font;
-};
-
-
-/**
- * Set the x offset.
- *
- * @param {number} offsetX Horizontal text offset.
- */
-ol.style.Text.prototype.setOffsetX = function(offsetX) {
-  this.offsetX_ = offsetX;
-};
-
-
-/**
- * Set the y offset.
- *
- * @param {number} offsetY Vertical text offset.
- */
-ol.style.Text.prototype.setOffsetY = function(offsetY) {
-  this.offsetY_ = offsetY;
+ol.Geolocation.prototype.getPosition = function() {
+  return /** @type {ol.Coordinate|undefined} */ (
+      this.get(ol.GeolocationProperty.POSITION));
 };
 
 
 /**
- * Set the fill.
- *
- * @param {ol.style.Fill} fill Fill style.
+ * Get the projection associated with the position.
+ * @return {ol.proj.Projection|undefined} The projection the position is
+ *     reported in.
+ * @observable
  * @api
  */
-ol.style.Text.prototype.setFill = function(fill) {
-  this.fill_ = fill;
+ol.Geolocation.prototype.getProjection = function() {
+  return /** @type {ol.proj.Projection|undefined} */ (
+      this.get(ol.GeolocationProperty.PROJECTION));
 };
 
 
 /**
- * Set the rotation.
- *
- * @param {number|undefined} rotation Rotation.
+ * Get the speed in meters per second.
+ * @return {number|undefined} The instantaneous speed of the device in meters
+ *     per second.
+ * @observable
  * @api
  */
-ol.style.Text.prototype.setRotation = function(rotation) {
-  this.rotation_ = rotation;
+ol.Geolocation.prototype.getSpeed = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.GeolocationProperty.SPEED));
 };
 
 
 /**
- * Set the scale.
- *
- * @param {number|undefined} scale Scale.
+ * Determine if the device location is being tracked.
+ * @return {boolean} The device location is being tracked.
+ * @observable
  * @api
  */
-ol.style.Text.prototype.setScale = function(scale) {
-  this.scale_ = scale;
+ol.Geolocation.prototype.getTracking = function() {
+  return /** @type {boolean} */ (
+      this.get(ol.GeolocationProperty.TRACKING));
 };
 
 
 /**
- * Set the stroke.
- *
- * @param {ol.style.Stroke} stroke Stroke style.
+ * 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
  */
-ol.style.Text.prototype.setStroke = function(stroke) {
-  this.stroke_ = stroke;
+ol.Geolocation.prototype.getTrackingOptions = function() {
+  return /** @type {GeolocationPositionOptions|undefined} */ (
+      this.get(ol.GeolocationProperty.TRACKING_OPTIONS));
 };
 
 
 /**
- * Set the text.
- *
- * @param {string|undefined} text Text.
+ * Set the projection to use for transforming the coordinates.
+ * @param {ol.ProjectionLike} projection The projection the position is
+ *     reported in.
+ * @observable
  * @api
  */
-ol.style.Text.prototype.setText = function(text) {
-  this.text_ = text;
+ol.Geolocation.prototype.setProjection = function(projection) {
+  this.set(ol.GeolocationProperty.PROJECTION, ol.proj.get(projection));
 };
 
 
 /**
- * Set the text alignment.
- *
- * @param {string|undefined} textAlign Text align.
+ * Enable or disable tracking.
+ * @param {boolean} tracking Enable tracking.
+ * @observable
  * @api
  */
-ol.style.Text.prototype.setTextAlign = function(textAlign) {
-  this.textAlign_ = textAlign;
+ol.Geolocation.prototype.setTracking = function(tracking) {
+  this.set(ol.GeolocationProperty.TRACKING, tracking);
 };
 
 
 /**
- * Set the text baseline.
- *
- * @param {string|undefined} textBaseline Text baseline.
+ * 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
  */
-ol.style.Text.prototype.setTextBaseline = function(textBaseline) {
-  this.textBaseline_ = textBaseline;
+ol.Geolocation.prototype.setTrackingOptions = function(options) {
+  this.set(ol.GeolocationProperty.TRACKING_OPTIONS, options);
 };
 
-// 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.provide('ol.geom.Circle');
 
-goog.require('goog.Uri');
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom.NodeType');
-goog.require('goog.math');
 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.extent');
 goog.require('ol.geom.GeometryLayout');
 goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.LinearRing');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.proj');
-goog.require('ol.style.Fill');
-goog.require('ol.style.Icon');
-goog.require('ol.style.IconAnchorUnits');
-goog.require('ol.style.IconOrigin');
-goog.require('ol.style.Image');
-goog.require('ol.style.Stroke');
-goog.require('ol.style.Style');
-goog.require('ol.style.Text');
-goog.require('ol.xml');
-
-
-/**
- * @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_;
-
+goog.require('ol.geom.SimpleGeometry');
+goog.require('ol.geom.flat.deflate');
 
 
 /**
  * @classdesc
- * Feature format for reading and writing data in the KML format.
+ * Circle geometry.
  *
  * @constructor
- * @extends {ol.format.XMLFeature}
- * @param {olx.format.KMLOptions=} opt_options Options.
- * @api stable
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {ol.Coordinate} center Center.
+ * @param {number=} opt_radius Radius.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api
  */
-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 {Object.<string, (Array.<ol.style.Style>|string)>}
-   */
-  this.sharedStyles_ = {};
-
+ol.geom.Circle = function(center, opt_radius, opt_layout) {
+  ol.geom.SimpleGeometry.call(this);
+  var radius = opt_radius ? opt_radius : 0;
+  this.setCenterAndRadius(center, radius, opt_layout);
 };
-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];
+ol.inherits(ol.geom.Circle, ol.geom.SimpleGeometry);
 
 
 /**
- * @const
- * @type {string}
- * @private
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.Circle} Clone.
+ * @override
+ * @api
  */
-ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ =
-    'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png';
-
-
-/**
- * @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: 0.5,
-  size: ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_,
-  src: ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_
-});
+ol.geom.Circle.prototype.clone = function() {
+  var circle = new ol.geom.Circle(null);
+  circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+  return circle;
+};
 
 
 /**
- * @const
- * @type {ol.style.Stroke}
- * @private
+ * @inheritDoc
  */
-ol.format.KML.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
-  color: ol.format.KML.DEFAULT_COLOR_,
-  width: 1
-});
+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;
+  }
+};
 
 
 /**
- * @const
- * @type {ol.style.Text}
- * @private
+ * @inheritDoc
  */
-ol.format.KML.DEFAULT_TEXT_STYLE_ = new ol.style.Text({
-  font: 'normal 16px Helvetica',
-  fill: ol.format.KML.DEFAULT_FILL_STYLE_,
-  stroke: ol.format.KML.DEFAULT_STROKE_STYLE_,
-  scale: 1
-});
+ol.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_();
+};
 
 
 /**
- * @const
- * @type {ol.style.Style}
- * @private
+ * Return the center of the circle as {@link ol.Coordinate coordinate}.
+ * @return {ol.Coordinate} Center.
+ * @api
  */
-ol.format.KML.DEFAULT_STYLE_ = new ol.style.Style({
-  fill: ol.format.KML.DEFAULT_FILL_STYLE_,
-  image: ol.format.KML.DEFAULT_IMAGE_STYLE_,
-  text: ol.format.KML.DEFAULT_TEXT_STYLE_,
-  stroke: ol.format.KML.DEFAULT_STROKE_STYLE_,
-  zIndex: 0
-});
+ol.geom.Circle.prototype.getCenter = function() {
+  return this.flatCoordinates.slice(0, this.stride);
+};
 
 
 /**
- * @const
- * @type {Array.<ol.style.Style>}
- * @private
+ * @inheritDoc
  */
-ol.format.KML.DEFAULT_STYLE_ARRAY_ = [ol.format.KML.DEFAULT_STYLE_];
+ol.geom.Circle.prototype.computeExtent = function(extent) {
+  var flatCoordinates = this.flatCoordinates;
+  var radius = flatCoordinates[this.stride] - flatCoordinates[0];
+  return ol.extent.createOrUpdate(
+      flatCoordinates[0] - radius, flatCoordinates[1] - radius,
+      flatCoordinates[0] + radius, flatCoordinates[1] + radius,
+      extent);
+};
 
 
 /**
- * @const
- * @type {Object.<string, ol.style.IconAnchorUnits>}
- * @private
+ * Return the radius of the circle.
+ * @return {number} Radius.
+ * @api
  */
-ol.format.KML.ICON_ANCHOR_UNITS_MAP_ = {
-  'fraction': ol.style.IconAnchorUnits.FRACTION,
-  'pixels': ol.style.IconAnchorUnits.PIXELS
+ol.geom.Circle.prototype.getRadius = function() {
+  return Math.sqrt(this.getRadiusSquared_());
 };
 
 
 /**
- * @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.
- * @return {ol.FeatureStyleFunction} Feature style function.
  * @private
+ * @return {number} Radius squared.
  */
-ol.format.KML.createFeatureStyleFunction_ = function(
-    style, styleUrl, defaultStyle, sharedStyles) {
-  return (
-      /**
-       * @param {number} resolution Resolution.
-       * @return {Array.<ol.style.Style>} Style.
-       * @this {ol.Feature}
-       */
-      function(resolution) {
-        if (style) {
-          return style;
-        }
-        if (styleUrl) {
-          return ol.format.KML.findStyle_(styleUrl, defaultStyle, sharedStyles);
-        }
-        return defaultStyle;
-      });
+ol.geom.Circle.prototype.getRadiusSquared_ = function() {
+  var dx = this.flatCoordinates[this.stride] - this.flatCoordinates[0];
+  var dy = this.flatCoordinates[this.stride + 1] - this.flatCoordinates[1];
+  return dx * dx + dy * dy;
 };
 
 
 /**
- * @param {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
+ * @inheritDoc
+ * @api
  */
-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;
-  }
+ol.geom.Circle.prototype.getType = function() {
+  return ol.geom.GeometryType.CIRCLE;
 };
 
 
 /**
- * @param {Node} node Node.
- * @private
- * @return {ol.Color|undefined} Color.
+ * @inheritDoc
+ * @api
  */
-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
-    ];
+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;
+    }
 
-  } else {
-    return undefined;
+    return ol.extent.forEachCorner(extent, this.intersectsCoordinate, this);
   }
+  return false;
+
 };
 
 
 /**
- * @param {Node} node Node.
- * @private
- * @return {Array.<number>|undefined} Flat coordinates.
+ * Set the center of the circle as {@link ol.Coordinate coordinate}.
+ * @param {ol.Coordinate} center Center.
+ * @api
  */
-ol.format.KML.readFlatCoordinates_ = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  var flatCoordinates = [];
-  // The KML specification states that coordinate tuples should not include
-  // spaces, but we tolerate them.
-  var re =
-      /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i;
-  var m;
-  while ((m = re.exec(s))) {
-    var x = parseFloat(m[1]);
-    var y = parseFloat(m[2]);
-    var z = m[3] ? parseFloat(m[3]) : 0;
-    flatCoordinates.push(x, y, z);
-    s = s.substr(m[0].length);
-  }
-  if (s !== '') {
-    return undefined;
+ol.geom.Circle.prototype.setCenter = function(center) {
+  var stride = this.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];
   }
-  return flatCoordinates;
+  this.setFlatCoordinates(this.layout, flatCoordinates);
 };
 
 
 /**
- * @param {Node} node Node.
- * @private
- * @return {string|undefined} Style URL.
+ * 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.format.KML.readStyleUrl_ = function(node) {
-  var s = ol.xml.getAllTextContent(node, false).trim();
-  if (node.baseURI) {
-    return goog.Uri.resolve(node.baseURI, s).toString();
+ol.geom.Circle.prototype.setCenterAndRadius = function(center, radius, opt_layout) {
+  if (!center) {
+    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
   } else {
-    return s;
+    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 {Node} node Node.
- * @private
- * @return {string} URI.
+ * @inheritDoc
  */
-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();
-  }
-};
+ol.geom.Circle.prototype.getCoordinates = function() {};
 
 
 /**
- * @param {Node} node Node.
- * @private
- * @return {ol.format.KMLVec2_} Vec2.
+ * @inheritDoc
  */
-ol.format.KML.readVec2_ = function(node) {
-  var xunits = node.getAttribute('xunits');
-  var yunits = node.getAttribute('yunits');
-  return {
-    x: parseFloat(node.getAttribute('x')),
-    xunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[xunits],
-    y: parseFloat(node.getAttribute('y')),
-    yunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[yunits]
-  };
-};
+ol.geom.Circle.prototype.setCoordinates = function(coordinates, opt_layout) {};
 
 
 /**
- * @param {Node} node Node.
- * @private
- * @return {number|undefined} Scale.
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
  */
-ol.format.KML.readScale_ = function(node) {
-  var number = ol.format.XSD.readDecimal(node);
-  if (number !== undefined) {
-    return Math.sqrt(number);
-  } else {
-    return undefined;
-  }
+ol.geom.Circle.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<ol.style.Style>|string|undefined} StyleMap.
+ * Set the radius of the circle. The radius is in the units of the projection.
+ * @param {number} radius Radius.
+ * @api
  */
-ol.format.KML.readStyleMapValue_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-      /** @type {Array.<ol.style.Style>|string|undefined} */ (undefined),
-      ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack);
+ol.geom.Circle.prototype.setRadius = function(radius) {
+  this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius;
+  this.changed();
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * 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.ProjectionLike} source The current projection.  Can be a
+ *     string identifier or a {@link ol.proj.Projection} object.
+ * @param {ol.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
+ */
+ol.geom.Circle.prototype.transform;
+
+goog.provide('ol.geom.flat.geodesic');
+
+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.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;
-  }
+ol.geom.flat.geodesic.line_ = function(interpolate, transform, squaredTolerance) {
+  // FIXME reduce garbage generation
+  // FIXME optimize stack operations
 
-  var offset;
-  var x = /** @type {number|undefined} */
-      (IconObject['x']);
-  var y = /** @type {number|undefined} */
-      (IconObject['y']);
-  if (x !== undefined && y !== undefined) {
-    offset = [x, y];
-  }
+  /** @type {Array.<number>} */
+  var flatCoordinates = [];
 
-  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 geoA = interpolate(0);
+  var geoB = interpolate(1);
 
-  var rotation;
-  var heading = /** @type {number} */
-      (object['heading']);
-  if (heading !== undefined) {
-    rotation = goog.math.toRadians(heading);
-  }
+  var a = transform(geoA);
+  var b = transform(geoB);
 
-  var scale = /** @type {number|undefined} */
-      (object['scale']);
-  if (src == ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) {
-    size = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_;
+  /** @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();
+      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);
+    }
   }
 
-  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;
+  return flatCoordinates;
 };
 
 
 /**
- * @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;
+* 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);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * 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.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;
+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);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * 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.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;
-  }
+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('ol.coordinate');
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryLayout');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.flat.geodesic');
+goog.require('ol.math');
+goog.require('ol.proj');
+goog.require('ol.render.EventType');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Stroke');
+goog.require('ol.style.Text');
+
+
+/**
+ * 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;
+
+ /**
+  * @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;
+
+  /**
+   * @type {Array.<ol.GraticuleLabelDataType>}
+   * @private
+   */
+  this.meridiansLabels_ = null;
+
+  /**
+   * @type {Array.<ol.GraticuleLabelDataType>}
+   * @private
+   */
+  this.parallelsLabels_ = null;
+
+  if (options.showLabels == true) {
+    var degreesToString = ol.coordinate.degreesToStringHDMS;
 
-/**
- * @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));
-};
+    /**
+     * @type {null|function(number):string}
+     * @private
+     */
+    this.lonLabelFormatter_ = options.lonLabelFormatter == undefined ?
+        degreesToString.bind(this, 'EW') : options.lonLabelFormatter;
 
+    /**
+     * @type {function(number):string}
+     * @private
+     */
+    this.latLabelFormatter_ = options.latLabelFormatter == undefined ?
+        degreesToString.bind(this, 'NS') : options.latLabelFormatter;
 
-/**
- * @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);
-  }
-};
+    /**
+     * Longitude label position in fractions (0..1) of view extent. 0 means
+     * bottom, 1 means top.
+     * @type {number}
+     * @private
+     */
+    this.lonLabelPosition_ = options.lonLabelPosition == undefined ? 0 :
+        options.lonLabelPosition;
 
+    /**
+     * Latitude Label position in fractions (0..1) of view extent. 0 means left, 1
+     * means right.
+     * @type {number}
+     * @private
+     */
+    this.latLabelPosition_ = options.latLabelPosition == undefined ? 1 :
+        options.latLabelPosition;
 
-/**
- * @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;
-};
+    /**
+     * @type {ol.style.Text}
+     * @private
+     */
+    this.lonLabelStyle_ = options.lonLabelStyle !== undefined ? options.lonLabelStyle :
+        new ol.style.Text({
+          font: '12px Calibri,sans-serif',
+          textBaseline: 'bottom',
+          fill: new ol.style.Fill({
+            color: 'rgba(0,0,0,1)'
+          }),
+          stroke: new ol.style.Stroke({
+            color: 'rgba(255,255,255,1)',
+            width: 3
+          })
+        });
 
+    /**
+     * @type {ol.style.Text}
+     * @private
+     */
+    this.latLabelStyle_ = options.latLabelStyle !== undefined ? options.latLabelStyle :
+        new ol.style.Text({
+          font: '12px Calibri,sans-serif',
+          textAlign: 'end',
+          fill: new ol.style.Fill({
+            color: 'rgba(0,0,0,1)'
+          }),
+          stroke: new ol.style.Stroke({
+            color: 'rgba(255,255,255,1)',
+            width: 3
+          })
+        });
 
-/**
- * @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];
+    this.meridiansLabels_ = [];
+    this.parallelsLabels_ = [];
   }
-  var lineString = new ol.geom.LineString(null);
-  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates);
-  return lineString;
+
+  this.setMap(options.map !== undefined ? options.map : null);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @type {ol.style.Stroke}
  * @private
- * @return {Object} Icon object.
+ * @const
  */
-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;
-  }
-};
+ol.Graticule.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
+  color: 'rgba(0,0,0,0.2)'
+});
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * TODO can be configurable
+ * @type {Array.<number>}
  * @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));
-};
+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 {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @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
- * @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;
+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)) {
+    if (this.meridiansLabels_) {
+      var textPoint = this.getMeridianPoint_(lineString, extent, index);
+      this.meridiansLabels_[index] = {
+        geom: textPoint,
+        text: this.lonLabelFormatter_(lon)
+      };
+    }
+    this.meridians_[index++] = lineString;
   }
+  return index;
 };
 
-
 /**
- * @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 {ol.geom.LineString} lineString Meridian
+ * @param {ol.Extent} extent Extent.
+ * @param {number} index Index.
+ * @return {ol.geom.Point} Meridian point.
+ * @private
+ */
+ol.Graticule.prototype.getMeridianPoint_ = function(lineString, extent, index) {
+  var flatCoordinates = lineString.getFlatCoordinates();
+  var clampedBottom = Math.max(extent[1], flatCoordinates[1]);
+  var clampedTop = Math.min(extent[3], flatCoordinates[flatCoordinates.length - 1]);
+  var lat = ol.math.clamp(
+      extent[1] + Math.abs(extent[1] - extent[3]) * this.lonLabelPosition_,
+      clampedBottom, clampedTop);
+  var coordinate = [flatCoordinates[0], lat];
+  var point = this.meridiansLabels_[index] !== undefined ?
+      this.meridiansLabels_[index].geom : new ol.geom.Point(null);
+  point.setCoordinates(coordinate);
+  return point;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @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
- * @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;
+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)) {
+    if (this.parallelsLabels_) {
+      var textPoint = this.getParallelPoint_(lineString, extent, index);
+      this.parallelsLabels_[index] = {
+        geom: textPoint,
+        text: this.latLabelFormatter_(lat)
+      };
     }
-  } else {
-    return new ol.geom.GeometryCollection(geometries);
+    this.parallels_[index++] = lineString;
   }
+  return index;
 };
 
 
 /**
- * @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 {ol.geom.LineString} lineString Parallels.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} index Index.
+ * @return {ol.geom.Point} Parallel point.
+ * @private
+ */
+ol.Graticule.prototype.getParallelPoint_ = function(lineString, extent, index) {
+  var flatCoordinates = lineString.getFlatCoordinates();
+  var clampedLeft = Math.max(extent[0], flatCoordinates[0]);
+  var clampedRight = Math.min(extent[2], flatCoordinates[flatCoordinates.length - 2]);
+  var lon = ol.math.clamp(
+      extent[0] + Math.abs(extent[0] - extent[2]) * this.latLabelPosition_,
+      clampedLeft, clampedRight);
+  var coordinate = [lon, flatCoordinates[1]];
+  var point = this.parallelsLabels_[index] !== undefined ?
+      this.parallelsLabels_[index].geom : new ol.geom.Point(null);
+  point.setCoordinates(coordinate);
+  return point;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {number} squaredTolerance Squared tolerance.
  * @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);
+ol.Graticule.prototype.createGraticule_ = function(extent, center, resolution, squaredTolerance) {
+
+  var interval = this.getInterval_(resolution);
+  if (interval == -1) {
+    this.meridians_.length = this.parallels_.length = 0;
+    if (this.meridiansLabels_) {
+      this.meridiansLabels_.length = 0;
     }
-    polygon.setFlatCoordinates(
-        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
-    polygon.setProperties(properties);
-    return polygon;
-  } else {
-    return undefined;
+    if (this.parallelsLabels_) {
+      this.parallelsLabels_.length = 0;
+    }
+    return;
   }
-};
 
+  var centerLonLat = this.toLonLatTransform_(center);
+  var centerLon = centerLonLat[0];
+  var centerLat = centerLonLat[1];
+  var maxLines = this.maxLines_;
+  var cnt, idx, lat, lon;
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {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
-  })];
-};
-
+  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_)
+  ];
 
-/**
- * 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);
-  }
-};
+  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
 
-/**
- * @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;
-    }
-  }
-};
+  centerLon = Math.floor(centerLon / interval) * interval;
+  lon = ol.math.clamp(centerLon, this.minLon_, this.maxLon_);
 
+  idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, 0);
 
-/**
- * @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);
-};
+  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_);
 
-/**
- * @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;
+  cnt = 0;
+  while (lon != this.maxLon_ && cnt++ < maxLines) {
+    lon = Math.min(lon + interval, this.maxLon_);
+    idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
   }
-  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;
-    }
+
+  this.meridians_.length = idx;
+  if (this.meridiansLabels_) {
+    this.meridiansLabels_.length = idx;
   }
-};
 
+  // Create parallels
 
-/**
- * @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');
-  }
-};
+  centerLat = Math.floor(centerLat / interval) * interval;
+  lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_);
 
+  idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, 0);
 
-/**
- * @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);
-};
+  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_);
 
-/**
- * @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;
+  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;
+  if (this.parallelsLabels_) {
+    this.parallelsLabels_.length = idx;
   }
+
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {number} resolution Resolution.
+ * @return {number} The interval in degrees.
  * @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);
+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;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Get the map associated with this graticule.
+ * @return {ol.Map} The map.
+ * @api
  */
-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;
-  }
+ol.Graticule.prototype.getMap = function() {
+  return this.map_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @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.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);
+ol.Graticule.prototype.getMeridian_ = function(lon, minLat, maxLat,
+                                               squaredTolerance, index) {
+  var flatCoordinates = ol.geom.flat.geodesic.meridian(lon,
+     minLat, maxLat, this.projection_, squaredTolerance);
+  var lineString = this.meridians_[index] !== undefined ?
+     this.meridians_[index] : new ol.geom.LineString(null);
+  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
+  return lineString;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Get the list of meridians.  Meridians are lines of equal longitude.
+ * @return {Array.<ol.geom.LineString>} The meridians.
+ * @api
  */
-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);
-  }
+ol.Graticule.prototype.getMeridians = function() {
+  return this.meridians_;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @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.format.KML.DATA_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'value': ol.xml.makeReplacer(ol.format.XSD.readString)
-    });
+ol.Graticule.prototype.getParallel_ = function(lat, minLon, maxLon,
+                                               squaredTolerance, index) {
+  var flatCoordinates = ol.geom.flat.geodesic.parallel(lat,
+     this.minLon_, this.maxLon_, this.projection_, squaredTolerance);
+  var lineString = this.parallels_[index] !== undefined ?
+     this.parallels_[index] : new ol.geom.LineString(null);
+  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
+  return lineString;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Get the list of parallels.  Pallels are lines of equal latitude.
+ * @return {Array.<ol.geom.LineString>} The parallels.
+ * @api
  */
-ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Data': ol.format.KML.DataParser_,
-      'SchemaData': ol.format.KML.SchemaDataParser_
-    });
+ol.Graticule.prototype.getParallels = function() {
+  return this.parallels_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.render.Event} e Event.
  * @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)
-    });
+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);
 
-/**
- * @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_)
-    });
+  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);
 
-/**
- * @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_
-    });
+  // 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.drawGeometry(line);
+  }
+  for (i = 0, l = this.parallels_.length; i < l; ++i) {
+    line = this.parallels_[i];
+    vectorContext.drawGeometry(line);
+  }
+  var labelData;
+  if (this.meridiansLabels_) {
+    for (i = 0, l = this.meridiansLabels_.length; i < l; ++i) {
+      labelData = this.meridiansLabels_[i];
+      this.lonLabelStyle_.setText(labelData.text);
+      vectorContext.setTextStyle(this.lonLabelStyle_);
+      vectorContext.drawGeometry(labelData.geom);
+    }
+  }
+  if (this.parallelsLabels_) {
+    for (i = 0, l = this.parallelsLabels_.length; i < l; ++i) {
+      labelData = this.parallelsLabels_[i];
+      this.latLabelStyle_.setText(labelData.text);
+      vectorContext.setTextStyle(this.latLabelStyle_);
+      vectorContext.drawGeometry(labelData.geom);
+    }
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.proj.Projection} projection Projection.
  * @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_
-        }));
+ol.Graticule.prototype.updateProjectionInfo_ = function(projection) {
+  var epsg4326Projection = ol.proj.get('EPSG:4326');
 
+  var extent = projection.getExtent();
+  var worldExtent = projection.getWorldExtent();
+  var worldExtentP = ol.proj.transformExtent(worldExtent,
+     epsg4326Projection, projection);
 
-/**
- * @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_)
-    });
+  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];
 
-/**
- * @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)
-        }));
+  this.maxLat_ = maxLat;
+  this.maxLon_ = maxLon;
+  this.minLat_ = minLat;
+  this.minLon_ = minLon;
 
+  this.maxLatP_ = maxLatP;
+  this.maxLonP_ = maxLonP;
+  this.minLatP_ = minLatP;
+  this.minLonP_ = minLonP;
 
-/**
- * @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_)
-    });
 
+  this.fromLonLatTransform_ = ol.proj.getTransform(
+     epsg4326Projection, projection);
 
-/**
- * @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_)
-    });
+  this.toLonLatTransform_ = ol.proj.getTransform(
+     projection, epsg4326Projection);
 
+  this.projectionCenterLonLat_ = this.toLonLatTransform_(
+     ol.extent.getCenter(extent));
 
-/**
- * @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_)
-    });
+  this.projection_ = projection;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Set the map for this graticule.  The graticule will be rendered on the
+ * provided map.
+ * @param {ol.Map} map Map.
+ * @api
  */
-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)
-    });
+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.ImageBase');
 
-/**
- * @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_)
-    });
+goog.require('ol');
+goog.require('ol.events.EventTarget');
+goog.require('ol.events.EventType');
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @constructor
+ * @abstract
+ * @extends {ol.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.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.GX_NAMESPACE_URIS_, {
-      'Track': ol.xml.makeArrayPusher(ol.format.KML.readGxTrack_)
-    });
+ol.ImageBase = function(extent, resolution, pixelRatio, state, attributions) {
 
+  ol.events.EventTarget.call(this);
 
-/**
- * @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)
-    });
+  /**
+   * @private
+   * @type {Array.<ol.Attribution>}
+   */
+  this.attributions_ = attributions;
 
+  /**
+   * @protected
+   * @type {ol.Extent}
+   */
+  this.extent = extent;
 
-/**
- * @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_)
-    });
+  /**
+   * @private
+   * @type {number}
+   */
+  this.pixelRatio_ = pixelRatio;
 
+  /**
+   * @protected
+   * @type {number|undefined}
+   */
+  this.resolution = resolution;
 
-/**
- * @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_)
-    });
+  /**
+   * @protected
+   * @type {ol.ImageState}
+   */
+  this.state = state;
+
+};
+ol.inherits(ol.ImageBase, ol.events.EventTarget);
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @protected
  */
-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_)
-    });
+ol.ImageBase.prototype.changed = function() {
+  this.dispatchEvent(ol.events.EventType.CHANGE);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {Array.<ol.Attribution>} Attributions.
  */
-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')
-        }
-    ));
+ol.ImageBase.prototype.getAttributions = function() {
+  return this.attributions_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {ol.Extent} Extent.
  */
-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)
-    });
+ol.ImageBase.prototype.getExtent = function() {
+  return this.extent;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @abstract
+ * @param {Object=} opt_context Object.
+ * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
  */
-ol.format.KML.SCHEMA_DATA_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'SimpleData': ol.format.KML.SimpleDataParser_
-    });
+ol.ImageBase.prototype.getImage = function(opt_context) {};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {number} PixelRatio.
  */
-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_
-    });
+ol.ImageBase.prototype.getPixelRatio = function() {
+  return this.pixelRatio_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {number} Resolution.
  */
-ol.format.KML.STYLE_MAP_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Pair': ol.format.KML.PairDataParser_
-    });
+ol.ImageBase.prototype.getResolution = function() {
+  return /** @type {number} */ (this.resolution);
+};
 
 
 /**
- * @inheritDoc
+ * @return {ol.ImageState} State.
  */
-ol.format.KML.prototype.getExtensions = function() {
-  return ol.format.KML.EXTENSIONS_;
+ol.ImageBase.prototype.getState = function() {
+  return this.state;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<ol.Feature>|undefined} Features.
+ * Load not yet loaded URI.
+ * @abstract
  */
-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;
-  }
-};
+ol.ImageBase.prototype.load = function() {};
+
+goog.provide('ol.Image');
+
+goog.require('ol');
+goog.require('ol.ImageBase');
+goog.require('ol.ImageState');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
+goog.require('ol.obj');
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Feature.
+ * @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.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);
+ol.Image = function(extent, resolution, pixelRatio, attributions, src,
+    crossOrigin, imageLoadFunction) {
+
+  ol.ImageBase.call(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;
   }
-  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'];
+  /**
+   * @private
+   * @type {Object.<number, (HTMLCanvasElement|Image|HTMLVideoElement)>}
+   */
+  this.imageByContext_ = {};
+
+  /**
+   * @private
+   * @type {Array.<ol.EventsKey>}
+   */
+  this.imageListenerKeys_ = null;
 
-  if (this.extractStyles_) {
-    var style = object['Style'];
-    var styleUrl = object['styleUrl'];
-    var styleFunction = ol.format.KML.createFeatureStyleFunction_(
-        style, styleUrl, this.defaultStyle_, this.sharedStyles_);
-    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
+  /**
+   * @protected
+   * @type {ol.ImageState}
+   */
+  this.state = ol.ImageState.IDLE;
 
-  feature.setProperties(object);
+  /**
+   * @private
+   * @type {ol.ImageLoadFunctionType}
+   */
+  this.imageLoadFunction_ = imageLoadFunction;
 
-  return feature;
 };
+ol.inherits(ol.Image, ol.ImageBase);
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @inheritDoc
+ * @api
  */
-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;
+ol.Image.prototype.getImage = function(opt_context) {
+  if (opt_context !== undefined) {
+    var image;
+    var key = ol.getUid(opt_context);
+    if (key in this.imageByContext_) {
+      return this.imageByContext_[key];
+    } else if (ol.obj.isEmpty(this.imageByContext_)) {
+      image = this.image_;
+    } else {
+      image = /** @type {Image} */ (this.image_.cloneNode(false));
     }
+    this.imageByContext_[key] = image;
+    return image;
+  } else {
+    return this.image_;
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * Tracks loading or read errors.
+ *
  * @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;
+ol.Image.prototype.handleImageError_ = function() {
+  this.state = ol.ImageState.ERROR;
+  this.unlistenImage_();
+  this.changed();
 };
 
 
 /**
- * Read the first feature from a KML source.
+ * Tracks successful image load.
  *
- * @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
+ * @private
  */
-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;
+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();
 };
 
 
 /**
- * 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
+ * Load the image or retry if loading previously failed.
+ * Loading is taken care of by the tile queue, and calling this method is
+ * only needed for preloading or for reloading in case of an error.
+ * @override
+ * @api
  */
-ol.format.KML.prototype.readFeatures;
+ol.Image.prototype.load = function() {
+  if (this.state == ol.ImageState.IDLE || this.state == ol.ImageState.ERROR) {
+    this.state = ol.ImageState.LOADING;
+    this.changed();
+    this.imageListenerKeys_ = [
+      ol.events.listenOnce(this.image_, ol.events.EventType.ERROR,
+          this.handleImageError_, this),
+      ol.events.listenOnce(this.image_, ol.events.EventType.LOAD,
+          this.handleImageLoad_, this)
+    ];
+    this.imageLoadFunction_(this, this.src_);
+  }
+};
 
 
 /**
- * @inheritDoc
+ * @param {HTMLCanvasElement|Image|HTMLVideoElement} image Image.
  */
-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 [];
-  }
+ol.Image.prototype.setImage = function(image) {
+  this.image_ = image;
 };
 
 
 /**
- * Read the name of the KML.
+ * Discards event handlers which listen for load completion or errors.
  *
- * @param {Document|Node|string} source Souce.
- * @return {string|undefined} Name.
- * @api stable
+ * @private
  */
-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;
-  }
+ol.Image.prototype.unlistenImage_ = function() {
+  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
+  this.imageListenerKeys_ = null;
 };
 
+goog.provide('ol.ImageCanvas');
+
+goog.require('ol');
+goog.require('ol.ImageBase');
+goog.require('ol.ImageState');
+
 
 /**
- * @param {Document} doc Document.
- * @return {string|undefined} Name.
+ * @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.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;
+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;
+
+  ol.ImageBase.call(this, extent, resolution, pixelRatio, state, attributions);
+
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.canvas_ = canvas;
+
+  /**
+   * @private
+   * @type {Error}
+   */
+  this.error_ = null;
+
 };
+ol.inherits(ol.ImageCanvas, ol.ImageBase);
 
 
 /**
- * @param {Node} node Node.
- * @return {string|undefined} Name.
+ * Get any error associated with asynchronous rendering.
+ * @return {Error} Any error that occurred during rendering.
  */
-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;
+ol.ImageCanvas.prototype.getError = function() {
+  return this.error_;
 };
 
 
 /**
- * Read the network links of the KML.
- *
- * @param {Document|Node|string} source Source.
- * @return {Array.<Object>} Network links.
- * @api
+ * Handle async drawing complete.
+ * @param {Error} err Any error during drawing.
+ * @private
  */
-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));
+ol.ImageCanvas.prototype.handleLoad_ = function(err) {
+  if (err) {
+    this.error_ = err;
+    this.state = ol.ImageState.ERROR;
   } else {
-    goog.asserts.fail('unknown type for source');
+    this.state = ol.ImageState.LOADED;
   }
-  return networkLinks;
+  this.changed();
 };
 
 
 /**
- * @param {Document} doc Document.
- * @return {Array.<Object>} Network links.
+ * @inheritDoc
  */
-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));
-    }
+ol.ImageCanvas.prototype.load = function() {
+  if (this.state == ol.ImageState.IDLE) {
+    this.state = ol.ImageState.LOADING;
+    this.changed();
+    this.loader_(this.handleLoad_.bind(this));
   }
-  return networkLinks;
 };
 
 
 /**
- * @param {Node} node Node.
- * @return {Array.<Object>} Network links.
+ * @inheritDoc
  */
-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;
+ol.ImageCanvas.prototype.getImage = function(opt_context) {
+  return this.canvas_;
 };
 
+goog.provide('ol.Tile');
+
+goog.require('ol');
+goog.require('ol.TileState');
+goog.require('ol.events.EventTarget');
+goog.require('ol.events.EventType');
+
 
 /**
- * Read the projection from a KML source.
+ * @classdesc
+ * Base class for tiles.
  *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
+ * @constructor
+ * @abstract
+ * @extends {ol.events.EventTarget}
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.TileState} state State.
  */
-ol.format.KML.prototype.readProjection;
+ol.Tile = function(tileCoord, state) {
+
+  ol.events.EventTarget.call(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 = '';
+
+};
+ol.inherits(ol.Tile, ol.events.EventTarget);
 
 
 /**
- * @param {Node} node Node to append a TextNode with the color to.
- * @param {ol.Color|string} color Color.
- * @private
+ * @protected
  */
-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(''));
+ol.Tile.prototype.changed = function() {
+  this.dispatchEvent(ol.events.EventType.CHANGE);
 };
 
 
 /**
- * @param {Node} node Node to append a TextNode with the coordinates to.
- * @param {Array.<number>} coordinates Coordinates.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @return {string} Key.
  */
-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'];
+ol.Tile.prototype.getKey = function() {
+  return this.key + '/' + this.tileCoord;
+};
 
-  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');
+/**
+ * Get the interim tile most suitable for rendering using the chain of interim
+ * tiles. This corresponds to the  most recent tile that has been loaded, if no
+ * such tile exists, the original tile is returned.
+ * @return {!ol.Tile} Best tile for rendering.
+ */
+ol.Tile.prototype.getInterimTile = function() {
+  if (!this.interimTile) {
+    //empty chain
+    return this;
   }
+  var tile = this.interimTile;
 
-  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];
-      }
+  // find the first loaded tile and return it. Since the chain is sorted in
+  // decreasing order of creation time, there is no need to search the remainder
+  // of the list (all those tiles correspond to older requests and will be
+  // cleaned up by refreshInterimChain)
+  do {
+    if (tile.getState() == ol.TileState.LOADED) {
+      return tile;
     }
-  }
-  ol.format.XSD.writeStringTextNode(node, text);
+    tile = tile.interimTile;
+  } while (tile);
+
+  // we can not find a better tile
+  return this;
 };
 
+/**
+ * Goes through the chain of interim tiles and discards sections of the chain
+ * that are no longer relevant.
+ */
+ol.Tile.prototype.refreshInterimChain = function() {
+  if (!this.interimTile) {
+    return;
+  }
+
+  var tile = this.interimTile;
+  var prev = this;
+
+  do {
+    if (tile.getState() == ol.TileState.LOADED) {
+      //we have a loaded tile, we can discard the rest of the list
+      //we would could abort any LOADING tile request
+      //older than this tile (i.e. any LOADING tile following this entry in the chain)
+      tile.interimTile = null;
+      break;
+    } else if (tile.getState() == ol.TileState.LOADING) {
+      //keep this LOADING tile any loaded tiles later in the chain are
+      //older than this tile, so we're still interested in the request
+      prev = tile;
+    } else if (tile.getState() == ol.TileState.IDLE) {
+      //the head of the list is the most current tile, we don't need
+      //to start any other requests for this chain
+      prev.interimTile = tile.interimTile;
+    } else {
+      prev = tile;
+    }
+    tile = prev.interimTile;
+  } while (tile);
+};
 
 /**
- * @param {Node} node Node.
- * @param {Array.<ol.Feature>} features Features.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Get the tile coordinate for this tile.
+ * @return {ol.TileCoord} The tile coordinate.
+ * @api
  */
-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);
+ol.Tile.prototype.getTileCoord = function() {
+  return this.tileCoord;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Object} icon Icon object.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @return {ol.TileState} State.
  */
-ol.format.KML.writeIcon_ = function(node, icon, objectStack) {
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.KML.ICON_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(icon, orderedKeys);
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.ICON_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      values, objectStack, orderedKeys);
-  orderedKeys =
-      ol.format.KML.ICON_SEQUENCE_[ol.format.KML.GX_NAMESPACE_URIS_[0]];
-  values = ol.xml.makeSequence(icon, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_SERIALIZERS_,
-      ol.format.KML.GX_NODE_FACTORY_, values, objectStack, orderedKeys);
+ol.Tile.prototype.getState = function() {
+  return this.state;
+};
+
+/**
+ * @param {ol.TileState} state State.
+ */
+ol.Tile.prototype.setState = function(state) {
+  this.state = state;
+  this.changed();
 };
 
+/**
+ * Load the image or retry if loading previously failed.
+ * Loading is taken care of by the tile queue, and calling this method is
+ * only needed for preloading or for reloading in case of an error.
+ * @abstract
+ * @api
+ */
+ol.Tile.prototype.load = function() {};
+
+goog.provide('ol.ImageTile');
+
+goog.require('ol');
+goog.require('ol.Tile');
+goog.require('ol.TileState');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+
 
 /**
- * @param {Node} node Node.
- * @param {ol.style.Icon} style Icon style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @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.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
-  };
+ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction) {
 
-  if (size) {
-    iconProperties['w'] = size[0];
-    iconProperties['h'] = size[1];
-    var anchor = style.getAnchor(); // top-left
-    var origin = style.getOrigin(); // top-left
+  ol.Tile.call(this, tileCoord, state);
 
-    if (origin && iconImageSize && origin[0] !== 0 && origin[1] !== size[1]) {
-      iconProperties['x'] = origin[0];
-      iconProperties['y'] = iconImageSize[1] - (origin[1] + size[1]);
-    }
+  /**
+   * Image URI
+   *
+   * @private
+   * @type {string}
+   */
+  this.src_ = src;
 
-    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;
-    }
+  /**
+   * @private
+   * @type {Image}
+   */
+  this.image_ = new Image();
+  if (crossOrigin !== null) {
+    this.image_.crossOrigin = crossOrigin;
   }
 
-  properties['Icon'] = iconProperties;
-
-  var scale = style.getScale();
-  if (scale !== 1) {
-    properties['scale'] = scale;
-  }
+  /**
+   * @private
+   * @type {Array.<ol.EventsKey>}
+   */
+  this.imageListenerKeys_ = null;
 
-  var rotation = style.getRotation();
-  if (rotation !== 0) {
-    properties['heading'] = rotation; // 0-360
-  }
+  /**
+   * @private
+   * @type {ol.TileLoadFunctionType}
+   */
+  this.tileLoadFunction_ = tileLoadFunction;
 
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.KML.ICON_STYLE_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_STYLE_SERIALIZERS_,
-      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
 };
+ol.inherits(ol.ImageTile, ol.Tile);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.style.Text} style style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @inheritDoc
  */
-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();
+ol.ImageTile.prototype.disposeInternal = function() {
+  if (this.state == ol.TileState.LOADING) {
+    this.unlistenImage_();
   }
-  var scale = style.getScale();
-  if (scale && scale !== 1) {
-    properties['scale'] = scale;
+  if (this.interimTile) {
+    this.interimTile.dispose();
   }
-  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);
+  this.state = ol.TileState.ABORT;
+  this.changed();
+  ol.Tile.prototype.disposeInternal.call(this);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.style.Stroke} style style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Get the HTML image element for this tile (may be a Canvas, Image, or Video).
+ * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image.
+ * @api
  */
-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);
+ol.ImageTile.prototype.getImage = function() {
+  return this.image_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @inheritDoc
  */
-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);
+ol.ImageTile.prototype.getKey = function() {
+  return this.src_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LinearRing} linearRing Linear ring.
- * @param {Array.<*>} objectStack Object stack.
+ * Tracks loading or read errors.
+ *
  * @private
  */
-ol.format.KML.writeBoundaryIs_ = function(node, linearRing, objectStack) {
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.BOUNDARY_IS_SERIALIZERS_,
-      ol.format.KML.LINEAR_RING_NODE_FACTORY_, [linearRing], objectStack);
+ol.ImageTile.prototype.handleImageError_ = function() {
+  this.state = ol.TileState.ERROR;
+  this.image_ = ol.ImageTile.blankImage;
+  this.unlistenImage_();
+  this.changed();
 };
 
 
 /**
- * FIXME currently we do serialize arbitrary/custom feature properties
- * (ExtendedData).
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Object stack.
+ * Tracks successful image load.
+ *
  * @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) {
-      properties['Style'] = styles[0];
-      var textStyle = styles[0].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.ImageTile.prototype.handleImageLoad_ = function() {
+  if (this.image_.naturalWidth && this.image_.naturalHeight) {
+    this.state = ol.TileState.LOADED;
+  } else {
+    this.state = ol.TileState.EMPTY;
   }
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_,
-      ol.format.KML.GEOMETRY_NODE_FACTORY_, [geometry], objectStack);
+  this.unlistenImage_();
+  this.changed();
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.SimpleGeometry} geometry Geometry.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @inheritDoc
+ * @api
  */
-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);
+ol.ImageTile.prototype.load = function() {
+  if (this.state == ol.TileState.IDLE || this.state == ol.TileState.ERROR) {
+    this.state = ol.TileState.LOADING;
+    this.changed();
+    this.imageListenerKeys_ = [
+      ol.events.listenOnce(this.image_, ol.events.EventType.ERROR,
+          this.handleImageError_, this),
+      ol.events.listenOnce(this.image_, ol.events.EventType.LOAD,
+          this.handleImageLoad_, this)
+    ];
+    this.tileLoadFunction_(this, this.src_);
+  }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Polygon} polygon Polygon.
- * @param {Array.<*>} objectStack Object stack.
+ * Discards event handlers which listen for load completion or errors.
+ *
  * @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);
+ol.ImageTile.prototype.unlistenImage_ = function() {
+  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
+  this.imageListenerKeys_ = null;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.style.Fill} style Style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * A blank image.
+ * @type {Image}
  */
-ol.format.KML.writePolyStyle_ = function(node, style, objectStack) {
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.POLY_STYLE_SERIALIZERS_,
-      ol.format.KML.COLOR_NODE_FACTORY_, [style.getColor()], objectStack);
-};
+ol.ImageTile.blankImage = new Image();
+ol.ImageTile.blankImage.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
+
+// FIXME should handle all geo-referenced data, not just vector data
+
+goog.provide('ol.interaction.DragAndDrop');
+
+goog.require('ol');
+goog.require('ol.functions');
+goog.require('ol.events');
+goog.require('ol.events.Event');
+goog.require('ol.events.EventType');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.proj');
 
 
 /**
- * @param {Node} node Node to append a TextNode with the scale to.
- * @param {number|undefined} scale Scale.
- * @private
+ * @classdesc
+ * Handles input of vector data by drag and drop.
+ *
+ * @constructor
+ * @extends {ol.interaction.Interaction}
+ * @fires ol.interaction.DragAndDrop.Event
+ * @param {olx.interaction.DragAndDropOptions=} opt_options Options.
+ * @api
  */
-ol.format.KML.writeScaleTextNode_ = function(node, scale) {
-  ol.format.XSD.writeDecimalTextNode(node, scale * scale);
+ol.interaction.DragAndDrop = function(opt_options) {
+
+  var options = opt_options ? opt_options : {};
+
+  ol.interaction.Interaction.call(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 {Array.<ol.EventsKey>}
+   */
+  this.dropListenKeys_ = null;
+
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.target = options.target ? options.target : null;
+
 };
+ol.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.style.Style} style Style.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {Event} event Event.
+ * @this {ol.interaction.DragAndDrop}
  * @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) {
-    properties['IconStyle'] = imageStyle;
-  }
-  if (textStyle) {
-    properties['LabelStyle'] = textStyle;
-  }
-  if (strokeStyle) {
-    properties['LineStyle'] = strokeStyle;
-  }
-  if (fillStyle) {
-    properties['PolyStyle'] = fillStyle;
+ol.interaction.DragAndDrop.handleDrop_ = function(event) {
+  var files = event.dataTransfer.files;
+  var i, ii, file;
+  for (i = 0, ii = files.length; i < ii; ++i) {
+    file = files.item(i);
+    var reader = new FileReader();
+    reader.addEventListener(ol.events.EventType.LOAD,
+        this.handleResult_.bind(this, file));
+    reader.readAsText(file);
   }
-  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.
+ * @param {Event} event Event.
  * @private
  */
-ol.format.KML.writeVec2_ = function(node, vec2) {
-  node.setAttribute('x', vec2.x);
-  node.setAttribute('y', vec2.y);
-  node.setAttribute('xunits', vec2.xunits);
-  node.setAttribute('yunits', vec2.yunits);
+ol.interaction.DragAndDrop.handleStop_ = function(event) {
+  event.stopPropagation();
+  event.preventDefault();
+  event.dataTransfer.dropEffect = 'copy';
 };
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
+ * @param {File} file File.
+ * @param {Event} event Load event.
  * @private
  */
-ol.format.KML.KML_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'Document', 'Placemark'
-    ]);
-
+ol.interaction.DragAndDrop.prototype.handleResult_ = function(file, event) {
+  var result = event.target.result;
+  var map = this.getMap();
+  var projection = this.projection_;
+  if (!projection) {
+    var view = map.getView();
+    projection = view.getProjection();
+  }
 
-/**
- * @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_)
+  var formatConstructors = this.formatConstructors_;
+  var features = [];
+  var i, ii;
+  for (i = 0, ii = formatConstructors.length; i < ii; ++i) {
+    /**
+     * Avoid "cannot instantiate abstract class" error.
+     * @type {Function}
+     */
+    var formatConstructor = formatConstructors[i];
+    /**
+     * @type {ol.format.Feature}
+     */
+    var format = new formatConstructor();
+    features = this.tryReadFeatures_(format, result, {
+      featureProjection: projection
     });
+    if (features && features.length > 0) {
+      break;
+    }
+  }
+  this.dispatchEvent(
+      new ol.interaction.DragAndDrop.Event(
+          ol.interaction.DragAndDrop.EventType_.ADD_FEATURES, file,
+          features, projection));
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * Handles the {@link ol.MapBrowserEvent map browser event} unconditionally and
+ * neither prevents the browser default nor stops event propagation.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.DragAndDrop}
+ * @api
  */
-ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_)
-    });
+ol.interaction.DragAndDrop.handleEvent = ol.functions.TRUE;
 
 
 /**
- * @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'
+ol.interaction.DragAndDrop.prototype.registerListeners_ = function() {
+  var map = this.getMap();
+  if (map) {
+    var dropArea = this.target ? this.target : map.getViewport();
+    this.dropListenKeys_ = [
+      ol.events.listen(dropArea, ol.events.EventType.DROP,
+          ol.interaction.DragAndDrop.handleDrop_, this),
+      ol.events.listen(dropArea, ol.events.EventType.DRAGENTER,
+          ol.interaction.DragAndDrop.handleStop_, this),
+      ol.events.listen(dropArea, ol.events.EventType.DRAGOVER,
+          ol.interaction.DragAndDrop.handleStop_, this),
+      ol.events.listen(dropArea, ol.events.EventType.DROP,
+          ol.interaction.DragAndDrop.handleStop_, this)
+    ];
+  }
 };
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
+ * @inheritDoc
  */
-ol.format.KML.ICON_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'href'
-    ],
-    ol.xml.makeStructureNS(
-        ol.format.KML.GX_NAMESPACE_URIS_, [
-          'x', 'y', 'w', 'h'
-    ]));
+ol.interaction.DragAndDrop.prototype.setActive = function(active) {
+  ol.interaction.Interaction.prototype.setActive.call(this, active);
+  if (active) {
+    this.registerListeners_();
+  } else {
+    this.unregisterListeners_();
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @inheritDoc
  */
-ol.format.KML.ICON_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'href': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
-    }, ol.xml.makeStructureNS(
-        ol.format.KML.GX_NAMESPACE_URIS_, {
-          'x': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-          'y': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-          'w': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-          'h': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode)
-        }));
+ol.interaction.DragAndDrop.prototype.setMap = function(map) {
+  this.unregisterListeners_();
+  ol.interaction.Interaction.prototype.setMap.call(this, map);
+  if (this.getActive()) {
+    this.registerListeners_();
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
+ * @param {ol.format.Feature} format Format.
+ * @param {string} text Text.
+ * @param {olx.format.ReadOptions} options Read options.
  * @private
+ * @return {Array.<ol.Feature>} Features.
  */
-ol.format.KML.ICON_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'scale', 'heading', 'Icon', 'hotSpot'
-    ]);
+ol.interaction.DragAndDrop.prototype.tryReadFeatures_ = function(format, text, options) {
+  try {
+    return format.readFeatures(text, options);
+  } catch (e) {
+    return null;
+  }
+};
 
 
 /**
- * @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_)
-    });
+ol.interaction.DragAndDrop.prototype.unregisterListeners_ = function() {
+  if (this.dropListenKeys_) {
+    this.dropListenKeys_.forEach(ol.events.unlistenByKey);
+    this.dropListenKeys_ = null;
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
+ * @enum {string}
  * @private
  */
-ol.format.KML.LABEL_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'color', 'scale'
-    ]);
+ol.interaction.DragAndDrop.EventType_ = {
+  /**
+   * Triggered when features are added
+   * @event ol.interaction.DragAndDrop.Event#addfeatures
+   * @api
+   */
+  ADD_FEATURES: 'addfeatures'
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @classdesc
+ * Events emitted by {@link ol.interaction.DragAndDrop} instances are instances
+ * of this type.
+ *
+ * @constructor
+ * @extends {ol.events.Event}
+ * @implements {oli.interaction.DragAndDropEvent}
+ * @param {ol.interaction.DragAndDrop.EventType_} type Type.
+ * @param {File} file File.
+ * @param {Array.<ol.Feature>=} opt_features Features.
+ * @param {ol.proj.Projection=} opt_projection Projection.
  */
-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_)
-    });
+ol.interaction.DragAndDrop.Event = function(type, file, opt_features, opt_projection) {
+
+  ol.events.Event.call(this, type);
+
+  /**
+   * The features parsed from dropped data.
+   * @type {Array.<ol.Feature>|undefined}
+   * @api
+   */
+  this.features = opt_features;
+
+  /**
+   * The dropped file.
+   * @type {File}
+   * @api
+   */
+  this.file = file;
+
+  /**
+   * The feature projection.
+   * @type {ol.proj.Projection|undefined}
+   * @api
+   */
+  this.projection = opt_projection;
+
+};
+ol.inherits(ol.interaction.DragAndDrop.Event, ol.events.Event);
+
+goog.provide('ol.interaction.DragRotateAndZoom');
+
+goog.require('ol');
+goog.require('ol.RotationConstraint');
+goog.require('ol.ViewHint');
+goog.require('ol.events.condition');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.interaction.Pointer');
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
+ * @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
  */
-ol.format.KML.LINE_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'color', 'width'
-    ]);
+ol.interaction.DragRotateAndZoom = function(opt_options) {
+
+  var options = opt_options ? opt_options : {};
+
+  ol.interaction.Pointer.call(this, {
+    handleDownEvent: ol.interaction.DragRotateAndZoom.handleDownEvent_,
+    handleDragEvent: ol.interaction.DragRotateAndZoom.handleDragEvent_,
+    handleUpEvent: ol.interaction.DragRotateAndZoom.handleUpEvent_
+  });
+
+  /**
+   * @private
+   * @type {ol.EventsConditionType}
+   */
+  this.condition_ = options.condition ?
+      options.condition : ol.events.condition.shiftKeyOnly;
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.lastAngle_ = undefined;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.lastMagnitude_ = undefined;
 
-/**
- * @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)
-    });
+  /**
+   * @private
+   * @type {number}
+   */
+  this.lastScaleDelta_ = 0;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.duration_ = options.duration !== undefined ? options.duration : 400;
 
-/**
- * @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_)
-    });
+};
+ol.inherits(ol.interaction.DragRotateAndZoom, ol.interaction.Pointer);
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.DragRotateAndZoom}
  * @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_)
-    });
+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 deltaX = offset[0] - size[0] / 2;
+  var deltaY = size[1] / 2 - offset[1];
+  var theta = Math.atan2(deltaY, deltaX);
+  var magnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+  var view = map.getView();
+  if (view.getConstraints().rotation !== ol.RotationConstraint.disable && this.lastAngle_ !== undefined) {
+    var angleDelta = theta - this.lastAngle_;
+    ol.interaction.Interaction.rotateWithoutConstraints(
+        view, view.getRotation() - angleDelta);
+  }
+  this.lastAngle_ = theta;
+  if (this.lastMagnitude_ !== undefined) {
+    var resolution = this.lastMagnitude_ * (view.getResolution() / magnitude);
+    ol.interaction.Interaction.zoomWithoutConstraints(view, resolution);
+  }
+  if (this.lastMagnitude_ !== undefined) {
+    this.lastScaleDelta_ = this.lastMagnitude_ / magnitude;
+  }
+  this.lastMagnitude_ = magnitude;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragRotateAndZoom}
  * @private
  */
-ol.format.KML.PLACEMARK_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'name', 'open', 'visibility', 'address', 'phoneNumber', 'description',
-      'styleUrl', 'Style'
-    ]);
+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(view, view.getRotation());
+  ol.interaction.Interaction.zoom(view, view.getResolution(),
+      undefined, this.duration_, direction);
+  this.lastScaleDelta_ = 0;
+  return false;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragRotateAndZoom}
  * @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)
-    });
+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;
+  }
+};
 
-/**
- * @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_)
-    });
+goog.provide('ol.interaction.DrawEventType');
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @enum {string}
  */
-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_)
-    });
+ol.interaction.DrawEventType = {
+  /**
+   * Triggered upon feature draw start
+   * @event ol.interaction.Draw.Event#drawstart
+   * @api
+   */
+  DRAWSTART: 'drawstart',
+  /**
+   * Triggered upon feature draw end
+   * @event ol.interaction.Draw.Event#drawend
+   * @api
+   */
+  DRAWEND: 'drawend'
+};
 
+goog.provide('ol.render.canvas.Instruction');
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @enum {number}
  */
-ol.format.KML.POLY_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_)
-    });
+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
+};
 
+goog.provide('ol.render.canvas.Replay');
 
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.KML.STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'IconStyle', 'LabelStyle', 'LineStyle', 'PolyStyle'
-    ]);
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.extent');
+goog.require('ol.extent.Relationship');
+goog.require('ol.geom.flat.transform');
+goog.require('ol.has');
+goog.require('ol.obj');
+goog.require('ol.render.VectorContext');
+goog.require('ol.render.canvas.Instruction');
+goog.require('ol.transform');
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @constructor
+ * @extends {ol.render.VectorContext}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @param {boolean} overlaps The replay can have overlapping geometries.
+ * @struct
  */
-ol.format.KML.STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'IconStyle': ol.xml.makeChildAppender(ol.format.KML.writeIconStyle_),
-      'LabelStyle': ol.xml.makeChildAppender(ol.format.KML.writeLabelStyle_),
-      'LineStyle': ol.xml.makeChildAppender(ol.format.KML.writeLineStyle_),
-      'PolyStyle': ol.xml.makeChildAppender(ol.format.KML.writePolyStyle_)
-    });
+ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, overlaps) {
+  ol.render.VectorContext.call(this);
 
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.tolerance = tolerance;
 
-/**
- * @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);
-};
+  /**
+   * @protected
+   * @const
+   * @type {ol.Extent}
+   */
+  this.maxExtent = maxExtent;
 
+  /**
+   * @protected
+   * @type {boolean}
+   */
+  this.overlaps = overlaps;
 
-/**
- * @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');
-};
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.maxLineWidth = 0;
+
+  /**
+   * @protected
+   * @const
+   * @type {number}
+   */
+  this.resolution = resolution;
 
+  /**
+   * @private
+   * @type {ol.Coordinate}
+   */
+  this.fillOrigin_;
 
-/**
- * @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()]);
-  }
-};
+  /**
+   * @private
+   * @type {Array.<*>}
+   */
+  this.beginGeometryInstruction1_ = null;
 
+  /**
+   * @private
+   * @type {Array.<*>}
+   */
+  this.beginGeometryInstruction2_ = null;
 
-/**
- * A factory for creating coordinates nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.COLOR_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('color');
+  /**
+   * @protected
+   * @type {Array.<*>}
+   */
+  this.instructions = [];
 
+  /**
+   * @protected
+   * @type {Array.<number>}
+   */
+  this.coordinates = [];
 
-/**
- * A factory for creating coordinates nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.COORDINATES_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('coordinates');
+  /**
+   * @private
+   * @type {!ol.Transform}
+   */
+  this.renderedTransform_ = ol.transform.create();
 
+  /**
+   * @protected
+   * @type {Array.<*>}
+   */
+  this.hitDetectionInstructions = [];
 
-/**
- * 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');
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.pixelCoordinates_ = null;
 
+  /**
+   * @private
+   * @type {!ol.Transform}
+   */
+  this.tmpLocalTransform_ = ol.transform.create();
 
-/**
- * A factory for creating Point nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.POINT_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('Point');
+  /**
+   * @private
+   * @type {!ol.Transform}
+   */
+  this.resetTransform_ = ol.transform.create();
+};
+ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext);
 
 
 /**
- * A factory for creating LineString nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {boolean} closed Last input coordinate equals first.
+ * @param {boolean} skipFirst Skip first coordinate.
+ * @protected
+ * @return {number} My end.
  */
-ol.format.KML.LINE_STRING_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('LineString');
+ol.render.canvas.Replay.prototype.appendFlatCoordinates = function(flatCoordinates, offset, end, stride, closed, skipFirst) {
 
+  var myEnd = this.coordinates.length;
+  var extent = this.getBufferedMaxExtent();
+  if (skipFirst) {
+    offset += stride;
+  }
+  var lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]];
+  var nextCoord = [NaN, NaN];
+  var skipped = true;
 
-/**
- * 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');
+  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;
+  }
+
+  // Last coordinate equals first or only one point to append:
+  if ((closed && skipped) || i === offset + stride) {
+    this.coordinates[myEnd++] = lastCoord[0];
+    this.coordinates[myEnd++] = lastCoord[1];
+  }
+  return myEnd;
+};
 
 
 /**
- * A factory for creating Polygon nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
+ * @protected
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
  */
-ol.format.KML.POLYGON_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('Polygon');
+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_);
+};
 
 
 /**
- * A factory for creating outerBoundaryIs nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
  * @private
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} rotation Rotation.
  */
-ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('outerBoundaryIs');
+ol.render.canvas.Replay.prototype.fill_ = function(context, rotation) {
+  if (this.fillOrigin_) {
+    var origin = ol.transform.apply(this.renderedTransform_, this.fillOrigin_.slice());
+    context.translate(origin[0], origin[1]);
+    context.rotate(rotation);
+  }
+  context.fill();
+  if (this.fillOrigin_) {
+    context.setTransform.apply(context, this.resetTransform_);
+  }
+};
 
 
 /**
- * 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
+ * @private
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.Transform} 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.format.KML.prototype.writeFeatures;
+ol.render.canvas.Replay.prototype.replay_ = function(
+    context, pixelRatio, transform, viewRotation, skippedFeaturesHash,
+    instructions, featureCallback, opt_hitExtent) {
+  /** @type {Array.<number>} */
+  var pixelCoordinates;
+  if (this.pixelCoordinates_ && ol.array.equals(transform, this.renderedTransform_)) {
+    pixelCoordinates = this.pixelCoordinates_;
+  } else {
+    if (!this.pixelCoordinates_) {
+      this.pixelCoordinates_ = [];
+    }
+    pixelCoordinates = ol.geom.flat.transform.transform2D(
+        this.coordinates, 0, this.coordinates.length, 2,
+        transform, this.pixelCoordinates_);
+    ol.transform.setFromArray(this.renderedTransform_, transform);
+  }
+  var skipFeatures = !ol.obj.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 resetTransform = this.resetTransform_;
+  var prevX, prevY, roundX, roundY;
+  var pendingFill = 0;
+  var pendingStroke = 0;
+  // When the batch size gets too big, performance decreases. 200 is a good
+  // balance between batch size and number of fill/stroke instructions.
+  var batchSize =
+      this.instructions != instructions || this.overlaps ? 0 : 200;
+  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[ol.getUid(feature).toString()]) ||
+            !feature.getGeometry()) {
+          i = /** @type {number} */ (instruction[2]);
+        } else if (opt_hitExtent !== undefined && !ol.extent.intersects(
+            opt_hitExtent, feature.getGeometry().getExtent())) {
+          i = /** @type {number} */ (instruction[2]) + 1;
+        } else {
+          ++i;
+        }
+        break;
+      case ol.render.canvas.Instruction.BEGIN_PATH:
+        if (pendingFill > batchSize) {
+          this.fill_(context, viewRotation);
+          pendingFill = 0;
+        }
+        if (pendingStroke > batchSize) {
+          context.stroke();
+          pendingStroke = 0;
+        }
+        if (!pendingFill && !pendingStroke) {
+          context.beginPath();
+          prevX = prevY = NaN;
+        }
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.CIRCLE:
+        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.moveTo(x1 + r, y1);
+        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:
+        d = /** @type {number} */ (instruction[1]);
+        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 = Math.round(x);
+            y = Math.round(y);
+          }
+          if (scale != 1 || rotation !== 0) {
+            var centerX = x + anchorX;
+            var centerY = y + anchorY;
+            ol.transform.compose(localTransform,
+                centerX, centerY, scale, scale, rotation, -centerX, -centerY);
+            context.setTransform.apply(context, localTransform);
+          }
+          var alpha = context.globalAlpha;
+          if (opacity != 1) {
+            context.globalAlpha = alpha * opacity;
+          }
 
+          var w = (width + originX > image.width) ? image.width - originX : width;
+          var h = (height + originY > image.height) ? image.height - originY : height;
 
-/**
- * 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_);
+          context.drawImage(image, originX, originY, w, h,
+              x, y, w * pixelRatio, h * pixelRatio);
 
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: kml};
-  var properties = {};
-  if (features.length > 1) {
-    properties['Document'] = features;
-  } else if (features.length == 1) {
-    properties['Placemark'] = features[0];
-  }
-  var orderedKeys = ol.format.KML.KML_SEQUENCE_[kml.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.KML_SERIALIZERS_,
-      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, [opt_options], orderedKeys);
-  return kml;
-};
+          if (opacity != 1) {
+            context.globalAlpha = alpha;
+          }
+          if (scale != 1 || rotation !== 0) {
+            context.setTransform.apply(context, resetTransform);
+          }
+        }
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.DRAW_TEXT:
+        d = /** @type {number} */ (instruction[1]);
+        dd = /** @type {number} */ (instruction[2]);
+        text = /** @type {string} */ (instruction[3]);
+        var offsetX = /** @type {number} */ (instruction[4]) * pixelRatio;
+        var offsetY = /** @type {number} */ (instruction[5]) * pixelRatio;
+        rotation = /** @type {number} */ (instruction[6]);
+        scale = /** @type {number} */ (instruction[7]) * pixelRatio;
+        fill = /** @type {boolean} */ (instruction[8]);
+        stroke = /** @type {boolean} */ (instruction[9]);
+        rotateWithView = /** @type {boolean} */ (instruction[10]);
+        if (rotateWithView) {
+          rotation += viewRotation;
+        }
+        for (; d < dd; d += 2) {
+          x = pixelCoordinates[d] + offsetX;
+          y = pixelCoordinates[d + 1] + offsetY;
+          if (scale != 1 || rotation !== 0) {
+            ol.transform.compose(localTransform, x, y, scale, scale, rotation, -x, -y);
+            context.setTransform.apply(context, localTransform);
+          }
 
-// FIXME add typedef for stack state objects
-goog.provide('ol.format.OSMXML');
+          // 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;
+          }
 
-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');
+          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.apply(context, resetTransform);
+          }
+        }
+        ++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:
+        if (batchSize) {
+          pendingFill++;
+        } else {
+          this.fill_(context, viewRotation);
+        }
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.MOVE_TO_LINE_TO:
+        d = /** @type {number} */ (instruction[1]);
+        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 (d == dd - 2 || roundX !== prevX || roundY !== prevY) {
+            context.lineTo(x, y);
+            prevX = roundX;
+            prevY = roundY;
+          }
+        }
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.SET_FILL_STYLE:
+        this.fillOrigin_ = instruction[2];
+
+        if (pendingFill) {
+          this.fill_(context, viewRotation);
+          pendingFill = 0;
+          if (pendingStroke) {
+            context.stroke();
+            pendingStroke = 0;
+          }
+        }
 
-/**
- * @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);
+        context.fillStyle = /** @type {ol.ColorLike} */ (instruction[1]);
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.SET_STROKE_STYLE:
+        var usePixelRatio = instruction[8] !== undefined ?
+            instruction[8] : true;
+        var renderedPixelRatio = instruction[9];
 
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+        var lineWidth = /** @type {number} */ (instruction[2]);
+        if (pendingStroke) {
+          context.stroke();
+          pendingStroke = 0;
+        }
+        context.strokeStyle = /** @type {ol.ColorLike} */ (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) {
+          var lineDash = /** @type {Array.<number>} */ (instruction[6]);
+          var lineDashOffset = /** @type {number} */ (instruction[7]);
+          if (usePixelRatio && pixelRatio !== renderedPixelRatio) {
+            lineDash = lineDash.map(function(dash) {
+              return dash * pixelRatio / renderedPixelRatio;
+            });
+            lineDashOffset *= pixelRatio / renderedPixelRatio;
+            instruction[6] = lineDash;
+            instruction[7] = lineDashOffset;
+            instruction[9] = pixelRatio;
+          }
+          context.lineDashOffset = lineDashOffset;
+          context.setLineDash(lineDash);
+        }
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.SET_TEXT_STYLE:
+        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:
+        if (batchSize) {
+          pendingStroke++;
+        } else {
+          context.stroke();
+        }
+        ++i;
+        break;
+      default:
+        ++i; // consume the instruction anyway, to avoid an infinite loop
+        break;
+    }
+  }
+  if (pendingFill) {
+    this.fill_(context, viewRotation);
+  }
+  if (pendingStroke) {
+    context.stroke();
+  }
+  return undefined;
 };
-goog.inherits(ol.format.OSMXML, ol.format.XMLFeature);
 
 
 /**
- * @const
- * @type {Array.<string>}
- * @private
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.Transform} transform Transform.
+ * @param {number} viewRotation View rotation.
+ * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+ *     to skip.
  */
-ol.format.OSMXML.EXTENSIONS_ = ['.osm'];
+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, undefined);
+};
 
 
 /**
- * @inheritDoc
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {ol.Transform} 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.format.OSMXML.prototype.getExtensions = function() {
-  return ol.format.OSMXML.EXTENSIONS_;
+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);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Reverse the hit detection instructions.
  */
-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);
+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) {
+      begin = i;
+    } else if (type == ol.render.canvas.Instruction.BEGIN_GEOMETRY) {
+      instruction[2] = i;
+      ol.array.reverseSubArray(this.hitDetectionInstructions, begin, i);
+      begin = -1;
+    }
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
  */
-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);
+ol.render.canvas.Replay.prototype.endGeometry = function(geometry, feature) {
+  this.beginGeometryInstruction1_[2] = this.instructions.length;
+  this.beginGeometryInstruction1_ = 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);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Track.
+ * FIXME empty description for jsdoc
  */
-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'));
-};
+ol.render.canvas.Replay.prototype.finish = ol.nullFunction;
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Track.
+ * 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.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');
+ol.render.canvas.Replay.prototype.getBufferedMaxExtent = function() {
+  return this.maxExtent;
 };
 
+goog.provide('ol.render.canvas.ImageReplay');
 
-/**
- * @const
- * @private
- * @type {Array.<string>}
- */
-ol.format.OSMXML.NAMESPACE_URIS_ = [
-  null
-];
+goog.require('ol');
+goog.require('ol.render.canvas.Instruction');
+goog.require('ol.render.canvas.Replay');
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @constructor
+ * @extends {ol.render.canvas.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @param {boolean} overlaps The replay can have overlapping geometries.
+ * @struct
  */
-ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OSMXML.NAMESPACE_URIS_, {
-      'nd': ol.format.OSMXML.readNd_,
-      'tag': ol.format.OSMXML.readTag_
-    });
+ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution, overlaps) {
+  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);
 
+  /**
+   * @private
+   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
+   */
+  this.hitDetectionImage_ = null;
 
-/**
- * @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_
-    });
+  /**
+   * @private
+   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
+   */
+  this.image_ = null;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.anchorX_ = undefined;
 
-/**
- * @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_
-    });
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.anchorY_ = undefined;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.height_ = undefined;
 
-/**
- * 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;
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.opacity_ = undefined;
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.originX_ = undefined;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.originY_ = undefined;
 
-/**
- * @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 [];
-};
+  /**
+   * @private
+   * @type {boolean|undefined}
+   */
+  this.rotateWithView_ = undefined;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.rotation_ = undefined;
 
-/**
- * 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;
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.scale_ = undefined;
 
-goog.provide('ol.format.XLink');
+  /**
+   * @private
+   * @type {boolean|undefined}
+   */
+  this.snapToPixel_ = undefined;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.width_ = undefined;
 
-/**
- * @const
- * @type {string}
- */
-ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink';
+};
+ol.inherits(ol.render.canvas.ImageReplay, ol.render.canvas.Replay);
 
 
 /**
- * @param {Node} node Node.
- * @return {boolean|undefined} Boolean.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @private
+ * @return {number} My end.
  */
-ol.format.XLink.readHref = function(node) {
-  return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href');
+ol.render.canvas.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) {
+  return this.appendFlatCoordinates(
+      flatCoordinates, offset, end, stride, false, false);
 };
 
-goog.provide('ol.format.XML');
-
-goog.require('goog.asserts');
-goog.require('ol.xml');
 
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) {
+  if (!this.image_) {
+    return;
+  }
+  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);
+};
 
 
 /**
- * @classdesc
- * Generic format for reading non-feature XML data
- *
- * @constructor
+ * @inheritDoc
  */
-ol.format.XML = function() {
+ol.render.canvas.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) {
+  if (!this.image_) {
+    return;
+  }
+  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);
 };
 
 
 /**
- * @param {Document|Node|string} source Source.
- * @return {Object}
+ * @inheritDoc
  */
-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;
-  }
+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;
 };
 
 
 /**
- * @param {Document} doc Document.
- * @return {Object}
- */
-ol.format.XML.prototype.readFromDocument = goog.abstractMethod;
-
-
-/**
- * @param {Node} node Node.
- * @return {Object}
+ * @inheritDoc
  */
-ol.format.XML.prototype.readFromNode = goog.abstractMethod;
-
-goog.provide('ol.format.OWS');
+ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) {
+  var anchor = imageStyle.getAnchor();
+  var size = imageStyle.getSize();
+  var hitDetectionImage = imageStyle.getHitDetectionImage(1);
+  var image = imageStyle.getImage(1);
+  var origin = imageStyle.getOrigin();
+  this.anchorX_ = anchor[0];
+  this.anchorY_ = anchor[1];
+  this.hitDetectionImage_ = hitDetectionImage;
+  this.image_ = image;
+  this.height_ = size[1];
+  this.opacity_ = imageStyle.getOpacity();
+  this.originX_ = origin[0];
+  this.originY_ = origin[1];
+  this.rotateWithView_ = imageStyle.getRotateWithView();
+  this.rotation_ = imageStyle.getRotation();
+  this.scale_ = imageStyle.getScale();
+  this.snapToPixel_ = imageStyle.getSnapToPixel();
+  this.width_ = size[0];
+};
 
-goog.require('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');
+goog.provide('ol.render.canvas.LineStringReplay');
 
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.colorlike');
+goog.require('ol.extent');
+goog.require('ol.render.canvas');
+goog.require('ol.render.canvas.Instruction');
+goog.require('ol.render.canvas.Replay');
 
 
 /**
  * @constructor
- * @extends {ol.format.XML}
+ * @extends {ol.render.canvas.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @param {boolean} overlaps The replay can have overlapping geometries.
+ * @struct
  */
-ol.format.OWS = function() {
-  goog.base(this);
-};
-goog.inherits(ol.format.OWS, ol.format.XML);
+ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution, overlaps) {
 
+  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);
 
-/**
- * @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;
-};
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.bufferedMaxExtent_ = null;
 
+  /**
+   * @private
+   * @type {{currentStrokeStyle: (ol.ColorLike|undefined),
+   *         currentLineCap: (string|undefined),
+   *         currentLineDash: Array.<number>,
+   *         currentLineDashOffset: (number|undefined),
+   *         currentLineJoin: (string|undefined),
+   *         currentLineWidth: (number|undefined),
+   *         currentMiterLimit: (number|undefined),
+   *         lastStroke: number,
+   *         strokeStyle: (ol.ColorLike|undefined),
+   *         lineCap: (string|undefined),
+   *         lineDash: Array.<number>,
+   *         lineDashOffset: (number|undefined),
+   *         lineJoin: (string|undefined),
+   *         lineWidth: (number|undefined),
+   *         miterLimit: (number|undefined)}|null}
+   */
+  this.state_ = {
+    currentStrokeStyle: undefined,
+    currentLineCap: undefined,
+    currentLineDash: null,
+    currentLineDashOffset: undefined,
+    currentLineJoin: undefined,
+    currentLineWidth: undefined,
+    currentMiterLimit: undefined,
+    lastStroke: 0,
+    strokeStyle: undefined,
+    lineCap: undefined,
+    lineDash: null,
+    lineDashOffset: undefined,
+    lineJoin: undefined,
+    lineWidth: undefined,
+    miterLimit: undefined
+  };
 
-/**
- * @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;
 };
+ol.inherits(ol.render.canvas.LineStringReplay, ol.render.canvas.Replay);
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
  * @private
- * @return {Object|undefined}
+ * @return {number} end.
  */
-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);
+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, false);
+  var moveToLineToInstruction =
+      [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd];
+  this.instructions.push(moveToLineToInstruction);
+  this.hitDetectionInstructions.push(moveToLineToInstruction);
+  return end;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
+ * @inheritDoc
  */
-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);
+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_;
 };
 
 
 /**
- * @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;
+ol.render.canvas.LineStringReplay.prototype.setStrokeStyle_ = function() {
+  var state = this.state_;
+  var strokeStyle = state.strokeStyle;
+  var lineCap = state.lineCap;
+  var lineDash = state.lineDash;
+  var lineDashOffset = state.lineDashOffset;
+  var lineJoin = state.lineJoin;
+  var lineWidth = state.lineWidth;
+  var miterLimit = state.miterLimit;
+  if (state.currentStrokeStyle != strokeStyle ||
+      state.currentLineCap != lineCap ||
+      !ol.array.equals(state.currentLineDash, lineDash) ||
+      state.currentLineDashOffset != lineDashOffset ||
+      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, lineDashOffset, true, 1
+    ], [
+      ol.render.canvas.Instruction.BEGIN_PATH
+    ]);
+    state.currentStrokeStyle = strokeStyle;
+    state.currentLineCap = lineCap;
+    state.currentLineDash = lineDash;
+    state.currentLineDashOffset = lineDashOffset;
+    state.currentLineJoin = lineJoin;
+    state.currentLineWidth = lineWidth;
+    state.currentMiterLimit = miterLimit;
   }
-  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}
+ * @inheritDoc
  */
-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);
+ol.render.canvas.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) {
+  var state = this.state_;
+  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, state.lineDashOffset, true, 1
+  ], [
+    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);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
+ * @inheritDoc
  */
-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);
+ol.render.canvas.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {
+  var state = this.state_;
+  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, state.lineDashOffset, true, 1
+  ], [
+    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);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
+ * @inheritDoc
  */
-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;
+ol.render.canvas.LineStringReplay.prototype.finish = function() {
+  var state = this.state_;
+  if (state.lastStroke != this.coordinates.length) {
+    this.instructions.push([ol.render.canvas.Instruction.STROKE]);
   }
-  return ol.xml.pushParseAndPop({'href': href},
-      ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack);
+  this.reverseHitDetectionInstructions();
+  this.state_ = null;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
+ * @inheritDoc
  */
-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);
+ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
+  var strokeStyleColor = strokeStyle.getColor();
+  this.state_.strokeStyle = ol.colorlike.asColorLike(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 strokeStyleLineDashOffset = strokeStyle.getLineDashOffset();
+  this.state_.lineDashOffset = strokeStyleLineDashOffset ?
+      strokeStyleLineDashOffset : ol.render.canvas.defaultLineDashOffset;
+  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;
+  }
 };
 
+goog.provide('ol.render.canvas.PolygonReplay');
+
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.color');
+goog.require('ol.colorlike');
+goog.require('ol.extent');
+goog.require('ol.geom.flat.simplify');
+goog.require('ol.render.canvas');
+goog.require('ol.render.canvas.Instruction');
+goog.require('ol.render.canvas.Replay');
+
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
+ * @constructor
+ * @extends {ol.render.canvas.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @param {boolean} overlaps The replay can have overlapping geometries.
+ * @struct
  */
-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;
+ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution, overlaps) {
+
+  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);
+
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.bufferedMaxExtent_ = null;
+
+  /**
+   * @private
+   * @type {{currentFillStyle: (ol.ColorLike|undefined),
+   *         currentStrokeStyle: (ol.ColorLike|undefined),
+   *         currentLineCap: (string|undefined),
+   *         currentLineDash: Array.<number>,
+   *         currentLineDashOffset: (number|undefined),
+   *         currentLineJoin: (string|undefined),
+   *         currentLineWidth: (number|undefined),
+   *         currentMiterLimit: (number|undefined),
+   *         fillStyle: (ol.ColorLike|undefined),
+   *         strokeStyle: (ol.ColorLike|undefined),
+   *         lineCap: (string|undefined),
+   *         lineDash: Array.<number>,
+   *         lineDashOffset: (number|undefined),
+   *         lineJoin: (string|undefined),
+   *         lineWidth: (number|undefined),
+   *         miterLimit: (number|undefined)}|null}
+   */
+  this.state_ = {
+    currentFillStyle: undefined,
+    currentStrokeStyle: undefined,
+    currentLineCap: undefined,
+    currentLineDash: null,
+    currentLineDashOffset: undefined,
+    currentLineJoin: undefined,
+    currentLineWidth: undefined,
+    currentMiterLimit: undefined,
+    fillStyle: undefined,
+    strokeStyle: undefined,
+    lineCap: undefined,
+    lineDash: null,
+    lineDashOffset: undefined,
+    lineJoin: undefined,
+    lineWidth: undefined,
+    miterLimit: undefined
+  };
 
 };
+ol.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay);
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
  * @private
- * @return {Object|undefined}
+ * @return {number} End.
  */
-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);
+ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ = function(flatCoordinates, offset, ends, stride) {
+  var state = this.state_;
+  var fill = state.fillStyle !== undefined;
+  var stroke = state.strokeStyle != undefined;
+  var numEnds = ends.length;
+  var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH];
+  this.instructions.push(beginPathInstruction);
+  this.hitDetectionInstructions.push(beginPathInstruction);
+  for (var i = 0; i < numEnds; ++i) {
+    var end = ends[i];
+    var myBegin = this.coordinates.length;
+    var myEnd = this.appendFlatCoordinates(
+        flatCoordinates, offset, end, stride, true, !stroke);
+    var moveToLineToInstruction =
+        [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd];
+    this.instructions.push(moveToLineToInstruction);
+    this.hitDetectionInstructions.push(moveToLineToInstruction);
+    if (stroke) {
+      // Performance optimization: only call closePath() when we have a stroke.
+      // Otherwise the ring is closed already (see appendFlatCoordinates above).
+      var closePathInstruction = [ol.render.canvas.Instruction.CLOSE_PATH];
+      this.instructions.push(closePathInstruction);
+      this.hitDetectionInstructions.push(closePathInstruction);
+    }
+    offset = end;
+  }
+  var fillInstruction = [ol.render.canvas.Instruction.FILL];
+  this.hitDetectionInstructions.push(fillInstruction);
+  if (fill) {
+    this.instructions.push(fillInstruction);
+  }
+  if (stroke) {
+    var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
+    this.instructions.push(strokeInstruction);
+    this.hitDetectionInstructions.push(strokeInstruction);
+  }
+  return offset;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
+ * @inheritDoc
  */
-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);
+ol.render.canvas.PolygonReplay.prototype.drawCircle = function(circleGeometry, feature) {
+  var state = this.state_;
+  var fillStyle = state.fillStyle;
+  var strokeStyle = state.strokeStyle;
+  if (fillStyle === undefined && strokeStyle === undefined) {
+    return;
+  }
+  this.setFillStrokeStyles_(circleGeometry);
+  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, state.lineDashOffset, true, 1
+    ]);
+  }
+  var flatCoordinates = circleGeometry.getFlatCoordinates();
+  var stride = circleGeometry.getStride();
+  var myBegin = this.coordinates.length;
+  this.appendFlatCoordinates(
+      flatCoordinates, 0, flatCoordinates.length, stride, false, 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) {
+    var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
+    this.instructions.push(strokeInstruction);
+    this.hitDetectionInstructions.push(strokeInstruction);
+  }
+  this.endGeometry(circleGeometry, feature);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
+ * @inheritDoc
  */
-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);
+ol.render.canvas.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) {
+  var state = this.state_;
+  this.setFillStrokeStyles_(polygonGeometry);
+  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, state.lineDashOffset, true, 1
+    ]);
+  }
+  var ends = polygonGeometry.getEnds();
+  var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates();
+  var stride = polygonGeometry.getStride();
+  this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride);
+  this.endGeometry(polygonGeometry, feature);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
+ * @inheritDoc
  */
-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);
+ol.render.canvas.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {
+  var state = this.state_;
+  var fillStyle = state.fillStyle;
+  var strokeStyle = state.strokeStyle;
+  if (fillStyle === undefined && strokeStyle === undefined) {
+    return;
+  }
+  this.setFillStrokeStyles_(multiPolygonGeometry);
+  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, state.lineDashOffset, true, 1
+    ]);
+  }
+  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);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
+ * @inheritDoc
  */
-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);
+ol.render.canvas.PolygonReplay.prototype.finish = function() {
+  this.reverseHitDetectionInstructions();
+  this.state_ = null;
+  // We want to preserve topology when drawing polygons.  Polygons are
+  // simplified using quantization and point elimination. However, we might
+  // have received a mix of quantized and non-quantized geometries, so ensure
+  // that all are quantized by quantizing all coordinates in the batch.
+  var tolerance = this.tolerance;
+  if (tolerance !== 0) {
+    var coordinates = this.coordinates;
+    var i, ii;
+    for (i = 0, ii = coordinates.length; i < ii; ++i) {
+      coordinates[i] = ol.geom.flat.simplify.snap(coordinates[i], tolerance);
+    }
+  }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {string|undefined}
+ * @inheritDoc
  */
-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);
+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_;
 };
 
 
 /**
- * @const
- * @type {Array.<string>}
- * @private
+ * @inheritDoc
  */
-ol.format.OWS.NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/ows/1.1'
-];
+ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
+  var state = this.state_;
+  if (fillStyle) {
+    var fillStyleColor = fillStyle.getColor();
+    state.fillStyle = ol.colorlike.asColorLike(fillStyleColor ?
+        fillStyleColor : ol.render.canvas.defaultFillStyle);
+  } else {
+    state.fillStyle = undefined;
+  }
+  if (strokeStyle) {
+    var strokeStyleColor = strokeStyle.getColor();
+    state.strokeStyle = ol.colorlike.asColorLike(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 strokeStyleLineDashOffset = strokeStyle.getLineDashOffset();
+    state.lineDashOffset = strokeStyleLineDashOffset ?
+        strokeStyleLineDashOffset : ol.render.canvas.defaultLineDashOffset;
+    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.lineDashOffset = undefined;
+    state.lineJoin = undefined;
+    state.lineWidth = undefined;
+    state.miterLimit = undefined;
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
  * @private
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
  */
-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_)
-    });
+ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function(geometry) {
+  var state = this.state_;
+  var fillStyle = state.fillStyle;
+  var strokeStyle = state.strokeStyle;
+  var lineCap = state.lineCap;
+  var lineDash = state.lineDash;
+  var lineDashOffset = state.lineDashOffset;
+  var lineJoin = state.lineJoin;
+  var lineWidth = state.lineWidth;
+  var miterLimit = state.miterLimit;
+  if (fillStyle !== undefined && (typeof fillStyle !== 'string' || state.currentFillStyle != fillStyle)) {
+    var fillInstruction = [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle];
+    if (typeof fillStyle !== 'string') {
+      var fillExtent = geometry.getExtent();
+      fillInstruction.push([fillExtent[0], fillExtent[3]]);
+    }
+    this.instructions.push(fillInstruction);
+    state.currentFillStyle = state.fillStyle;
+  }
+  if (strokeStyle !== undefined) {
+    if (state.currentStrokeStyle != strokeStyle ||
+        state.currentLineCap != lineCap ||
+        !ol.array.equals(state.currentLineDash, lineDash) ||
+        state.currentLineDashOffset != lineDashOffset ||
+        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, lineDashOffset, true, 1
+      ]);
+      state.currentStrokeStyle = strokeStyle;
+      state.currentLineCap = lineCap;
+      state.currentLineDash = lineDash;
+      state.currentLineDashOffset = lineDashOffset;
+      state.currentLineJoin = lineJoin;
+      state.currentLineWidth = lineWidth;
+      state.currentMiterLimit = miterLimit;
+    }
+  }
+};
 
+goog.provide('ol.render.canvas.TextReplay');
 
-/**
- * @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)
-    });
+goog.require('ol');
+goog.require('ol.colorlike');
+goog.require('ol.render.canvas');
+goog.require('ol.render.canvas.Instruction');
+goog.require('ol.render.canvas.Replay');
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @constructor
+ * @extends {ol.render.canvas.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @param {boolean} overlaps The replay can have overlapping geometries.
+ * @struct
  */
-ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Value': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readValue_)
-    });
+ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution, overlaps) {
 
+  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);
 
-/**
- * @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_)
-    });
+  /**
+   * @private
+   * @type {?ol.CanvasFillState}
+   */
+  this.replayFillState_ = null;
 
+  /**
+   * @private
+   * @type {?ol.CanvasStrokeState}
+   */
+  this.replayStrokeState_ = null;
 
-/**
- * @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_)
-    });
+  /**
+   * @private
+   * @type {?ol.CanvasTextState}
+   */
+  this.replayTextState_ = null;
 
+  /**
+   * @private
+   * @type {string}
+   */
+  this.text_ = '';
 
-/**
- * @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_)
-    });
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textOffsetX_ = 0;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textOffsetY_ = 0;
 
-/**
- * @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
-    });
+  /**
+   * @private
+   * @type {boolean|undefined}
+   */
+  this.textRotateWithView_ = undefined;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textRotation_ = 0;
 
-/**
- * @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_)
-    });
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textScale_ = 0;
 
+  /**
+   * @private
+   * @type {?ol.CanvasFillState}
+   */
+  this.textFillState_ = null;
 
-/**
- * @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_
-    });
+  /**
+   * @private
+   * @type {?ol.CanvasStrokeState}
+   */
+  this.textStrokeState_ = null;
 
+  /**
+   * @private
+   * @type {?ol.CanvasTextState}
+   */
+  this.textState_ = null;
 
-/**
- * @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)
-    });
+};
+ol.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay);
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @inheritDoc
  */
-ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Constraint': ol.xml.makeObjectPropertyPusher(
-          ol.format.OWS.readConstraint_)
-    });
+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, 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.textRotateWithView_];
+  this.instructions.push(drawTextInstruction);
+  this.hitDetectionInstructions.push(drawTextInstruction);
+  this.endGeometry(geometry, feature);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.CanvasFillState} fillState Fill state.
  * @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_)
-    });
+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;
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.CanvasStrokeState} strokeState Stroke state.
  * @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)
-    });
+ol.render.canvas.TextReplay.prototype.setReplayStrokeState_ = function(strokeState) {
+  var replayStrokeState = this.replayStrokeState_;
+  if (replayStrokeState &&
+      replayStrokeState.lineCap == strokeState.lineCap &&
+      replayStrokeState.lineDash == strokeState.lineDash &&
+      replayStrokeState.lineDashOffset == strokeState.lineDashOffset &&
+      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, strokeState.lineDashOffset, false, 1
+  ];
+  this.instructions.push(setStrokeStyleInstruction);
+  this.hitDetectionInstructions.push(setStrokeStyleInstruction);
+  if (!replayStrokeState) {
+    this.replayStrokeState_ = {
+      lineCap: strokeState.lineCap,
+      lineDash: strokeState.lineDash,
+      lineDashOffset: strokeState.lineDashOffset,
+      lineJoin: strokeState.lineJoin,
+      lineWidth: strokeState.lineWidth,
+      miterLimit: strokeState.miterLimit,
+      strokeStyle: strokeState.strokeStyle
+    };
+  } else {
+    replayStrokeState.lineCap = strokeState.lineCap;
+    replayStrokeState.lineDash = strokeState.lineDash;
+    replayStrokeState.lineDashOffset = strokeState.lineDashOffset;
+    replayStrokeState.lineJoin = strokeState.lineJoin;
+    replayStrokeState.lineWidth = strokeState.lineWidth;
+    replayStrokeState.miterLimit = strokeState.miterLimit;
+    replayStrokeState.strokeStyle = strokeState.strokeStyle;
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.CanvasTextState} textState Text state.
  * @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');
+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;
+  }
+};
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-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;
+ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) {
+  if (!textStyle) {
+    this.text_ = '';
   } 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++];
+    var textFillStyle = textStyle.getFill();
+    if (!textFillStyle) {
+      this.textFillState_ = null;
+    } else {
+      var textFillStyleColor = textFillStyle.getColor();
+      var fillStyle = ol.colorlike.asColorLike(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 textStrokeStyleLineDashOffset = textStrokeStyle.getLineDashOffset();
+      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 lineDashOffset = textStrokeStyleLineDashOffset !== undefined ?
+          textStrokeStyleLineDashOffset : ol.render.canvas.defaultLineDashOffset;
+      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.colorlike.asColorLike(textStrokeStyleColor ?
+          textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle);
+      if (!this.textStrokeState_) {
+        this.textStrokeState_ = {
+          lineCap: lineCap,
+          lineDash: lineDash,
+          lineDashOffset: lineDashOffset,
+          lineJoin: lineJoin,
+          lineWidth: lineWidth,
+          miterLimit: miterLimit,
+          strokeStyle: strokeStyle
+        };
+      } else {
+        var textStrokeState = this.textStrokeState_;
+        textStrokeState.lineCap = lineCap;
+        textStrokeState.lineDash = lineDash;
+        textStrokeState.lineDashOffset = lineDashOffset;
+        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 textRotateWithView = textStyle.getRotateWithView();
+    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.textRotateWithView_ = textRotateWithView !== undefined ? textRotateWithView : false;
+    this.textRotation_ = textRotation !== undefined ? textRotation : 0;
+    this.textScale_ = textScale !== undefined ? textScale : 1;
   }
-  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');
+goog.provide('ol.render.canvas.ReplayGroup');
 
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.geom.flat.transform');
+goog.require('ol.obj');
+goog.require('ol.render.ReplayGroup');
+goog.require('ol.render.canvas.ImageReplay');
+goog.require('ol.render.canvas.LineStringReplay');
+goog.require('ol.render.canvas.PolygonReplay');
+goog.require('ol.render.canvas.TextReplay');
+goog.require('ol.render.replay');
+goog.require('ol.transform');
 
 
 /**
- * @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
+ * @extends {ol.render.ReplayGroup}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Max extent.
+ * @param {number} resolution Resolution.
+ * @param {boolean} overlaps The replay group can have overlapping geometries.
+ * @param {number=} opt_renderBuffer Optional rendering buffer.
+ * @struct
  */
-ol.format.Polyline = function(opt_options) {
+ol.render.canvas.ReplayGroup = function(
+    tolerance, maxExtent, resolution, overlaps, opt_renderBuffer) {
+  ol.render.ReplayGroup.call(this);
 
-  var options = opt_options ? opt_options : {};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.tolerance_ = tolerance;
 
-  goog.base(this);
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.maxExtent_ = maxExtent;
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {boolean}
    */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+  this.overlaps_ = overlaps;
 
   /**
    * @private
    * @type {number}
    */
-  this.factor_ = options.factor ? options.factor : 1e5;
+  this.resolution_ = resolution;
 
   /**
    * @private
-   * @type {ol.geom.GeometryLayout}
+   * @type {number|undefined}
    */
-  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);
+  this.renderBuffer_ = opt_renderBuffer;
 
-  var i, ii;
-  for (i = 0, ii = numbers.length; i < ii;) {
-    for (d = 0; d < stride; ++d, ++i) {
-      lastNumbers[d] += numbers[i];
+  /**
+   * @private
+   * @type {!Object.<string,
+   *        Object.<ol.render.ReplayType, ol.render.canvas.Replay>>}
+   */
+  this.replaysByZIndex_ = {};
 
-      numbers[i] = lastNumbers[d];
-    }
-  }
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.hitDetectionContext_ = ol.dom.createCanvasContext2D(1, 1);
 
-  return numbers;
+  /**
+   * @private
+   * @type {ol.Transform}
+   */
+  this.hitDetectionTransform_ = ol.transform.create();
 };
+ol.inherits(ol.render.canvas.ReplayGroup, ol.render.ReplayGroup);
 
 
 /**
- * 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
+ * This cache is used for storing calculated pixel circles for increasing performance.
+ * It is a static property to allow each Replaygroup to access it.
+ * @type {Object.<number, Array.<Array.<(boolean|undefined)>>>}
+ * @private
  */
-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);
+ol.render.canvas.ReplayGroup.circleArrayCache_ = {
+  0: [[true]]
 };
 
 
 /**
- * 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
+ * This method fills a row in the array from the given coordinate to the
+ * middle with `true`.
+ * @param {Array.<Array.<(boolean|undefined)>>} array The array that will be altered.
+ * @param {number} x X coordinate.
+ * @param {number} y Y coordinate.
+ * @private
  */
-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;
+ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_ = function(array, x, y) {
+  var i;
+  var radius = Math.floor(array.length / 2);
+  if (x >= radius) {
+    for (i = radius; i < x; i++) {
+      array[i][y] = true;
+    }
+  } else if (x < radius) {
+    for (i = x + 1; i < radius; i++) {
+      array[i][y] = true;
+    }
   }
-  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.
+ * This methods creates a circle inside a fitting array. Points inside the
+ * circle are marked by true, points on the outside are undefined.
+ * It uses the midpoint circle algorithm.
+ * A cache is used to increase performance.
+ * @param {number} radius Radius.
+ * @returns {Array.<Array.<(boolean|undefined)>>} An array with marked circle points.
+ * @private
  */
-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);
+ol.render.canvas.ReplayGroup.getCircleArray_ = function(radius) {
+  if (ol.render.canvas.ReplayGroup.circleArrayCache_[radius] !== undefined) {
+    return ol.render.canvas.ReplayGroup.circleArrayCache_[radius];
   }
-  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);
+  var arraySize = radius * 2 + 1;
+  var arr = new Array(arraySize);
+  for (var i = 0; i < arraySize; i++) {
+    arr[i] = new Array(arraySize);
   }
-  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;
-};
+  var x = radius;
+  var y = 0;
+  var error = 0;
 
+  while (x >= y) {
+    ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + x, radius + y);
+    ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + y, radius + x);
+    ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - y, radius + x);
+    ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - x, radius + y);
+    ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - x, radius - y);
+    ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - y, radius - x);
+    ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + y, radius - x);
+    ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + x, radius - y);
 
-/**
- * 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;
+    y++;
+    error += 1 + 2 * y;
+    if (2 * (error - x) + 1 > 0) {
+      x -= 1;
+      error += 1 - 2 * x;
     }
   }
-  return numbers;
-};
 
+  ol.render.canvas.ReplayGroup.circleArrayCache_[radius] = arr;
+  return arr;
+};
 
 /**
- * Encode one single unsigned integer and return an encoded string
- *
- * @param {number} num Unsigned integer that should be encoded.
- * @return {string} The encoded string.
+ * FIXME empty description for jsdoc
  */
-ol.format.Polyline.encodeUnsignedInteger = function(num) {
-  var value, encoded = '';
-  while (num >= 0x20) {
-    value = (0x20 | (num & 0x1f)) + 63;
-    encoded += String.fromCharCode(value);
-    num >>= 5;
+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();
+    }
   }
-  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
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} resolution Resolution.
+ * @param {number} rotation Rotation.
+ * @param {number} hitTolerance Hit tolerance in pixels.
+ * @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.format.Polyline.prototype.readFeature;
-
+ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
+    coordinate, resolution, rotation, hitTolerance, skippedFeaturesHash, callback) {
 
-/**
- * @inheritDoc
- */
-ol.format.Polyline.prototype.readFeatureFromText = function(text, opt_options) {
-  var geometry = this.readGeometryFromText(text, opt_options);
-  return new ol.Feature(geometry);
-};
+  hitTolerance = Math.round(hitTolerance);
+  var contextSize = hitTolerance * 2 + 1;
+  var transform = ol.transform.compose(this.hitDetectionTransform_,
+      hitTolerance + 0.5, hitTolerance + 0.5,
+      1 / resolution, -1 / resolution,
+      -rotation,
+      -coordinate[0], -coordinate[1]);
+  var context = this.hitDetectionContext_;
 
+  if (context.canvas.width !== contextSize || context.canvas.height !== contextSize) {
+    context.canvas.width = contextSize;
+    context.canvas.height = contextSize;
+  } else {
+    context.clearRect(0, 0, contextSize, contextSize);
+  }
 
-/**
- * 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;
+  /**
+   * @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_ + hitTolerance), hitExtent);
+  }
 
+  var mask = ol.render.canvas.ReplayGroup.getCircleArray_(hitTolerance);
 
-/**
- * @inheritDoc
- */
-ol.format.Polyline.prototype.readFeaturesFromText =
-    function(text, opt_options) {
-  var feature = this.readFeatureFromText(text, opt_options);
-  return [feature];
+  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, contextSize, contextSize).data;
+        for (var i = 0; i < contextSize; i++) {
+          for (var j = 0; j < contextSize; j++) {
+            if (mask[i][j]) {
+              if (imageData[(j * contextSize + i) * 4 + 3] > 0) {
+                var result = callback(feature);
+                if (result) {
+                  return result;
+                } else {
+                  context.clearRect(0, 0, contextSize, contextSize);
+                  return undefined;
+                }
+              }
+            }
+          }
+        }
+      }, hitExtent);
 };
 
 
 /**
- * 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
+ * @param {ol.Transform} transform Transform.
+ * @return {Array.<number>} Clip coordinates.
  */
-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)));
+ol.render.canvas.ReplayGroup.prototype.getClipCoords = function(transform) {
+  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);
+  return flatClipCoords;
 };
 
 
-/**
- * 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 '';
+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.ReplayGroup.BATCH_CONSTRUCTORS_[replayType];
+    replay = new Constructor(this.tolerance_, this.maxExtent_,
+        this.resolution_, this.overlaps_);
+    replays[replayType] = replay;
   }
+  return replay;
 };
 
 
 /**
  * @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);
+ol.render.canvas.ReplayGroup.prototype.isEmpty = function() {
+  return ol.obj.isEmpty(this.replaysByZIndex_);
 };
 
 
 /**
- * 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
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.Transform} transform Transform.
+ * @param {number} viewRotation View rotation.
+ * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+ *     to skip.
+ * @param {Array.<ol.render.ReplayType>=} opt_replayTypes Ordered replay types
+ *     to replay. Default is {@link ol.render.replay.ORDER}
  */
-ol.format.Polyline.prototype.writeGeometry;
+ol.render.canvas.ReplayGroup.prototype.replay = function(context, pixelRatio,
+    transform, viewRotation, skippedFeaturesHash, opt_replayTypes) {
 
+  /** @type {Array.<number>} */
+  var zs = Object.keys(this.replaysByZIndex_).map(Number);
+  zs.sort(ol.array.numberSafeCompareFunction);
 
-/**
- * @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');
+  // setup clipping so that the parts of over-simplified geometries are not
+  // visible outside the current extent when panning
+  var flatClipCoords = this.getClipCoords(transform);
+  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.clip();
 
-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');
+  var replayTypes = opt_replayTypes ? opt_replayTypes : ol.render.replay.ORDER;
+  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 = replayTypes.length; j < jj; ++j) {
+      replay = replays[replayTypes[j]];
+      if (replay !== undefined) {
+        replay.replay(context, pixelRatio, transform, viewRotation,
+            skippedFeaturesHash);
+      }
+    }
+  }
 
+  context.restore();
+};
 
 
 /**
- * @classdesc
- * Feature format for reading and writing data in the TopoJSON format.
- *
- * @constructor
- * @extends {ol.format.JSONFeature}
- * @param {olx.format.TopoJSONOptions=} opt_options Options.
- * @api stable
+ * @private
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {ol.Transform} 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.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');
+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;
 };
-goog.inherits(ol.format.TopoJSON, ol.format.JSONFeature);
 
 
 /**
- * @const {Array.<string>}
+ * @const
  * @private
+ * @type {Object.<ol.render.ReplayType,
+ *                function(new: ol.render.canvas.Replay, number, ol.Extent,
+ *                number, boolean)>}
  */
-ol.format.TopoJSON.EXTENSIONS_ = ['.topojson'];
+ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_ = {
+  'Circle': ol.render.canvas.PolygonReplay,
+  'Image': ol.render.canvas.ImageReplay,
+  'LineString': ol.render.canvas.LineStringReplay,
+  'Polygon': ol.render.canvas.PolygonReplay,
+  'Text': ol.render.canvas.TextReplay
+};
 
+goog.provide('ol.renderer.Layer');
 
-/**
- * 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;
-};
+goog.require('ol');
+goog.require('ol.ImageState');
+goog.require('ol.Observable');
+goog.require('ol.TileState');
+goog.require('ol.asserts');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.functions');
+goog.require('ol.source.State');
 
 
 /**
- * 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
+ * @constructor
+ * @extends {ol.Observable}
+ * @param {ol.layer.Layer} layer Layer.
+ * @struct
  */
-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);
+ol.renderer.Layer = function(layer) {
+
+  ol.Observable.call(this);
+
+  /**
+   * @private
+   * @type {ol.layer.Layer}
+   */
+  this.layer_ = layer;
+
+
 };
+ol.inherits(ol.renderer.Layer, ol.Observable);
 
 
 /**
- * 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
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {number} hitTolerance Hit tolerance in pixels.
+ * @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.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);
-};
+ol.renderer.Layer.prototype.forEachFeatureAtCoordinate = ol.nullFunction;
 
 
 /**
- * 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
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState Frame state.
+ * @return {boolean} Is there a feature at the given coordinate?
  */
-ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) {
-  var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs);
-  return new ol.geom.LineString(coordinates);
-};
+ol.renderer.Layer.prototype.hasFeatureAtCoordinate = ol.functions.FALSE;
 
 
 /**
- * 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
+ * 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.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);
+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) {
+        function callback(tile) {
+          if (!tiles[zoom]) {
+            tiles[zoom] = {};
+          }
+          tiles[zoom][tile.tileCoord.toString()] = tile;
+        }
+        return source.forEachLoadedTile(projection, zoom, tileRange, callback);
+      });
 };
 
 
 /**
- * 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
+ * @return {ol.layer.Layer} Layer.
  */
-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);
+ol.renderer.Layer.prototype.getLayer = function() {
+  return this.layer_;
 };
 
 
 /**
- * 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.
+ * Handle changes in image state.
+ * @param {ol.events.Event} event Image change event.
  * @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;
+ol.renderer.Layer.prototype.handleImageChange_ = function(event) {
+  var image = /** @type {ol.Image} */ (event.target);
+  if (image.getState() === ol.ImageState.LOADED) {
+    this.renderIfReadyAndVisible();
   }
-  return new ol.geom.MultiPolygon(coordinates);
 };
 
 
 /**
- * @inheritDoc
+ * 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.format.TopoJSON.prototype.getExtensions = function() {
-  return ol.format.TopoJSON.EXTENSIONS_;
+ol.renderer.Layer.prototype.loadImage = function(image) {
+  var imageState = image.getState();
+  if (imageState != ol.ImageState.LOADED &&
+      imageState != ol.ImageState.ERROR) {
+    ol.events.listen(image, ol.events.EventType.CHANGE,
+        this.handleImageChange_, this);
+  }
+  if (imageState == ol.ImageState.IDLE) {
+    image.load();
+    imageState = image.getState();
+  }
+  return imageState == ol.ImageState.LOADED;
 };
 
 
 /**
- * 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
+ * @protected
  */
-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);
+ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() {
+  var layer = this.getLayer();
+  if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) {
+    this.changed();
   }
-  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
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.source.Tile} tileSource Tile source.
+ * @protected
  */
-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) {
-    feature.setId(object.id);
-  }
-  if (object.properties) {
-    feature.setProperties(object.properties);
+ol.renderer.Layer.prototype.scheduleExpireCache = function(frameState, tileSource) {
+  if (tileSource.canExpireCache()) {
+    /**
+     * @param {ol.source.Tile} tileSource Tile source.
+     * @param {ol.Map} map Map.
+     * @param {olx.FrameState} frameState Frame state.
+     */
+    var postRenderFunction = function(tileSource, map, frameState) {
+      var tileSourceKey = ol.getUid(tileSource).toString();
+      tileSource.expireCache(frameState.viewState.projection,
+                             frameState.usedTiles[tileSourceKey]);
+    }.bind(null, tileSource);
+
+    frameState.postRenderFunctions.push(
+      /** @type {ol.PostRenderFunction} */ (postRenderFunction)
+    );
   }
-  return feature;
 };
 
 
 /**
- * Read all features from a TopoJSON source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
+ * @param {Object.<string, ol.Attribution>} attributionsSet Attributions
+ *     set (target).
+ * @param {Array.<ol.Attribution>} attributions Attributions (source).
+ * @protected
  */
-ol.format.TopoJSON.prototype.readFeatures;
+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[ol.getUid(attribution).toString()] = attribution;
+    }
+  }
+};
 
 
 /**
- * @inheritDoc
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.source.Source} source Source.
+ * @protected
  */
-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));
-      }
+ol.renderer.Layer.prototype.updateLogos = function(frameState, source) {
+  var logo = source.getLogo();
+  if (logo !== undefined) {
+    if (typeof logo === 'string') {
+      frameState.logos[logo] = '';
+    } else if (logo) {
+      ol.asserts.assert(typeof logo.href == 'string', 44); // `logo.href` should be a string.
+      ol.asserts.assert(typeof logo.src == 'string', 45); // `logo.src` should be a string.
+      frameState.logos[logo.src] = logo.href;
     }
-    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
+ * @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.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);
+ol.renderer.Layer.prototype.updateUsedTiles = function(usedTiles, tileSource, z, tileRange) {
+  // FIXME should we use tilesToDrawByZ instead?
+  var tileSourceKey = ol.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;
   }
 };
 
 
 /**
- * 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
+ * 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.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);
+ol.renderer.Layer.prototype.manageTilePyramid = function(
+    frameState, tileSource, tileGrid, pixelRatio, projection, extent,
+    currentZ, preload, opt_tileCallback, opt_this) {
+  var tileSourceKey = ol.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[tile.getKey()] = 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.renderer.canvas.Layer');
+
+goog.require('ol');
+goog.require('ol.extent');
+goog.require('ol.functions');
+goog.require('ol.render.Event');
+goog.require('ol.render.EventType');
+goog.require('ol.render.canvas');
+goog.require('ol.render.canvas.Immediate');
+goog.require('ol.renderer.Layer');
+goog.require('ol.transform');
+
 
 /**
- * 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
+ * @constructor
+ * @abstract
+ * @extends {ol.renderer.Layer}
+ * @param {ol.layer.Layer} layer Layer.
  */
-ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) {
-  vertex[0] = vertex[0] * scale[0] + translate[0];
-  vertex[1] = vertex[1] * scale[1] + translate[1];
+ol.renderer.canvas.Layer = function(layer) {
+
+  ol.renderer.Layer.call(this, layer);
+
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.renderedResolution;
+
+  /**
+   * @private
+   * @type {ol.Transform}
+   */
+  this.transform_ = ol.transform.create();
+
 };
+ol.inherits(ol.renderer.canvas.Layer, ol.renderer.Layer);
 
 
 /**
- * Read the projection from a TopoJSON source.
- *
- * @function
- * @param {Document|Node|Object|string} object Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.Extent} extent Clip extent.
+ * @protected
  */
-ol.format.TopoJSON.prototype.readProjection = function(object) {
-  return this.defaultDataProjection;
+ol.renderer.canvas.Layer.prototype.clip = function(context, frameState, extent) {
+  var pixelRatio = frameState.pixelRatio;
+  var width = frameState.size[0] * pixelRatio;
+  var height = frameState.size[1] * pixelRatio;
+  var rotation = frameState.viewState.rotation;
+  var topLeft = ol.extent.getTopLeft(/** @type {ol.Extent} */ (extent));
+  var topRight = ol.extent.getTopRight(/** @type {ol.Extent} */ (extent));
+  var bottomRight = ol.extent.getBottomRight(/** @type {ol.Extent} */ (extent));
+  var bottomLeft = ol.extent.getBottomLeft(/** @type {ol.Extent} */ (extent));
+
+  ol.transform.apply(frameState.coordinateToPixelTransform, topLeft);
+  ol.transform.apply(frameState.coordinateToPixelTransform, topRight);
+  ol.transform.apply(frameState.coordinateToPixelTransform, bottomRight);
+  ol.transform.apply(frameState.coordinateToPixelTransform, bottomLeft);
+
+  context.save();
+  ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2);
+  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();
+  ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2);
 };
 
 
 /**
- * @const
+ * @param {ol.render.EventType} type Event type.
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.Transform=} opt_transform Transform.
  * @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_
+ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState, opt_transform) {
+  var layer = this.getLayer();
+  if (layer.hasListener(type)) {
+    var width = frameState.size[0] * frameState.pixelRatio;
+    var height = frameState.size[1] * frameState.pixelRatio;
+    var rotation = frameState.viewState.rotation;
+    ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2);
+    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, render, frameState,
+        context, null);
+    layer.dispatchEvent(composeEvent);
+    ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2);
+  }
 };
 
-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
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState FrameState.
+ * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer
+ *     callback.
+ * @param {S} thisArg Value to use as `this` when executing `callback`.
+ * @return {T|undefined} Callback result.
+ * @template S,T,U
  */
-ol.format.WFS = function(opt_options) {
-  var options = opt_options ? opt_options : {};
+ol.renderer.canvas.Layer.prototype.forEachLayerAtCoordinate = function(coordinate, frameState, callback, thisArg) {
+  var hasFeature = this.forEachFeatureAtCoordinate(
+      coordinate, frameState, 0, ol.functions.TRUE, this);
 
-  /**
-   * @private
-   * @type {Array.<string>|string|undefined}
-   */
-  this.featureType_ = options.featureType;
+  if (hasFeature) {
+    return callback.call(thisArg, this.getLayer(), null);
+  } else {
+    return undefined;
+  }
+};
 
-  /**
-   * @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();
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.LayerState} layerState Layer state.
+ * @param {ol.Transform=} opt_transform Transform.
+ * @protected
+ */
+ol.renderer.canvas.Layer.prototype.postCompose = function(context, frameState, layerState, opt_transform) {
+  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, context,
+      frameState, opt_transform);
+};
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.schemaLocation_ = options.schemaLocation ?
-      options.schemaLocation : ol.format.WFS.SCHEMA_LOCATION;
 
-  goog.base(this);
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.Transform=} opt_transform Transform.
+ * @protected
+ */
+ol.renderer.canvas.Layer.prototype.preCompose = function(context, frameState, opt_transform) {
+  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, context,
+      frameState, opt_transform);
 };
-goog.inherits(ol.format.WFS, ol.format.XMLFeature);
 
 
 /**
- * @const
- * @type {string}
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.Transform=} opt_transform Transform.
+ * @protected
  */
-ol.format.WFS.FEATURE_PREFIX = 'feature';
+ol.renderer.canvas.Layer.prototype.dispatchRenderEvent = function(context, frameState, opt_transform) {
+  this.dispatchComposeEvent_(ol.render.EventType.RENDER, context,
+      frameState, opt_transform);
+};
 
 
 /**
- * @const
- * @type {string}
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {number} offsetX Offset on the x-axis in view coordinates.
+ * @protected
+ * @return {!ol.Transform} Transform.
  */
-ol.format.WFS.XMLNS = 'http://www.w3.org/2000/xmlns/';
+ol.renderer.canvas.Layer.prototype.getTransform = function(frameState, offsetX) {
+  var viewState = frameState.viewState;
+  var pixelRatio = frameState.pixelRatio;
+  var dx1 = pixelRatio * frameState.size[0] / 2;
+  var dy1 = pixelRatio * frameState.size[1] / 2;
+  var sx = pixelRatio / viewState.resolution;
+  var sy = -sx;
+  var angle = -viewState.rotation;
+  var dx2 = -viewState.center[0] + offsetX;
+  var dy2 = -viewState.center[1];
+  return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2);
+};
 
 
 /**
- * Number of features; bounds/extent.
- * @typedef {{numberOfFeatures: number,
- *            bounds: ol.Extent}}
- * @api stable
+ * @abstract
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.LayerState} layerState Layer state.
+ * @param {CanvasRenderingContext2D} context Context.
  */
-ol.format.WFS.FeatureCollectionMetadata;
-
+ol.renderer.canvas.Layer.prototype.composeFrame = function(frameState, layerState, context) {};
 
 /**
- * Total deleted; total inserted; total updated; array of insert ids.
- * @typedef {{totalDeleted: number,
- *            totalInserted: number,
- *            totalUpdated: number,
- *            insertIds: Array.<string>}}
- * @api stable
+ * @abstract
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.LayerState} layerState Layer state.
+ * @return {boolean} whether composeFrame should be called.
  */
-ol.format.WFS.TransactionResponse;
+ol.renderer.canvas.Layer.prototype.prepareFrame = function(frameState, layerState) {};
 
+goog.provide('ol.renderer.vector');
 
-/**
- * @const
- * @type {string}
- */
-ol.format.WFS.SCHEMA_LOCATION = 'http://www.opengis.net/wfs ' +
-    'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd';
+goog.require('ol');
+goog.require('ol.ImageState');
+goog.require('ol.render.ReplayType');
 
 
 /**
- * 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
+ * @param {ol.Feature|ol.render.Feature} feature1 Feature 1.
+ * @param {ol.Feature|ol.render.Feature} feature2 Feature 2.
+ * @return {number} Order.
  */
-ol.format.WFS.prototype.readFeatures;
+ol.renderer.vector.defaultOrder = function(feature1, feature2) {
+  return ol.getUid(feature1) - ol.getUid(feature2);
+};
 
 
 /**
- * @inheritDoc
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {number} Squared pixel tolerance.
  */
-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;
+ol.renderer.vector.getSquaredTolerance = function(resolution, pixelRatio) {
+  var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio);
+  return tolerance * tolerance;
 };
 
 
 /**
- * Read transaction response of the source.
- *
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response.
- * @api stable
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {number} Pixel tolerance.
  */
-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;
-  }
+ol.renderer.vector.getTolerance = function(resolution, pixelRatio) {
+  return ol.SIMPLIFY_TOLERANCE * resolution / pixelRatio;
 };
 
 
 /**
- * Read feature collection metadata of the source.
- *
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.format.WFS.FeatureCollectionMetadata|undefined}
- *     FeatureCollection metadata.
- * @api stable
+ * @param {ol.render.ReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Circle} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
+ * @private
  */
-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;
+ol.renderer.vector.renderCircleGeometry_ = function(replayGroup, geometry, style, feature) {
+  var fillStyle = style.getFill();
+  var strokeStyle = style.getStroke();
+  if (fillStyle || strokeStyle) {
+    var circleReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.CIRCLE);
+    circleReplay.setFillStrokeStyle(fillStyle, strokeStyle);
+    circleReplay.drawCircle(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 {Document} doc Document.
- * @return {ol.format.WFS.FeatureCollectionMetadata|undefined}
- *     FeatureCollection metadata.
+ * @param {ol.render.ReplayGroup} 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, ol.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.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);
+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.ImageState.LOADED ||
+        imageState == ol.ImageState.ERROR) {
+      imageStyle.unlistenImageChange(listener, thisArg);
+    } else {
+      if (imageState == ol.ImageState.IDLE) {
+        imageStyle.load();
+      }
+      imageState = imageStyle.getImageState();
+      imageStyle.listenImageChange(listener, thisArg);
+      loading = true;
     }
   }
-  return undefined;
+  ol.renderer.vector.renderFeature_(replayGroup, feature, style,
+      squaredTolerance);
+  return loading;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.render.ReplayGroup} replayGroup Replay group.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {ol.style.Style} style Style.
+ * @param {number} squaredTolerance Squared tolerance.
  * @private
  */
-ol.format.WFS.FEATURE_COLLECTION_PARSERS_ = {
-  'http://www.opengis.net/gml': {
-    'boundedBy': ol.xml.makeObjectPropertySetter(
-        ol.format.GMLBase.prototype.readGeometryElement, 'bounds')
+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()];
+  geometryRenderer(replayGroup, simplifiedGeometry, style, feature);
 };
 
 
 /**
- * @param {Node} node Node.
- * @return {ol.format.WFS.FeatureCollectionMetadata|undefined}
- *     FeatureCollection metadata.
+ * @param {ol.render.ReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.GeometryCollection} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
+ * @private
  */
-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_);
+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()];
+    geometryRenderer(replayGroup, geometries[i], style, feature);
+  }
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.render.ReplayGroup} 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.format.WFS.TRANSACTION_SUMMARY_PARSERS_ = {
-  'http://www.opengis.net/wfs': {
-    'totalInserted': ol.xml.makeObjectPropertySetter(
-        ol.format.XSD.readNonNegativeInteger),
-    'totalUpdated': ol.xml.makeObjectPropertySetter(
-        ol.format.XSD.readNonNegativeInteger),
-    'totalDeleted': ol.xml.makeObjectPropertySetter(
-        ol.format.XSD.readNonNegativeInteger)
+ol.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.drawLineString(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 {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Transaction Summary.
+ * @param {ol.render.ReplayGroup} 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.format.WFS.readTransactionSummary_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_, node, objectStack);
+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.drawMultiLineString(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);
+  }
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.render.ReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.MultiPolygon} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
  * @private
  */
-ol.format.WFS.OGC_FID_PARSERS_ = {
-  'http://www.opengis.net/ogc': {
-    'FeatureId': ol.xml.makeArrayPusher(function(node, objectStack) {
-      return node.getAttribute('fid');
-    })
+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.drawMultiPolygon(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 {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.render.ReplayGroup} 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.format.WFS.fidParser_ = function(node, objectStack) {
-  ol.xml.parseNode(ol.format.WFS.OGC_FID_PARSERS_, node, objectStack);
+ol.renderer.vector.renderPointGeometry_ = function(replayGroup, geometry, style, feature) {
+  var imageStyle = style.getImage();
+  if (imageStyle) {
+    if (imageStyle.getImageState() != ol.ImageState.LOADED) {
+      return;
+    }
+    var imageReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.IMAGE);
+    imageReplay.setImageStyle(imageStyle);
+    imageReplay.drawPoint(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);
+  }
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.render.ReplayGroup} 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.format.WFS.INSERT_RESULTS_PARSERS_ = {
-  'http://www.opengis.net/wfs': {
-    'Feature': ol.format.WFS.fidParser_
+ol.renderer.vector.renderMultiPointGeometry_ = function(replayGroup, geometry, style, feature) {
+  var imageStyle = style.getImage();
+  if (imageStyle) {
+    if (imageStyle.getImageState() != ol.ImageState.LOADED) {
+      return;
+    }
+    var imageReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.IMAGE);
+    imageReplay.setImageStyle(imageStyle);
+    imageReplay.drawMultiPoint(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 {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<string>|undefined} Insert results.
+ * @param {ol.render.ReplayGroup} 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.format.WFS.readInsertResults_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-      [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack);
+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.drawPolygon(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
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
  * @private
+ * @type {Object.<ol.geom.GeometryType,
+ *                function(ol.render.ReplayGroup, ol.geom.Geometry,
+ *                         ol.style.Style, Object)>}
  */
-ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_ = {
-  'http://www.opengis.net/wfs': {
-    'TransactionSummary': ol.xml.makeObjectPropertySetter(
-        ol.format.WFS.readTransactionSummary_, 'transactionSummary'),
-    'InsertResults': ol.xml.makeObjectPropertySetter(
-        ol.format.WFS.readInsertResults_, 'insertIds')
-  }
+ol.renderer.vector.GEOMETRY_RENDERERS_ = {
+  'Point': ol.renderer.vector.renderPointGeometry_,
+  'LineString': ol.renderer.vector.renderLineStringGeometry_,
+  'Polygon': ol.renderer.vector.renderPolygonGeometry_,
+  'MultiPoint': ol.renderer.vector.renderMultiPointGeometry_,
+  'MultiLineString': ol.renderer.vector.renderMultiLineStringGeometry_,
+  'MultiPolygon': ol.renderer.vector.renderMultiPolygonGeometry_,
+  'GeometryCollection': ol.renderer.vector.renderGeometryCollectionGeometry_,
+  'Circle': ol.renderer.vector.renderCircleGeometry_
 };
 
+goog.provide('ol.renderer.canvas.VectorLayer');
 
-/**
- * @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;
-};
+goog.require('ol');
+goog.require('ol.ViewHint');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.render.EventType');
+goog.require('ol.render.canvas');
+goog.require('ol.render.canvas.ReplayGroup');
+goog.require('ol.renderer.canvas.Layer');
+goog.require('ol.renderer.vector');
 
 
 /**
- * @param {Node} node Node.
- * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response.
+ * @constructor
+ * @extends {ol.renderer.canvas.Layer}
+ * @param {ol.layer.Vector} vectorLayer Vector layer.
  */
-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, []);
-};
+ol.renderer.canvas.VectorLayer = function(vectorLayer) {
 
+  ol.renderer.canvas.Layer.call(this, vectorLayer);
 
-/**
- * @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)
-  }
-};
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.dirty_ = false;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedRevision_ = -1;
 
-/**
- * @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);
-};
+  /**
+   * @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;
 
-/**
- * @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);
-};
+  /**
+   * @private
+   * @type {ol.render.canvas.ReplayGroup}
+   */
+  this.replayGroup_ = null;
 
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context_ = ol.dom.createCanvasContext2D();
 
-/**
- * @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);
-  }
 };
+ol.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @inheritDoc
  */
-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.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 = /** @type {ol.source.Vector} */ (this.getLayer().getSource());
+
+  var transform = this.getTransform(frameState, 0);
+
+  this.preCompose(context, frameState, transform);
+
+  // clipped rendering if layer extent is set
+  var clipExtent = layerState.extent;
+  var clipped = clipExtent !== undefined;
+  if (clipped) {
+    this.clip(context, frameState,  /** @type {ol.Extent} */ (clipExtent));
+  }
+  var replayGroup = this.replayGroup_;
+  if (replayGroup && !replayGroup.isEmpty()) {
+    var layer = this.getLayer();
+    var drawOffsetX = 0;
+    var drawOffsetY = 0;
+    var replayContext;
+    if (layer.hasListener(ol.render.EventType.RENDER)) {
+      var drawWidth = context.canvas.width;
+      var drawHeight = context.canvas.height;
+      if (rotation) {
+        var drawSize = Math.round(Math.sqrt(drawWidth * drawWidth + drawHeight * drawHeight));
+        drawOffsetX = (drawSize - drawWidth) / 2;
+        drawOffsetY = (drawSize - drawHeight) / 2;
+        drawWidth = drawHeight = drawSize;
+      }
+      // resize and clear
+      this.context_.canvas.width = drawWidth;
+      this.context_.canvas.height = drawHeight;
+      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;
+    if (replayContext != context) {
+      replayContext.translate(drawOffsetX, drawOffsetY);
+    }
+
+    var width = frameState.size[0] * pixelRatio;
+    var height = frameState.size[1] * pixelRatio;
+    ol.render.canvas.rotateAtOffset(replayContext, -rotation,
+        width / 2, height / 2);
+    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);
     }
-    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);
-  }
-};
-
+    ol.render.canvas.rotateAtOffset(replayContext, rotation,
+        width / 2, height / 2);
 
-/**
- * @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);
+    if (replayContext != context) {
+      this.dispatchRenderEvent(replayContext, frameState, transform);
+      context.drawImage(replayContext.canvas, -drawOffsetX, -drawOffsetY);
+      replayContext.translate(-drawOffsetX, -drawOffsetY);
     }
+    replayContext.globalAlpha = alpha;
   }
-};
 
-
-/**
- * @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);
+  if (clipped) {
+    context.restore();
   }
+  this.postCompose(context, frameState, layerState, transform);
+
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @inheritDoc
  */
-ol.format.WFS.TRANSACTION_SERIALIZERS_ = {
-  'http://www.opengis.net/wfs': {
-    'Insert': ol.xml.makeChildAppender(ol.format.WFS.writeFeature_),
-    'Update': ol.xml.makeChildAppender(ol.format.WFS.writeUpdate_),
-    'Delete': ol.xml.makeChildAppender(ol.format.WFS.writeDelete_),
-    'Property': ol.xml.makeChildAppender(ol.format.WFS.writeProperty_),
-    'Native': ol.xml.makeChildAppender(ol.format.WFS.writeNative_)
+ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) {
+  if (!this.replayGroup_) {
+    return undefined;
+  } else {
+    var resolution = frameState.viewState.resolution;
+    var rotation = frameState.viewState.rotation;
+    var layer = this.getLayer();
+    /** @type {Object.<string, boolean>} */
+    var features = {};
+    return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution,
+        rotation, hitTolerance, {},
+        /**
+         * @param {ol.Feature|ol.render.Feature} feature Feature.
+         * @return {?} Callback result.
+         */
+        function(feature) {
+          var key = ol.getUid(feature).toString();
+          if (!(key in features)) {
+            features[key] = true;
+            return callback.call(thisArg, feature, layer);
+          }
+        });
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {string} featureType Feature type.
- * @param {Array.<*>} objectStack Node stack.
+ * Handle changes in image style state.
+ * @param {ol.events.Event} event Image style change event.
  * @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);
-  }
+ol.renderer.canvas.VectorLayer.prototype.handleStyleImageChange_ = function(event) {
+  this.renderIfReadyAndVisible();
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {string} value PropertyName value.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @inheritDoc
  */
-ol.format.WFS.writeOgcPropertyName_ = function(node, value, objectStack) {
-  var property = ol.xml.createElementNS('http://www.opengis.net/ogc',
-      'PropertyName');
-  ol.format.XSD.writeStringTextNode(property, value);
-  node.appendChild(property);
-};
+ol.renderer.canvas.VectorLayer.prototype.prepareFrame = function(frameState, layerState) {
 
+  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
+  var vectorSource = vectorLayer.getSource();
 
-/**
- * @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);
-};
+  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();
 
-/**
- * @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_)
+  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();
 
-/**
- * @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);
-};
+  if (vectorLayerRenderOrder === undefined) {
+    vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
+  }
 
+  var extent = ol.extent.buffer(frameStateExtent,
+      vectorLayerRenderBuffer * resolution);
+  var projectionExtent = viewState.projection.getExtent();
 
-/**
- * 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 (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;
+  }
+
+  this.replayGroup_ = null;
+
+  this.dirty_ = false;
+
+  var replayGroup =
+      new ol.render.canvas.ReplayGroup(
+          ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
+          resolution, vectorSource.getOverlaps(), vectorLayer.getRenderBuffer());
+  vectorSource.loadFeatures(extent, resolution, projection);
+  /**
+   * @param {ol.Feature} feature Feature.
+   * @this {ol.renderer.canvas.VectorLayer}
+   */
+  var renderFeature = 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 (options.count !== undefined) {
-      node.setAttribute('count', options.count);
+    if (styles) {
+      var dirty = this.renderFeature(
+          feature, resolution, pixelRatio, styles, replayGroup);
+      this.dirty_ = this.dirty_ || dirty;
     }
-  }
-  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;
+  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;
 };
 
 
 /**
- * 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
+ * @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.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);
+ol.renderer.canvas.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) {
+  if (!styles) {
+    return false;
   }
-  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);
+  var loading = false;
+  if (Array.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 node;
+  return loading;
 };
 
+// This file is automatically generated, do not edit
+/* eslint openlayers-internal/no-missing-requires: 0 */
+goog.provide('ol.renderer.webgl.defaultmapshader');
 
-/**
- * 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;
+goog.require('ol');
+goog.require('ol.webgl.Fragment');
+goog.require('ol.webgl.Vertex');
 
+if (ol.ENABLE_WEBGL) {
 
-/**
- * @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;
-};
+  /**
+   * @constructor
+   * @extends {ol.webgl.Fragment}
+   * @struct
+   */
+  ol.renderer.webgl.defaultmapshader.Fragment = function() {
+    ol.webgl.Fragment.call(this, ol.renderer.webgl.defaultmapshader.Fragment.SOURCE);
+  };
+  ol.inherits(ol.renderer.webgl.defaultmapshader.Fragment, ol.webgl.Fragment);
 
 
-/**
- * @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');
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.renderer.webgl.defaultmapshader.Fragment.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';
 
-  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;
-};
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.renderer.webgl.defaultmapshader.Fragment.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;}';
 
-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');
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.renderer.webgl.defaultmapshader.Fragment.SOURCE = ol.DEBUG_WEBGL ?
+      ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE :
+      ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE;
 
 
+  ol.renderer.webgl.defaultmapshader.fragment = new ol.renderer.webgl.defaultmapshader.Fragment();
 
-/**
- * @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 : {};
+  /**
+   * @constructor
+   * @extends {ol.webgl.Vertex}
+   * @struct
+   */
+  ol.renderer.webgl.defaultmapshader.Vertex = function() {
+    ol.webgl.Vertex.call(this, ol.renderer.webgl.defaultmapshader.Vertex.SOURCE);
+  };
+  ol.inherits(ol.renderer.webgl.defaultmapshader.Vertex, ol.webgl.Vertex);
 
-  goog.base(this);
 
   /**
-   * Split GeometryCollection into multiple features.
-   * @type {boolean}
-   * @private
+   * @const
+   * @type {string}
    */
-  this.splitCollection_ = options.splitCollection !== undefined ?
-      options.splitCollection : false;
+  ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n  gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n  v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n';
 
-};
-goog.inherits(ol.format.WKT, ol.format.TextFeature);
 
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.renderer.webgl.defaultmapshader.Vertex.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.format.WKT.EMPTY = 'EMPTY';
 
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.renderer.webgl.defaultmapshader.Vertex.SOURCE = ol.DEBUG_WEBGL ?
+      ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE :
+      ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE;
 
-/**
- * @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];
-};
 
+  ol.renderer.webgl.defaultmapshader.vertex = new ol.renderer.webgl.defaultmapshader.Vertex();
 
-/**
- * @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(',');
-};
 
+  /**
+   * @constructor
+   * @param {WebGLRenderingContext} gl GL.
+   * @param {WebGLProgram} program Program.
+   * @struct
+   */
+  ol.renderer.webgl.defaultmapshader.Locations = function(gl, program) {
 
-/**
- * @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(',');
-};
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_opacity = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_opacity' : 'f');
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_projectionMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'e');
 
-/**
- * @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(',');
-};
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_texCoordMatrix = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_texCoordMatrix' : 'd');
 
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_texture = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_texture' : 'g');
 
-/**
- * @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(',');
-};
+    /**
+     * @type {number}
+     */
+    this.a_position = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_position' : 'b');
+
+    /**
+     * @type {number}
+     */
+    this.a_texCoord = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'c');
+  };
+
+}
+
+goog.provide('ol.renderer.webgl.Layer');
+
+goog.require('ol');
+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.defaultmapshader');
+goog.require('ol.transform');
+goog.require('ol.vec.Mat4');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Buffer');
+goog.require('ol.webgl.Context');
+
+
+if (ol.ENABLE_WEBGL) {
 
+  /**
+   * @constructor
+   * @abstract
+   * @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) {
 
-/**
- * @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(',');
-};
+    ol.renderer.Layer.call(this, layer);
 
+    /**
+     * @protected
+     * @type {ol.renderer.webgl.Map}
+     */
+    this.mapRenderer = mapRenderer;
 
-/**
- * @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(',');
-};
+    /**
+     * @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;
 
-/**
- * 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 + ')';
-};
+    /**
+     * @protected
+     * @type {WebGLFramebuffer}
+     */
+    this.framebuffer = null;
 
+    /**
+     * @protected
+     * @type {number|undefined}
+     */
+    this.framebufferDimension = undefined;
 
-/**
- * @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_
-};
+    /**
+     * @protected
+     * @type {ol.Transform}
+     */
+    this.texCoordMatrix = ol.transform.create();
 
+    /**
+     * @protected
+     * @type {ol.Transform}
+     */
+    this.projectionMatrix = ol.transform.create();
 
-/**
- * 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();
-};
+    /**
+     * @type {Array.<number>}
+     * @private
+     */
+    this.tmpMat4_ = ol.vec.Mat4.create();
 
+    /**
+     * @private
+     * @type {ol.renderer.webgl.defaultmapshader.Locations}
+     */
+    this.defaultLocations_ = null;
 
-/**
- * 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;
+  };
+  ol.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer);
 
 
-/**
- * @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;
-};
+  /**
+   * @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();
 
-/**
- * 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;
+    if (this.framebufferDimension === undefined ||
+        this.framebufferDimension != framebufferDimension) {
+      /**
+       * @param {WebGLRenderingContext} gl GL.
+       * @param {WebGLFramebuffer} framebuffer Framebuffer.
+       * @param {WebGLTexture} texture Texture.
+       */
+      var postRenderFunction = function(gl, framebuffer, texture) {
+        if (!gl.isContextLost()) {
+          gl.deleteFramebuffer(framebuffer);
+          gl.deleteTexture(texture);
+        }
+      }.bind(null, gl, this.framebuffer, this.texture);
 
+      frameState.postRenderFunctions.push(
+        /** @type {ol.PostRenderFunction} */ (postRenderFunction)
+      );
 
-/**
- * @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;
-};
+      var texture = ol.webgl.Context.createEmptyTexture(
+          gl, framebufferDimension, framebufferDimension);
 
+      var framebuffer = gl.createFramebuffer();
+      gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, framebuffer);
+      gl.framebufferTexture2D(ol.webgl.FRAMEBUFFER,
+          ol.webgl.COLOR_ATTACHMENT0, ol.webgl.TEXTURE_2D, texture, 0);
 
-/**
- * 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;
+      this.texture = texture;
+      this.framebuffer = framebuffer;
+      this.framebufferDimension = framebufferDimension;
 
+    } else {
+      gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, this.framebuffer);
+    }
 
-/**
- * @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;
+  /**
+   * @param {olx.FrameState} frameState Frame state.
+   * @param {ol.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);
 
-/**
- * @inheritDoc
- */
-ol.format.WKT.prototype.writeFeatureText = function(feature, opt_options) {
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    return this.writeGeometryText(geometry, opt_options);
-  }
-  return '';
-};
+    context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.arrayBuffer_);
 
+    var gl = context.getGL();
 
-/**
- * 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;
+    var fragmentShader = ol.renderer.webgl.defaultmapshader.fragment;
+    var vertexShader = ol.renderer.webgl.defaultmapshader.vertex;
 
+    var program = context.getProgram(fragmentShader, vertexShader);
 
-/**
- * @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);
-};
+    var locations;
+    if (!this.defaultLocations_) {
+      // eslint-disable-next-line openlayers-internal/no-missing-requires
+      locations = new ol.renderer.webgl.defaultmapshader.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, ol.webgl.FLOAT, false, 16, 0);
+      gl.enableVertexAttribArray(locations.a_texCoord);
+      gl.vertexAttribPointer(
+          locations.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8);
+      gl.uniform1i(locations.u_texture, 0);
+    }
 
-/**
- * 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;
+    gl.uniformMatrix4fv(locations.u_texCoordMatrix, false,
+        ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getTexCoordMatrix()));
+    gl.uniformMatrix4fv(locations.u_projectionMatrix, false,
+        ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getProjectionMatrix()));
+    gl.uniform1f(locations.u_opacity, layerState.opacity);
+    gl.bindTexture(ol.webgl.TEXTURE_2D, this.getTexture());
+    gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4);
 
+    this.dispatchComposeEvent_(
+        ol.render.EventType.POSTCOMPOSE, context, frameState);
 
-/**
- * @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;
+  /**
+   * @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, render, frameState, null, context);
+      layer.dispatchEvent(composeEvent);
+    }
+  };
 
-/**
- * @const
- * @enum {number}
- */
-ol.format.WKT.TokenType = {
-  TEXT: 1,
-  LEFT_PAREN: 2,
-  RIGHT_PAREN: 3,
-  NUMBER: 4,
-  COMMA: 5,
-  EOF: 6
-};
 
+  /**
+   * @return {!ol.Transform} Matrix.
+   */
+  ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() {
+    return this.texCoordMatrix;
+  };
 
 
-/**
- * Class to tokenize a WKT string.
- * @param {string} wkt WKT string.
- * @constructor
- * @protected
- */
-ol.format.WKT.Lexer = function(wkt) {
+  /**
+   * @return {WebGLTexture} Texture.
+   */
+  ol.renderer.webgl.Layer.prototype.getTexture = function() {
+    return this.texture;
+  };
+
 
   /**
-   * @type {string}
+   * @return {!ol.Transform} Matrix.
    */
-  this.wkt = wkt;
+  ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() {
+    return this.projectionMatrix;
+  };
+
 
   /**
-   * @type {number}
-   * @private
+   * Handle webglcontextlost.
    */
-  this.index_ = -1;
-};
+  ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() {
+    this.texture = null;
+    this.framebuffer = null;
+    this.framebufferDimension = undefined;
+  };
 
 
-/**
- * @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';
-};
+  /**
+   * @abstract
+   * @param {olx.FrameState} frameState Frame state.
+   * @param {ol.LayerState} layerState Layer state.
+   * @param {ol.webgl.Context} context Context.
+   * @return {boolean} whether composeFrame should be called.
+   */
+  ol.renderer.webgl.Layer.prototype.prepareFrame = function(frameState, layerState, context) {};
 
 
-/**
- * @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;
-};
+  /**
+   * @abstract
+   * @param {ol.Pixel} pixel Pixel.
+   * @param {olx.FrameState} frameState FrameState.
+   * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer
+   *     callback.
+   * @param {S} thisArg Value to use as `this` when executing `callback`.
+   * @return {T|undefined} Callback result.
+   * @template S,T,U
+   */
+  ol.renderer.webgl.Layer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {};
 
+}
 
-/**
- * @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';
-};
+goog.provide('ol.renderer.webgl.VectorLayer');
 
+goog.require('ol');
+goog.require('ol.ViewHint');
+goog.require('ol.extent');
+goog.require('ol.render.webgl.ReplayGroup');
+goog.require('ol.renderer.vector');
+goog.require('ol.renderer.webgl.Layer');
+goog.require('ol.transform');
 
-/**
- * @return {string} Next string character.
- * @private
- */
-ol.format.WKT.Lexer.prototype.nextChar_ = function() {
-  return this.wkt.charAt(++this.index_);
-};
 
+if (ol.ENABLE_WEBGL) {
 
-/**
- * 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};
+  /**
+   * @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) {
 
-  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);
-  }
+    ol.renderer.webgl.Layer.call(this, mapRenderer, vectorLayer);
 
-  return token;
-};
+    /**
+     * @private
+     * @type {boolean}
+     */
+    this.dirty_ = false;
 
+    /**
+     * @private
+     * @type {number}
+     */
+    this.renderedRevision_ = -1;
 
-/**
- * @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_--));
-};
+    /**
+     * @private
+     * @type {number}
+     */
+    this.renderedResolution_ = NaN;
 
+    /**
+     * @private
+     * @type {ol.Extent}
+     */
+    this.renderedExtent_ = ol.extent.createEmpty();
 
-/**
- * @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();
-};
+    /**
+     * @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.LayerState}
+     */
+    this.layerState_ = null;
 
-/**
- * Class to parse the tokens from the WKT string.
- * @param {ol.format.WKT.Lexer} lexer
- * @constructor
- * @protected
- */
-ol.format.WKT.Parser = function(lexer) {
+  };
+  ol.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer);
 
-  /**
-   * @type {ol.format.WKT.Lexer}
-   * @private
-   */
-  this.lexer_ = lexer;
 
   /**
-   * @type {ol.format.WKT.Token}
-   * @private
+   * @inheritDoc
    */
-  this.token_;
+  ol.renderer.webgl.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) {
+    this.layerState_ = layerState;
+    var viewState = frameState.viewState;
+    var replayGroup = this.replayGroup_;
+    var size = frameState.size;
+    var pixelRatio = frameState.pixelRatio;
+    var gl = this.mapRenderer.getGL();
+    if (replayGroup && !replayGroup.isEmpty()) {
+      gl.enable(gl.SCISSOR_TEST);
+      gl.scissor(0, 0, size[0] * pixelRatio, size[1] * pixelRatio);
+      replayGroup.replay(context,
+          viewState.center, viewState.resolution, viewState.rotation,
+          size, pixelRatio, layerState.opacity,
+          layerState.managed ? frameState.skippedFeatureUids : {});
+      gl.disable(gl.SCISSOR_TEST);
+    }
+
+  };
+
 
   /**
-   * @type {number}
-   * @private
+   * @inheritDoc
    */
-  this.dimension_ = 2;
-};
-
+  ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() {
+    var replayGroup = this.replayGroup_;
+    if (replayGroup) {
+      var context = this.mapRenderer.getContext();
+      replayGroup.getDeleteResourcesFunction(context)();
+      this.replayGroup_ = null;
+    }
+    ol.renderer.webgl.Layer.prototype.disposeInternal.call(this);
+  };
 
-/**
- * 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();
-};
 
+  /**
+   * @inheritDoc
+   */
+  ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, 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,
+          {},
+          /**
+           * @param {ol.Feature|ol.render.Feature} feature Feature.
+           * @return {?} Callback result.
+           */
+          function(feature) {
+            var key = ol.getUid(feature).toString();
+            if (!(key in features)) {
+              features[key] = true;
+              return callback.call(thisArg, feature, layer);
+            }
+          });
+    }
+  };
 
-/**
- * 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;
-};
 
+  /**
+   * @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);
+    }
+  };
 
-/**
- * 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;
-};
 
+  /**
+   * @inheritDoc
+   */
+  ol.renderer.webgl.VectorLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
+    var coordinate = ol.transform.apply(
+        frameState.pixelToCoordinateTransform, pixel.slice());
+    var hasFeature = this.hasFeatureAtCoordinate(coordinate, frameState);
 
-/**
- * @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);
+    if (hasFeature) {
+      return callback.call(thisArg, this.getLayer(), null);
     } 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);
+      return undefined;
     }
-  }
-  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_());
-};
+  /**
+   * Handle changes in image style state.
+   * @param {ol.events.Event} event Image style change event.
+   * @private
+   */
+  ol.renderer.webgl.VectorLayer.prototype.handleStyleImageChange_ = function(event) {
+    this.renderIfReadyAndVisible();
+  };
 
 
-/**
- * @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_());
-};
+  /**
+   * @inheritDoc
+   */
+  ol.renderer.webgl.VectorLayer.prototype.prepareFrame = function(frameState, layerState, context) {
 
+    var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
+    var vectorSource = vectorLayer.getSource();
 
-/**
- * @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;
+    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;
     }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
 
+    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();
 
-/**
- * @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;
+    if (vectorLayerRenderOrder === undefined) {
+      vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
     }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
 
+    var extent = ol.extent.buffer(frameStateExtent,
+        vectorLayerRenderBuffer * resolution);
 
-/**
- * @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.dirty_ &&
+        this.renderedResolution_ == resolution &&
+        this.renderedRevision_ == vectorLayerRevision &&
+        this.renderedRenderOrder_ == vectorLayerRenderOrder &&
+        ol.extent.containsExtent(this.renderedExtent_, extent)) {
+      return true;
     }
-    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
-      return coordinates;
+
+    if (this.replayGroup_) {
+      frameState.postRenderFunctions.push(
+          this.replayGroup_.getDeleteResourcesFunction(context));
     }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
 
+    this.dirty_ = false;
 
-/**
- * @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;
+    var replayGroup = new ol.render.webgl.ReplayGroup(
+        ol.renderer.vector.getTolerance(resolution, pixelRatio),
+        extent, vectorLayer.getRenderBuffer());
+    vectorSource.loadFeatures(extent, resolution, projection);
+    /**
+     * @param {ol.Feature} feature Feature.
+     * @this {ol.renderer.webgl.VectorLayer}
+     */
+    var renderFeature = 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);
     }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
+    replayGroup.finish(context);
 
+    this.renderedResolution_ = resolution;
+    this.renderedRevision_ = vectorLayerRevision;
+    this.renderedRenderOrder_ = vectorLayerRenderOrder;
+    this.renderedExtent_ = extent;
+    this.replayGroup_ = replayGroup;
 
-/**
- * @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 true;
+  };
 
 
-/**
- * @return {!Array.<number>} A point.
- * @private
- */
-ol.format.WKT.Parser.prototype.parsePoint_ = function() {
-  var coordinates = [];
-  for (var i = 0; i < this.dimension_; ++i) {
-    var token = this.token_;
-    if (this.match(ol.format.WKT.TokenType.NUMBER)) {
-      coordinates.push(token.value);
+  /**
+   * @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 (Array.isArray(styles)) {
+      for (var i = styles.length - 1, ii = 0; i >= ii; --i) {
+        loading = ol.renderer.vector.renderFeature(
+            replayGroup, feature, styles[i],
+            ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
+            this.handleStyleImageChange_, this) || loading;
+      }
     } else {
-      break;
+      loading = ol.renderer.vector.renderFeature(
+          replayGroup, feature, styles,
+          ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
+          this.handleStyleImageChange_, this) || loading;
     }
-  }
-  if (coordinates.length == this.dimension_) {
-    return coordinates;
-  }
-  throw new Error(this.formatErrorMessage_());
-};
+    return loading;
+  };
 
+}
 
-/**
- * @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;
-};
+goog.provide('ol.layer.Vector');
+
+goog.require('ol');
+goog.require('ol.layer.Layer');
+goog.require('ol.obj');
+goog.require('ol.renderer.Type');
+goog.require('ol.renderer.canvas.VectorLayer');
+goog.require('ol.renderer.webgl.VectorLayer');
+goog.require('ol.style.Style');
 
 
 /**
- * @return {!Array.<!Array.<number>>} An array of points.
- * @private
+ * @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
  */
-ol.format.WKT.Parser.prototype.parsePointTextList_ = function() {
-  var coordinates = [this.parsePointText_()];
-  while (this.match(ol.format.WKT.TokenType.COMMA)) {
-    coordinates.push(this.parsePointText_());
-  }
-  return coordinates;
-};
+ol.layer.Vector = function(opt_options) {
+  var options = opt_options ?
+     opt_options : /** @type {olx.layer.VectorOptions} */ ({});
 
+  var baseOptions = ol.obj.assign({}, options);
 
-/**
- * @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;
-};
+  delete baseOptions.style;
+  delete baseOptions.renderBuffer;
+  delete baseOptions.updateWhileAnimating;
+  delete baseOptions.updateWhileInteracting;
+  ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));
 
+ /**
+  * @type {number}
+  * @private
+  */
+  this.renderBuffer_ = options.renderBuffer !== undefined ?
+     options.renderBuffer : 100;
 
-/**
- * @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;
-};
+ /**
+  * User provided style.
+  * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
+  * @private
+  */
+  this.style_ = null;
 
+ /**
+  * Style function for use within the library.
+  * @type {ol.StyleFunction|undefined}
+  * @private
+  */
+  this.styleFunction_ = undefined;
 
-/**
- * @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;
-};
+  this.setStyle(options.style);
 
+ /**
+  * @type {boolean}
+  * @private
+  */
+  this.updateWhileAnimating_ = options.updateWhileAnimating !== undefined ?
+     options.updateWhileAnimating : false;
 
-/**
- * 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 + '`';
+ /**
+  * @type {boolean}
+  * @private
+  */
+  this.updateWhileInteracting_ = options.updateWhileInteracting !== undefined ?
+     options.updateWhileInteracting : false;
 };
+ol.inherits(ol.layer.Vector, ol.layer.Layer);
 
 
 /**
- * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout.<string>=)}
- * @private
+ * @inheritDoc
  */
-ol.format.WKT.Parser.GeometryConstructor_ = {
-  'POINT': ol.geom.Point,
-  'LINESTRING': ol.geom.LineString,
-  'POLYGON': ol.geom.Polygon,
-  'MULTIPOINT': ol.geom.MultiPoint,
-  'MULTILINESTRING': ol.geom.MultiLineString,
-  'MULTIPOLYGON': ol.geom.MultiPolygon
+ol.layer.Vector.prototype.createRenderer = function(mapRenderer) {
+  var renderer = null;
+  var type = mapRenderer.getType();
+  if (ol.ENABLE_CANVAS && type === ol.renderer.Type.CANVAS) {
+    renderer = new ol.renderer.canvas.VectorLayer(this);
+  } else if (ol.ENABLE_WEBGL && type === ol.renderer.Type.WEBGL) {
+    renderer = new ol.renderer.webgl.VectorLayer(/** @type {ol.renderer.webgl.Map} */ (mapRenderer), this);
+  }
+  return renderer;
 };
 
 
 /**
- * @enum {(function(): Array)}
- * @private
+ * @return {number|undefined} Render buffer.
  */
-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_
+ol.layer.Vector.prototype.getRenderBuffer = function() {
+  return this.renderBuffer_;
 };
 
-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
+ * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render
+ *     order.
  */
-ol.format.WMSCapabilities = function() {
-
-  goog.base(this);
-
-  /**
-   * @type {string|undefined}
-   */
-  this.version = undefined;
+ol.layer.Vector.prototype.getRenderOrder = function() {
+  return /** @type {ol.RenderOrderFunction|null|undefined} */ (
+      this.get(ol.layer.Vector.Property_.RENDER_ORDER));
 };
-goog.inherits(ol.format.WMSCapabilities, ol.format.XML);
 
 
 /**
- * Read a WMS capabilities document.
- *
+ * Return the associated {@link ol.source.Vector vectorsource} of the layer.
  * @function
- * @param {Document|Node|string} source The XML source.
- * @return {Object} An object representing the WMS capabilities.
+ * @return {ol.source.Vector} Source.
  * @api
  */
-ol.format.WMSCapabilities.prototype.read;
+ol.layer.Vector.prototype.getSource;
 
 
 /**
- * @param {Document} doc Document.
- * @return {Object} WMS Capability object.
+ * 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.StyleFunction}
+ *     Layer style.
+ * @api
  */
-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;
+ol.layer.Vector.prototype.getStyle = function() {
+  return this.style_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @return {Object} WMS Capability object.
+ * Get the style function.
+ * @return {ol.StyleFunction|undefined} Layer style function.
+ * @api
  */
-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;
+ol.layer.Vector.prototype.getStyleFunction = function() {
+  return this.styleFunction_;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Attribution object.
+ * @return {boolean} Whether the rendered layer should be updated while
+ *     animating.
  */
-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);
+ol.layer.Vector.prototype.getUpdateWhileAnimating = function() {
+  return this.updateWhileAnimating_;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object} Bounding box object.
+ * @return {boolean} Whether the rendered layer should be updated while
+ *     interacting.
  */
-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
-  };
+ol.layer.Vector.prototype.getUpdateWhileInteracting = function() {
+  return this.updateWhileInteracting_;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.Extent|undefined} Bounding box object.
+ * @param {ol.RenderOrderFunction|null|undefined} renderOrder
+ *     Render order.
  */
-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
-  ]);
+ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) {
+  this.set(ol.layer.Vector.Property_.RENDER_ORDER, renderOrder);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Capability object.
+ * 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.StyleFunction|null|undefined}
+ *     style Layer style.
+ * @api
  */
-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);
+ol.layer.Vector.prototype.setStyle = function(style) {
+  this.style_ = style !== undefined ? style : ol.style.Style.defaultFunction;
+  this.styleFunction_ = style === null ?
+      undefined : ol.style.Style.createFunction(this.style_);
+  this.changed();
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @enum {string}
  * @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);
+ol.layer.Vector.Property_ = {
+  RENDER_ORDER: 'renderOrder'
 };
 
-
-/**
- * @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);
-};
+goog.provide('ol.loadingstrategy');
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Contact person object.
+ * 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.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);
+ol.loadingstrategy.all = function(extent, resolution) {
+  return [[-Infinity, -Infinity, Infinity, Infinity]];
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Contact address object.
+ * 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.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);
+ol.loadingstrategy.bbox = function(extent, resolution) {
+  return [extent];
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<string>|undefined} Format array.
+ * 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.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);
+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.source.Source');
 
-/**
- * @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);
-};
+goog.require('ol');
+goog.require('ol.Attribution');
+goog.require('ol.Object');
+goog.require('ol.proj');
+goog.require('ol.source.State');
 
 
 /**
- * @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));
+ * @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
+ * @abstract
+ * @extends {ol.Object}
+ * @param {ol.SourceSourceOptions} options Source options.
+ * @api
+ */
+ol.source.Source = function(options) {
 
-  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;
+  ol.Object.call(this);
 
-  var cascaded = ol.format.XSD.readNonNegativeIntegerString(
-      node.getAttribute('cascaded'));
-  if (cascaded === undefined) {
-    cascaded = parentLayerObject['cascaded'];
-  }
-  layerObject['cascaded'] = cascaded;
+  /**
+   * @private
+   * @type {ol.proj.Projection}
+   */
+  this.projection_ = ol.proj.get(options.projection);
 
-  var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque'));
-  if (opaque === undefined) {
-    opaque = parentLayerObject['opaque'];
-  }
-  layerObject['opaque'] = opaque !== undefined ? opaque : false;
+  /**
+   * @private
+   * @type {Array.<ol.Attribution>}
+   */
+  this.attributions_ = ol.source.Source.toAttributionsArray_(options.attributions);
 
-  var noSubsets =
-      ol.format.XSD.readBooleanString(node.getAttribute('noSubsets'));
-  if (noSubsets === undefined) {
-    noSubsets = parentLayerObject['noSubsets'];
-  }
-  layerObject['noSubsets'] = noSubsets !== undefined ? noSubsets : false;
+  /**
+   * @private
+   * @type {string|olx.LogoOptions|undefined}
+   */
+  this.logo_ = options.logo;
 
-  var fixedWidth =
-      ol.format.XSD.readDecimalString(node.getAttribute('fixedWidth'));
-  if (!fixedWidth) {
-    fixedWidth = parentLayerObject['fixedWidth'];
-  }
-  layerObject['fixedWidth'] = fixedWidth;
+  /**
+   * @private
+   * @type {ol.source.State}
+   */
+  this.state_ = options.state !== undefined ?
+      options.state : ol.source.State.READY;
 
-  var fixedHeight =
-      ol.format.XSD.readDecimalString(node.getAttribute('fixedHeight'));
-  if (!fixedHeight) {
-    fixedHeight = parentLayerObject['fixedHeight'];
-  }
-  layerObject['fixedHeight'] = fixedHeight;
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.wrapX_ = options.wrapX !== undefined ? options.wrapX : false;
 
-  // 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;
-    }
-  });
+};
+ol.inherits(ol.source.Source, ol.Object);
 
-  var replaceKeys = ['EX_GeographicBoundingBox', 'BoundingBox', 'Dimension',
-    'Attribution', 'MinScaleDenominator', 'MaxScaleDenominator'];
-  replaceKeys.forEach(function(key) {
-    if (!(key in layerObject)) {
-      var parentValue = parentLayerObject[key];
-      layerObject[key] = parentValue;
+/**
+ * Turns various ways of defining an attribution to an array of `ol.Attributions`.
+ *
+ * @param {ol.AttributionLike|undefined}
+ *     attributionLike The attributions as string, array of strings,
+ *     `ol.Attribution`, array of `ol.Attribution` or undefined.
+ * @return {Array.<ol.Attribution>} The array of `ol.Attribution` or null if
+ *     `undefined` was given.
+ */
+ol.source.Source.toAttributionsArray_ = function(attributionLike) {
+  if (typeof attributionLike === 'string') {
+    return [new ol.Attribution({html: attributionLike})];
+  } else if (attributionLike instanceof ol.Attribution) {
+    return [attributionLike];
+  } else if (Array.isArray(attributionLike)) {
+    var len = attributionLike.length;
+    var attributions = new Array(len);
+    for (var i = 0; i < len; i++) {
+      var item = attributionLike[i];
+      if (typeof item === 'string') {
+        attributions[i] = new ol.Attribution({html: item});
+      } else {
+        attributions[i] = item;
+      }
     }
-  });
-
-  return layerObject;
+    return attributions;
+  } else {
+    return null;
+  }
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object} Dimension object.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} resolution Resolution.
+ * @param {number} rotation Rotation.
+ * @param {number} hitTolerance Hit tolerance in pixels.
+ * @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.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;
-};
+ol.source.Source.prototype.forEachFeatureAtCoordinate = ol.nullFunction;
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Online resource object.
+ * Get the attributions of the source.
+ * @return {Array.<ol.Attribution>} Attributions.
+ * @api
  */
-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);
+ol.source.Source.prototype.getAttributions = function() {
+  return this.attributions_;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Request object.
+ * Get the logo of the source.
+ * @return {string|olx.LogoOptions|undefined} Logo.
+ * @api
  */
-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);
+ol.source.Source.prototype.getLogo = function() {
+  return this.logo_;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} DCP type object.
+ * Get the projection of the source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
  */
-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);
+ol.source.Source.prototype.getProjection = function() {
+  return this.projection_;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} HTTP object.
+ * @abstract
+ * @return {Array.<number>|undefined} Resolutions.
  */
-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);
-};
+ol.source.Source.prototype.getResolutions = function() {};
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Operation type object.
+ * Get the state of the source, see {@link ol.source.State} for possible states.
+ * @return {ol.source.State} State.
+ * @api
  */
-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);
+ol.source.Source.prototype.getState = function() {
+  return this.state_;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Online resource object.
+ * @return {boolean|undefined} Wrap X.
  */
-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;
+ol.source.Source.prototype.getWrapX = function() {
+  return this.wrapX_;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Authority URL object.
+ * Refreshes the source and finally dispatches a 'change' event.
+ * @api
  */
-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;
+ol.source.Source.prototype.refresh = function() {
+  this.changed();
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Metadata URL object.
+ * Set the attributions of the source.
+ * @param {ol.AttributionLike|undefined} attributions Attributions.
+ *     Can be passed as `string`, `Array<string>`, `{@link ol.Attribution}`,
+ *     `Array<{@link ol.Attribution}>` or `undefined`.
+ * @api
  */
-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;
+ol.source.Source.prototype.setAttributions = function(attributions) {
+  this.attributions_ = ol.source.Source.toAttributionsArray_(attributions);
+  this.changed();
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Style object.
+ * Set the logo of the source.
+ * @param {string|olx.LogoOptions|undefined} logo Logo.
  */
-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);
+ol.source.Source.prototype.setLogo = function(logo) {
+  this.logo_ = logo;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<string>|undefined} Keyword list.
+ * Set the state of the source.
+ * @param {ol.source.State} state State.
+ * @protected
  */
-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);
+ol.source.Source.prototype.setState = function(state) {
+  this.state_ = state;
+  this.changed();
 };
 
+goog.provide('ol.source.VectorEventType');
 
 /**
- * @const
- * @private
- * @type {Array.<string>}
+ * @enum {string}
  */
-ol.format.WMSCapabilities.NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/wms'
-];
+ol.source.VectorEventType = {
+  /**
+   * Triggered when a feature is added to the source.
+   * @event ol.source.Vector.Event#addfeature
+   * @api
+   */
+  ADDFEATURE: 'addfeature',
 
+  /**
+   * Triggered when a feature is updated.
+   * @event ol.source.Vector.Event#changefeature
+   * @api
+   */
+  CHANGEFEATURE: 'changefeature',
 
-/**
- * @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_)
-    });
+  /**
+   * Triggered when the clear method is called on the source.
+   * @event ol.source.Vector.Event#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.Vector.Event#removefeature
+   * @api
+   */
+  REMOVEFEATURE: 'removefeature'
+};
 
-/**
- * @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_)
-    });
+// FIXME bulk feature upload - suppress events
+// FIXME make change-detection more refined (notably, geometry hint)
 
+goog.provide('ol.source.Vector');
 
-/**
- * @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)
-    });
+goog.require('ol');
+goog.require('ol.Collection');
+goog.require('ol.CollectionEventType');
+goog.require('ol.ObjectEventType');
+goog.require('ol.array');
+goog.require('ol.asserts');
+goog.require('ol.events');
+goog.require('ol.events.Event');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
+goog.require('ol.featureloader');
+goog.require('ol.functions');
+goog.require('ol.loadingstrategy');
+goog.require('ol.obj');
+goog.require('ol.source.Source');
+goog.require('ol.source.State');
+goog.require('ol.source.VectorEventType');
+goog.require('ol.structs.RBush');
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @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.Vector.Event
+ * @param {olx.source.VectorOptions=} opt_options Vector source options.
+ * @api
  */
-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)
-    });
+ol.source.Vector = function(opt_options) {
 
+  var options = opt_options || {};
 
-/**
- * @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)
-    });
+  ol.source.Source.call(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;
 
-/**
- * @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)
-    });
+  /**
+   * @private
+   * @type {ol.format.Feature|undefined}
+   */
+  this.format_ = options.format;
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.overlaps_ = options.overlaps == undefined ? true : options.overlaps;
 
-/**
- * @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)
-    });
+  /**
+   * @private
+   * @type {string|ol.FeatureUrlFunction|undefined}
+   */
+  this.url_ = options.url;
 
+  if (options.loader !== undefined) {
+    this.loader_ = options.loader;
+  } else if (this.url_ !== undefined) {
+    ol.asserts.assert(this.format_, 7); // `format` must be set when `url` is set
+    // create a XHR feature loader for "url" and "format"
+    this.loader_ = ol.featureloader.xhr(this.url_, /** @type {ol.format.Feature} */ (this.format_));
+  }
 
-/**
- * @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_)
-    });
+  /**
+   * @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 ol.getUid(feature)).
+   * @private
+   * @type {Object.<string, ol.Feature>}
+   */
+  this.undefIdIndex_ = {};
+
+  /**
+   * @private
+   * @type {Object.<string, Array.<ol.EventsKey>>}
+   */
+  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 (Array.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);
+  }
+
+};
+ol.inherits(ol.source.Vector, ol.source.Source);
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * 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. A feature will not be added to the source if feature with
+ * the same id is already there. The reason for this behavior is to avoid
+ * feature duplication when using bbox or tile loading strategies.
+ * @param {ol.Feature} feature Feature to add.
+ * @api
  */
-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_)
-    });
+ol.source.Vector.prototype.addFeature = function(feature) {
+  this.addFeatureInternal(feature);
+  this.changed();
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Add a feature without firing a `change` event.
+ * @param {ol.Feature} feature Feature.
+ * @protected
  */
-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)
-    });
+ol.source.Vector.prototype.addFeatureInternal = function(feature) {
+  var featureKey = ol.getUid(feature).toString();
 
+  if (!this.addToIndex_(featureKey, feature)) {
+    return;
+  }
 
-/**
- * @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_)
-    });
+  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;
+  }
 
-/**
- * @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_)
-    });
+  this.dispatchEvent(
+      new ol.source.Vector.Event(ol.source.VectorEventType.ADDFEATURE, feature));
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {string} featureKey Unique identifier for the feature.
+ * @param {ol.Feature} feature The feature.
  * @private
  */
-ol.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'HTTP': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readHTTP_)
-    });
+ol.source.Vector.prototype.setupChangeEvents_ = function(featureKey, feature) {
+  this.featureChangeKeys_[featureKey] = [
+    ol.events.listen(feature, ol.events.EventType.CHANGE,
+        this.handleFeatureChange_, this),
+    ol.events.listen(feature, ol.ObjectEventType.PROPERTYCHANGE,
+        this.handleFeatureChange_, this)
+  ];
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {string} featureKey Unique identifier for the feature.
+ * @param {ol.Feature} feature The feature.
+ * @return {boolean} The feature is "valid", in the sense that it is also a
+ *     candidate for insertion into the Rtree.
  * @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_)
-    });
+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 {
+    ol.asserts.assert(!(featureKey in this.undefIdIndex_),
+        30); // The passed `feature` was already added to the source
+    this.undefIdIndex_[featureKey] = feature;
+  }
+  return valid;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Add a batch of features to the source.
+ * @param {Array.<ol.Feature>} features Features to add.
+ * @api
  */
-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_)
-    });
+ol.source.Vector.prototype.addFeatures = function(features) {
+  this.addFeaturesInternal(features);
+  this.changed();
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Add features without firing a `change` event.
+ * @param {Array.<ol.Feature>} features Features.
+ * @protected
  */
-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)
-    });
+ol.source.Vector.prototype.addFeaturesInternal = function(features) {
+  var featureKey, i, length, feature;
 
+  var extents = [];
+  var newFeatures = [];
+  var geometryFeatures = [];
 
-/**
- * @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)
-    });
+  for (i = 0, length = features.length; i < length; i++) {
+    feature = features[i];
+    featureKey = ol.getUid(feature).toString();
+    if (this.addToIndex_(featureKey, feature)) {
+      newFeatures.push(feature);
+    }
+  }
 
-goog.provide('ol.format.WMSGetFeatureInfo');
+  for (i = 0, length = newFeatures.length; i < length; i++) {
+    feature = newFeatures[i];
+    featureKey = ol.getUid(feature).toString();
+    this.setupChangeEvents_(featureKey, feature);
 
-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');
+    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.Vector.Event(
+        ol.source.VectorEventType.ADDFEATURE, newFeatures[i]));
+  }
+};
 
 
 /**
- * @classdesc
- * Format for reading WMSGetFeatureInfo format. It uses
- * {@link ol.format.GML2} to read features.
- *
- * @constructor
- * @extends {ol.format.XMLFeature}
- * @api
+ * @param {!ol.Collection.<ol.Feature>} collection Collection.
+ * @private
  */
-ol.format.WMSGetFeatureInfo = function() {
+ol.source.Vector.prototype.bindFeaturesCollection_ = function(collection) {
+  var modifyingCollection = false;
+  ol.events.listen(this, ol.source.VectorEventType.ADDFEATURE,
+      function(evt) {
+        if (!modifyingCollection) {
+          modifyingCollection = true;
+          collection.push(evt.feature);
+          modifyingCollection = false;
+        }
+      });
+  ol.events.listen(this, ol.source.VectorEventType.REMOVEFEATURE,
+      function(evt) {
+        if (!modifyingCollection) {
+          modifyingCollection = true;
+          collection.remove(evt.feature);
+          modifyingCollection = false;
+        }
+      });
+  ol.events.listen(collection, ol.CollectionEventType.ADD,
+      function(evt) {
+        if (!modifyingCollection) {
+          modifyingCollection = true;
+          this.addFeature(/** @type {ol.Feature} */ (evt.element));
+          modifyingCollection = false;
+        }
+      }, this);
+  ol.events.listen(collection, ol.CollectionEventType.REMOVE,
+      function(evt) {
+        if (!modifyingCollection) {
+          modifyingCollection = true;
+          this.removeFeature(/** @type {ol.Feature} */ (evt.element));
+          modifyingCollection = false;
+        }
+      }, this);
+  this.featuresCollection_ = collection;
+};
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.featureNS_ = 'http://mapserver.gis.umn.edu/mapserver';
 
+/**
+ * Remove all features from the source.
+ * @param {boolean=} opt_fast Skip dispatching of {@link removefeature} events.
+ * @api
+ */
+ol.source.Vector.prototype.clear = function(opt_fast) {
+  if (opt_fast) {
+    for (var featureId in this.featureChangeKeys_) {
+      var keys = this.featureChangeKeys_[featureId];
+      keys.forEach(ol.events.unlistenByKey);
+    }
+    if (!this.featuresCollection_) {
+      this.featureChangeKeys_ = {};
+      this.idIndex_ = {};
+      this.undefIdIndex_ = {};
+    }
+  } else {
+    if (this.featuresRtree_) {
+      this.featuresRtree_.forEach(this.removeFeatureInternal, this);
+      for (var id in this.nullGeometryFeatures_) {
+        this.removeFeatureInternal(this.nullGeometryFeatures_[id]);
+      }
+    }
+  }
+  if (this.featuresCollection_) {
+    this.featuresCollection_.clear();
+  }
 
-  /**
-   * @private
-   * @type {ol.format.GML2}
-   */
-  this.gmlFormat_ = new ol.format.GML2();
+  if (this.featuresRtree_) {
+    this.featuresRtree_.clear();
+  }
+  this.loadedExtentsRtree_.clear();
+  this.nullGeometryFeatures_ = {};
 
-  goog.base(this);
+  var clearEvent = new ol.source.Vector.Event(ol.source.VectorEventType.CLEAR);
+  this.dispatchEvent(clearEvent);
+  this.changed();
 };
-goog.inherits(ol.format.WMSGetFeatureInfo, ol.format.XMLFeature);
 
 
 /**
- * @const
- * @type {string}
- * @private
+ * 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
  */
-ol.format.WMSGetFeatureInfo.featureIdentifier_ = '_feature';
+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);
+  }
+};
 
 
 /**
- * @const
- * @type {string}
- * @private
+ * 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.format.WMSGetFeatureInfo.layerIdentifier_ = '_layer';
+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();
+    if (geometry.intersectsCoordinate(coordinate)) {
+      return callback.call(opt_this, feature);
+    } else {
+      return undefined;
+    }
+  });
+};
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<ol.Feature>} Features.
- * @private
+ * 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.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;
-    }
+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);
   }
-  return features;
 };
 
 
 /**
- * Read all features from a WMSGetFeatureInfo response.
+ * 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.
  *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
+ * 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.format.WMSGetFeatureInfo.prototype.readFeatures;
+ol.source.Vector.prototype.forEachFeatureIntersectingExtent = function(extent, callback, opt_this) {
+  return this.forEachFeatureInExtent(extent,
+      /**
+       * @param {ol.Feature} feature Feature.
+       * @return {S|undefined} The return value from the last call to the callback.
+       * @template S
+       */
+      function(feature) {
+        var geometry = feature.getGeometry();
+        if (geometry.intersectsExtent(extent)) {
+          var result = callback.call(opt_this, feature);
+          if (result) {
+            return result;
+          }
+        }
+      });
+};
 
 
 /**
- * @inheritDoc
+ * 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>} The collection of features.
+ * @api
  */
-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]);
+ol.source.Vector.prototype.getFeaturesCollection = function() {
+  return this.featuresCollection_;
 };
 
-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');
 
+/**
+ * Get all features on the source in random order.
+ * @return {Array.<ol.Feature>} Features.
+ * @api
+ */
+ol.source.Vector.prototype.getFeatures = function() {
+  var features;
+  if (this.featuresCollection_) {
+    features = this.featuresCollection_.getArray();
+  } else if (this.featuresRtree_) {
+    features = this.featuresRtree_.getAll();
+    if (!ol.obj.isEmpty(this.nullGeometryFeatures_)) {
+      ol.array.extend(
+          features, ol.obj.getValues(this.nullGeometryFeatures_));
+    }
+  }
+  return /** @type {Array.<ol.Feature>} */ (features);
+};
 
 
 /**
- * @classdesc
- * Format for reading WMTS capabilities data.
- *
- * @constructor
- * @extends {ol.format.XML}
+ * Get all features whose geometry intersects the provided coordinate.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {Array.<ol.Feature>} Features.
  * @api
  */
-ol.format.WMTSCapabilities = function() {
-  goog.base(this);
-
-  /**
-   * @type {ol.format.OWS}
-   * @private
-   */
-  this.owsParser_ = new ol.format.OWS();
+ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) {
+  var features = [];
+  this.forEachFeatureAtCoordinateDirect(coordinate, function(feature) {
+    features.push(feature);
+  });
+  return features;
 };
-goog.inherits(ol.format.WMTSCapabilities, ol.format.XML);
 
 
 /**
- * Read a WMTS capabilities document.
+ * Get all features in the provided extent.  Note that this returns an array of
+ * all features intersecting the given extent in random order (so it may include
+ * features whose geometries do not intersect the extent).
  *
- * @function
- * @param {Document|Node|string} source The XML source.
- * @return {Object} An object representing the WMTS capabilities.
+ * 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.format.WMTSCapabilities.prototype.read;
+ol.source.Vector.prototype.getFeaturesInExtent = function(extent) {
+  return this.featuresRtree_.getInExtent(extent);
+};
 
 
 /**
- * @param {Document} doc Document.
- * @return {Object} WMTS Capability object.
+ * 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.
+ * @param {function(ol.Feature):boolean=} opt_filter Feature filter function.
+ *     The filter function will receive one argument, the {@link ol.Feature feature}
+ *     and it should return a boolean value. By default, no filtering is made.
+ * @return {ol.Feature} Closest feature.
+ * @api
  */
-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;
+ol.source.Vector.prototype.getClosestFeatureToCoordinate = function(coordinate, opt_filter) {
+  // 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];
+  var filter = opt_filter ? opt_filter : ol.functions.TRUE;
+  this.featuresRtree_.forEachInExtent(extent,
+      /**
+       * @param {ol.Feature} feature Feature.
+       */
+      function(feature) {
+        if (filter(feature)) {
+          var geometry = feature.getGeometry();
+          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;
 };
 
 
 /**
- * @param {Node} node Node.
- * @return {Object} WMTS Capability object.
+ * 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`.
+ * @param {ol.Extent=} opt_extent Destination extent. If provided, no new extent
+ *     will be created. Instead, that extent's coordinates will be overwritten.
+ * @return {!ol.Extent} Extent.
+ * @api
  */
-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;
+ol.source.Vector.prototype.getExtent = function(opt_extent) {
+  return this.featuresRtree_.getExtent(opt_extent);
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Attribution object.
+ * 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
  */
-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);
+ol.source.Vector.prototype.getFeatureById = function(id) {
+  var feature = this.idIndex_[id.toString()];
+  return feature !== undefined ? feature : null;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Layers object.
+ * Get the format associated with this source.
+ *
+ * @return {ol.format.Feature|undefined} The feature format.
+ * @api
  */
-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);
+ol.source.Vector.prototype.getFormat = function() {
+  return this.format_;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Tile Matrix Set object.
+ * @return {boolean} The source can have overlapping geometries.
  */
-ol.format.WMTSCapabilities.readTileMatrixSet_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.TMS_PARSERS_, node, objectStack);
+ol.source.Vector.prototype.getOverlaps = function() {
+  return this.overlaps_;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Style object.
+ * @override
  */
-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;
-
-};
+ol.source.Vector.prototype.getResolutions = function() {};
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Tile Matrix Set Link object.
+ * Get the url associated with this source.
+ *
+ * @return {string|ol.FeatureUrlFunction|undefined} The url.
+ * @api
  */
-ol.format.WMTSCapabilities.readTileMatrixSetLink_ = function(node,
-    objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_, node, objectStack);
+ol.source.Vector.prototype.getUrl = function() {
+  return this.url_;
 };
 
 
 /**
+ * @param {ol.events.Event} event Event.
  * @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;
+ol.source.Vector.prototype.handleFeatureChange_ = function(event) {
+  var feature = /** @type {ol.Feature} */ (event.target);
+  var featureKey = ol.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);
+      }
+    }
   }
-  if (resourceType) {
-    resource['resourceType'] = resourceType;
+  var id = feature.getId();
+  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) {
+        this.removeFromIdIndex_(feature);
+        this.idIndex_[sid] = feature;
+      }
+    }
+  } else {
+    if (!(featureKey in this.undefIdIndex_)) {
+      this.removeFromIdIndex_(feature);
+      this.undefIdIndex_[featureKey] = feature;
+    }
   }
-  return resource;
+  this.changed();
+  this.dispatchEvent(new ol.source.Vector.Event(
+      ol.source.VectorEventType.CHANGEFEATURE, feature));
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} WGS84 BBox object.
+ * @return {boolean} Is empty.
  */
-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);
+ol.source.Vector.prototype.isEmpty = function() {
+  return this.featuresRtree_.isEmpty() &&
+      ol.obj.isEmpty(this.nullGeometryFeatures_);
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Legend object.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} resolution Resolution.
+ * @param {ol.proj.Projection} projection Projection.
  */
-ol.format.WMTSCapabilities.readLegendUrl_ = function(node, objectStack) {
-  var legend = {};
-  legend['format'] = node.getAttribute('format');
-  legend['href'] = ol.format.XLink.readHref(node);
-  return legend;
+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()});
+    }
+  }
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Coordinates object.
+ * 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
  */
-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;
+ol.source.Vector.prototype.removeFeature = function(feature) {
+  var featureKey = ol.getUid(feature).toString();
+  if (featureKey in this.nullGeometryFeatures_) {
+    delete this.nullGeometryFeatures_[featureKey];
+  } else {
+    if (this.featuresRtree_) {
+      this.featuresRtree_.remove(feature);
+    }
   }
-  return [x, y];
+  this.removeFeatureInternal(feature);
+  this.changed();
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} TileMatrix object.
+ * Remove feature without firing a `change` event.
+ * @param {ol.Feature} feature Feature.
+ * @protected
  */
-ol.format.WMTSCapabilities.readTileMatrix_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.TM_PARSERS_, node, objectStack);
+ol.source.Vector.prototype.removeFeatureInternal = function(feature) {
+  var featureKey = ol.getUid(feature).toString();
+  this.featureChangeKeys_[featureKey].forEach(ol.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.Vector.Event(
+      ol.source.VectorEventType.REMOVEFEATURE, feature));
 };
 
 
 /**
- * @const
+ * 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
- * @type {Array.<string>}
  */
-ol.format.WMTSCapabilities.NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/wmts/1.0'
-];
+ol.source.Vector.prototype.removeFromIdIndex_ = function(feature) {
+  var removed = false;
+  for (var id in this.idIndex_) {
+    if (this.idIndex_[id] === feature) {
+      delete this.idIndex_[id];
+      removed = true;
+      break;
+    }
+  }
+  return removed;
+};
 
 
 /**
- * @const
- * @private
- * @type {Array.<string>}
+ * @classdesc
+ * Events emitted by {@link ol.source.Vector} instances are instances of this
+ * type.
+ *
+ * @constructor
+ * @extends {ol.events.Event}
+ * @implements {oli.source.Vector.Event}
+ * @param {string} type Type.
+ * @param {ol.Feature=} opt_feature Feature.
  */
-ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/ows/1.1'
-];
-
+ol.source.Vector.Event = function(type, opt_feature) {
 
-/**
- * @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_)
-    });
+  ol.events.Event.call(this, type);
 
+  /**
+   * The feature being added or removed.
+   * @type {ol.Feature|undefined}
+   * @api
+   */
+  this.feature = opt_feature;
 
-/**
- * @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_)
-    });
+};
+ol.inherits(ol.source.Vector.Event, ol.events.Event);
 
+goog.provide('ol.interaction.Draw');
 
-/**
- * @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_),
-      '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)
-    }));
+goog.require('ol');
+goog.require('ol.Feature');
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.Object');
+goog.require('ol.coordinate');
+goog.require('ol.events');
+goog.require('ol.events.Event');
+goog.require('ol.events.condition');
+goog.require('ol.extent');
+goog.require('ol.functions');
+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.interaction.DrawEventType');
+goog.require('ol.interaction.Pointer');
+goog.require('ol.interaction.Property');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Style');
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @classdesc
+ * Interaction for drawing feature geometries.
+ *
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @fires ol.interaction.Draw.Event
+ * @param {olx.interaction.DrawOptions} options Options.
+ * @api
  */
-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)
-    }));
+ol.interaction.Draw = function(options) {
 
+  ol.interaction.Pointer.call(this, {
+    handleDownEvent: ol.interaction.Draw.handleDownEvent_,
+    handleEvent: ol.interaction.Draw.handleEvent,
+    handleUpEvent: ol.interaction.Draw.handleUpEvent_
+  });
 
-/**
- * @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)
-    });
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.shouldHandle_ = false;
 
+  /**
+   * @type {ol.Pixel}
+   * @private
+   */
+  this.downPx_ = null;
 
-/**
- * @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_)
-    });
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.freehand_ = false;
 
+  /**
+   * Target source for drawn features.
+   * @type {ol.source.Vector}
+   * @private
+   */
+  this.source_ = options.source ? options.source : null;
 
-/**
- * @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)
-    }));
+  /**
+   * 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;
 
-/**
- * @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)
-    }));
+  /**
+   * Geometry type.
+   * @type {ol.geom.GeometryType}
+   * @private
+   */
+  this.type_ = options.type;
 
-goog.provide('ol.sphere.WGS84');
+  /**
+   * Drawing mode (derived from geometry type.
+   * @type {ol.interaction.Draw.Mode_}
+   * @private
+   */
+  this.mode_ = ol.interaction.Draw.getMode_(this.type_);
 
-goog.require('ol.Sphere');
+  /**
+   * 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.Draw.Mode_.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;
 
-/**
- * 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);
+  /**
+   * A function to decide if a potential finish coordinate is permissible
+   * @private
+   * @type {ol.EventsConditionType}
+   */
+  this.finishCondition_ = options.finishCondition ? options.finishCondition : ol.functions.TRUE;
 
-// FIXME handle geolocation not supported
+  var geometryFunction = options.geometryFunction;
+  if (!geometryFunction) {
+    if (this.type_ === ol.geom.GeometryType.CIRCLE) {
+      /**
+       * @param {!Array.<ol.Coordinate>} coordinates
+       *     The coordinates.
+       * @param {ol.geom.SimpleGeometry=} opt_geometry Optional geometry.
+       * @return {ol.geom.SimpleGeometry} A geometry.
+       */
+      geometryFunction = function(coordinates, opt_geometry) {
+        var circle = opt_geometry ? /** @type {ol.geom.Circle} */ (opt_geometry) :
+            new ol.geom.Circle([NaN, NaN]);
+        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.Draw.Mode_.POINT) {
+        Constructor = ol.geom.Point;
+      } else if (mode === ol.interaction.Draw.Mode_.LINE_STRING) {
+        Constructor = ol.geom.LineString;
+      } else if (mode === ol.interaction.Draw.Mode_.POLYGON) {
+        Constructor = ol.geom.Polygon;
+      }
+      /**
+       * @param {!Array.<ol.Coordinate>} coordinates
+       *     The coordinates.
+       * @param {ol.geom.SimpleGeometry=} opt_geometry Optional geometry.
+       * @return {ol.geom.SimpleGeometry} A geometry.
+       */
+      geometryFunction = function(coordinates, opt_geometry) {
+        var geometry = opt_geometry;
+        if (geometry) {
+          if (mode === ol.interaction.Draw.Mode_.POLYGON) {
+            geometry.setCoordinates([coordinates[0].concat([coordinates[0][0]])]);
+          } else {
+            geometry.setCoordinates(coordinates);
+          }
+        } else {
+          geometry = new Constructor(coordinates);
+        }
+        return geometry;
+      };
+    }
+  }
 
-goog.provide('ol.Geolocation');
-goog.provide('ol.GeolocationProperty');
+  /**
+   * @type {ol.DrawGeometryFunctionType}
+   * @private
+   */
+  this.geometryFunction_ = geometryFunction;
 
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.math');
-goog.require('ol.Coordinate');
-goog.require('ol.Object');
-goog.require('ol.geom.Geometry');
-goog.require('ol.geom.Polygon');
-goog.require('ol.has');
-goog.require('ol.proj');
-goog.require('ol.sphere.WGS84');
+  /**
+   * 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;
 
-/**
- * @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'
-};
+  /**
+   * 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;
 
-/**
- * @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) {
+  /**
+   * Sketch line coordinates. Used when drawing a polygon or circle.
+   * @type {Array.<ol.Coordinate>}
+   * @private
+   */
+  this.sketchLineCoords_ = null;
 
-  goog.base(this);
+  /**
+   * 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;
 
-  var options = opt_options || {};
+  /**
+   * 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()
+  });
 
   /**
-   * The unprojected (EPSG:4326) device position.
+   * Name of the geometry attribute for newly created features.
+   * @type {string|undefined}
    * @private
-   * @type {ol.Coordinate}
    */
-  this.position_ = null;
+  this.geometryName_ = options.geometryName;
 
   /**
    * @private
-   * @type {ol.TransformFunction}
+   * @type {ol.EventsConditionType}
    */
-  this.transform_ = ol.proj.identityTransform;
+  this.condition_ = options.condition ?
+      options.condition : ol.events.condition.noModifierKeys;
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {ol.EventsConditionType}
    */
-  this.watchId_ = undefined;
+  this.freehandCondition_;
+  if (options.freehand) {
+    this.freehandCondition_ = ol.events.condition.always;
+  } else {
+    this.freehandCondition_ = options.freehandCondition ?
+        options.freehandCondition : ol.events.condition.shiftKeyOnly;
+  }
 
-  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);
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.interaction.Property.ACTIVE),
+      this.updateState_, this);
 
-  if (options.projection !== undefined) {
-    this.setProjection(ol.proj.get(options.projection));
-  }
-  if (options.trackingOptions !== undefined) {
-    this.setTrackingOptions(options.trackingOptions);
-  }
+};
+ol.inherits(ol.interaction.Draw, ol.interaction.Pointer);
 
-  this.setTracking(options.tracking !== undefined ? options.tracking : false);
 
+/**
+ * @return {ol.StyleFunction} Styles.
+ */
+ol.interaction.Draw.getDefaultStyleFunction = function() {
+  var styles = ol.style.Style.createDefaultEditing();
+  return function(feature, resolution) {
+    return styles[feature.getGeometry().getType()];
+  };
 };
-goog.inherits(ol.Geolocation, ol.Object);
 
 
 /**
  * @inheritDoc
  */
-ol.Geolocation.prototype.disposeInternal = function() {
-  this.setTracking(false);
-  goog.base(this, 'disposeInternal');
+ol.interaction.Draw.prototype.setMap = function(map) {
+  ol.interaction.Pointer.prototype.setMap.call(this, map);
+  this.updateState_();
 };
 
 
 /**
- * @private
+ * Handles the {@link ol.MapBrowserEvent map browser event} and may actually
+ * draw or finish the drawing.
+ * @param {ol.MapBrowserEvent} event Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.Draw}
+ * @api
  */
-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_));
-    }
+ol.interaction.Draw.handleEvent = function(event) {
+  this.freehand_ = this.mode_ !== ol.interaction.Draw.Mode_.POINT && this.freehandCondition_(event);
+  var pass = !this.freehand_;
+  if (this.freehand_ &&
+      event.type === ol.MapBrowserEventType.POINTERDRAG && this.sketchFeature_ !== null) {
+    this.addToDrawing_(event);
+    pass = false;
+  } else if (event.type ===
+      ol.MapBrowserEventType.POINTERMOVE) {
+    pass = this.handlePointerMove_(event);
+  } else if (event.type === ol.MapBrowserEventType.DBLCLICK) {
+    pass = false;
   }
+  return ol.interaction.Pointer.handleEvent.call(this, event) && pass;
 };
 
 
 /**
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.Draw}
  * @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;
+ol.interaction.Draw.handleDownEvent_ = function(event) {
+  this.shouldHandle_ = !this.freehand_;
+
+  if (this.freehand_) {
+    this.downPx_ = event.pixel;
+    if (!this.finishCoordinate_) {
+      this.startDrawing_(event);
     }
+    return true;
+  } else if (this.condition_(event)) {
+    this.downPx_ = event.pixel;
+    return true;
+  } else {
+    return false;
   }
 };
 
 
 /**
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Draw}
  * @private
- * @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 : goog.math.toRadians(coords.heading));
-  if (!this.position_) {
-    this.position_ = [coords.longitude, coords.latitude];
-  } else {
-    this.position_[0] = coords.longitude;
-    this.position_[1] = coords.latitude;
+ol.interaction.Draw.handleUpEvent_ = function(event) {
+  var pass = true;
+
+  this.handlePointerMove_(event);
+
+  var circleMode = this.mode_ === ol.interaction.Draw.Mode_.CIRCLE;
+
+  if (this.shouldHandle_) {
+    if (!this.finishCoordinate_) {
+      this.startDrawing_(event);
+      if (this.mode_ === ol.interaction.Draw.Mode_.POINT) {
+        this.finishDrawing();
+      }
+    } else if (this.freehand_ || circleMode) {
+      this.finishDrawing();
+    } else if (this.atFinish_(event)) {
+      if (this.finishCondition_(event)) {
+        this.finishDrawing();
+      }
+    } else {
+      this.addToDrawing_(event);
+    }
+    pass = false;
+  } else if (this.freehand_) {
+    this.finishCoordinate_ = null;
+    this.abortDrawing_();
   }
-  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();
+  return pass;
 };
 
 
 /**
+ * Handle move events.
+ * @param {ol.MapBrowserEvent} event A move event.
+ * @return {boolean} Pass the event to other interactions.
  * @private
- * @param {GeolocationPositionError} error error object.
  */
-ol.Geolocation.prototype.positionError_ = function(error) {
-  error.type = goog.events.EventType.ERROR;
-  this.setTracking(false);
-  this.dispatchEvent(error);
+ol.interaction.Draw.prototype.handlePointerMove_ = function(event) {
+  if (this.downPx_ &&
+      ((!this.freehand_ && this.shouldHandle_) ||
+      (this.freehand_ && !this.shouldHandle_))) {
+    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;
+    this.shouldHandle_ = this.freehand_ ?
+        squaredDistance > this.squaredClickTolerance_ :
+        squaredDistance <= this.squaredClickTolerance_;
+  }
+
+  if (this.finishCoordinate_) {
+    this.modifyDrawing_(event);
+  } else {
+    this.createOrUpdateSketchPoint_(event);
+  }
+  return true;
 };
 
 
 /**
- * Get the accuracy of the position in meters.
- * @return {number|undefined} The accuracy of the position measurement in
- *     meters.
- * @observable
- * @api stable
+ * 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.Geolocation.prototype.getAccuracy = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.ACCURACY));
+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.Draw.Mode_.LINE_STRING) {
+      potentiallyDone = this.sketchCoords_.length > this.minPoints_;
+    } else if (this.mode_ === ol.interaction.Draw.Mode_.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 snapTolerance = this.freehand_ ? 1 : this.snapTolerance_;
+        at = Math.sqrt(dx * dx + dy * dy) <= snapTolerance;
+        if (at) {
+          this.finishCoordinate_ = finishCoordinate;
+          break;
+        }
+      }
+    }
+  }
+  return at;
 };
 
 
 /**
- * Get a geometry of the position accuracy.
- * @return {?ol.geom.Geometry} A geometry of the position accuracy.
- * @observable
- * @api stable
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
  */
-ol.Geolocation.prototype.getAccuracyGeometry = function() {
-  return /** @type {?ol.geom.Geometry} */ (
-      this.get(ol.GeolocationProperty.ACCURACY_GEOMETRY) || null);
+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 = /** @type {ol.geom.Point} */ (this.sketchPoint_.getGeometry());
+    sketchPointGeom.setCoordinates(coordinates);
+  }
 };
 
 
 /**
- * Get the altitude associated with the position.
- * @return {number|undefined} The altitude of the position in meters above mean
- *     sea level.
- * @observable
- * @api stable
+ * Start the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
  */
-ol.Geolocation.prototype.getAltitude = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.ALTITUDE));
+ol.interaction.Draw.prototype.startDrawing_ = function(event) {
+  var start = event.coordinate;
+  this.finishCoordinate_ = start;
+  if (this.mode_ === ol.interaction.Draw.Mode_.POINT) {
+    this.sketchCoords_ = start.slice();
+  } else if (this.mode_ === ol.interaction.Draw.Mode_.POLYGON) {
+    this.sketchCoords_ = [[start.slice(), start.slice()]];
+    this.sketchLineCoords_ = this.sketchCoords_[0];
+  } else {
+    this.sketchCoords_ = [start.slice(), start.slice()];
+    if (this.mode_ === ol.interaction.Draw.Mode_.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_);
+  this.sketchFeature_ = new ol.Feature();
+  if (this.geometryName_) {
+    this.sketchFeature_.setGeometryName(this.geometryName_);
+  }
+  this.sketchFeature_.setGeometry(geometry);
+  this.updateSketchFeatures_();
+  this.dispatchEvent(new ol.interaction.Draw.Event(
+      ol.interaction.DrawEventType.DRAWSTART, this.sketchFeature_));
 };
 
 
 /**
- * Get the altitude accuracy of the position.
- * @return {number|undefined} The accuracy of the altitude measurement in
- *     meters.
- * @observable
- * @api stable
+ * Modify the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
  */
-ol.Geolocation.prototype.getAltitudeAccuracy = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.ALTITUDE_ACCURACY));
+ol.interaction.Draw.prototype.modifyDrawing_ = function(event) {
+  var coordinate = event.coordinate;
+  var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry());
+  var coordinates, last;
+  if (this.mode_ === ol.interaction.Draw.Mode_.POINT) {
+    last = this.sketchCoords_;
+  } else if (this.mode_ === ol.interaction.Draw.Mode_.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];
+  this.geometryFunction_(/** @type {!Array.<ol.Coordinate>} */ (this.sketchCoords_), geometry);
+  if (this.sketchPoint_) {
+    var sketchPointGeom = /** @type {ol.geom.Point} */ (this.sketchPoint_.getGeometry());
+    sketchPointGeom.setCoordinates(coordinate);
+  }
+  var sketchLineGeom;
+  if (geometry instanceof ol.geom.Polygon &&
+      this.mode_ !== ol.interaction.Draw.Mode_.POLYGON) {
+    if (!this.sketchLine_) {
+      this.sketchLine_ = new ol.Feature(new ol.geom.LineString(null));
+    }
+    var ring = geometry.getLinearRing(0);
+    sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry());
+    sketchLineGeom.setFlatCoordinates(
+        ring.getLayout(), ring.getFlatCoordinates());
+  } else if (this.sketchLineCoords_) {
+    sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry());
+    sketchLineGeom.setCoordinates(this.sketchLineCoords_);
+  }
+  this.updateSketchFeatures_();
 };
 
 
 /**
- * Get the heading as radians clockwise from North.
- * @return {number|undefined} The heading of the device in radians from north.
- * @observable
- * @api stable
+ * Add a new coordinate to the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
  */
-ol.Geolocation.prototype.getHeading = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.HEADING));
+ol.interaction.Draw.prototype.addToDrawing_ = function(event) {
+  var coordinate = event.coordinate;
+  var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry());
+  var done;
+  var coordinates;
+  if (this.mode_ === ol.interaction.Draw.Mode_.LINE_STRING) {
+    this.finishCoordinate_ = coordinate.slice();
+    coordinates = this.sketchCoords_;
+    if (coordinates.length >= this.maxPoints_) {
+      if (this.freehand_) {
+        coordinates.pop();
+      } else {
+        done = true;
+      }
+    }
+    coordinates.push(coordinate.slice());
+    this.geometryFunction_(coordinates, geometry);
+  } else if (this.mode_ === ol.interaction.Draw.Mode_.POLYGON) {
+    coordinates = this.sketchCoords_[0];
+    if (coordinates.length >= this.maxPoints_) {
+      if (this.freehand_) {
+        coordinates.pop();
+      } else {
+        done = true;
+      }
+    }
+    coordinates.push(coordinate.slice());
+    if (done) {
+      this.finishCoordinate_ = coordinates[0];
+    }
+    this.geometryFunction_(this.sketchCoords_, geometry);
+  }
+  this.updateSketchFeatures_();
+  if (done) {
+    this.finishDrawing();
+  }
 };
 
 
 /**
- * Get the position of the device.
- * @return {ol.Coordinate|undefined} The current position of the device reported
- *     in the current projection.
- * @observable
- * @api stable
+ * Remove last point of the feature currently being drawn.
+ * @api
  */
-ol.Geolocation.prototype.getPosition = function() {
-  return /** @type {ol.Coordinate|undefined} */ (
-      this.get(ol.GeolocationProperty.POSITION));
-};
+ol.interaction.Draw.prototype.removeLastPoint = function() {
+  if (!this.sketchFeature_) {
+    return;
+  }
+  var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry());
+  var coordinates, sketchLineGeom;
+  if (this.mode_ === ol.interaction.Draw.Mode_.LINE_STRING) {
+    coordinates = this.sketchCoords_;
+    coordinates.splice(-2, 1);
+    this.geometryFunction_(coordinates, geometry);
+    if (coordinates.length >= 2) {
+      this.finishCoordinate_ = coordinates[coordinates.length - 2].slice();
+    }
+  } else if (this.mode_ === ol.interaction.Draw.Mode_.POLYGON) {
+    coordinates = this.sketchCoords_[0];
+    coordinates.splice(-2, 1);
+    sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry());
+    sketchLineGeom.setCoordinates(coordinates);
+    this.geometryFunction_(this.sketchCoords_, geometry);
+  }
 
+  if (coordinates.length === 0) {
+    this.finishCoordinate_ = null;
+  }
 
-/**
- * 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));
+  this.updateSketchFeatures_();
 };
 
 
 /**
- * Get the speed in meters per second.
- * @return {number|undefined} The instantaneous speed of the device in meters
- *     per second.
- * @observable
- * @api stable
+ * Stop drawing and add the sketch feature to the target layer.
+ * The {@link ol.interaction.DrawEventType.DRAWEND} event is dispatched before
+ * inserting the feature.
+ * @api
  */
-ol.Geolocation.prototype.getSpeed = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.SPEED));
-};
+ol.interaction.Draw.prototype.finishDrawing = function() {
+  var sketchFeature = this.abortDrawing_();
+  var coordinates = this.sketchCoords_;
+  var geometry = /** @type {ol.geom.SimpleGeometry} */ (sketchFeature.getGeometry());
+  if (this.mode_ === ol.interaction.Draw.Mode_.LINE_STRING) {
+    // remove the redundant last point
+    coordinates.pop();
+    this.geometryFunction_(coordinates, geometry);
+  } else if (this.mode_ === ol.interaction.Draw.Mode_.POLYGON) {
+    // remove the redundant last point in ring
+    coordinates[0].pop();
+    this.geometryFunction_(coordinates, geometry);
+    coordinates = geometry.getCoordinates();
+  }
 
+  // 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]));
+  }
 
-/**
- * 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));
+  // First dispatch event to allow full set up of feature
+  this.dispatchEvent(new ol.interaction.Draw.Event(
+      ol.interaction.DrawEventType.DRAWEND, sketchFeature));
+
+  // Then insert feature
+  if (this.features_) {
+    this.features_.push(sketchFeature);
+  }
+  if (this.source_) {
+    this.source_.addFeature(sketchFeature);
+  }
 };
 
 
 /**
- * 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
+ * Stop drawing without adding the sketch feature to the target layer.
+ * @return {ol.Feature} The sketch feature (or null if none).
+ * @private
  */
-ol.Geolocation.prototype.getTrackingOptions = function() {
-  return /** @type {GeolocationPositionOptions|undefined} */ (
-      this.get(ol.GeolocationProperty.TRACKING_OPTIONS));
+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;
 };
 
 
 /**
- * Set the projection to use for transforming the coordinates.
- * @param {ol.proj.Projection} projection The projection the position is
- *     reported in.
- * @observable
- * @api stable
+ * 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.Geolocation.prototype.setProjection = function(projection) {
-  this.set(ol.GeolocationProperty.PROJECTION, projection);
+ol.interaction.Draw.prototype.extend = function(feature) {
+  var geometry = feature.getGeometry();
+  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.Draw.Event(
+      ol.interaction.DrawEventType.DRAWSTART, this.sketchFeature_));
 };
 
 
 /**
- * Enable or disable tracking.
- * @param {boolean} tracking Enable tracking.
- * @observable
- * @api stable
+ * @inheritDoc
  */
-ol.Geolocation.prototype.setTracking = function(tracking) {
-  this.set(ol.GeolocationProperty.TRACKING, tracking);
-};
+ol.interaction.Draw.prototype.shouldStopEvent = ol.functions.FALSE;
 
 
 /**
- * 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
+ * Redraw the sketch features.
+ * @private
  */
-ol.Geolocation.prototype.setTrackingOptions = function(options) {
-  this.set(ol.GeolocationProperty.TRACKING_OPTIONS, options);
+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);
 };
 
-goog.provide('ol.geom.flat.geodesic');
-
-goog.require('goog.asserts');
-goog.require('goog.math');
-goog.require('goog.object');
-goog.require('ol.TransformFunction');
-goog.require('ol.math');
-goog.require('ol.proj');
-
 
 /**
  * @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 (!goog.object.containsKey(fractions, key)) {
-      flatCoordinates.push(a[0], a[1]);
-      fractions[key] = true;
-    }
-    // Pop the b coordinate off the stack
-    fracB = fractionStack.pop();
-    geoB = geoStack.pop();
-    b = stack.pop();
-    // Find the m point between the a and b coordinates
-    fracM = (fracA + fracB) / 2;
-    geoM = interpolate(fracM);
-    m = transform(geoM);
-    if (ol.math.squaredSegmentDistance(m[0], m[1], a[0], a[1],
-        b[0], b[1]) < squaredTolerance) {
-      // If the m point is sufficiently close to the straight line, then we
-      // discard it.  Just use the b coordinate and move on to the next line
-      // segment.
-      flatCoordinates.push(b[0], b[1]);
-      key = fracB.toString();
-      goog.asserts.assert(!goog.object.containsKey(fractions, key),
-          'fractions object should contain key : ' + key);
-      fractions[key] = true;
-    } else {
-      // Otherwise, we need to subdivide the current line segment.  Split it
-      // into two and push the two line segments onto the stack.
-      fractionStack.push(fracB, fracM, fracM, fracA);
-      stack.push(b, m, m, a);
-      geoStack.push(geoB, geoM, geoM, geoA);
-    }
+ol.interaction.Draw.prototype.updateState_ = function() {
+  var map = this.getMap();
+  var active = this.getActive();
+  if (!map || !active) {
+    this.abortDrawing_();
   }
-  goog.asserts.assert(maxIterations > 0,
-      'maxIterations should be more than 0');
-
-  return flatCoordinates;
+  this.overlay_.setMap(active ? map : null);
 };
 
 
 /**
-* Generate a great-circle arcs between two lat/lon points.
-* @param {number} lon1 Longitude 1 in degrees.
-* @param {number} lat1 Latitude 1 in degrees.
-* @param {number} lon2 Longitude 2 in degrees.
-* @param {number} lat2 Latitude 2 in degrees.
- * @param {ol.proj.Projection} projection Projection.
-* @param {number} squaredTolerance Squared tolerance.
-* @return {Array.<number>} Flat coordinates.
-*/
-ol.geom.flat.geodesic.greatCircleArc = function(
-    lon1, lat1, lon2, lat2, projection, squaredTolerance) {
-
-  var geoProjection = ol.proj.get('EPSG:4326');
-
-  var cosLat1 = Math.cos(goog.math.toRadians(lat1));
-  var sinLat1 = Math.sin(goog.math.toRadians(lat1));
-  var cosLat2 = Math.cos(goog.math.toRadians(lat2));
-  var sinLat2 = Math.sin(goog.math.toRadians(lat2));
-  var cosDeltaLon = Math.cos(goog.math.toRadians(lon2 - lon1));
-  var sinDeltaLon = Math.sin(goog.math.toRadians(lon2 - lon1));
-  var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon;
-
-  return ol.geom.flat.geodesic.line_(
+ * Create a `geometryFunction` for `type: '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.DrawGeometryFunctionType} Function that draws a
+ *     polygon.
+ * @api
+ */
+ol.interaction.Draw.createRegularPolygon = function(opt_sides, opt_angle) {
+  return (
       /**
-       * @param {number} frac Fraction.
-       * @return {ol.Coordinate} Coordinate.
+       * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates
+       * @param {ol.geom.SimpleGeometry=} opt_geometry
+       * @return {ol.geom.SimpleGeometry}
        */
-      function(frac) {
-        if (1 <= d) {
-          return [lon2, lat2];
-        }
-        var D = frac * Math.acos(d);
-        var cosD = Math.cos(D);
-        var sinD = Math.sin(D);
-        var y = sinDeltaLon * cosLat2;
-        var x = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosDeltaLon;
-        var theta = Math.atan2(y, x);
-        var lat = Math.asin(sinLat1 * cosD + cosLat1 * sinD * Math.cos(theta));
-        var lon = goog.math.toRadians(lon1) +
-            Math.atan2(Math.sin(theta) * sinD * cosLat1,
-                       cosD - sinLat1 * Math.sin(lat));
-        return [goog.math.toDegrees(lon), goog.math.toDegrees(lat)];
-      }, ol.proj.getTransform(geoProjection, projection), squaredTolerance);
+      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 ? /** @type {ol.geom.Polygon} */ (opt_geometry) :
+            ol.geom.Polygon.fromCircle(new ol.geom.Circle(center), opt_sides);
+        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;
+      }
+  );
 };
 
 
 /**
- * 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.
+ * Create a `geometryFunction` that will create a box-shaped polygon (aligned
+ * with the coordinate system axes).  Use this with the draw interaction and
+ * `type: 'Circle'` to return a box instead of a circle geometry.
+ * @return {ol.DrawGeometryFunctionType} Function that draws a box-shaped polygon.
+ * @api
  */
-ol.geom.flat.geodesic.meridian =
-    function(lon, lat1, lat2, projection, squaredTolerance) {
-  var epsg4326Projection = ol.proj.get('EPSG:4326');
-  return ol.geom.flat.geodesic.line_(
-      /**
-       * @param {number} frac Fraction.
-       * @return {ol.Coordinate} Coordinate.
-       */
-      function(frac) {
-        return [lon, lat1 + ((lat2 - lat1) * frac)];
-      },
-      ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance);
+ol.interaction.Draw.createBox = function() {
+  return (
+    /**
+     * @param {Array.<ol.Coordinate>} coordinates
+     * @param {ol.geom.SimpleGeometry=} opt_geometry
+     * @return {ol.geom.SimpleGeometry}
+     */
+    function(coordinates, opt_geometry) {
+      var extent = ol.extent.boundingExtent(coordinates);
+      var geometry = opt_geometry || new ol.geom.Polygon(null);
+      geometry.setCoordinates([[
+        ol.extent.getBottomLeft(extent),
+        ol.extent.getBottomRight(extent),
+        ol.extent.getTopRight(extent),
+        ol.extent.getTopLeft(extent),
+        ol.extent.getBottomLeft(extent)
+      ]]);
+      return geometry;
+    }
+  );
 };
 
 
 /**
- * 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.
+ * 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.Draw.Mode_} Drawing mode.
+ * @private
  */
-ol.geom.flat.geodesic.parallel =
-    function(lat, lon1, lon2, projection, squaredTolerance) {
-  var epsg4326Projection = ol.proj.get('EPSG:4326');
-  return ol.geom.flat.geodesic.line_(
-      /**
-       * @param {number} frac Fraction.
-       * @return {ol.Coordinate} Coordinate.
-       */
-      function(frac) {
-        return [lon1 + ((lon2 - lon1) * frac), lat];
-      },
-      ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance);
+ol.interaction.Draw.getMode_ = function(type) {
+  var mode;
+  if (type === ol.geom.GeometryType.POINT ||
+      type === ol.geom.GeometryType.MULTI_POINT) {
+    mode = ol.interaction.Draw.Mode_.POINT;
+  } else if (type === ol.geom.GeometryType.LINE_STRING ||
+      type === ol.geom.GeometryType.MULTI_LINE_STRING) {
+    mode = ol.interaction.Draw.Mode_.LINE_STRING;
+  } else if (type === ol.geom.GeometryType.POLYGON ||
+      type === ol.geom.GeometryType.MULTI_POLYGON) {
+    mode = ol.interaction.Draw.Mode_.POLYGON;
+  } else if (type === ol.geom.GeometryType.CIRCLE) {
+    mode = ol.interaction.Draw.Mode_.CIRCLE;
+  }
+  return /** @type {!ol.interaction.Draw.Mode_} */ (mode);
 };
 
-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');
-
 
+/**
+ * Draw mode.  This collapses multi-part geometry types with their single-part
+ * cousins.
+ * @enum {string}
+ * @private
+ */
+ol.interaction.Draw.Mode_ = {
+  POINT: 'Point',
+  LINE_STRING: 'LineString',
+  POLYGON: 'Polygon',
+  CIRCLE: 'Circle'
+};
 
 /**
- * Render a grid for a coordinate system on a map.
+ * @classdesc
+ * Events emitted by {@link ol.interaction.Draw} instances are instances of
+ * this type.
+ *
  * @constructor
- * @param {olx.GraticuleOptions=} opt_options Options.
- * @api
+ * @extends {ol.events.Event}
+ * @implements {oli.DrawEvent}
+ * @param {ol.interaction.DrawEventType} type Type.
+ * @param {ol.Feature} feature The feature drawn.
  */
-ol.Graticule = function(opt_options) {
-
-  var options = opt_options || {};
+ol.interaction.Draw.Event = function(type, feature) {
 
-  /**
-   * @type {ol.Map}
-   * @private
-   */
-  this.map_ = null;
+  ol.events.Event.call(this, type);
 
   /**
-   * @type {ol.proj.Projection}
-   * @private
+   * The feature being drawn.
+   * @type {ol.Feature}
+   * @api
    */
-  this.projection_ = null;
+  this.feature = feature;
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.maxLat_ = Infinity;
+};
+ol.inherits(ol.interaction.Draw.Event, ol.events.Event);
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.maxLon_ = Infinity;
+goog.provide('ol.interaction.Extent');
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.minLat_ = -Infinity;
+goog.require('ol');
+goog.require('ol.Feature');
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.MapBrowserPointerEvent');
+goog.require('ol.coordinate');
+goog.require('ol.events.Event');
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryType');
+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.style.Style');
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.minLon_ = -Infinity;
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.maxLatP_ = Infinity;
+/**
+ * @classdesc
+ * Allows the user to draw a vector box by clicking and dragging on the map.
+ * Once drawn, the vector box can be modified by dragging its vertices or edges.
+ * This interaction is only supported for mouse devices.
+ *
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @fires ol.interaction.Extent.Event
+ * @param {olx.interaction.ExtentOptions=} opt_options Options.
+ * @api
+ */
+ol.interaction.Extent = function(opt_options) {
 
   /**
-   * @type {number}
+   * Extent of the drawn box
+   * @type {ol.Extent}
    * @private
    */
-  this.maxLonP_ = Infinity;
+  this.extent_ = null;
 
   /**
-   * @type {number}
+   * Handler for pointer move events
+   * @type {function (ol.Coordinate): ol.Extent|null}
    * @private
    */
-  this.minLatP_ = -Infinity;
+  this.pointerHandler_ = null;
 
   /**
+   * Pixel threshold to snap to extent
    * @type {number}
    * @private
    */
-  this.minLonP_ = -Infinity;
+  this.pixelTolerance_ = 10;
 
   /**
-   * @type {number}
+   * Is the pointer snapped to an extent vertex
+   * @type {boolean}
    * @private
    */
-  this.targetSize_ = options.targetSize !== undefined ?
-      options.targetSize : 100;
+  this.snappedToVertex_ = false;
 
   /**
-   * @type {number}
+   * Feature for displaying the visible extent
+   * @type {ol.Feature}
    * @private
    */
-  this.maxLines_ = options.maxLines !== undefined ? options.maxLines : 100;
-  goog.asserts.assert(this.maxLines_ > 0,
-      'this.maxLines_ should be more than 0');
+  this.extentFeature_ = null;
 
   /**
-   * @type {Array.<ol.geom.LineString>}
+   * Feature for displaying the visible pointer
+   * @type {ol.Feature}
    * @private
    */
-  this.meridians_ = [];
+  this.vertexFeature_ = null;
 
-  /**
-   * @type {Array.<ol.geom.LineString>}
-   * @private
-   */
-  this.parallels_ = [];
+  if (!opt_options) {
+    opt_options = {};
+  }
 
-  /**
-   * @type {ol.style.Stroke}
-   * @private
-   */
-  this.strokeStyle_ = options.strokeStyle !== undefined ?
-      options.strokeStyle : ol.Graticule.DEFAULT_STROKE_STYLE_;
+  if (opt_options.extent) {
+    this.setExtent(opt_options.extent);
+  }
 
-  /**
-   * @type {ol.TransformFunction|undefined}
-   * @private
-   */
-  this.fromLonLatTransform_ = undefined;
+  /* Inherit ol.interaction.Pointer */
+  ol.interaction.Pointer.call(this, {
+    handleDownEvent: ol.interaction.Extent.handleDownEvent_,
+    handleDragEvent: ol.interaction.Extent.handleDragEvent_,
+    handleEvent: ol.interaction.Extent.handleEvent_,
+    handleUpEvent: ol.interaction.Extent.handleUpEvent_
+  });
 
   /**
-   * @type {ol.TransformFunction|undefined}
+   * Layer for the extentFeature
+   * @type {ol.layer.Vector}
    * @private
    */
-  this.toLonLatTransform_ = undefined;
+  this.extentOverlay_ = new ol.layer.Vector({
+    source: new ol.source.Vector({
+      useSpatialIndex: false,
+      wrapX: !!opt_options.wrapX
+    }),
+    style: opt_options.boxStyle ? opt_options.boxStyle : ol.interaction.Extent.getDefaultExtentStyleFunction_(),
+    updateWhileAnimating: true,
+    updateWhileInteracting: true
+  });
 
   /**
-   * @type {ol.Coordinate}
+   * Layer for the vertexFeature
+   * @type {ol.layer.Vector}
    * @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_);
+  this.vertexOverlay_ = new ol.layer.Vector({
+    source: new ol.source.Vector({
+      useSpatialIndex: false,
+      wrapX: !!opt_options.wrapX
+    }),
+    style: opt_options.pointerStyle ? opt_options.pointerStyle : ol.interaction.Extent.getDefaultPointerStyleFunction_(),
+    updateWhileAnimating: true,
+    updateWhileInteracting: true
+  });
+};
 
-  idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, 0);
+ol.inherits(ol.interaction.Extent, ol.interaction.Pointer);
 
-  cnt = 0;
-  while (lat != this.minLat_ && cnt++ < maxLines) {
-    lat = Math.max(lat - interval, this.minLat_);
-    idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx);
+/**
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Event.
+ * @return {boolean} Propagate event?
+ * @this {ol.interaction.Extent}
+ * @private
+ */
+ol.interaction.Extent.handleEvent_ = function(mapBrowserEvent) {
+  if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
+    return true;
   }
-
-  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);
+  //display pointer (if not dragging)
+  if (mapBrowserEvent.type == ol.MapBrowserEventType.POINTERMOVE && !this.handlingDownUpSequence) {
+    this.handlePointerMove_(mapBrowserEvent);
   }
-
-  this.parallels_.length = idx;
-
+  //call pointer to determine up/down/drag
+  ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent);
+  //return false to stop propagation
+  return false;
 };
 
-
 /**
- * @param {number} resolution Resolution.
- * @return {number} The interval in degrees.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Event handled?
+ * @this {ol.interaction.Extent}
  * @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;
+ol.interaction.Extent.handleDownEvent_ = function(mapBrowserEvent) {
+  var pixel = mapBrowserEvent.pixel;
+  var map = mapBrowserEvent.map;
+
+  var extent = this.getExtent();
+  var vertex = this.snapToVertex_(pixel, map);
+
+  //find the extent corner opposite the passed corner
+  var getOpposingPoint = function(point) {
+    var x_ = null;
+    var y_ = null;
+    if (point[0] == extent[0]) {
+      x_ = extent[2];
+    } else if (point[0] == extent[2]) {
+      x_ = extent[0];
     }
-    interval = ol.Graticule.intervals_[i];
+    if (point[1] == extent[1]) {
+      y_ = extent[3];
+    } else if (point[1] == extent[3]) {
+      y_ = extent[1];
+    }
+    if (x_ !== null && y_ !== null) {
+      return [x_, y_];
+    }
+    return null;
+  };
+  if (vertex && extent) {
+    var x = (vertex[0] == extent[0] || vertex[0] == extent[2]) ? vertex[0] : null;
+    var y = (vertex[1] == extent[1] || vertex[1] == extent[3]) ? vertex[1] : null;
+
+    //snap to point
+    if (x !== null && y !== null) {
+      this.pointerHandler_ = ol.interaction.Extent.getPointHandler_(getOpposingPoint(vertex));
+    //snap to edge
+    } else if (x !== null) {
+      this.pointerHandler_ = ol.interaction.Extent.getEdgeHandler_(
+        getOpposingPoint([x, extent[1]]),
+        getOpposingPoint([x, extent[3]])
+      );
+    } else if (y !== null) {
+      this.pointerHandler_ = ol.interaction.Extent.getEdgeHandler_(
+        getOpposingPoint([extent[0], y]),
+        getOpposingPoint([extent[2], y])
+      );
+    }
+  //no snap - new bbox
+  } else {
+    vertex = map.getCoordinateFromPixel(pixel);
+    this.setExtent([vertex[0], vertex[1], vertex[0], vertex[1]]);
+    this.pointerHandler_ = ol.interaction.Extent.getPointHandler_(vertex);
   }
-  return interval;
+  return true; //event handled; start downup sequence
 };
 
-
 /**
- * Get the map associated with this graticule.
- * @return {ol.Map} The map.
- * @api
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Event handled?
+ * @this {ol.interaction.Extent}
+ * @private
  */
-ol.Graticule.prototype.getMap = function() {
-  return this.map_;
+ol.interaction.Extent.handleDragEvent_ = function(mapBrowserEvent) {
+  if (this.pointerHandler_) {
+    var pixelCoordinate = mapBrowserEvent.coordinate;
+    this.setExtent(this.pointerHandler_(pixelCoordinate));
+    this.createOrUpdatePointerFeature_(pixelCoordinate);
+  }
+  return true;
 };
 
-
 /**
- * @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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Extent}
  * @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;
+ol.interaction.Extent.handleUpEvent_ = function(mapBrowserEvent) {
+  this.pointerHandler_ = null;
+  //If bbox is zero area, set to null;
+  var extent = this.getExtent();
+  if (!extent || ol.extent.getArea(extent) === 0) {
+    this.setExtent(null);
+  }
+  return false; //Stop handling downup sequence
 };
 
-
 /**
- * Get the list of meridians.  Meridians are lines of equal longitude.
- * @return {Array.<ol.geom.LineString>} The meridians.
- * @api
+ * Returns the default style for the drawn bbox
+ *
+ * @return {ol.StyleFunction} Default Extent style
+ * @private
  */
-ol.Graticule.prototype.getMeridians = function() {
-  return this.meridians_;
+ol.interaction.Extent.getDefaultExtentStyleFunction_ = function() {
+  var style = ol.style.Style.createDefaultEditing();
+  return function(feature, resolution) {
+    return style[ol.geom.GeometryType.POLYGON];
+  };
 };
 
-
 /**
- * @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.
+ * Returns the default style for the pointer
+ *
+ * @return {ol.StyleFunction} Default pointer style
  * @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;
+ol.interaction.Extent.getDefaultPointerStyleFunction_ = function() {
+  var style = ol.style.Style.createDefaultEditing();
+  return function(feature, resolution) {
+    return style[ol.geom.GeometryType.POINT];
+  };
 };
 
-
 /**
- * Get the list of parallels.  Pallels are lines of equal latitude.
- * @return {Array.<ol.geom.LineString>} The parallels.
- * @api
+ * @param {ol.Coordinate} fixedPoint corner that will be unchanged in the new extent
+ * @returns {function (ol.Coordinate): ol.Extent} event handler
+ * @private
  */
-ol.Graticule.prototype.getParallels = function() {
-  return this.parallels_;
+ol.interaction.Extent.getPointHandler_ = function(fixedPoint) {
+  return function(point) {
+    return ol.extent.boundingExtent([fixedPoint, point]);
+  };
 };
 
+/**
+ * @param {ol.Coordinate} fixedP1 first corner that will be unchanged in the new extent
+ * @param {ol.Coordinate} fixedP2 second corner that will be unchanged in the new extent
+ * @returns {function (ol.Coordinate): ol.Extent|null} event handler
+ * @private
+ */
+ol.interaction.Extent.getEdgeHandler_ = function(fixedP1, fixedP2) {
+  if (fixedP1[0] == fixedP2[0]) {
+    return function(point) {
+      return ol.extent.boundingExtent([fixedP1, [point[0], fixedP2[1]]]);
+    };
+  } else if (fixedP1[1] == fixedP2[1]) {
+    return function(point) {
+      return ol.extent.boundingExtent([fixedP1, [fixedP2[0], point[1]]]);
+    };
+  } else {
+    return null;
+  }
+};
 
 /**
- * @param {ol.render.Event} e Event.
+ * @param {ol.Extent} extent extent
+ * @returns {Array<Array<ol.Coordinate>>} extent line segments
  * @private
  */
-ol.Graticule.prototype.handlePostCompose_ = function(e) {
-  var vectorContext = e.vectorContext;
-  var frameState = e.frameState;
-  var extent = frameState.extent;
-  var viewState = frameState.viewState;
-  var center = viewState.center;
-  var projection = viewState.projection;
-  var resolution = viewState.resolution;
-  var pixelRatio = frameState.pixelRatio;
-  var squaredTolerance =
-      resolution * resolution / (4 * pixelRatio * pixelRatio);
+ol.interaction.Extent.getSegments_ = function(extent) {
+  return [
+    [[extent[0], extent[1]], [extent[0], extent[3]]],
+    [[extent[0], extent[3]], [extent[2], extent[3]]],
+    [[extent[2], extent[3]], [extent[2], extent[1]]],
+    [[extent[2], extent[1]], [extent[0], extent[1]]]
+  ];
+};
 
-  var updateProjectionInfo = !this.projection_ ||
-      !ol.proj.equivalent(this.projection_, projection);
+/**
+ * @param {ol.Pixel} pixel cursor location
+ * @param {ol.Map} map map
+ * @returns {ol.Coordinate|null} snapped vertex on extent
+ * @private
+ */
+ol.interaction.Extent.prototype.snapToVertex_ = function(pixel, map) {
+  var pixelCoordinate = map.getCoordinateFromPixel(pixel);
+  var sortByDistance = function(a, b) {
+    return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a) -
+        ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b);
+  };
+  var extent = this.getExtent();
+  if (extent) {
+    //convert extents to line segments and find the segment closest to pixelCoordinate
+    var segments = ol.interaction.Extent.getSegments_(extent);
+    segments.sort(sortByDistance);
+    var closestSegment = segments[0];
 
-  if (updateProjectionInfo) {
-    this.updateProjectionInfo_(projection);
-  }
+    var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
+        closestSegment));
+    var vertexPixel = map.getPixelFromCoordinate(vertex);
 
-  //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]
-      ];
+    //if the distance is within tolerance, snap to the segment
+    if (ol.coordinate.distance(pixel, vertexPixel) <= this.pixelTolerance_) {
+      //test if we should further snap to a vertex
+      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];
+      }
+      return vertex;
     }
   }
+  return null;
+};
 
-  this.createGraticule_(extent, center, resolution, squaredTolerance);
+/**
+ * @param {ol.MapBrowserEvent} mapBrowserEvent pointer move event
+ * @private
+ */
+ol.interaction.Extent.prototype.handlePointerMove_ = function(mapBrowserEvent) {
+  var pixel = mapBrowserEvent.pixel;
+  var map = mapBrowserEvent.map;
 
-  // 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);
+  var vertex = this.snapToVertex_(pixel, map);
+  if (!vertex) {
+    vertex = map.getCoordinateFromPixel(pixel);
   }
+  this.createOrUpdatePointerFeature_(vertex);
 };
 
-
 /**
- * @param {ol.proj.Projection} projection Projection.
+ * @param {ol.Extent} extent extent
+ * @returns {ol.Feature} extent as featrue
  * @private
  */
-ol.Graticule.prototype.updateProjectionInfo_ = function(projection) {
-  goog.asserts.assert(projection, 'projection cannot be null');
+ol.interaction.Extent.prototype.createOrUpdateExtentFeature_ = function(extent) {
+  var extentFeature = this.extentFeature_;
 
-  var epsg4326Projection = ol.proj.get('EPSG:4326');
+  if (!extentFeature) {
+    if (!extent) {
+      extentFeature = new ol.Feature({});
+    } else {
+      extentFeature = new ol.Feature(ol.geom.Polygon.fromExtent(extent));
+    }
+    this.extentFeature_ = extentFeature;
+    this.extentOverlay_.getSource().addFeature(extentFeature);
+  } else {
+    if (!extent) {
+      extentFeature.setGeometry(undefined);
+    } else {
+      extentFeature.setGeometry(ol.geom.Polygon.fromExtent(extent));
+    }
+  }
+  return extentFeature;
+};
 
-  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];
+/**
+ * @param {ol.Coordinate} vertex location of feature
+ * @returns {ol.Feature} vertex as feature
+ * @private
+ */
+ol.interaction.Extent.prototype.createOrUpdatePointerFeature_ = function(vertex) {
+  var vertexFeature = this.vertexFeature_;
+  if (!vertexFeature) {
+    vertexFeature = new ol.Feature(new ol.geom.Point(vertex));
+    this.vertexFeature_ = vertexFeature;
+    this.vertexOverlay_.getSource().addFeature(vertexFeature);
+  } else {
+    var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry());
+    geometry.setCoordinates(vertex);
+  }
+  return vertexFeature;
+};
 
-  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');
+/**
+ * @inheritDoc
+ */
+ol.interaction.Extent.prototype.setMap = function(map) {
+  this.extentOverlay_.setMap(map);
+  this.vertexOverlay_.setMap(map);
+  ol.interaction.Pointer.prototype.setMap.call(this, map);
+};
 
-  this.maxLat_ = maxLat;
-  this.maxLon_ = maxLon;
-  this.minLat_ = minLat;
-  this.minLon_ = minLon;
+/**
+ * Returns the current drawn extent in the view projection
+ *
+ * @return {ol.Extent} Drawn extent in the view projection.
+ * @api
+ */
+ol.interaction.Extent.prototype.getExtent = function() {
+  return this.extent_;
+};
 
-  this.maxLatP_ = maxLatP;
-  this.maxLonP_ = maxLonP;
-  this.minLatP_ = minLatP;
-  this.minLonP_ = minLonP;
+/**
+ * Manually sets the drawn extent, using the view projection.
+ *
+ * @param {ol.Extent} extent Extent
+ * @api
+ */
+ol.interaction.Extent.prototype.setExtent = function(extent) {
+  //Null extent means no bbox
+  this.extent_ = extent ? extent : null;
+  this.createOrUpdateExtentFeature_(extent);
+  this.dispatchEvent(new ol.interaction.Extent.Event(this.extent_));
+};
 
 
-  this.fromLonLatTransform_ = ol.proj.getTransform(
-      epsg4326Projection, projection);
+/**
+ * @classdesc
+ * Events emitted by {@link ol.interaction.Extent} instances are instances of
+ * this type.
+ *
+ * @constructor
+ * @param {ol.Extent} extent the new extent
+ * @extends {ol.events.Event}
+ */
+ol.interaction.Extent.Event = function(extent) {
+  ol.events.Event.call(this, ol.interaction.Extent.EventType_.EXTENTCHANGED);
 
-  this.toLonLatTransform_ = ol.proj.getTransform(
-      projection, epsg4326Projection);
+  /**
+   * The current extent.
+   * @type {ol.Extent}
+   * @api
+   */
+  this.extent_ = extent;
+};
+ol.inherits(ol.interaction.Extent.Event, ol.events.Event);
 
-  this.projectionCenterLonLat_ = this.toLonLatTransform_(
-      ol.extent.getCenter(extent));
 
-  this.projection_ = projection;
+/**
+ * @enum {string}
+ * @private
+ */
+ol.interaction.Extent.EventType_ = {
+  /**
+   * Triggered after the extent is changed
+   * @event ol.interaction.Extent.Event
+   * @api
+   */
+  EXTENTCHANGED: 'extentchanged'
 };
 
+goog.provide('ol.interaction.ModifyEventType');
+
 
 /**
- * Set the map for this graticule.  The graticule will be rendered on the
- * provided map.
- * @param {ol.Map} map Map.
- * @api
+ * @enum {string}
  */
-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;
+ol.interaction.ModifyEventType = {
+  /**
+   * Triggered upon feature modification start
+   * @event ol.interaction.Modify.Event#modifystart
+   * @api
+   */
+  MODIFYSTART: 'modifystart',
+  /**
+   * Triggered upon feature modification end
+   * @event ol.interaction.Modify.Event#modifyend
+   * @api
+   */
+  MODIFYEND: 'modifyend'
 };
 
-goog.provide('ol.Image');
+goog.provide('ol.interaction.Modify');
 
-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');
+goog.require('ol.CollectionEventType');
+goog.require('ol.Feature');
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.MapBrowserPointerEvent');
+goog.require('ol.ViewHint');
+goog.require('ol.array');
+goog.require('ol.coordinate');
+goog.require('ol.events');
+goog.require('ol.events.Event');
+goog.require('ol.events.EventType');
+goog.require('ol.events.condition');
 goog.require('ol.extent');
-
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.Point');
+goog.require('ol.interaction.ModifyEventType');
+goog.require('ol.interaction.Pointer');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.Vector');
+goog.require('ol.structs.RBush');
+goog.require('ol.style.Style');
 
 
 /**
+ * @classdesc
+ * Interaction for modifying feature geometries.
+ *
  * @constructor
- * @extends {ol.ImageBase}
- * @param {ol.Extent} extent Extent.
- * @param {number|undefined} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {Array.<ol.Attribution>} attributions Attributions.
- * @param {string} src Image source URI.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
+ * @extends {ol.interaction.Pointer}
+ * @param {olx.interaction.ModifyOptions} options Options.
+ * @fires ol.interaction.Modify.Event
+ * @api
  */
-ol.Image = function(extent, resolution, pixelRatio, attributions, src,
-    crossOrigin, imageLoadFunction) {
+ol.interaction.Modify = function(options) {
 
-  goog.base(this, extent, resolution, pixelRatio, ol.ImageState.IDLE,
-      attributions);
+  ol.interaction.Pointer.call(this, {
+    handleDownEvent: ol.interaction.Modify.handleDownEvent_,
+    handleDragEvent: ol.interaction.Modify.handleDragEvent_,
+    handleEvent: ol.interaction.Modify.handleEvent,
+    handleUpEvent: ol.interaction.Modify.handleUpEvent_
+  });
 
   /**
    * @private
-   * @type {string}
+   * @type {ol.EventsConditionType}
    */
-  this.src_ = src;
+  this.condition_ = options.condition ?
+      options.condition : ol.events.condition.primaryAction;
+
 
   /**
    * @private
-   * @type {Image}
+   * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event.
+   * @return {boolean} Combined condition result.
    */
-  this.image_ = new Image();
-  if (crossOrigin) {
-    this.image_.crossOrigin = crossOrigin;
-  }
+  this.defaultDeleteCondition_ = function(mapBrowserEvent) {
+    return ol.events.condition.noModifierKeys(mapBrowserEvent) &&
+      ol.events.condition.singleClick(mapBrowserEvent);
+  };
 
   /**
+   * @type {ol.EventsConditionType}
    * @private
-   * @type {Object.<number, Image>}
    */
-  this.imageByContext_ = {};
+  this.deleteCondition_ = options.deleteCondition ?
+      options.deleteCondition : this.defaultDeleteCondition_;
 
   /**
+   * @type {ol.EventsConditionType}
    * @private
-   * @type {Array.<goog.events.Key>}
    */
-  this.imageListenerKeys_ = null;
+  this.insertVertexCondition_ = options.insertVertexCondition ?
+      options.insertVertexCondition : ol.events.condition.always;
 
   /**
-   * @protected
-   * @type {ol.ImageState}
+   * Editing vertex.
+   * @type {ol.Feature}
+   * @private
    */
-  this.state = ol.ImageState.IDLE;
+  this.vertexFeature_ = null;
 
   /**
+   * Segments intersecting {@link this.vertexFeature_} by segment uid.
+   * @type {Object.<string, boolean>}
    * @private
-   * @type {ol.ImageLoadFunctionType}
    */
-  this.imageLoadFunction_ = imageLoadFunction;
+  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.ModifySegmentDataType>}
+   * @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_ = [];
+
+  /**
+   * 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_,
+    'Circle': this.writeCircleGeometry_,
+    'GeometryCollection': this.writeGeometryCollectionGeometry_
+  };
+
+  /**
+   * @type {ol.Collection.<ol.Feature>}
+   * @private
+   */
+  this.features_ = options.features;
+
+  this.features_.forEach(this.addFeature_, this);
+  ol.events.listen(this.features_, ol.CollectionEventType.ADD,
+      this.handleFeatureAdd_, this);
+  ol.events.listen(this.features_, ol.CollectionEventType.REMOVE,
+      this.handleFeatureRemove_, this);
+
+  /**
+   * @type {ol.MapBrowserPointerEvent}
+   * @private
+   */
+  this.lastPointerEvent_ = null;
 
 };
-goog.inherits(ol.Image, ol.ImageBase);
+ol.inherits(ol.interaction.Modify, ol.interaction.Pointer);
 
 
 /**
- * Get the HTML image element (may be a Canvas, Image, or Video).
- * @param {Object=} opt_context Object.
- * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
- * @api
+ * @define {number} The segment index assigned to a circle's center when
+ * breaking up a cicrle into ModifySegmentDataType segments.
  */
-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_;
+ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX = 0;
+
+/**
+ * @define {number} The segment index assigned to a circle's circumference when
+ * breaking up a circle into ModifySegmentDataType segments.
+ */
+ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX = 1;
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @private
+ */
+ol.interaction.Modify.prototype.addFeature_ = function(feature) {
+  var geometry = feature.getGeometry();
+  if (geometry && geometry.getType() in this.SEGMENT_WRITERS_) {
+    this.SEGMENT_WRITERS_[geometry.getType()].call(this, feature, geometry);
+  }
+  var map = this.getMap();
+  if (map && map.isRendered() && this.getActive()) {
+    this.handlePointerAtPixel_(this.lastPixel_, map);
+  }
+  ol.events.listen(feature, ol.events.EventType.CHANGE,
+      this.handleFeatureChange_, 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.Modify.Event(
+        ol.interaction.ModifyEventType.MODIFYSTART, this.features_, evt));
   }
 };
 
 
 /**
- * Tracks loading or read errors.
- *
+ * @param {ol.Feature} feature Feature.
  * @private
  */
-ol.Image.prototype.handleImageError_ = function() {
-  this.state = ol.ImageState.ERROR;
-  this.unlistenImage_();
-  this.changed();
+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;
+  }
+  ol.events.unlisten(feature, ol.events.EventType.CHANGE,
+      this.handleFeatureChange_, this);
 };
 
 
 /**
- * Tracks successful image load.
- *
+ * @param {ol.Feature} feature Feature.
  * @private
  */
-ol.Image.prototype.handleImageLoad_ = function() {
-  if (this.resolution === undefined) {
-    this.resolution = ol.extent.getHeight(this.extent) / this.image_.height;
+ol.interaction.Modify.prototype.removeFeatureSegmentData_ = function(feature) {
+  var rBush = this.rBush_;
+  var /** @type {Array.<ol.ModifySegmentDataType>} */ nodesToRemove = [];
+  rBush.forEach(
+      /**
+       * @param {ol.ModifySegmentDataType} 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]);
   }
-  this.state = ol.ImageState.LOADED;
-  this.unlistenImage_();
-  this.changed();
 };
 
 
 /**
- * Load not yet loaded URI.
+ * @inheritDoc
  */
-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_);
+ol.interaction.Modify.prototype.setActive = function(active) {
+  if (this.vertexFeature_ && !active) {
+    this.overlay_.getSource().removeFeature(this.vertexFeature_);
+    this.vertexFeature_ = null;
   }
+  ol.interaction.Pointer.prototype.setActive.call(this, active);
 };
 
 
 /**
- * Discards event handlers which listen for load completion or errors.
- *
- * @private
+ * @inheritDoc
  */
-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;
+ol.interaction.Modify.prototype.setMap = function(map) {
+  this.overlay_.setMap(map);
+  ol.interaction.Pointer.prototype.setMap.call(this, map);
 };
 
-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
+ * @param {ol.Collection.Event} evt Event.
+ * @private
  */
-ol.ImageLoadFunctionType;
-
-goog.provide('ol.TileLoadFunctionType');
-goog.provide('ol.TileVectorLoadFunctionType');
+ol.interaction.Modify.prototype.handleFeatureAdd_ = function(evt) {
+  this.addFeature_(/** @type {ol.Feature} */ (evt.element));
+};
 
 
 /**
- * A function that takes an {@link ol.ImageTile} for the image tile and a
- * `{string}` for the src as arguments.
- *
- * @typedef {function(ol.ImageTile, string)}
- * @api
+ * @param {ol.events.Event} evt Event.
+ * @private
  */
-ol.TileLoadFunctionType;
+ol.interaction.Modify.prototype.handleFeatureChange_ = function(evt) {
+  if (!this.changingFeature_) {
+    var feature = /** @type {ol.Feature} */ (evt.target);
+    this.removeFeature_(feature);
+    this.addFeature_(feature);
+  }
+};
 
 
 /**
- * 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
+ * @param {ol.Collection.Event} evt Event.
+ * @private
  */
-ol.TileVectorLoadFunctionType;
-
-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');
-
+ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) {
+  var feature = /** @type {ol.Feature} */ (evt.element);
+  this.removeFeature_(feature);
+};
 
 
 /**
- * @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.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Point} geometry Geometry.
+ * @private
  */
-ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction) {
-
-  goog.base(this, tileCoord, state);
+ol.interaction.Modify.prototype.writePointGeometry_ = function(feature, geometry) {
+  var coordinates = geometry.getCoordinates();
+  var segmentData = /** @type {ol.ModifySegmentDataType} */ ({
+    feature: feature,
+    geometry: geometry,
+    segment: [coordinates, coordinates]
+  });
+  this.rBush_.insert(geometry.getExtent(), segmentData);
+};
 
-  /**
-   * Image URI
-   *
-   * @private
-   * @type {string}
-   */
-  this.src_ = src;
 
-  /**
-   * @private
-   * @type {Image}
-   */
-  this.image_ = new Image();
-  if (crossOrigin) {
-    this.image_.crossOrigin = crossOrigin;
+/**
+ * @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.ModifySegmentDataType} */ ({
+      feature: feature,
+      geometry: geometry,
+      depth: [i],
+      index: i,
+      segment: [coordinates, coordinates]
+    });
+    this.rBush_.insert(geometry.getExtent(), segmentData);
   }
+};
 
-  /**
-   * @private
-   * @type {Object.<number, Image>}
-   */
-  this.imageByContext_ = {};
 
-  /**
-   * @private
-   * @type {Array.<goog.events.Key>}
-   */
-  this.imageListenerKeys_ = null;
+/**
+ * @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.ModifySegmentDataType} */ ({
+      feature: feature,
+      geometry: geometry,
+      index: i,
+      segment: segment
+    });
+    this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+  }
+};
 
-  /**
-   * @private
-   * @type {ol.TileLoadFunctionType}
-   */
-  this.tileLoadFunction_ = tileLoadFunction;
 
+/**
+ * @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.ModifySegmentDataType} */ ({
+        feature: feature,
+        geometry: geometry,
+        depth: [j],
+        index: i,
+        segment: segment
+      });
+      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+    }
+  }
 };
-goog.inherits(ol.ImageTile, ol.Tile);
 
 
 /**
- * @inheritDoc
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Polygon} geometry Geometry.
+ * @private
  */
-ol.ImageTile.prototype.disposeInternal = function() {
-  if (this.state == ol.TileState.LOADING) {
-    this.unlistenImage_();
+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.ModifySegmentDataType} */ ({
+        feature: feature,
+        geometry: geometry,
+        depth: [j],
+        index: i,
+        segment: segment
+      });
+      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+    }
   }
-  goog.base(this, 'disposeInternal');
 };
 
 
 /**
- * Get the image element for this tile.
- * @inheritDoc
- * @api
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiPolygon} geometry Geometry.
+ * @private
  */
-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));
+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.ModifySegmentDataType} */ ({
+          feature: feature,
+          geometry: geometry,
+          depth: [j, k],
+          index: i,
+          segment: segment
+        });
+        this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+      }
     }
-    this.imageByContext_[key] = image;
-    return image;
-  } else {
-    return this.image_;
   }
 };
 
 
 /**
- * @inheritDoc
+ * We convert a circle into two segments.  The segment at index
+ * {@link ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX} is the
+ * circle's center (a point).  The segment at index
+ * {@link ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX} is
+ * the circumference, and is not a line segment.
+ *
+ * @param {ol.Feature} feature Feature.
+ * @param {ol.geom.Circle} geometry Geometry.
+ * @private
  */
-ol.ImageTile.prototype.getKey = function() {
-  return this.src_;
+ol.interaction.Modify.prototype.writeCircleGeometry_ = function(feature, geometry) {
+  var coordinates = geometry.getCenter();
+  var centerSegmentData = /** @type {ol.ModifySegmentDataType} */ ({
+    feature: feature,
+    geometry: geometry,
+    index: ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX,
+    segment: [coordinates, coordinates]
+  });
+  var circumferenceSegmentData = /** @type {ol.ModifySegmentDataType} */ ({
+    feature: feature,
+    geometry: geometry,
+    index: ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX,
+    segment: [coordinates, coordinates]
+  });
+  var featureSegments = [centerSegmentData, circumferenceSegmentData];
+  centerSegmentData.featureSegments = circumferenceSegmentData.featureSegments = featureSegments;
+  this.rBush_.insert(ol.extent.createOrUpdateFromCoordinate(coordinates), centerSegmentData);
+  this.rBush_.insert(geometry.getExtent(), circumferenceSegmentData);
 };
 
 
 /**
- * Tracks loading or read errors.
- *
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.GeometryCollection} geometry Geometry.
  * @private
  */
-ol.ImageTile.prototype.handleImageError_ = function() {
-  this.state = ol.TileState.ERROR;
-  this.unlistenImage_();
-  this.changed();
+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]);
+  }
 };
 
 
 /**
- * Tracks successful image load.
- *
+ * @param {ol.Coordinate} coordinates Coordinates.
+ * @return {ol.Feature} Vertex feature.
  * @private
  */
-ol.ImageTile.prototype.handleImageLoad_ = function() {
-  if (this.image_.naturalWidth && this.image_.naturalHeight) {
-    this.state = ol.TileState.LOADED;
+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 {
-    this.state = ol.TileState.EMPTY;
+    var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry());
+    geometry.setCoordinates(coordinates);
   }
-  this.unlistenImage_();
-  this.changed();
+  return vertexFeature;
 };
 
 
 /**
- * Load not yet loaded URI.
+ * @param {ol.ModifySegmentDataType} a The first segment data.
+ * @param {ol.ModifySegmentDataType} b The second segment data.
+ * @return {number} The difference in indexes.
+ * @private
  */
-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_);
-  }
+ol.interaction.Modify.compareIndexes_ = function(a, b) {
+  return a.index - b.index;
 };
 
 
 /**
- * Discards event handlers which listen for load completion or errors.
- *
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.Modify}
  * @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;
+ol.interaction.Modify.handleDownEvent_ = function(evt) {
+  if (!this.condition_(evt)) {
+    return false;
+  }
+  this.handlePointerAtPixel_(evt.pixel, evt.map);
+  var pixelCoordinate = evt.map.getCoordinateFromPixel(evt.pixel);
+  this.dragSegments_.length = 0;
+  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 = ol.getUid(segmentDataMatch.feature);
+      var depth = segmentDataMatch.depth;
+      if (depth) {
+        uid += '-' + depth.join('-'); // separate feature components
+      }
+      if (!componentSegments[uid]) {
+        componentSegments[uid] = new Array(2);
+      }
+      if (segmentDataMatch.geometry.getType() === ol.geom.GeometryType.CIRCLE &&
+      segmentDataMatch.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
+
+        var closestVertex = ol.interaction.Modify.closestOnSegmentData_(pixelCoordinate, segmentDataMatch);
+        if (ol.coordinate.equals(closestVertex, vertex) && !componentSegments[uid][0]) {
+          this.dragSegments_.push([segmentDataMatch, 0]);
+          componentSegments[uid][0] = segmentDataMatch;
+        }
+      } else 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 (this.insertVertexCondition_(evt) && ol.getUid(segment) in this.vertexSegments_ &&
+          (!componentSegments[uid][0] && !componentSegments[uid][1])) {
+        insertVertices.push([segmentDataMatch, vertex]);
+      }
+    }
+    if (insertVertices.length) {
+      this.willModifyFeatures_(evt);
+    }
+    for (var j = insertVertices.length - 1; j >= 0; --j) {
+      this.insertVertex_.apply(this, insertVertices[j]);
+    }
+  }
+  return !!this.vertexFeature_;
 };
 
-goog.provide('ol.ImageUrlFunction');
-goog.provide('ol.ImageUrlFunctionType');
 
-goog.require('ol.Size');
+/**
+ * @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;
+    var segment = segmentData.segment;
+    var index = dragSegment[1];
+
+    while (vertex.length < geometry.getStride()) {
+      vertex.push(segment[index][vertex.length]);
+    }
+
+    switch (geometry.getType()) {
+      case ol.geom.GeometryType.POINT:
+        coordinates = vertex;
+        segment[0] = segment[1] = vertex;
+        break;
+      case ol.geom.GeometryType.MULTI_POINT:
+        coordinates = geometry.getCoordinates();
+        coordinates[segmentData.index] = vertex;
+        segment[0] = segment[1] = vertex;
+        break;
+      case ol.geom.GeometryType.LINE_STRING:
+        coordinates = geometry.getCoordinates();
+        coordinates[segmentData.index + index] = vertex;
+        segment[index] = vertex;
+        break;
+      case ol.geom.GeometryType.MULTI_LINE_STRING:
+        coordinates = geometry.getCoordinates();
+        coordinates[depth[0]][segmentData.index + index] = vertex;
+        segment[index] = vertex;
+        break;
+      case ol.geom.GeometryType.POLYGON:
+        coordinates = geometry.getCoordinates();
+        coordinates[depth[0]][segmentData.index + index] = vertex;
+        segment[index] = vertex;
+        break;
+      case ol.geom.GeometryType.MULTI_POLYGON:
+        coordinates = geometry.getCoordinates();
+        coordinates[depth[1]][depth[0]][segmentData.index + index] = vertex;
+        segment[index] = vertex;
+        break;
+      case ol.geom.GeometryType.CIRCLE:
+        segment[0] = segment[1] = vertex;
+        if (segmentData.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX) {
+          this.changingFeature_ = true;
+          geometry.setCenter(vertex);
+          this.changingFeature_ = false;
+        } else { // We're dragging the circle's circumference:
+          this.changingFeature_ = true;
+          geometry.setRadius(ol.coordinate.distance(geometry.getCenter(), vertex));
+          this.changingFeature_ = false;
+        }
+        break;
+      default:
+        // pass
+    }
+
+    if (coordinates) {
+      this.setGeometryCoordinates_(geometry, coordinates);
+    }
+  }
+  this.createOrUpdateVertexFeature_(vertex);
+};
 
 
 /**
- * @typedef {function(this:ol.source.Image, ol.Extent, ol.Size,
- *     ol.proj.Projection): (string|undefined)}
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Modify}
+ * @private
  */
-ol.ImageUrlFunctionType;
+ol.interaction.Modify.handleUpEvent_ = function(evt) {
+  var segmentData;
+  var geometry;
+  for (var i = this.dragSegments_.length - 1; i >= 0; --i) {
+    segmentData = this.dragSegments_[i][0];
+    geometry = segmentData.geometry;
+    if (geometry.getType() === ol.geom.GeometryType.CIRCLE) {
+      // Update a circle object in the R* bush:
+      var coordinates = geometry.getCenter();
+      var centerSegmentData = segmentData.featureSegments[0];
+      var circumferenceSegmentData = segmentData.featureSegments[1];
+      centerSegmentData.segment[0] = centerSegmentData.segment[1] = coordinates;
+      circumferenceSegmentData.segment[0] = circumferenceSegmentData.segment[1] = coordinates;
+      this.rBush_.update(ol.extent.createOrUpdateFromCoordinate(coordinates), centerSegmentData);
+      this.rBush_.update(geometry.getExtent(), circumferenceSegmentData);
+    } else {
+      this.rBush_.update(ol.extent.boundingExtent(segmentData.segment),
+          segmentData);
+    }
+  }
+  if (this.modified_) {
+    this.dispatchEvent(new ol.interaction.Modify.Event(
+        ol.interaction.ModifyEventType.MODIFYEND, this.features_, evt));
+    this.modified_ = false;
+  }
+  return false;
+};
 
 
 /**
- * @param {string} baseUrl Base URL (may have query data).
- * @param {Object.<string,*>} params to encode in the URL.
- * @param {function(string, Object.<string,*>, ol.Extent, ol.Size,
- *     ol.proj.Projection): (string|undefined)} paramsFunction params function.
- * @return {ol.ImageUrlFunctionType} Image URL function.
+ * 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.ImageUrlFunction.createFromParamsFunction =
-    function(baseUrl, params, paramsFunction) {
-  return (
-      /**
-       * @this {ol.source.Image}
-       * @param {ol.Extent} extent Extent.
-       * @param {ol.Size} size Size.
-       * @param {ol.proj.Projection} projection Projection.
-       * @return {string|undefined} URL.
-       */
-      function(extent, size, projection) {
-        return paramsFunction(baseUrl, params, extent, size, projection);
-      });
+ol.interaction.Modify.handleEvent = function(mapBrowserEvent) {
+  if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
+    return true;
+  }
+  this.lastPointerEvent_ = mapBrowserEvent;
+
+  var handled;
+  if (!mapBrowserEvent.map.getView().getHints()[ol.ViewHint.INTERACTING] &&
+      mapBrowserEvent.type == ol.MapBrowserEventType.POINTERMOVE &&
+      !this.handlingDownUpSequence) {
+    this.handlePointerMove_(mapBrowserEvent);
+  }
+  if (this.vertexFeature_ && this.deleteCondition_(mapBrowserEvent)) {
+    if (mapBrowserEvent.type != ol.MapBrowserEventType.SINGLECLICK ||
+        !this.ignoreNextSingleClick_) {
+      handled = this.removePoint();
+    } else {
+      handled = true;
+    }
+  }
+
+  if (mapBrowserEvent.type == ol.MapBrowserEventType.SINGLECLICK) {
+    this.ignoreNextSingleClick_ = false;
+  }
+
+  return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) &&
+      !handled;
 };
 
 
 /**
- * @this {ol.source.Image}
- * @param {ol.Extent} extent Extent.
- * @param {ol.Size} size Size.
- * @return {string|undefined} Image URL.
+ * @param {ol.MapBrowserEvent} evt Event.
+ * @private
  */
-ol.ImageUrlFunction.nullImageUrlFunction =
-    function(extent, size) {
-  return undefined;
+ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) {
+  this.lastPixel_ = evt.pixel;
+  this.handlePointerAtPixel_(evt.pixel, evt.map);
 };
 
-// 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
+ * @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.interaction.Modify.pointDistanceToSegmentDataSquared_(pixelCoordinate, a) -
+        ol.interaction.Modify.pointDistanceToSegmentDataSquared_(pixelCoordinate, b);
+  };
+
+  var box = ol.extent.buffer(
+      ol.extent.createOrUpdateFromCoordinate(pixelCoordinate),
+      map.getView().getResolution() * this.pixelTolerance_);
+
+  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.interaction.Modify.closestOnSegmentData_(pixelCoordinate, node);
+    var vertexPixel = map.getPixelFromCoordinate(vertex);
+    var dist = ol.coordinate.distance(pixel, vertexPixel);
+    if (dist <= this.pixelTolerance_) {
+      var vertexSegments = {};
 
-goog.provide('goog.events.FileDropHandler');
-goog.provide('goog.events.FileDropHandler.EventType');
+      if (node.geometry.getType() === ol.geom.GeometryType.CIRCLE &&
+      node.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
 
-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');
+        this.snappedToVertex_ = true;
+        this.createOrUpdateVertexFeature_(vertex);
+      } else {
+        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);
+        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 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[ol.getUid(segment)] = true;
+          } else {
+            break;
+          }
+        }
+      }
 
+      vertexSegments[ol.getUid(closestSegment)] = true;
+      this.vertexSegments_ = vertexSegments;
+      return;
+    }
+  }
+  if (this.vertexFeature_) {
+    this.overlay_.getSource().removeFeature(this.vertexFeature_);
+    this.vertexFeature_ = null;
+  }
+};
 
 
 /**
- * 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}.
+ * Returns the distance from a point to a line segment.
  *
- * @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
+ * @param {ol.Coordinate} pointCoordinates The coordinates of the point from
+ *        which to calculate the distance.
+ * @param {ol.ModifySegmentDataType} segmentData The object describing the line
+ *        segment we are calculating the distance to.
+ * @return {number} The square of the distance between a point and a line segment.
  */
-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);
-  }
+ol.interaction.Modify.pointDistanceToSegmentDataSquared_ = function(pointCoordinates, segmentData) {
+  var geometry = segmentData.geometry;
 
-  // Add dragenter listener to the owner document of the element.
-  this.eventHandler_.listen(doc,
-                            goog.events.EventType.DRAGENTER,
-                            this.onDocDragEnter_);
+  if (geometry.getType() === ol.geom.GeometryType.CIRCLE) {
+    var circleGeometry = /** @type {ol.geom.Circle} */ (geometry);
 
-  // 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_);
+    if (segmentData.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
+      var distanceToCenterSquared =
+            ol.coordinate.squaredDistance(circleGeometry.getCenter(), pointCoordinates);
+      var distanceToCircumference =
+            Math.sqrt(distanceToCenterSquared) - circleGeometry.getRadius();
+      return distanceToCircumference * distanceToCircumference;
+    }
   }
-
-  // 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_);
+  return ol.coordinate.squaredDistanceToSegment(pointCoordinates, segmentData.segment);
 };
-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}
+ * Returns the point closest to a given line segment.
+ *
+ * @param {ol.Coordinate} pointCoordinates The point to which a closest point
+ *        should be found.
+ * @param {ol.ModifySegmentDataType} segmentData The object describing the line
+ *        segment which should contain the closest point.
+ * @return {ol.Coordinate} The point closest to the specified line segment.
  */
-goog.events.FileDropHandler.prototype.dndContainsFiles_ = false;
+ol.interaction.Modify.closestOnSegmentData_ = function(pointCoordinates, segmentData) {
+  var geometry = segmentData.geometry;
+
+  if (geometry.getType() === ol.geom.GeometryType.CIRCLE &&
+  segmentData.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
+    return geometry.getClosestPoint(pointCoordinates);
+  }
+  return ol.coordinate.closestOnSegment(pointCoordinates, segmentData.segment);
+};
 
 
 /**
- * A logger, used to help us debug the algorithm.
- * @type {goog.log.Logger}
+ * @param {ol.ModifySegmentDataType} segmentData Segment data.
+ * @param {ol.Coordinate} vertex Vertex.
  * @private
  */
-goog.events.FileDropHandler.prototype.logger_ =
-    goog.log.getLogger('goog.events.FileDropHandler');
+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 = /** @type {number} */ (segmentData.index);
+  var coordinates;
 
+  while (vertex.length < geometry.getStride()) {
+    vertex.push(0);
+  }
 
-/**
- * The types of events fired by this class.
- * @enum {string}
- */
-goog.events.FileDropHandler.EventType = {
-  DROP: goog.events.EventType.DROP
-};
+  switch (geometry.getType()) {
+    case ol.geom.GeometryType.MULTI_LINE_STRING:
+      coordinates = geometry.getCoordinates();
+      coordinates[depth[0]].splice(index + 1, 0, vertex);
+      break;
+    case ol.geom.GeometryType.POLYGON:
+      coordinates = geometry.getCoordinates();
+      coordinates[depth[0]].splice(index + 1, 0, vertex);
+      break;
+    case ol.geom.GeometryType.MULTI_POLYGON:
+      coordinates = geometry.getCoordinates();
+      coordinates[depth[1]][depth[0]].splice(index + 1, 0, vertex);
+      break;
+    case ol.geom.GeometryType.LINE_STRING:
+      coordinates = geometry.getCoordinates();
+      coordinates.splice(index + 1, 0, vertex);
+      break;
+    default:
+      return;
+  }
 
+  this.setGeometryCoordinates_(geometry, coordinates);
+  var rTree = this.rBush_;
+  rTree.remove(segmentData);
+  this.updateSegmentIndices_(geometry, index, depth, 1);
+  var newSegmentData = /** @type {ol.ModifySegmentDataType} */ ({
+    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]);
 
-/** @override */
-goog.events.FileDropHandler.prototype.disposeInternal = function() {
-  goog.events.FileDropHandler.superClass_.disposeInternal.call(this);
-  this.eventHandler_.dispose();
+  var newSegmentData2 = /** @type {ol.ModifySegmentDataType} */ ({
+    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;
 };
 
-
 /**
- * Dispatches the DROP event.
- * @param {goog.events.BrowserEvent} e The underlying browser event.
- * @private
+ * Removes the vertex currently being pointed.
+ * @return {boolean} True when a vertex was removed.
+ * @api
  */
-goog.events.FileDropHandler.prototype.dispatch_ = function(e) {
-  goog.log.fine(this.logger_, 'Firing DROP event...');
-  var event = new goog.events.BrowserEvent(e.getBrowserEvent());
-  event.type = goog.events.FileDropHandler.EventType.DROP;
-  this.dispatchEvent(event);
+ol.interaction.Modify.prototype.removePoint = function() {
+  if (this.lastPointerEvent_ && this.lastPointerEvent_.type != ol.MapBrowserEventType.POINTERDRAG) {
+    var evt = this.lastPointerEvent_;
+    this.willModifyFeatures_(evt);
+    this.removeVertex_();
+    this.dispatchEvent(new ol.interaction.Modify.Event(
+        ol.interaction.ModifyEventType.MODIFYEND, this.features_, evt));
+    this.modified_ = false;
+    return true;
+  }
+  return false;
 };
 
-
 /**
- * Handles dragenter on the document.
- * @param {goog.events.BrowserEvent} e The dragenter event.
+ * Removes a vertex from all matching features.
+ * @return {boolean} True when a vertex was removed.
  * @private
  */
-goog.events.FileDropHandler.prototype.onDocDragEnter_ = function(e) {
-  goog.log.log(this.logger_, goog.log.Level.FINER,
-      '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
-  var dt = e.getBrowserEvent().dataTransfer;
-  // Check whether the drag event contains files.
-  this.dndContainsFiles_ = !!(dt &&
-      ((dt.types &&
-          (goog.array.contains(dt.types, 'Files') ||
-          goog.array.contains(dt.types, 'public.file-url'))) ||
-      (dt.files && dt.files.length > 0)));
-  // If it does
-  if (this.dndContainsFiles_) {
-    // Prevent default actions.
-    e.preventDefault();
+ol.interaction.Modify.prototype.removeVertex_ = function() {
+  var dragSegments = this.dragSegments_;
+  var segmentsByFeature = {};
+  var deleted = false;
+  var component, coordinates, dragSegment, geometry, i, index, left;
+  var newIndex, right, segmentData, uid;
+  for (i = dragSegments.length - 1; i >= 0; --i) {
+    dragSegment = dragSegments[i];
+    segmentData = dragSegment[0];
+    uid = ol.getUid(segmentData.feature);
+    if (segmentData.depth) {
+      // separate feature components
+      uid += '-' + segmentData.depth.join('-');
+    }
+    if (!(uid in segmentsByFeature)) {
+      segmentsByFeature[uid] = {};
+    }
+    if (dragSegment[1] === 0) {
+      segmentsByFeature[uid].right = segmentData;
+      segmentsByFeature[uid].index = segmentData.index;
+    } else if (dragSegment[1] == 1) {
+      segmentsByFeature[uid].left = segmentData;
+      segmentsByFeature[uid].index = segmentData.index + 1;
+    }
+
   }
-  goog.log.log(this.logger_, goog.log.Level.FINER,
-      'dndContainsFiles_: ' + this.dndContainsFiles_);
-};
+  for (uid in segmentsByFeature) {
+    right = segmentsByFeature[uid].right;
+    left = segmentsByFeature[uid].left;
+    index = segmentsByFeature[uid].index;
+    newIndex = index - 1;
+    if (left !== undefined) {
+      segmentData = left;
+    } else {
+      segmentData = right;
+    }
+    if (newIndex < 0) {
+      newIndex = 0;
+    }
+    geometry = segmentData.geometry;
+    coordinates = geometry.getCoordinates();
+    component = coordinates;
+    deleted = false;
+    switch (geometry.getType()) {
+      case ol.geom.GeometryType.MULTI_LINE_STRING:
+        if (coordinates[segmentData.depth[0]].length > 2) {
+          coordinates[segmentData.depth[0]].splice(index, 1);
+          deleted = true;
+        }
+        break;
+      case ol.geom.GeometryType.LINE_STRING:
+        if (coordinates.length > 2) {
+          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;
+      default:
+        // pass
+    }
 
+    if (deleted) {
+      this.setGeometryCoordinates_(geometry, coordinates);
+      var segments = [];
+      if (left !== undefined) {
+        this.rBush_.remove(left);
+        segments.push(left.segment[0]);
+      }
+      if (right !== undefined) {
+        this.rBush_.remove(right);
+        segments.push(right.segment[1]);
+      }
+      if (left !== undefined && right !== undefined) {
+        var newSegmentData = /** @type {ol.ModifySegmentDataType} */ ({
+          depth: segmentData.depth,
+          feature: segmentData.feature,
+          geometry: segmentData.geometry,
+          index: newIndex,
+          segment: segments
+        });
+        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;
+      }
+      dragSegments.length = 0;
+    }
 
-/**
- * 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';
   }
+  return deleted;
 };
 
 
 /**
- * Handles dragging something over the element (drop zone).
- * @param {goog.events.BrowserEvent} e The dragover event.
+ * @param {ol.geom.SimpleGeometry} geometry Geometry.
+ * @param {Array} coordinates Coordinates.
  * @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';
-  }
+ol.interaction.Modify.prototype.setGeometryCoordinates_ = function(geometry, coordinates) {
+  this.changingFeature_ = true;
+  geometry.setCoordinates(coordinates);
+  this.changingFeature_ = false;
 };
 
 
 /**
- * Handles dropping something onto the element (drop zone).
- * @param {goog.events.BrowserEvent} e The drop event.
+ * @param {ol.geom.SimpleGeometry} geometry Geometry.
+ * @param {number} index Index.
+ * @param {Array.<number>|undefined} depth Depth.
+ * @param {number} delta Delta (1 or -1).
  * @private
  */
-goog.events.FileDropHandler.prototype.onElemDrop_ = function(e) {
-  goog.log.log(this.logger_, goog.log.Level.FINER,
-      '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
-  // If the drag and drop event contains files.
-  if (this.dndContainsFiles_) {
-    // Prevent default actions and stop the event from propagating further to
-    // the document. Both lines are needed! (See comment above).
-    e.preventDefault();
-    e.stopPropagation();
-    // Dispatch DROP event.
-    this.dispatch_(e);
-  }
+ol.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 ||
+        ol.array.equals(segmentDataMatch.depth, depth)) &&
+        segmentDataMatch.index > index) {
+      segmentDataMatch.index += delta;
+    }
+  });
 };
 
-// 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.
+ * @return {ol.StyleFunction} Styles.
  */
+ol.interaction.Modify.getDefaultStyleFunction = function() {
+  var style = ol.style.Style.createDefaultEditing();
+  return function(feature, resolution) {
+    return style[ol.geom.GeometryType.POINT];
+  };
+};
+
 
 /**
- * @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.
+ * @classdesc
+ * Events emitted by {@link ol.interaction.Modify} instances are instances of
+ * this type.
  *
- * @author arv@google.com (Erik Arvidsson)
- * @author brenneman@google.com (Shawn Brenneman)
+ * @constructor
+ * @extends {ol.events.Event}
+ * @implements {oli.ModifyEvent}
+ * @param {ol.interaction.ModifyEventType} type Type.
+ * @param {ol.Collection.<ol.Feature>} features The features modified.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserPointerEvent Associated
+ *     {@link ol.MapBrowserPointerEvent}.
  */
+ol.interaction.Modify.Event = function(type, features, mapBrowserPointerEvent) {
+
+  ol.events.Event.call(this, type);
+
+  /**
+   * The features being modified.
+   * @type {ol.Collection.<ol.Feature>}
+   * @api
+   */
+  this.features = features;
 
-goog.provide('goog.async.Deferred');
-goog.provide('goog.async.Deferred.AlreadyCalledError');
-goog.provide('goog.async.Deferred.CanceledError');
+  /**
+   * Associated {@link ol.MapBrowserEvent}.
+   * @type {ol.MapBrowserEvent}
+   * @api
+   */
+  this.mapBrowserEvent = mapBrowserPointerEvent;
+};
+ol.inherits(ol.interaction.Modify.Event, ol.events.Event);
 
-goog.require('goog.Promise');
-goog.require('goog.Thenable');
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.debug.Error');
+goog.provide('ol.interaction.Select');
 
+goog.require('ol');
+goog.require('ol.CollectionEventType');
+goog.require('ol.array');
+goog.require('ol.events');
+goog.require('ol.events.Event');
+goog.require('ol.events.condition');
+goog.require('ol.functions');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.layer.Vector');
+goog.require('ol.obj');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Style');
 
 
 /**
- * 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.
+ * @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.
  *
- * 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.
+ * Selected features are added to an internal unmanaged layer.
  *
- * 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
+ * @extends {ol.interaction.Interaction}
+ * @param {olx.interaction.SelectOptions=} opt_options Options.
+ * @fires ol.interaction.Select.Event
+ * @api
  */
-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_ = [];
+ol.interaction.Select = function(opt_options) {
 
-  /**
-   * Optional function that will be called if the Deferred is canceled.
-   * @type {Function|undefined}
-   * @private
-   */
-  this.onCancelFunction_ = opt_onCancelFunction;
+  ol.interaction.Interaction.call(this, {
+    handleEvent: ol.interaction.Select.handleEvent
+  });
 
-  /**
-   * The default scope to execute callbacks and errbacks in.
-   * @type {Object}
-   * @private
-   */
-  this.defaultScope_ = opt_defaultScope || null;
+  var options = opt_options ? opt_options : {};
 
   /**
-   * Whether the Deferred has been fired.
-   * @type {boolean}
    * @private
+   * @type {ol.EventsConditionType}
    */
-  this.fired_ = false;
+  this.condition_ = options.condition ?
+      options.condition : ol.events.condition.singleClick;
 
   /**
-   * Whether the last result in the execution sequence was an error.
-   * @type {boolean}
    * @private
+   * @type {ol.EventsConditionType}
    */
-  this.hadError_ = false;
+  this.addCondition_ = options.addCondition ?
+      options.addCondition : ol.events.condition.never;
 
   /**
-   * The current Deferred result, updated as callbacks and errbacks are
-   * executed.
-   * @type {*}
    * @private
+   * @type {ol.EventsConditionType}
    */
-  this.result_ = undefined;
+  this.removeCondition_ = options.removeCondition ?
+      options.removeCondition : ol.events.condition.never;
 
   /**
-   * 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
+   * @type {ol.EventsConditionType}
    */
-  this.blocked_ = false;
+  this.toggleCondition_ = options.toggleCondition ?
+      options.toggleCondition : ol.events.condition.shiftKeyOnly;
 
   /**
-   * 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;
+  this.multi_ = options.multi ? options.multi : 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
+   * @type {ol.SelectFilterFunction}
    */
-  this.unhandledErrorId_ = 0;
+  this.filter_ = options.filter ? options.filter :
+      ol.functions.TRUE;
 
   /**
-   * If this Deferred was created by branch(), this will be the "parent"
-   * Deferred.
-   * @type {goog.async.Deferred}
    * @private
+   * @type {number}
    */
-  this.parent_ = null;
+  this.hitTolerance_ = options.hitTolerance ? options.hitTolerance : 0;
+
+  var 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
+  });
 
   /**
-   * The number of Deferred objects that have been branched off this one. This
-   * will be decremented whenever a branch is fired or canceled.
-   * @type {number}
    * @private
+   * @type {ol.layer.Vector}
    */
-  this.branches_ = 0;
+  this.featureOverlay_ = featureOverlay;
 
-  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);
+  /** @type {function(ol.layer.Layer): boolean} */
+  var layerFilter;
+  if (options.layers) {
+    if (typeof options.layers === 'function') {
+      layerFilter = options.layers;
     } else {
-      this.silentlyCanceled_ = true;
-    }
-    if (!this.hasFired()) {
-      this.errback(new goog.async.Deferred.CanceledError(this));
+      var layers = options.layers;
+      layerFilter = function(layer) {
+        return ol.array.includes(layers, layer);
+      };
     }
-  } else if (this.result_ instanceof goog.async.Deferred) {
-    this.result_.cancel();
+  } else {
+    layerFilter = ol.functions.TRUE;
   }
-};
 
+  /**
+   * @private
+   * @type {function(ol.layer.Layer): boolean}
+   */
+  this.layerFilter_ = layerFilter;
 
-/**
- * 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();
-  }
-};
+  /**
+   * An association between selected feature (key)
+   * and layer (value)
+   * @private
+   * @type {Object.<number, ol.layer.Layer>}
+   */
+  this.featureLayerAssociation_ = {};
 
+  var features = this.featureOverlay_.getSource().getFeaturesCollection();
+  ol.events.listen(features, ol.CollectionEventType.ADD,
+      this.addFeature_, this);
+  ol.events.listen(features, ol.CollectionEventType.REMOVE,
+      this.removeFeature_, this);
 
-/**
- * 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);
 };
+ol.inherits(ol.interaction.Select, ol.interaction.Interaction);
 
 
 /**
- * Updates the current result based on the success or failure of the last action
- * in the execution sequence.
- *
- * @param {boolean} isSuccess Whether the new result is a success or an error.
- * @param {*} res The result.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {ol.layer.Layer} layer Layer.
  * @private
  */
-goog.async.Deferred.prototype.updateResult_ = function(isSuccess, res) {
-  this.fired_ = true;
-  this.result_ = res;
-  this.hadError_ = !isSuccess;
-  this.fire_();
+ol.interaction.Select.prototype.addFeatureLayerAssociation_ = function(feature, layer) {
+  var key = ol.getUid(feature);
+  this.featureLayerAssociation_[key] = layer;
 };
 
 
 /**
- * Verifies that the Deferred has not yet been fired.
- *
- * @private
- * @throws {Error} If this has already been fired.
+ * Get the selected features.
+ * @return {ol.Collection.<ol.Feature>} Features collection.
+ * @api
  */
-goog.async.Deferred.prototype.check_ = function() {
-  if (this.hasFired()) {
-    if (!this.silentlyCanceled_) {
-      throw new goog.async.Deferred.AlreadyCalledError(this);
-    }
-    this.silentlyCanceled_ = false;
-  }
+ol.interaction.Select.prototype.getFeatures = function() {
+  return this.featureOverlay_.getSource().getFeaturesCollection();
 };
 
 
 /**
- * Fire the execution sequence for this Deferred by passing the starting result
- * to the first registered callback.
- * @param {VALUE=} opt_result The starting result.
+ * Returns the Hit-detection tolerance.
+ * @returns {number} Hit tolerance in pixels.
+ * @api
  */
-goog.async.Deferred.prototype.callback = function(opt_result) {
-  this.check_();
-  this.assertNotDeferred_(opt_result);
-  this.updateResult_(true /* isSuccess */, opt_result);
+ol.interaction.Select.prototype.getHitTolerance = function() {
+  return this.hitTolerance_;
 };
 
 
 /**
- * Fire the execution sequence for this Deferred by passing the starting error
- * result to the first registered errback.
- * @param {*=} opt_result The starting error.
+ * 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
  */
-goog.async.Deferred.prototype.errback = function(opt_result) {
-  this.check_();
-  this.assertNotDeferred_(opt_result);
-  this.makeStackTraceLong_(opt_result);
-  this.updateResult_(false /* isSuccess */, opt_result);
+ol.interaction.Select.prototype.getLayer = function(feature) {
+  var key = ol.getUid(feature);
+  return /** @type {ol.layer.Vector} */ (this.featureLayerAssociation_[key]);
 };
 
 
 /**
- * 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
+ * Handles the {@link ol.MapBrowserEvent map browser event} and may change the
+ * selected state of features.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.Select}
+ * @api
  */
-goog.async.Deferred.prototype.makeStackTraceLong_ = function(error) {
-  if (!goog.async.Deferred.LONG_STACK_TRACES) {
-    return;
-  }
-  if (this.constructorStack_ && goog.isObject(error) && error.stack &&
-      // Stack looks like it was system generated. See
-      // https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
-      (/^[^\n]+(\n   [^\n]+)+/).test(error.stack)) {
-    error.stack = error.stack + '\nDEFERRED OPERATION:\n' +
-        this.constructorStack_;
+ol.interaction.Select.handleEvent = function(mapBrowserEvent) {
+  if (!this.condition_(mapBrowserEvent)) {
+    return true;
   }
-};
-
-
-/**
- * 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;
+  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 deselected = [];
+  var selected = [];
+  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.
+    ol.obj.clear(this.featureLayerAssociation_);
+    map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
+      (/**
+         * @param {ol.Feature|ol.render.Feature} feature Feature.
+         * @param {ol.layer.Layer} layer Layer.
+         * @return {boolean|undefined} Continue to iterate over the features.
+         */
+        function(feature, layer) {
+          if (this.filter_(feature, layer)) {
+            selected.push(feature);
+            this.addFeatureLayerAssociation_(feature, layer);
+            return !this.multi_;
+          }
+        }).bind(this), {
+          layerFilter: this.layerFilter_,
+          hitTolerance: this.hitTolerance_
+        });
+    var i;
+    for (i = features.getLength() - 1; i >= 0; --i) {
+      var feature = features.item(i);
+      var index = selected.indexOf(feature);
+      if (index > -1) {
+        // feature is already selected
+        selected.splice(index, 1);
+      } else {
+        features.remove(feature);
+        deselected.push(feature);
+      }
     }
-    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);
+    if (selected.length !== 0) {
+      features.extend(selected);
     }
-  });
-  return promise.then(opt_onFulfilled, opt_onRejected, opt_context);
+  } 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.
+         * @return {boolean|undefined} Continue to iterate over the features.
+         */
+        function(feature, layer) {
+          if (this.filter_(feature, layer)) {
+            if ((add || toggle) &&
+                !ol.array.includes(features.getArray(), feature)) {
+              selected.push(feature);
+              this.addFeatureLayerAssociation_(feature, layer);
+            } else if ((remove || toggle) &&
+                ol.array.includes(features.getArray(), feature)) {
+              deselected.push(feature);
+              this.removeFeatureLayerAssociation_(feature);
+            }
+            return !this.multi_;
+          }
+        }).bind(this), {
+          layerFilter: this.layerFilter_,
+          hitTolerance: this.hitTolerance_
+        });
+    var j;
+    for (j = deselected.length - 1; j >= 0; --j) {
+      features.remove(deselected[j]);
+    }
+    features.extend(selected);
+  }
+  if (selected.length > 0 || deselected.length > 0) {
+    this.dispatchEvent(
+        new ol.interaction.Select.Event(ol.interaction.Select.EventType_.SELECT,
+            selected, deselected, mapBrowserEvent));
+  }
+  return ol.events.condition.pointerMove(mapBrowserEvent);
 };
-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.
+ * Hit-detection tolerance. Pixels inside the radius around the given position
+ * will be checked for features. This only works for the canvas renderer and
+ * not for WebGL.
+ * @param {number} hitTolerance Hit tolerance in pixels.
+ * @api
  */
-goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) {
-  this.addCallbacks(
-      otherDeferred.callback, otherDeferred.errback, otherDeferred);
-  return this;
+ol.interaction.Select.prototype.setHitTolerance = function(hitTolerance) {
+  this.hitTolerance_ = hitTolerance;
 };
 
 
 /**
- * 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.
+ * 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.
+ * @override
+ * @api
  */
-goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) {
-  if (!(otherDeferred instanceof goog.async.Deferred)) {
-    // The Thenable case.
-    return this.addCallback(function() {
-      return otherDeferred;
-    });
+ol.interaction.Select.prototype.setMap = function(map) {
+  var currentMap = this.getMap();
+  var selectedFeatures =
+      this.featureOverlay_.getSource().getFeaturesCollection();
+  if (currentMap) {
+    selectedFeatures.forEach(currentMap.unskipFeature, currentMap);
   }
-  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_++;
+  ol.interaction.Interaction.prototype.setMap.call(this, map);
+  this.featureOverlay_.setMap(map);
+  if (map) {
+    selectedFeatures.forEach(map.skipFeature, map);
   }
-  return d;
 };
 
 
 /**
- * @return {boolean} Whether the execution sequence has been started on this
- *     Deferred by invoking {@code callback} or {@code errback}.
+ * @return {ol.StyleFunction} Styles.
  */
-goog.async.Deferred.prototype.hasFired = function() {
-  return this.fired_;
+ol.interaction.Select.getDefaultStyleFunction = function() {
+  var styles = ol.style.Style.createDefaultEditing();
+  ol.array.extend(styles[ol.geom.GeometryType.POLYGON],
+      styles[ol.geom.GeometryType.LINE_STRING]);
+  ol.array.extend(styles[ol.geom.GeometryType.GEOMETRY_COLLECTION],
+      styles[ol.geom.GeometryType.LINE_STRING]);
+
+  return function(feature, resolution) {
+    if (!feature.getGeometry()) {
+      return null;
+    }
+    return styles[feature.getGeometry().getType()];
+  };
 };
 
 
 /**
- * @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
+ * @param {ol.Collection.Event} evt Event.
+ * @private
  */
-goog.async.Deferred.prototype.isError = function(res) {
-  return res instanceof Error;
+ol.interaction.Select.prototype.addFeature_ = function(evt) {
+  var map = this.getMap();
+  if (map) {
+    map.skipFeature(/** @type {ol.Feature} */ (evt.element));
+  }
 };
 
 
 /**
- * @return {boolean} Whether an errback exists in the remaining sequence.
+ * @param {ol.Collection.Event} evt Event.
  * @private
  */
-goog.async.Deferred.prototype.hasErrback_ = function() {
-  return goog.array.some(this.sequence_, function(sequenceRow) {
-    // The errback is the second element in the array.
-    return goog.isFunction(sequenceRow[1]);
-  });
+ol.interaction.Select.prototype.removeFeature_ = function(evt) {
+  var map = this.getMap();
+  if (map) {
+    map.unskipFeature(/** @type {ol.Feature} */ (evt.element));
+  }
 };
 
 
 /**
- * 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.
- *
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
  * @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);
-  }
+ol.interaction.Select.prototype.removeFeatureLayerAssociation_ = function(feature) {
+  var key = ol.getUid(feature);
+  delete this.featureLayerAssociation_[key];
 };
 
 
 /**
- * Creates a Deferred that has an initial result.
+ * @classdesc
+ * Events emitted by {@link ol.interaction.Select} instances are instances of
+ * this type.
  *
- * @param {*=} opt_result The result.
- * @return {!goog.async.Deferred} The new Deferred.
+ * @param {ol.interaction.Select.EventType_} 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 {ol.events.Event}
+ * @constructor
  */
-goog.async.Deferred.succeed = function(opt_result) {
-  var d = new goog.async.Deferred();
-  d.callback(opt_result);
-  return d;
-};
+ol.interaction.Select.Event = function(type, selected, deselected, mapBrowserEvent) {
+  ol.events.Event.call(this, type);
 
+  /**
+   * Selected features array.
+   * @type {Array.<ol.Feature>}
+   * @api
+   */
+  this.selected = selected;
 
-/**
- * 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;
+  /**
+   * Deselected features array.
+   * @type {Array.<ol.Feature>}
+   * @api
+   */
+  this.deselected = deselected;
+
+  /**
+   * Associated {@link ol.MapBrowserEvent}.
+   * @type {ol.MapBrowserEvent}
+   * @api
+   */
+  this.mapBrowserEvent = mapBrowserEvent;
 };
+ol.inherits(ol.interaction.Select.Event, ol.events.Event);
 
 
 /**
- * Creates a Deferred that has an initial error result.
- *
- * @param {*} res The error result.
- * @return {!goog.async.Deferred} The new Deferred.
+ * @enum {string}
+ * @private
  */
-goog.async.Deferred.fail = function(res) {
-  var d = new goog.async.Deferred();
-  d.errback(res);
-  return d;
+ol.interaction.Select.EventType_ = {
+  /**
+   * Triggered when feature(s) has been (de)selected.
+   * @event ol.interaction.Select.Event#select
+   * @api
+   */
+  SELECT: 'select'
 };
 
+goog.provide('ol.interaction.Snap');
 
-/**
- * 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;
-};
+goog.require('ol');
+goog.require('ol.Collection');
+goog.require('ol.CollectionEventType');
+goog.require('ol.coordinate');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
+goog.require('ol.functions');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.Polygon');
+goog.require('ol.interaction.Pointer');
+goog.require('ol.obj');
+goog.require('ol.source.Vector');
+goog.require('ol.source.VectorEventType');
+goog.require('ol.structs.RBush');
 
 
 /**
- * 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.
+ * @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.
  *
- * 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.
+ * The snap interaction modifies map browser event `coordinate` and `pixel`
+ * properties to force the snap to occur to any interaction that them.
  *
- * <pre>
- * var value;
- * if (isImmediate) {
- *   value = 3;
- * } else {
- *   value = new goog.async.Deferred();
- *   setTimeout(function() { value.callback(6); }, 2000);
- * }
+ * Example:
  *
- * var d = goog.async.Deferred.when(value, alert);
- * </pre>
+ *     var snap = new ol.interaction.Snap({
+ *       source: source
+ *     });
  *
- * @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
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @param {olx.interaction.SnapOptions=} opt_options Options.
+ * @api
  */
-goog.async.Deferred.when = function(value, callback, opt_scope) {
-  if (value instanceof goog.async.Deferred) {
-    return value.branch(true).addCallback(callback, opt_scope);
-  } else {
-    return goog.async.Deferred.succeed(value).addCallback(callback, opt_scope);
-  }
-};
+ol.interaction.Snap = function(opt_options) {
 
+  ol.interaction.Pointer.call(this, {
+    handleEvent: ol.interaction.Snap.handleEvent_,
+    handleDownEvent: ol.functions.TRUE,
+    handleUpEvent: ol.interaction.Snap.handleUpEvent_
+  });
 
+  var options = opt_options ? opt_options : {};
 
-/**
- * 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);
+  /**
+   * @type {ol.source.Vector}
+   * @private
+   */
+  this.source_ = options.source ? options.source : null;
 
   /**
-   * The Deferred that raised this error.
-   * @type {goog.async.Deferred}
+   * @private
+   * @type {boolean}
    */
-  this.deferred = deferred;
-};
-goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error);
+  this.vertex_ = options.vertex !== undefined ? options.vertex : true;
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.edge_ = options.edge !== undefined ? options.edge : true;
 
-/** @override */
-goog.async.Deferred.AlreadyCalledError.prototype.message =
-    'Deferred has already fired';
+  /**
+   * @type {ol.Collection.<ol.Feature>}
+   * @private
+   */
+  this.features_ = options.features ? options.features : null;
 
+  /**
+   * @type {Array.<ol.EventsKey>}
+   * @private
+   */
+  this.featuresListenerKeys_ = [];
 
-/** @override */
-goog.async.Deferred.AlreadyCalledError.prototype.name = 'AlreadyCalledError';
+  /**
+   * @type {Object.<number, ol.EventsKey>}
+   * @private
+   */
+  this.featureChangeListenerKeys_ = {};
 
+  /**
+   * 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_ = {};
 
-/**
- * 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);
+  /**
+   * Used for distance sorting in sortByDistance_
+   * @type {ol.Coordinate}
+   * @private
+   */
+  this.pixelCoordinate_ = null;
 
   /**
-   * The Deferred that raised this error.
-   * @type {goog.async.Deferred}
+   * @type {number}
+   * @private
    */
-  this.deferred = deferred;
-};
-goog.inherits(goog.async.Deferred.CanceledError, goog.debug.Error);
+  this.pixelTolerance_ = options.pixelTolerance !== undefined ?
+      options.pixelTolerance : 10;
 
+  /**
+   * @type {function(ol.SnapSegmentDataType, ol.SnapSegmentDataType): number}
+   * @private
+   */
+  this.sortByDistance_ = ol.interaction.Snap.sortByDistance.bind(this);
 
-/** @override */
-goog.async.Deferred.CanceledError.prototype.message = 'Deferred was canceled';
 
+  /**
+  * Segment RTree for each layer
+  * @type {ol.structs.RBush.<ol.SnapSegmentDataType>}
+  * @private
+  */
+  this.rBush_ = new ol.structs.RBush();
 
-/** @override */
-goog.async.Deferred.CanceledError.prototype.name = 'CanceledError';
 
+  /**
+  * @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_,
+    'Circle': this.writeCircleGeometry_
+  };
+};
+ol.inherits(ol.interaction.Snap, ol.interaction.Pointer);
 
 
 /**
- * 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
+ * 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 feature change or not
+ *     Defaults to `true`.
+ * @api
  */
-goog.async.Deferred.Error_ = function(error) {
-  /** @const @private {number} */
-  this.id_ = goog.global.setTimeout(goog.bind(this.throwError, this), 0);
+ol.interaction.Snap.prototype.addFeature = function(feature, opt_listen) {
+  var listen = opt_listen !== undefined ? opt_listen : true;
+  var feature_uid = ol.getUid(feature);
+  var geometry = feature.getGeometry();
+  if (geometry) {
+    var segmentWriter = this.SEGMENT_WRITERS_[geometry.getType()];
+    if (segmentWriter) {
+      this.indexedFeaturesExtents_[feature_uid] = geometry.getExtent(
+          ol.extent.createEmpty());
+      segmentWriter.call(this, feature, geometry);
+    }
+  }
 
-  /** @const @private {*} */
-  this.error_ = error;
+  if (listen) {
+    this.featureChangeListenerKeys_[feature_uid] = ol.events.listen(
+        feature,
+        ol.events.EventType.CHANGE,
+        this.handleFeatureChange_, this);
+  }
 };
 
 
 /**
- * Actually throws the error and removes it from the list of pending
- * deferred errors.
+ * @param {ol.Feature} feature Feature.
+ * @private
  */
-goog.async.Deferred.Error_.prototype.throwError = function() {
-  goog.asserts.assert(goog.async.Deferred.errorMap_[this.id_],
-      'Cannot throw an error that is not scheduled.');
-  delete goog.async.Deferred.errorMap_[this.id_];
-  throw this.error_;
+ol.interaction.Snap.prototype.forEachFeatureAdd_ = function(feature) {
+  this.addFeature(feature);
 };
 
 
 /**
- * Resets the error throw timer.
+ * @param {ol.Feature} feature Feature.
+ * @private
  */
-goog.async.Deferred.Error_.prototype.resetTimer = function() {
-  goog.global.clearTimeout(this.id_);
+ol.interaction.Snap.prototype.forEachFeatureRemove_ = function(feature) {
+  this.removeFeature(feature);
 };
 
 
 /**
- * Map of unhandled errors scheduled to be rethrown in a future timestep.
- * @private {!Object<number|string, goog.async.Deferred.Error_>}
+ * @return {ol.Collection.<ol.Feature>|Array.<ol.Feature>} Features.
+ * @private
  */
-goog.async.Deferred.errorMap_ = {};
+ol.interaction.Snap.prototype.getFeatures_ = function() {
+  var features;
+  if (this.features_) {
+    features = this.features_;
+  } else if (this.source_) {
+    features = this.source_.getFeatures();
+  }
+  return /** @type {!Array.<ol.Feature>|!ol.Collection.<ol.Feature>} */ (features);
+};
 
 
 /**
- * Schedules an error to be thrown after a delay.
- * @param {*} error Error from a failing deferred.
- * @return {number} Id of the error.
+ * @param {ol.source.Vector.Event|ol.Collection.Event} evt Event.
  * @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_;
+ol.interaction.Snap.prototype.handleFeatureAdd_ = function(evt) {
+  var feature;
+  if (evt instanceof ol.source.Vector.Event) {
+    feature = evt.feature;
+  } else if (evt instanceof ol.Collection.Event) {
+    feature = evt.element;
+  }
+  this.addFeature(/** @type {ol.Feature} */ (feature));
 };
 
 
 /**
- * Unschedules an error from being thrown.
- * @param {number} id Id of the deferred error to unschedule.
+ * @param {ol.source.Vector.Event|ol.Collection.Event} evt Event.
  * @private
  */
-goog.async.Deferred.unscheduleError_ = function(id) {
-  var error = goog.async.Deferred.errorMap_[id];
-  if (error) {
-    error.resetTimer();
-    delete goog.async.Deferred.errorMap_[id];
+ol.interaction.Snap.prototype.handleFeatureRemove_ = function(evt) {
+  var feature;
+  if (evt instanceof ol.source.Vector.Event) {
+    feature = evt.feature;
+  } else if (evt instanceof ol.Collection.Event) {
+    feature = evt.element;
   }
+  this.removeFeature(/** @type {ol.Feature} */ (feature));
 };
 
 
 /**
- * Asserts that there are no pending deferred errors. If there are any
- * scheduled errors, one will be thrown immediately to make this function fail.
+ * @param {ol.events.Event} evt Event.
+ * @private
  */
-goog.async.Deferred.assertNoErrors = function() {
-  var map = goog.async.Deferred.errorMap_;
-  for (var key in map) {
-    var error = map[key];
-    error.resetTimer();
-    error.throwError();
+ol.interaction.Snap.prototype.handleFeatureChange_ = function(evt) {
+  var feature = /** @type {ol.Feature} */ (evt.target);
+  if (this.handlingDownUpSequence) {
+    var uid = ol.getUid(feature);
+    if (!(uid in this.pendingFeatures_)) {
+      this.pendingFeatures_[uid] = feature;
+    }
+  } else {
+    this.updateFeature_(feature);
   }
 };
 
-// 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.
- *
+ * 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 feature 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 = ol.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]);
+    }
+  }
 
-goog.provide('goog.fs.Error');
-goog.provide('goog.fs.Error.ErrorCode');
-
-goog.require('goog.debug.Error');
-goog.require('goog.object');
-goog.require('goog.string');
-
+  if (unlisten) {
+    ol.events.unlistenByKey(this.featureChangeListenerKeys_[feature_uid]);
+    delete this.featureChangeListenerKeys_[feature_uid];
+  }
+};
 
 
 /**
- * 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
+ * @inheritDoc
  */
-goog.fs.Error = function(error, action) {
-  /** @type {string} */
-  this.name;
+ol.interaction.Snap.prototype.setMap = function(map) {
+  var currentMap = this.getMap();
+  var keys = this.featuresListenerKeys_;
+  var features = this.getFeatures_();
 
-  /**
-   * @type {goog.fs.Error.ErrorCode}
-   * @deprecated Use the 'name' or 'message' field instead.
-   */
-  this.code;
+  if (currentMap) {
+    keys.forEach(ol.events.unlistenByKey);
+    keys.length = 0;
+    features.forEach(this.forEachFeatureRemove_, this);
+  }
+  ol.interaction.Pointer.prototype.setMap.call(this, map);
 
-  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);
+  if (map) {
+    if (this.features_) {
+      keys.push(
+        ol.events.listen(this.features_, ol.CollectionEventType.ADD,
+            this.handleFeatureAdd_, this),
+        ol.events.listen(this.features_, ol.CollectionEventType.REMOVE,
+            this.handleFeatureRemove_, this)
+      );
+    } else if (this.source_) {
+      keys.push(
+        ol.events.listen(this.source_, ol.source.VectorEventType.ADDFEATURE,
+            this.handleFeatureAdd_, this),
+        ol.events.listen(this.source_, ol.source.VectorEventType.REMOVEFEATURE,
+            this.handleFeatureRemove_, this)
+      );
+    }
+    features.forEach(this.forEachFeatureAdd_, this);
   }
-  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}
+ * @inheritDoc
  */
-goog.fs.Error.ErrorName = {
-  ABORT: 'AbortError',
-  ENCODING: 'EncodingError',
-  INVALID_MODIFICATION: 'InvalidModificationError',
-  INVALID_STATE: 'InvalidStateError',
-  NOT_FOUND: 'NotFoundError',
-  NOT_READABLE: 'NotReadableError',
-  NO_MODIFICATION_ALLOWED: 'NoModificationAllowedError',
-  PATH_EXISTS: 'PathExistsError',
-  QUOTA_EXCEEDED: 'QuotaExceededError',
-  SECURITY: 'SecurityError',
-  SYNTAX: 'SyntaxError',
-  TYPE_MISMATCH: 'TypeMismatchError'
-};
+ol.interaction.Snap.prototype.shouldStopEvent = ol.functions.FALSE;
 
 
 /**
- * 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.
+ * @param {ol.Pixel} pixel Pixel
+ * @param {ol.Coordinate} pixelCoordinate Coordinate
+ * @param {ol.Map} map Map.
+ * @return {ol.SnapResultType} Snap result
  */
-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
+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);
+
+  // If snapping on vertices only, don't consider circles
+  if (this.vertex_ && !this.edge_) {
+    segments = segments.filter(function(segment) {
+      return segment.feature.getGeometry().getType() !==
+          ol.geom.GeometryType.CIRCLE;
+    });
+  }
+
+  var snappedToVertex = false;
+  var snapped = false;
+  var vertex = null;
+  var vertexPixel = null;
+  var dist, pixel1, pixel2, squaredDist1, squaredDist2;
+  if (segments.length > 0) {
+    this.pixelCoordinate_ = pixelCoordinate;
+    segments.sort(this.sortByDistance_);
+    var closestSegment = segments[0].segment;
+    var isCircle = segments[0].feature.getGeometry().getType() ===
+        ol.geom.GeometryType.CIRCLE;
+    if (this.vertex_ && !this.edge_) {
+      pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
+      pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
+      squaredDist1 = ol.coordinate.squaredDistance(pixel, pixel1);
+      squaredDist2 = ol.coordinate.squaredDistance(pixel, pixel2);
+      dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
+      snappedToVertex = dist <= this.pixelTolerance_;
+      if (snappedToVertex) {
+        snapped = true;
+        vertex = squaredDist1 > squaredDist2 ?
+            closestSegment[1] : closestSegment[0];
+        vertexPixel = map.getPixelFromCoordinate(vertex);
+      }
+    } else if (this.edge_) {
+      if (isCircle) {
+        vertex = ol.coordinate.closestOnCircle(pixelCoordinate,
+            /** @type {ol.geom.Circle} */ (segments[0].feature.getGeometry()));
+      } else {
+        vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
+            closestSegment));
+      }
+      vertexPixel = map.getPixelFromCoordinate(vertex);
+      if (ol.coordinate.distance(pixel, vertexPixel) <= this.pixelTolerance_) {
+        snapped = true;
+        if (this.vertex_ && !isCircle) {
+          pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
+          pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
+          squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);
+          squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);
+          dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
+          snappedToVertex = dist <= this.pixelTolerance_;
+          if (snappedToVertex) {
+            vertex = squaredDist1 > squaredDist2 ?
+                closestSegment[1] : closestSegment[0];
+            vertexPixel = map.getPixelFromCoordinate(vertex);
+          }
+        }
+      }
+    }
+    if (snapped) {
+      vertexPixel = [Math.round(vertexPixel[0]), Math.round(vertexPixel[1])];
+    }
+  }
+  return /** @type {ol.SnapResultType} */ ({
+    snapped: snapped,
+    vertex: vertex,
+    vertexPixel: vertexPixel
+  });
 };
 
 
 /**
- * @param {goog.fs.Error.ErrorCode} code
- * @return {string} name
+ * @param {ol.Feature} feature Feature
  * @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;
+ol.interaction.Snap.prototype.updateFeature_ = function(feature) {
+  this.removeFeature(feature, false);
+  this.addFeature(feature, false);
 };
 
 
 /**
- * Returns the code that corresponds to the given name.
- * @param {string} name
- * @return {goog.fs.Error.ErrorCode} code
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Circle} geometry Geometry.
  * @private
  */
-goog.fs.Error.getCodeFromName_ = function(name) {
-  return goog.fs.Error.NameToCodeMap_[name];
+ol.interaction.Snap.prototype.writeCircleGeometry_ = function(feature, geometry) {
+  var polygon = ol.geom.Polygon.fromCircle(geometry);
+  var coordinates = polygon.getCoordinates()[0];
+  var i, ii, segment, segmentData;
+  for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
+    segment = coordinates.slice(i, i + 2);
+    segmentData = /** @type {ol.SnapSegmentDataType} */ ({
+      feature: feature,
+      segment: segment
+    });
+    this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+  }
 };
 
 
 /**
- * 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>}
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.GeometryCollection} geometry Geometry.
+ * @private
  */
-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);
+ol.interaction.Snap.prototype.writeGeometryCollectionGeometry_ = function(feature, geometry) {
+  var i, geometries = geometry.getGeometriesArray();
+  for (i = 0; i < geometries.length; ++i) {
+    var segmentWriter = this.SEGMENT_WRITERS_[geometries[i].getType()];
+    if (segmentWriter) {
+      segmentWriter.call(this, feature, geometries[i]);
+    }
+  }
+};
 
-// 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.
- *
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.LineString} geometry Geometry.
+ * @private
  */
-goog.provide('goog.fs.ProgressEvent');
+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.SnapSegmentDataType} */ ({
+      feature: feature,
+      segment: segment
+    });
+    this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+  }
+};
 
-goog.require('goog.events.Event');
 
+/**
+ * @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.SnapSegmentDataType} */ ({
+        feature: feature,
+        segment: segment
+      });
+      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+    }
+  }
+};
 
 
 /**
- * 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
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiPoint} geometry Geometry.
+ * @private
  */
-goog.fs.ProgressEvent = function(event, target) {
-  goog.fs.ProgressEvent.base(this, 'constructor', event.type, target);
-
-  /**
-   * The underlying event object.
-   * @type {!ProgressEvent}
-   * @private
-   */
-  this.event_ = event;
+ol.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.SnapSegmentDataType} */ ({
+      feature: feature,
+      segment: [coordinates, coordinates]
+    });
+    this.rBush_.insert(geometry.getExtent(), segmentData);
+  }
 };
-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.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiPolygon} geometry Geometry.
+ * @private
  */
-goog.fs.ProgressEvent.prototype.isLengthComputable = function() {
-  return this.event_.lengthComputable;
+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.SnapSegmentDataType} */ ({
+          feature: feature,
+          segment: segment
+        });
+        this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+      }
+    }
+  }
 };
 
 
 /**
- * @return {number} The number of bytes saved so far.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Point} geometry Geometry.
+ * @private
  */
-goog.fs.ProgressEvent.prototype.getLoaded = function() {
-  return this.event_.loaded;
+ol.interaction.Snap.prototype.writePointGeometry_ = function(feature, geometry) {
+  var coordinates = geometry.getCoordinates();
+  var segmentData = /** @type {ol.SnapSegmentDataType} */ ({
+    feature: feature,
+    segment: [coordinates, coordinates]
+  });
+  this.rBush_.insert(geometry.getExtent(), segmentData);
 };
 
 
 /**
- * @return {number} The total number of bytes in the file being saved.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Polygon} geometry Geometry.
+ * @private
  */
-goog.fs.ProgressEvent.prototype.getTotal = function() {
-  return this.event_.total;
+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.SnapSegmentDataType} */ ({
+        feature: feature,
+        segment: segment
+      });
+      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+    }
+  }
 };
 
-// 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.
- *
+ * Handle all pointer events events.
+ * @param {ol.MapBrowserEvent} evt A move event.
+ * @return {boolean} Pass the event to other interactions.
+ * @this {ol.interaction.Snap}
+ * @private
  */
-
-goog.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');
-
+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);
+};
 
 
 /**
- * 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
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Snap}
+ * @private
  */
-goog.fs.FileReader = function() {
-  goog.fs.FileReader.base(this, 'constructor');
+ol.interaction.Snap.handleUpEvent_ = function(evt) {
+  var featuresToUpdate = ol.obj.getValues(this.pendingFeatures_);
+  if (featuresToUpdate.length) {
+    featuresToUpdate.forEach(this.updateFeature_, this);
+    this.pendingFeatures_ = {};
+  }
+  return false;
+};
 
-  /**
-   * 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);
+/**
+ * Sort segments by distance, helper function
+ * @param {ol.SnapSegmentDataType} a The first segment data.
+ * @param {ol.SnapSegmentDataType} b The second segment data.
+ * @return {number} The difference in distance.
+ * @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.inherits(goog.fs.FileReader, goog.events.EventTarget);
+
+goog.provide('ol.interaction.TranslateEventType');
 
 
 /**
- * Possible states for a FileReader.
- *
- * @enum {number}
+ * @enum {string}
  */
-goog.fs.FileReader.ReadyState = {
+ol.interaction.TranslateEventType = {
   /**
-   * The object has been constructed, but there is no pending read.
+   * Triggered upon feature translation start.
+   * @event ol.interaction.Translate.Event#translatestart
+   * @api
    */
-  INIT: 0,
+  TRANSLATESTART: 'translatestart',
   /**
-   * Data is being read.
+   * Triggered upon feature translation.
+   * @event ol.interaction.Translate.Event#translating
+   * @api
    */
-  LOADING: 1,
+  TRANSLATING: 'translating',
   /**
-   * The data has been read from the file, the read was aborted, or an error
-   * occurred.
+   * Triggered upon feature translation end.
+   * @event ol.interaction.Translate.Event#translateend
+   * @api
    */
-  DONE: 2
+  TRANSLATEEND: 'translateend'
 };
 
+goog.provide('ol.interaction.Translate');
+
+goog.require('ol');
+goog.require('ol.Collection');
+goog.require('ol.Object');
+goog.require('ol.events');
+goog.require('ol.events.Event');
+goog.require('ol.functions');
+goog.require('ol.array');
+goog.require('ol.interaction.Pointer');
+goog.require('ol.interaction.Property');
+goog.require('ol.interaction.TranslateEventType');
+
 
 /**
- * Events emitted by a FileReader.
+ * @classdesc
+ * Interaction for translating (moving) features.
  *
- * @enum {string}
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @fires ol.interaction.Translate.Event
+ * @param {olx.interaction.TranslateOptions=} opt_options Options.
+ * @api
  */
-goog.fs.FileReader.EventType = {
-  /**
-   * Emitted when the reading begins. readyState will be LOADING.
-   */
-  LOAD_START: 'loadstart',
+ol.interaction.Translate = function(opt_options) {
+  ol.interaction.Pointer.call(this, {
+    handleDownEvent: ol.interaction.Translate.handleDownEvent_,
+    handleDragEvent: ol.interaction.Translate.handleDragEvent_,
+    handleMoveEvent: ol.interaction.Translate.handleMoveEvent_,
+    handleUpEvent: ol.interaction.Translate.handleUpEvent_
+  });
+
+  var options = opt_options ? opt_options : {};
+
   /**
-   * Emitted when progress has been made in reading the file. readyState will be
-   * LOADING.
+   * The last position we translated to.
+   * @type {ol.Coordinate}
+   * @private
    */
-  PROGRESS: 'progress',
+  this.lastCoordinate_ = null;
+
+
   /**
-   * Emitted when the data has been successfully read. readyState will be
-   * LOADING.
+   * @type {ol.Collection.<ol.Feature>}
+   * @private
    */
-  LOAD: 'load',
+  this.features_ = options.features !== undefined ? options.features : null;
+
+  /** @type {function(ol.layer.Layer): boolean} */
+  var layerFilter;
+  if (options.layers) {
+    if (typeof options.layers === 'function') {
+      layerFilter = options.layers;
+    } else {
+      var layers = options.layers;
+      layerFilter = function(layer) {
+        return ol.array.includes(layers, layer);
+      };
+    }
+  } else {
+    layerFilter = ol.functions.TRUE;
+  }
+
   /**
-   * Emitted when the reading has been aborted. readyState will be LOADING.
+   * @private
+   * @type {function(ol.layer.Layer): boolean}
    */
-  ABORT: 'abort',
+  this.layerFilter_ = layerFilter;
+
   /**
-   * Emitted when an error is encountered or the reading has been aborted.
-   * readyState will be LOADING.
+   * @private
+   * @type {number}
    */
-  ERROR: 'error',
+  this.hitTolerance_ = options.hitTolerance ? options.hitTolerance : 0;
+
   /**
-   * Emitted when the reading is finished, whether successfully or not.
-   * readyState will be DONE.
+   * @type {ol.Feature}
+   * @private
    */
-  LOAD_END: 'loadend'
-};
+  this.lastFeature_ = null;
 
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.interaction.Property.ACTIVE),
+      this.handleActiveChanged_, this);
 
-/**
- * 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');
-  }
 };
+ol.inherits(ol.interaction.Translate, ol.interaction.Pointer);
 
 
 /**
- * @return {goog.fs.FileReader.ReadyState} The current state of the FileReader.
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.Translate}
+ * @private
  */
-goog.fs.FileReader.prototype.getReadyState = function() {
-  return /** @type {goog.fs.FileReader.ReadyState} */ (this.reader_.readyState);
-};
+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);
 
+    var features = this.features_ || new ol.Collection([this.lastFeature_]);
 
-/**
- * @return {*} The result of the file read.
- */
-goog.fs.FileReader.prototype.getResult = function() {
-  return this.reader_.result;
+    this.dispatchEvent(
+        new ol.interaction.Translate.Event(
+            ol.interaction.TranslateEventType.TRANSLATESTART, features,
+            event.coordinate));
+    return true;
+  }
+  return false;
 };
 
 
 /**
- * @return {goog.fs.Error} The error encountered while reading, if any.
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Translate}
+ * @private
  */
-goog.fs.FileReader.prototype.getError = function() {
-  return this.reader_.error &&
-      new goog.fs.Error(this.reader_.error, 'reading file');
+ol.interaction.Translate.handleUpEvent_ = function(event) {
+  if (this.lastCoordinate_) {
+    this.lastCoordinate_ = null;
+    ol.interaction.Translate.handleMoveEvent_.call(this, event);
+
+    var features = this.features_ || new ol.Collection([this.lastFeature_]);
+
+    this.dispatchEvent(
+        new ol.interaction.Translate.Event(
+            ol.interaction.TranslateEventType.TRANSLATEEND, features,
+            event.coordinate));
+    return true;
+  }
+  return false;
 };
 
 
 /**
- * Wrap a progress event emitted by the underlying file reader and re-emit it.
- *
- * @param {!ProgressEvent} event The underlying event.
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @this {ol.interaction.Translate}
  * @private
  */
-goog.fs.FileReader.prototype.dispatchProgressEvent_ = function(event) {
-  this.dispatchEvent(new goog.fs.ProgressEvent(event, this));
-};
+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];
 
+    var features = this.features_ || new ol.Collection([this.lastFeature_]);
+
+    features.forEach(function(feature) {
+      var geom = feature.getGeometry();
+      geom.translate(deltaX, deltaY);
+      feature.setGeometry(geom);
+    });
 
-/** @override */
-goog.fs.FileReader.prototype.disposeInternal = function() {
-  goog.fs.FileReader.base(this, 'disposeInternal');
-  delete this.reader_;
+    this.lastCoordinate_ = newCoordinate;
+    this.dispatchEvent(
+        new ol.interaction.Translate.Event(
+            ol.interaction.TranslateEventType.TRANSLATING, features,
+            newCoordinate));
+  }
 };
 
 
 /**
- * Starts reading a blob as a binary string.
- * @param {!Blob} blob The blob to read.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @this {ol.interaction.Translate}
+ * @private
  */
-goog.fs.FileReader.prototype.readAsBinaryString = function(blob) {
-  this.reader_.readAsBinaryString(blob);
+ol.interaction.Translate.handleMoveEvent_ = function(event) {
+  var elem = event.map.getViewport();
+
+  // Change the cursor to grab/grabbing if hovering any of the features managed
+  // by the interaction
+  if (this.featuresAtPixel_(event.pixel, event.map)) {
+    elem.classList.remove(this.lastCoordinate_ ? 'ol-grab' : 'ol-grabbing');
+    elem.classList.add(this.lastCoordinate_ ? 'ol-grabbing' : 'ol-grab');
+  } else {
+    elem.classList.remove('ol-grab', 'ol-grabbing');
+  }
 };
 
 
 /**
- * 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}.
+ * 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
  */
-goog.fs.FileReader.readAsBinaryString = function(blob) {
-  var reader = new goog.fs.FileReader();
-  var d = goog.fs.FileReader.createDeferred_(reader);
-  reader.readAsBinaryString(blob);
-  return d;
+ol.interaction.Translate.prototype.featuresAtPixel_ = function(pixel, map) {
+  return map.forEachFeatureAtPixel(pixel,
+      function(feature) {
+        if (!this.features_ ||
+            ol.array.includes(this.features_.getArray(), feature)) {
+          return feature;
+        }
+      }.bind(this), {
+        layerFilter: this.layerFilter_,
+        hitTolerance: this.hitTolerance_
+      });
 };
 
 
 /**
- * Starts reading a blob as an array buffer.
- * @param {!Blob} blob The blob to read.
+ * Returns the Hit-detection tolerance.
+ * @returns {number} Hit tolerance in pixels.
+ * @api
  */
-goog.fs.FileReader.prototype.readAsArrayBuffer = function(blob) {
-  this.reader_.readAsArrayBuffer(blob);
+ol.interaction.Translate.prototype.getHitTolerance = function() {
+  return this.hitTolerance_;
 };
 
 
 /**
- * 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}.
+ * Hit-detection tolerance. Pixels inside the radius around the given position
+ * will be checked for features. This only works for the canvas renderer and
+ * not for WebGL.
+ * @param {number} hitTolerance Hit tolerance in pixels.
+ * @api
  */
-goog.fs.FileReader.readAsArrayBuffer = function(blob) {
-  var reader = new goog.fs.FileReader();
-  var d = goog.fs.FileReader.createDeferred_(reader);
-  reader.readAsArrayBuffer(blob);
-  return d;
+ol.interaction.Translate.prototype.setHitTolerance = function(hitTolerance) {
+  this.hitTolerance_ = hitTolerance;
 };
 
 
 /**
- * Starts reading a blob as text.
- * @param {!Blob} blob The blob to read.
- * @param {string=} opt_encoding The name of the encoding to use.
+ * @inheritDoc
  */
-goog.fs.FileReader.prototype.readAsText = function(blob, opt_encoding) {
-  this.reader_.readAsText(blob, opt_encoding);
+ol.interaction.Translate.prototype.setMap = function(map) {
+  var oldMap = this.getMap();
+  ol.interaction.Pointer.prototype.setMap.call(this, map);
+  this.updateState_(oldMap);
 };
 
 
 /**
- * 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}.
+ * @private
  */
-goog.fs.FileReader.readAsText = function(blob, opt_encoding) {
-  var reader = new goog.fs.FileReader();
-  var d = goog.fs.FileReader.createDeferred_(reader);
-  reader.readAsText(blob, opt_encoding);
-  return d;
+ol.interaction.Translate.prototype.handleActiveChanged_ = function() {
+  this.updateState_(null);
 };
 
 
 /**
- * Starts reading a blob as a data URL.
- * @param {!Blob} blob The blob to read.
+ * @param {ol.Map} oldMap Old map.
+ * @private
  */
-goog.fs.FileReader.prototype.readAsDataUrl = function(blob) {
-  this.reader_.readAsDataURL(blob);
+ol.interaction.Translate.prototype.updateState_ = function(oldMap) {
+  var map = this.getMap();
+  var active = this.getActive();
+  if ((!map || !active)) {
+    if (!map) {
+      map = oldMap;
+    }
+
+    var elem = map.getViewport();
+    elem.classList.remove('ol-grab', 'ol-grabbing');
+  }
 };
 
 
 /**
- * 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}.
+ * @classdesc
+ * Events emitted by {@link ol.interaction.Translate} instances are instances of
+ * this type.
+ *
+ * @constructor
+ * @extends {ol.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.
  */
-goog.fs.FileReader.readAsDataUrl = function(blob) {
-  var reader = new goog.fs.FileReader();
-  var d = goog.fs.FileReader.createDeferred_(reader);
-  reader.readAsDataUrl(blob);
-  return d;
-};
+ol.interaction.Translate.Event = function(type, features, coordinate) {
 
+  ol.events.Event.call(this, type);
 
-/**
- * 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;
-};
+  /**
+   * The features being translated.
+   * @type {ol.Collection.<ol.Feature>}
+   * @api
+   */
+  this.features = features;
 
-// FIXME should handle all geo-referenced data, not just vector data
+  /**
+   * The coordinate of the drag event.
+   * @const
+   * @type {ol.Coordinate}
+   * @api
+   */
+  this.coordinate = coordinate;
+};
+ol.inherits(ol.interaction.Translate.Event, ol.events.Event);
 
-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');
+goog.provide('ol.layer.Heatmap');
 
+goog.require('ol.events');
+goog.require('ol');
+goog.require('ol.Object');
+goog.require('ol.dom');
+goog.require('ol.layer.Vector');
+goog.require('ol.math');
+goog.require('ol.obj');
+goog.require('ol.render.EventType');
+goog.require('ol.style.Icon');
+goog.require('ol.style.Style');
 
 
 /**
  * @classdesc
- * Handles input of vector data by drag and drop.
+ * 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.interaction.Interaction}
- * @fires ol.interaction.DragAndDropEvent
- * @param {olx.interaction.DragAndDropOptions=} opt_options Options.
- * @api stable
+ * @extends {ol.layer.Vector}
+ * @fires ol.render.Event
+ * @param {olx.layer.HeatmapOptions=} opt_options Options.
+ * @api
  */
-ol.interaction.DragAndDrop = function(opt_options) {
-
+ol.layer.Heatmap = function(opt_options) {
   var options = opt_options ? opt_options : {};
 
-  goog.base(this, {
-    handleEvent: ol.interaction.DragAndDrop.handleEvent
-  });
+  var baseOptions = ol.obj.assign({}, options);
+
+  delete baseOptions.gradient;
+  delete baseOptions.radius;
+  delete baseOptions.blur;
+  delete baseOptions.shadow;
+  delete baseOptions.weight;
+  ol.layer.Vector.call(this, /** @type {olx.layer.VectorOptions} */ (baseOptions));
 
   /**
    * @private
-   * @type {Array.<function(new: ol.format.Feature)>}
+   * @type {Uint8ClampedArray}
    */
-  this.formatConstructors_ = options.formatConstructors ?
-      options.formatConstructors : [];
+  this.gradient_ = null;
 
   /**
    * @private
-   * @type {ol.proj.Projection}
+   * @type {number}
    */
-  this.projection_ = options.projection ?
-      ol.proj.get(options.projection) : null;
+  this.shadow_ = options.shadow !== undefined ? options.shadow : 250;
 
   /**
    * @private
-   * @type {goog.events.FileDropHandler}
+   * @type {string|undefined}
    */
-  this.fileDropHandler_ = null;
+  this.circleImage_ = undefined;
 
   /**
    * @private
-   * @type {goog.events.Key|undefined}
+   * @type {Array.<Array.<ol.style.Style>>}
    */
-  this.dropListenKey_ = undefined;
+  this.styleCache_ = null;
 
-};
-goog.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction);
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.layer.Heatmap.Property_.GRADIENT),
+      this.handleGradientChanged_, this);
 
+  this.setGradient(options.gradient ?
+      options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT);
 
-/**
- * @inheritDoc
- */
-ol.interaction.DragAndDrop.prototype.disposeInternal = function() {
-  if (this.dropListenKey_) {
-    goog.events.unlistenByKey(this.dropListenKey_);
-  }
-  goog.base(this, 'disposeInternal');
-};
+  this.setBlur(options.blur !== undefined ? options.blur : 15);
 
+  this.setRadius(options.radius !== undefined ? options.radius : 8);
 
-/**
- * @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);
-  }
-};
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.layer.Heatmap.Property_.BLUR),
+      this.handleStyleChanged_, this);
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.layer.Heatmap.Property_.RADIUS),
+      this.handleStyleChanged_, this);
 
+  this.handleStyleChanged_();
 
-/**
- * @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);
-      }
-    }
+  var weight = options.weight ? options.weight : 'weight';
+  var weightFunction;
+  if (typeof weight === 'string') {
+    weightFunction = function(feature) {
+      return feature.get(weight);
+    };
+  } else {
+    weightFunction = weight;
   }
-  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);
-  }
-};
+  this.setStyle(function(feature, resolution) {
+    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;
+  }.bind(this));
 
+  // For performance reasons, don't sort the features before rendering.
+  // The render order is not relevant for a heatmap representation.
+  this.setRenderOrder(null);
 
-/**
- * @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;
-  }
+  ol.events.listen(this, ol.render.EventType.RENDER, this.handleRender_, this);
 };
+ol.inherits(ol.layer.Heatmap, ol.layer.Vector);
 
 
 /**
- * @enum {string}
+ * @const
+ * @type {Array.<string>}
  */
-ol.interaction.DragAndDropEventType = {
-  /**
-   * Triggered when features are added
-   * @event ol.interaction.DragAndDropEvent#addfeatures
-   * @api stable
-   */
-  ADD_FEATURES: 'addfeatures'
-};
-
+ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00'];
 
 
 /**
- * @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.
+ * @param {Array.<string>} colors A list of colored.
+ * @return {Uint8ClampedArray} An array.
+ * @private
  */
-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;
+ol.layer.Heatmap.createGradient_ = function(colors) {
+  var width = 1;
+  var height = 256;
+  var context = ol.dom.createCanvasContext2D(width, height);
 
-  /**
-   * The dropped file.
-   * @type {File}
-   * @api stable
-   */
-  this.file = file;
+  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]);
+  }
 
-  /**
-   * The feature projection.
-   * @type {ol.proj.Projection|undefined}
-   * @api
-   */
-  this.projection = opt_projection;
+  context.fillStyle = gradient;
+  context.fillRect(0, 0, width, height);
 
+  return context.getImageData(0, 0, width, height).data;
 };
-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}
+ * @return {string} Data URL for a circle.
+ * @private
  */
-goog.math.Vec2 = function(x, y) {
-  /**
-   * X-value
-   * @type {number}
-   */
-  this.x = x;
-
-  /**
-   * Y-value
-   * @type {number}
-   */
-  this.y = y;
+ol.layer.Heatmap.prototype.createCircle_ = function() {
+  var radius = this.getRadius();
+  var blur = this.getBlur();
+  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();
 };
-goog.inherits(goog.math.Vec2, goog.math.Coordinate);
 
 
 /**
- * @return {!goog.math.Vec2} A random unit-length vector.
+ * Return the blur size in pixels.
+ * @return {number} Blur size in pixels.
+ * @api
+ * @observable
  */
-goog.math.Vec2.randomUnit = function() {
-  var angle = Math.random() * Math.PI * 2;
-  return new goog.math.Vec2(Math.cos(angle), Math.sin(angle));
+ol.layer.Heatmap.prototype.getBlur = function() {
+  return /** @type {number} */ (this.get(ol.layer.Heatmap.Property_.BLUR));
 };
 
 
 /**
- * @return {!goog.math.Vec2} A random vector inside the unit-disc.
+ * Return the gradient colors as array of strings.
+ * @return {Array.<string>} Colors.
+ * @api
+ * @observable
  */
-goog.math.Vec2.random = function() {
-  var mag = Math.sqrt(Math.random());
-  var angle = Math.random() * Math.PI * 2;
-
-  return new goog.math.Vec2(Math.cos(angle) * mag, Math.sin(angle) * mag);
+ol.layer.Heatmap.prototype.getGradient = function() {
+  return /** @type {Array.<string>} */ (
+      this.get(ol.layer.Heatmap.Property_.GRADIENT));
 };
 
 
 /**
- * Returns a new Vec2 object from a given coordinate.
- * @param {!goog.math.Coordinate} a The coordinate.
- * @return {!goog.math.Vec2} A new vector object.
+ * Return the size of the radius in pixels.
+ * @return {number} Radius size in pixel.
+ * @api
+ * @observable
  */
-goog.math.Vec2.fromCoordinate = function(a) {
-  return new goog.math.Vec2(a.x, a.y);
+ol.layer.Heatmap.prototype.getRadius = function() {
+  return /** @type {number} */ (this.get(ol.layer.Heatmap.Property_.RADIUS));
 };
 
 
 /**
- * @return {!goog.math.Vec2} A new vector with the same coordinates as this one.
- * @override
+ * @private
  */
-goog.math.Vec2.prototype.clone = function() {
-  return new goog.math.Vec2(this.x, this.y);
+ol.layer.Heatmap.prototype.handleGradientChanged_ = function() {
+  this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient());
 };
 
 
 /**
- * Returns the magnitude of the vector measured from the origin.
- * @return {number} The length of the vector.
+ * @private
  */
-goog.math.Vec2.prototype.magnitude = function() {
-  return Math.sqrt(this.x * this.x + this.y * this.y);
+ol.layer.Heatmap.prototype.handleStyleChanged_ = function() {
+  this.circleImage_ = this.createCircle_();
+  this.styleCache_ = new Array(256);
+  this.changed();
 };
 
 
 /**
- * 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.
+ * @param {ol.render.Event} event Post compose event
+ * @private
  */
-goog.math.Vec2.prototype.squaredMagnitude = function() {
-  return this.x * this.x + this.y * this.y;
+ol.layer.Heatmap.prototype.handleRender_ = function(event) {
+  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);
 };
 
 
 /**
- * @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.
+ * Set the blur size in pixels.
+ * @param {number} blur Blur size in pixels.
+ * @api
+ * @observable
  */
-goog.math.Vec2.prototype.invert = function() {
-  this.x = -this.x;
-  this.y = -this.y;
-  return this;
+ol.layer.Heatmap.prototype.setBlur = function(blur) {
+  this.set(ol.layer.Heatmap.Property_.BLUR, blur);
 };
 
 
 /**
- * Normalizes the current vector to have a magnitude of 1.
- * @return {!goog.math.Vec2} The normalized vector.
+ * Set the gradient colors as array of strings.
+ * @param {Array.<string>} colors Gradient.
+ * @api
+ * @observable
  */
-goog.math.Vec2.prototype.normalize = function() {
-  return this.scale(1 / this.magnitude());
+ol.layer.Heatmap.prototype.setGradient = function(colors) {
+  this.set(ol.layer.Heatmap.Property_.GRADIENT, colors);
 };
 
 
 /**
- * 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.
+ * Set the size of the radius in pixels.
+ * @param {number} radius Radius size in pixel.
+ * @api
+ * @observable
  */
-goog.math.Vec2.prototype.add = function(b) {
-  this.x += b.x;
-  this.y += b.y;
-  return this;
+ol.layer.Heatmap.prototype.setRadius = function(radius) {
+  this.set(ol.layer.Heatmap.Property_.RADIUS, radius);
 };
 
 
 /**
- * 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.
+ * @enum {string}
+ * @private
  */
-goog.math.Vec2.prototype.subtract = function(b) {
-  this.x -= b.x;
-  this.y -= b.y;
-  return this;
+ol.layer.Heatmap.Property_ = {
+  BLUR: 'blur',
+  GRADIENT: 'gradient',
+  RADIUS: 'radius'
 };
 
+goog.provide('ol.renderer.canvas.IntermediateCanvas');
 
-/**
- * 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;
-};
+goog.require('ol');
+goog.require('ol.coordinate');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.renderer.canvas.Layer');
+goog.require('ol.transform');
 
 
 /**
- * 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.
+ * @constructor
+ * @abstract
+ * @extends {ol.renderer.canvas.Layer}
+ * @param {ol.layer.Layer} layer Layer.
  */
-goog.math.Vec2.rotateAroundPoint = function(v, axisPoint, angle) {
-  var res = v.clone();
-  return res.subtract(axisPoint).rotate(angle).add(axisPoint);
-};
+ol.renderer.canvas.IntermediateCanvas = function(layer) {
 
+  ol.renderer.canvas.Layer.call(this, layer);
+
+  /**
+   * @protected
+   * @type {ol.Transform}
+   */
+  this.coordinateToCanvasPixelTransform = ol.transform.create();
+
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.hitCanvasContext_ = null;
 
-/**
- * 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;
 };
+ol.inherits(ol.renderer.canvas.IntermediateCanvas, ol.renderer.canvas.Layer);
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.math.Vec2.distance = goog.math.Coordinate.distance;
+ol.renderer.canvas.IntermediateCanvas.prototype.composeFrame = function(frameState, layerState, context) {
 
+  this.preCompose(context, frameState);
 
-/**
- * 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;
+  var image = this.getImage();
+  if (image) {
 
+    // clipped rendering if layer extent is set
+    var extent = layerState.extent;
+    var clipped = extent !== undefined &&
+        !ol.extent.containsExtent(extent, frameState.extent) &&
+        ol.extent.intersects(extent, frameState.extent);
+    if (clipped) {
+      this.clip(context, frameState, /** @type {ol.Extent} */ (extent));
+    }
 
-/**
- * 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;
+    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
+    var dx = imageTransform[4];
+    var dy = imageTransform[5];
+    var dw = image.width * imageTransform[0];
+    var dh = image.height * imageTransform[3];
+    context.drawImage(image, 0, 0, +image.width, +image.height,
+        Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh));
+    context.globalAlpha = alpha;
 
+    if (clipped) {
+      context.restore();
+    }
+  }
 
-/**
- * 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);
+  this.postCompose(context, frameState, layerState);
 };
 
 
 /**
- * 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.
+ * @abstract
+ * @return {HTMLCanvasElement|HTMLVideoElement|Image} Canvas.
  */
-goog.math.Vec2.difference = function(a, b) {
-  return new goog.math.Vec2(a.x - b.x, a.y - b.y);
-};
+ol.renderer.canvas.IntermediateCanvas.prototype.getImage = function() {};
 
 
 /**
- * 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.
+ * @abstract
+ * @return {!ol.Transform} Image transform.
  */
-goog.math.Vec2.dot = function(a, b) {
-  return a.x * b.x + a.y * b.y;
-};
+ol.renderer.canvas.IntermediateCanvas.prototype.getImageTransform = function() {};
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-goog.math.Vec2.determinant = function(a, b) {
-  return a.x * b.y - a.y * b.x;
+ol.renderer.canvas.IntermediateCanvas.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, 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, hitTolerance, skippedFeatureUids,
+      /**
+       * @param {ol.Feature|ol.render.Feature} feature Feature.
+       * @return {?} Callback result.
+       */
+      function(feature) {
+        return callback.call(thisArg, feature, layer);
+      });
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-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));
+ol.renderer.canvas.IntermediateCanvas.prototype.forEachLayerAtCoordinate = function(coordinate, frameState, callback, thisArg) {
+  if (!this.getImage()) {
+    return undefined;
+  }
+
+  if (this.getLayer().getSource().forEachFeatureAtCoordinate !== ol.nullFunction) {
+    // for ImageVector sources use the original hit-detection logic,
+    // so that for example also transparent polygons are detected
+    return ol.renderer.canvas.Layer.prototype.forEachLayerAtCoordinate.apply(this, arguments);
+  } else {
+    var pixel = ol.transform.apply(this.coordinateToCanvasPixelTransform, coordinate.slice());
+    ol.coordinate.scale(pixel, frameState.viewState.resolution / this.renderedResolution);
+
+    if (!this.hitCanvasContext_) {
+      this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1);
+    }
+
+    this.hitCanvasContext_.clearRect(0, 0, 1, 1);
+    this.hitCanvasContext_.drawImage(this.getImage(), pixel[0], pixel[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(),  imageData);
+    } else {
+      return undefined;
+    }
+  }
 };
 
-goog.provide('ol.interaction.DragRotateAndZoom');
+goog.provide('ol.renderer.canvas.ImageLayer');
 
-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');
-
+goog.require('ol.extent');
+goog.require('ol.renderer.canvas.IntermediateCanvas');
+goog.require('ol.transform');
 
 
 /**
- * @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
+ * @extends {ol.renderer.canvas.IntermediateCanvas}
+ * @param {ol.layer.Image} imageLayer Single image layer.
  */
-ol.interaction.DragRotateAndZoom = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
+ol.renderer.canvas.ImageLayer = function(imageLayer) {
 
-  goog.base(this, {
-    handleDownEvent: ol.interaction.DragRotateAndZoom.handleDownEvent_,
-    handleDragEvent: ol.interaction.DragRotateAndZoom.handleDragEvent_,
-    handleUpEvent: ol.interaction.DragRotateAndZoom.handleUpEvent_
-  });
+  ol.renderer.canvas.IntermediateCanvas.call(this, imageLayer);
 
   /**
    * @private
-   * @type {ol.events.ConditionType}
+   * @type {?ol.ImageBase}
    */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.shiftKeyOnly;
+  this.image_ = null;
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {ol.Transform}
    */
-  this.lastAngle_ = undefined;
+  this.imageTransform_ = ol.transform.create();
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.lastMagnitude_ = undefined;
+};
+ol.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.IntermediateCanvas);
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.lastScaleDelta_ = 0;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration ? options.duration : 400;
+/**
+ * @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_;
 };
-goog.inherits(ol.interaction.DragRotateAndZoom, ol.interaction.Pointer);
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.DragRotateAndZoom}
- * @private
+ * @inheritDoc
  */
-ol.interaction.DragRotateAndZoom.handleDragEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return;
-  }
+ol.renderer.canvas.ImageLayer.prototype.prepareFrame = function(frameState, layerState) {
 
-  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);
+  var pixelRatio = frameState.pixelRatio;
+  var size = frameState.size;
+  var viewState = frameState.viewState;
+  var viewCenter = viewState.center;
+  var viewResolution = viewState.resolution;
+
+  var image;
+  var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer());
+  var imageSource = imageLayer.getSource();
+
+  var hints = frameState.viewHints;
+
+  var renderedExtent = frameState.extent;
+  if (layerState.extent !== undefined) {
+    renderedExtent = ol.extent.getIntersection(
+        renderedExtent, layerState.extent);
   }
-  this.lastAngle_ = theta;
-  if (this.lastMagnitude_ !== undefined) {
-    var resolution = this.lastMagnitude_ * (view.getResolution() / magnitude);
-    ol.interaction.Interaction.zoomWithoutConstraints(map, view, resolution);
+
+  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) {
+        projection = sourceProjection;
+      }
+    }
+    image = imageSource.getImage(
+        renderedExtent, viewResolution, pixelRatio, projection);
+    if (image) {
+      var loaded = this.loadImage(image);
+      if (loaded) {
+        this.image_ = image;
+      }
+    }
   }
-  if (this.lastMagnitude_ !== undefined) {
-    this.lastScaleDelta_ = this.lastMagnitude_ / magnitude;
+
+  if (this.image_) {
+    image = this.image_;
+    var imageExtent = image.getExtent();
+    var imageResolution = image.getResolution();
+    var imagePixelRatio = image.getPixelRatio();
+    var scale = pixelRatio * imageResolution /
+        (viewResolution * imagePixelRatio);
+    var transform = ol.transform.compose(this.imageTransform_,
+        pixelRatio * size[0] / 2, pixelRatio * size[1] / 2,
+        scale, scale,
+        0,
+        imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution,
+        imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution);
+    ol.transform.compose(this.coordinateToCanvasPixelTransform,
+        pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5],
+        pixelRatio / viewResolution, -pixelRatio / viewResolution,
+        0,
+        -viewCenter[0], -viewCenter[1]);
+
+    this.updateAttributions(frameState.attributions, image.getAttributions());
+    this.updateLogos(frameState, imageSource);
+    this.renderedResolution = viewResolution * pixelRatio / imagePixelRatio;
   }
-  this.lastMagnitude_ = magnitude;
+
+  return !!this.image_;
 };
 
+goog.provide('ol.reproj');
+
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.math');
+goog.require('ol.proj');
+
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.DragRotateAndZoom}
- * @private
+ * 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.interaction.DragRotateAndZoom.handleUpEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return true;
+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 =
+      ol.proj.getPointResolution(targetProj, targetResolution, targetCenter);
+
+  var targetMetersPerUnit = targetProj.getMetersPerUnit();
+  if (targetMetersPerUnit !== undefined) {
+    sourceResolution *= targetMetersPerUnit;
+  }
+  var sourceMetersPerUnit = sourceProj.getMetersPerUnit();
+  if (sourceMetersPerUnit !== undefined) {
+    sourceResolution /= sourceMetersPerUnit;
   }
 
-  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;
+  // 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 =
+      ol.proj.getPointResolution(sourceProj, sourceResolution, sourceCenter) /
+      sourceResolution;
+
+  if (isFinite(compensationFactor) && compensationFactor > 0) {
+    sourceResolution /= compensationFactor;
+  }
+
+  return sourceResolution;
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.DragRotateAndZoom}
+ * 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.interaction.DragRotateAndZoom.handleDownEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return false;
-  }
+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)];
+};
 
-  if (this.condition_(mapBrowserEvent)) {
-    mapBrowserEvent.map.getView().setHint(ol.ViewHint.INTERACTING, 1);
-    this.lastAngle_ = undefined;
-    this.lastMagnitude_ = undefined;
-    return true;
-  } else {
-    return false;
+
+/**
+ * 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 {number} gutter Gutter of the 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, gutter, opt_renderEdges) {
+
+  var context = ol.dom.createCanvasContext2D(Math.round(pixelRatio * width),
+                                             Math.round(pixelRatio * height));
+
+  if (sources.length === 0) {
+    return context.canvas;
   }
-};
 
-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');
+  context.scale(pixelRatio, pixelRatio);
 
-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');
+  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));
+
+  var stitchScale = pixelRatio / sourceResolution;
+
+  sources.forEach(function(src, i, arr) {
+    var xPos = src.extent[0] - sourceDataExtent[0];
+    var yPos = -(src.extent[3] - sourceDataExtent[3]);
+    var srcWidth = ol.extent.getWidth(src.extent);
+    var srcHeight = ol.extent.getHeight(src.extent);
+
+    stitchContext.drawImage(
+        src.image,
+        gutter, gutter,
+        src.image.width - 2 * gutter, src.image.height - 2 * gutter,
+        xPos * stitchScale, yPos * stitchScale,
+        srcWidth * stitchScale, srcHeight * stitchScale);
+  });
 
-/**
- * @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'
-};
+  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();
+    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(p1[0], p1[1]);
+    context.lineTo(p0[0], p0[1]);
+    context.lineTo(p2[0], p2[1]);
+    context.clip();
 
-/**
- * @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) {
+    context.transform(
+        affineCoefs[0], affineCoefs[2], affineCoefs[1], affineCoefs[3], u0, v0);
 
-  goog.base(this, type);
+    context.translate(sourceDataExtent[0] - sourceNumericalShiftX,
+                      sourceDataExtent[3] - sourceNumericalShiftY);
 
-  /**
-   * The feature being drawn.
-   * @type {ol.Feature}
-   * @api stable
-   */
-  this.feature = feature;
+    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(u1, v1);
+      context.lineTo(u0, v0);
+      context.lineTo(u2, v2);
+      context.closePath();
+      context.stroke();
+    });
 
+    context.restore();
+  }
+  return context.canvas;
 };
-goog.inherits(ol.interaction.DrawEvent, goog.events.Event);
 
+goog.provide('ol.reproj.Triangulation');
+
+goog.require('ol');
+goog.require('ol.extent');
+goog.require('ol.math');
+goog.require('ol.proj');
 
 
 /**
  * @classdesc
- * Interaction for drawing feature geometries.
- *
+ * 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
- * @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_
-  });
+ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent,
+    maxSourceExtent, errorThreshold) {
 
   /**
-   * @type {ol.Pixel}
+   * @type {ol.proj.Projection}
    * @private
    */
-  this.downPx_ = null;
+  this.sourceProj_ = sourceProj;
 
   /**
-   * @type {boolean}
+   * @type {ol.proj.Projection}
    * @private
    */
-  this.freehand_ = false;
+  this.targetProj_ = targetProj;
+
+  /** @type {!Object.<string, ol.Coordinate>} */
+  var transformInvCache = {};
+  var transformInv = ol.proj.getTransform(this.targetProj_, this.sourceProj_);
 
   /**
-   * Target source for drawn features.
-   * @type {ol.source.Vector}
+   * @param {ol.Coordinate} c A coordinate.
+   * @return {ol.Coordinate} Transformed coordinate.
    * @private
    */
-  this.source_ = options.source ? options.source : null;
+  this.transformInv_ = function(c) {
+    var key = c[0] + '/' + c[1];
+    if (!transformInvCache[key]) {
+      transformInvCache[key] = transformInv(c);
+    }
+    return transformInvCache[key];
+  };
 
   /**
-   * Target collection for drawn features.
-   * @type {ol.Collection.<ol.Feature>}
+   * @type {ol.Extent}
    * @private
    */
-  this.features_ = options.features ? options.features : null;
+  this.maxSourceExtent_ = maxSourceExtent;
 
   /**
-   * Pixel distance for snapping.
    * @type {number}
    * @private
    */
-  this.snapTolerance_ = options.snapTolerance ? options.snapTolerance : 12;
+  this.errorThresholdSquared_ = errorThreshold * errorThreshold;
 
   /**
-   * Geometry type.
-   * @type {ol.geom.GeometryType}
+   * @type {Array.<ol.ReprojTriangle>}
    * @private
    */
-  this.type_ = options.type;
+  this.triangles_ = [];
 
   /**
-   * Drawing mode (derived from geometry type.
-   * @type {ol.interaction.DrawMode}
+   * Indicates that the triangulation crosses edge of the source projection.
+   * @type {boolean}
    * @private
    */
-  this.mode_ = ol.interaction.Draw.getMode_(this.type_);
+  this.wrapsXInSource_ = false;
 
   /**
-   * 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}
+   * @type {boolean}
    * @private
    */
-  this.minPoints_ = options.minPoints ?
-      options.minPoints :
-      (this.mode_ === ol.interaction.DrawMode.POLYGON ? 3 : 2);
+  this.canWrapXInSource_ = this.sourceProj_.canWrapX() &&
+      !!maxSourceExtent &&
+      !!this.sourceProj_.getExtent() &&
+      (ol.extent.getWidth(maxSourceExtent) ==
+       ol.extent.getWidth(this.sourceProj_.getExtent()));
 
   /**
-   * The number of points that can be drawn before a polygon ring or line string
-   * is finished. The default is no restriction.
-   * @type {number}
+   * @type {?number}
    * @private
    */
-  this.maxPoints_ = options.maxPoints ? options.maxPoints : Infinity;
+  this.sourceWorldWidth_ = this.sourceProj_.getExtent() ?
+      ol.extent.getWidth(this.sourceProj_.getExtent()) : null;
 
-  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);
+  /**
+   * @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_) {
+    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_;
         }
-        return geometry;
-      };
+        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 The target a coordinate.
+ * @param {ol.Coordinate} b The target b coordinate.
+ * @param {ol.Coordinate} c The target c coordinate.
+ * @param {ol.Coordinate} aSrc The source a coordinate.
+ * @param {ol.Coordinate} bSrc The source b coordinate.
+ * @param {ol.Coordinate} cSrc The source c coordinate.
+ * @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 The target a coordinate.
+ * @param {ol.Coordinate} b The target b coordinate.
+ * @param {ol.Coordinate} c The target c coordinate.
+ * @param {ol.Coordinate} d The target d coordinate.
+ * @param {ol.Coordinate} aSrc The source a coordinate.
+ * @param {ol.Coordinate} bSrc The source b coordinate.
+ * @param {ol.Coordinate} cSrc The source c coordinate.
+ * @param {ol.Coordinate} dSrc The source d coordinate.
+ * @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;
+  var sourceWorldWidth = /** @type {number} */ (this.sourceWorldWidth_);
+
+  // 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) {
+        var centerSrcEstimX =
+            (ol.math.modulo(aSrc[0], sourceWorldWidth) +
+             ol.math.modulo(cSrc[0], sourceWorldWidth)) / 2;
+        dx = centerSrcEstimX -
+            ol.math.modulo(centerSrc[0], 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;
   }
 
-  /**
-   * @type {ol.interaction.DrawGeometryFunctionType}
-   * @private
-   */
-  this.geometryFunction_ = geometryFunction;
+  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.ReprojTriangle>} Array of the calculated triangles.
+ */
+ol.reproj.Triangulation.prototype.getTriangles = function() {
+  return this.triangles_;
+};
+
+goog.provide('ol.reproj.Image');
 
-  /**
-   * Finish coordinate for the feature (first point for polygons, last point for
-   * linestrings).
-   * @type {ol.Coordinate}
-   * @private
-   */
-  this.finishCoordinate_ = null;
+goog.require('ol');
+goog.require('ol.ImageBase');
+goog.require('ol.ImageState');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
+goog.require('ol.reproj');
+goog.require('ol.reproj.Triangulation');
 
-  /**
-   * Sketch feature.
-   * @type {ol.Feature}
-   * @private
-   */
-  this.sketchFeature_ = null;
+
+/**
+ * @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.ReprojImageFunctionType} getImageFunction
+ *     Function returning source images (extent, resolution, pixelRatio).
+ */
+ol.reproj.Image = function(sourceProj, targetProj,
+    targetExtent, targetResolution, pixelRatio, getImageFunction) {
 
   /**
-   * Sketch point.
-   * @type {ol.Feature}
    * @private
+   * @type {ol.proj.Projection}
    */
-  this.sketchPoint_ = null;
+  this.targetProj_ = targetProj;
 
   /**
-   * Sketch coordinates. Used when drawing a line or polygon.
-   * @type {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>}
    * @private
+   * @type {ol.Extent}
    */
-  this.sketchCoords_ = null;
+  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;
 
   /**
-   * Sketch line. Used when drawing polygon.
-   * @type {ol.Feature}
    * @private
+   * @type {!ol.reproj.Triangulation}
    */
-  this.sketchLine_ = null;
+  this.triangulation_ = new ol.reproj.Triangulation(
+      sourceProj, targetProj, limitedTargetExtent, this.maxSourceExtent_,
+      sourceResolution * errorThresholdInPixels);
 
   /**
-   * Sketch line coordinates. Used when drawing a polygon or circle.
-   * @type {Array.<ol.Coordinate>}
    * @private
+   * @type {number}
    */
-  this.sketchLineCoords_ = null;
+  this.targetResolution_ = targetResolution;
 
   /**
-   * 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
+   * @type {ol.Extent}
    */
-  this.squaredClickTolerance_ = options.clickTolerance ?
-      options.clickTolerance * options.clickTolerance : 36;
+  this.targetExtent_ = targetExtent;
+
+  var sourceExtent = this.triangulation_.calculateSourceExtent();
 
   /**
-   * Draw overlay where our sketch features are drawn.
-   * @type {ol.layer.Vector}
    * @private
+   * @type {ol.ImageBase}
    */
-  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()
-  });
+  this.sourceImage_ =
+      getImageFunction(sourceExtent, sourceResolution, pixelRatio);
 
   /**
-   * Name of the geometry attribute for newly created features.
-   * @type {string|undefined}
    * @private
+   * @type {number}
    */
-  this.geometryName_ = options.geometryName;
+  this.sourcePixelRatio_ =
+      this.sourceImage_ ? this.sourceImage_.getPixelRatio() : 1;
 
   /**
    * @private
-   * @type {ol.events.ConditionType}
+   * @type {HTMLCanvasElement}
    */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.noModifierKeys;
+  this.canvas_ = null;
 
   /**
    * @private
-   * @type {ol.events.ConditionType}
+   * @type {?ol.EventsKey}
    */
-  this.freehandCondition_ = options.freehandCondition ?
-      options.freehandCondition : ol.events.condition.shiftKeyOnly;
+  this.sourceListenerKey_ = null;
 
-  goog.events.listen(this,
-      ol.Object.getChangeEventType(ol.interaction.InteractionProperty.ACTIVE),
-      this.updateState_, false, this);
 
-};
-goog.inherits(ol.interaction.Draw, ol.interaction.Pointer);
+  var state = ol.ImageState.LOADED;
+  var attributions = [];
 
+  if (this.sourceImage_) {
+    state = ol.ImageState.IDLE;
+    attributions = this.sourceImage_.getAttributions();
+  }
 
-/**
- * @return {ol.style.StyleFunction} Styles.
- */
-ol.interaction.Draw.getDefaultStyleFunction = function() {
-  var styles = ol.style.createDefaultEditingStyles();
-  return function(feature, resolution) {
-    return styles[feature.getGeometry().getType()];
-  };
+  ol.ImageBase.call(this, targetExtent, targetResolution, this.sourcePixelRatio_,
+            state, attributions);
 };
+ol.inherits(ol.reproj.Image, ol.ImageBase);
 
 
 /**
  * @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;
+ol.reproj.Image.prototype.disposeInternal = function() {
+  if (this.state == ol.ImageState.LOADING) {
+    this.unlistenSource_();
   }
-  return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) && pass;
+  ol.ImageBase.prototype.disposeInternal.call(this);
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.Draw}
- * @private
+ * @inheritDoc
  */
-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;
-  }
+ol.reproj.Image.prototype.getImage = function(opt_context) {
+  return this.canvas_;
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Draw}
- * @private
+ * @return {ol.proj.Projection} Projection.
  */
-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;
+ol.reproj.Image.prototype.getProjection = function() {
+  return this.targetProj_;
 };
 
 
 /**
- * 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;
-};
+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_;
 
-
-/**
- * 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;
-        }
-      }
-    }
+    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()
+        }], 0);
   }
-  return at;
+  this.state = sourceState;
+  this.changed();
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} event Event.
- * @private
+ * @inheritDoc
  */
-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);
-  }
-};
-
+ol.reproj.Image.prototype.load = function() {
+  if (this.state == ol.ImageState.IDLE) {
+    this.state = ol.ImageState.LOADING;
+    this.changed();
 
-/**
- * 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_;
+    var sourceState = this.sourceImage_.getState();
+    if (sourceState == ol.ImageState.LOADED ||
+        sourceState == ol.ImageState.ERROR) {
+      this.reproject_();
+    } else {
+      this.sourceListenerKey_ = ol.events.listen(this.sourceImage_,
+          ol.events.EventType.CHANGE, function(e) {
+            var sourceState = this.sourceImage_.getState();
+            if (sourceState == ol.ImageState.LOADED ||
+                sourceState == ol.ImageState.ERROR) {
+              this.unlistenSource_();
+              this.reproject_();
+            }
+          }, this);
+      this.sourceImage_.load();
     }
   }
-  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_();
+ol.reproj.Image.prototype.unlistenSource_ = function() {
+  ol.events.unlistenByKey(/** @type {!ol.EventsKey} */ (this.sourceListenerKey_));
+  this.sourceListenerKey_ = null;
 };
 
+goog.provide('ol.source.Image');
 
-/**
- * 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();
-  }
-};
+goog.require('ol');
+goog.require('ol.ImageState');
+goog.require('ol.array');
+goog.require('ol.events.Event');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.reproj.Image');
+goog.require('ol.source.Source');
 
 
 /**
- * Remove last point of the feature currently being drawn.
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for sources providing a single image.
+ *
+ * @constructor
+ * @abstract
+ * @extends {ol.source.Source}
+ * @param {ol.SourceImageOptions} options Single image source options.
  * @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_();
-};
+ol.source.Image = function(options) {
+  ol.source.Source.call(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;
 
-/**
- * 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]));
-  }
+  /**
+   * @private
+   * @type {ol.reproj.Image}
+   */
+  this.reprojectedImage_ = null;
 
-  // 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);
-  }
+  /**
+   * @private
+   * @type {number}
+   */
+  this.reprojectedRevision_ = 0;
 };
+ol.inherits(ol.source.Image, ol.source.Source);
 
 
 /**
- * Stop drawing without adding the sketch feature to the target layer.
- * @return {ol.Feature} The sketch feature (or null if none).
- * @private
+ * @return {Array.<number>} Resolutions.
+ * @override
  */
-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;
+ol.source.Image.prototype.getResolutions = function() {
+  return this.resolutions_;
 };
 
 
 /**
- * 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
+ * @protected
+ * @param {number} resolution Resolution.
+ * @return {number} Resolution.
  */
-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_));
+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;
 };
 
 
 /**
- * @inheritDoc
- */
-ol.interaction.Draw.prototype.shouldStopEvent = goog.functions.FALSE;
-
-
-/**
- * Redraw the sketch features.
- * @private
+ * @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.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);
-};
+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,
+        function(extent, resolution, pixelRatio) {
+          return this.getImageInternal(extent, resolution,
+              pixelRatio, sourceProjection);
+        }.bind(this));
+    this.reprojectedRevision_ = this.getRevision();
 
-/**
- * @private
- */
-ol.interaction.Draw.prototype.updateState_ = function() {
-  var map = this.getMap();
-  var active = this.getActive();
-  if (!map || !active) {
-    this.abortDrawing_();
+    return this.reprojectedImage_;
   }
-  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
+ * @abstract
+ * @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.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;
-      }
-  );
-};
+ol.source.Image.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {};
 
 
 /**
- * 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
+ * Handle image change events.
+ * @param {ol.events.Event} event Event.
+ * @protected
  */
-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;
+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.Image.Event(ol.source.Image.EventType_.IMAGELOADSTART,
+              image));
+      break;
+    case ol.ImageState.LOADED:
+      this.dispatchEvent(
+          new ol.source.Image.Event(ol.source.Image.EventType_.IMAGELOADEND,
+              image));
+      break;
+    case ol.ImageState.ERROR:
+      this.dispatchEvent(
+          new ol.source.Image.Event(ol.source.Image.EventType_.IMAGELOADERROR,
+              image));
+      break;
+    default:
+      // pass
   }
-  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
+ * Default image load function for image sources that use ol.Image image
+ * instances.
+ * @param {ol.Image} image Image.
+ * @param {string} src Source.
  */
-ol.interaction.DrawGeometryFunctionType;
+ol.source.Image.defaultImageLoadFunction = function(image, src) {
+  image.getImage().src = src;
+};
 
 
 /**
- * Draw mode.  This collapses multi-part geometry types with their single-part
- * cousins.
- * @enum {string}
+ * @classdesc
+ * Events emitted by {@link ol.source.Image} instances are instances of this
+ * type.
+ *
+ * @constructor
+ * @extends {ol.events.Event}
+ * @implements {oli.source.ImageEvent}
+ * @param {string} type Type.
+ * @param {ol.Image} image The image.
  */
-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');
+ol.source.Image.Event = function(type, image) {
 
+  ol.events.Event.call(this, type);
 
-/**
- * @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
+   * The image related to the event.
+   * @type {ol.Image}
    * @api
    */
-  MODIFYEND: 'modifyend'
-};
+  this.image = image;
 
+};
+ol.inherits(ol.source.Image.Event, ol.events.Event);
 
 
 /**
- * @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}.
+ * @enum {string}
+ * @private
  */
-ol.interaction.ModifyEvent = function(type, features, mapBrowserPointerEvent) {
+ol.source.Image.EventType_ = {
 
-  goog.base(this, type);
+  /**
+   * Triggered when an image starts loading.
+   * @event ol.source.Image.Event#imageloadstart
+   * @api
+   */
+  IMAGELOADSTART: 'imageloadstart',
 
   /**
-   * The features being modified.
-   * @type {ol.Collection.<ol.Feature>}
+   * Triggered when an image finishes loading.
+   * @event ol.source.Image.Event#imageloadend
    * @api
    */
-  this.features = features;
+  IMAGELOADEND: 'imageloadend',
 
   /**
-   * Associated {@link ol.MapBrowserPointerEvent}.
-   * @type {ol.MapBrowserPointerEvent}
+   * Triggered if image loading results in an error.
+   * @event ol.source.Image.Event#imageloaderror
    * @api
    */
-  this.mapBrowserPointerEvent = mapBrowserPointerEvent;
-};
-goog.inherits(ol.interaction.ModifyEvent, goog.events.Event);
+  IMAGELOADERROR: 'imageloaderror'
 
+};
 
-/**
- * @typedef {{depth: (Array.<number>|undefined),
- *            feature: ol.Feature,
- *            geometry: ol.geom.SimpleGeometry,
- *            index: (number|undefined),
- *            segment: Array.<ol.Extent>}}
- */
-ol.interaction.SegmentDataType;
+goog.provide('ol.source.ImageCanvas');
 
+goog.require('ol');
+goog.require('ol.ImageCanvas');
+goog.require('ol.extent');
+goog.require('ol.source.Image');
 
 
 /**
  * @classdesc
- * Interaction for modifying feature geometries.
+ * Base class for image sources where a canvas element is the image.
  *
  * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.ModifyOptions} options Options.
- * @fires ol.interaction.ModifyEvent
+ * @extends {ol.source.Image}
+ * @param {olx.source.ImageCanvasOptions} options Constructor options.
  * @api
  */
-ol.interaction.Modify = function(options) {
+ol.source.ImageCanvas = 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_
+  ol.source.Image.call(this, {
+    attributions: options.attributions,
+    logo: options.logo,
+    projection: options.projection,
+    resolutions: options.resolutions,
+    state: options.state
   });
 
   /**
-   * @type {ol.events.ConditionType}
    * @private
+   * @type {ol.CanvasFunctionType}
    */
-  this.deleteCondition_ = options.deleteCondition ?
-      options.deleteCondition :
-      /** @type {ol.events.ConditionType} */ (goog.functions.and(
-          ol.events.condition.noModifierKeys,
-          ol.events.condition.singleClick));
+  this.canvasFunction_ = options.canvasFunction;
 
   /**
-   * Editing vertex.
-   * @type {ol.Feature}
    * @private
+   * @type {ol.ImageCanvas}
    */
-  this.vertexFeature_ = null;
+  this.canvas_ = null;
 
   /**
-   * Segments intersecting {@link this.vertexFeature_} by segment uid.
-   * @type {Object.<string, boolean>}
    * @private
+   * @type {number}
    */
-  this.vertexSegments_ = null;
+  this.renderedRevision_ = 0;
 
   /**
-   * @type {ol.Pixel}
    * @private
+   * @type {number}
    */
-  this.lastPixel_ = [0, 0];
+  this.ratio_ = options.ratio !== undefined ?
+      options.ratio : 1.5;
 
-  /**
-   * Tracks if the next `singleclick` event should be ignored to prevent
-   * accidental deletion right after vertex creation.
-   * @type {boolean}
-   * @private
-   */
-  this.ignoreNextSingleClick_ = false;
+};
+ol.inherits(ol.source.ImageCanvas, ol.source.Image);
 
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.modified_ = false;
+
+/**
+ * @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.source.ImageVector');
+
+goog.require('ol');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
+goog.require('ol.render.canvas.ReplayGroup');
+goog.require('ol.renderer.vector');
+goog.require('ol.source.ImageCanvas');
+goog.require('ol.style.Style');
+goog.require('ol.transform');
+
+
+/**
+ * @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) {
 
   /**
-   * Segment RTree for each layer
-   * @type {ol.structs.RBush.<ol.interaction.SegmentDataType>}
    * @private
+   * @type {ol.source.Vector}
    */
-  this.rBush_ = new ol.structs.RBush();
+  this.source_ = options.source;
 
   /**
-   * @type {number}
    * @private
+   * @type {ol.Transform}
    */
-  this.pixelTolerance_ = options.pixelTolerance !== undefined ?
-      options.pixelTolerance : 10;
+  this.transform_ = ol.transform.create();
 
   /**
-   * @type {boolean}
    * @private
+   * @type {CanvasRenderingContext2D}
    */
-  this.snappedToVertex_ = false;
+  this.canvasContext_ = ol.dom.createCanvasContext2D();
 
   /**
-   * Indicate whether the interaction is currently changing a feature's
-   * coordinates.
-   * @type {boolean}
    * @private
+   * @type {ol.Size}
    */
-  this.changingFeature_ = false;
+  this.canvasSize_ = [0, 0];
 
   /**
-   * @type {Array}
    * @private
+   * @type {number}
    */
-  this.dragSegments_ = null;
+  this.renderBuffer_ = options.renderBuffer == undefined ? 100 : options.renderBuffer;
 
   /**
-   * Draw overlay where sketch features are drawn.
-   * @type {ol.layer.Vector}
    * @private
+   * @type {ol.render.canvas.ReplayGroup}
    */
-  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
+  this.replayGroup_ = null;
+
+  ol.source.ImageCanvas.call(this, {
+    attributions: options.attributions,
+    canvasFunction: this.canvasFunctionInternal_.bind(this),
+    logo: options.logo,
+    projection: options.projection,
+    ratio: options.ratio,
+    resolutions: options.resolutions,
+    state: this.source_.getState()
   });
 
   /**
-  * @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_
-  };
+   * User provided style.
+   * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
+   * @private
+   */
+  this.style_ = null;
 
   /**
-   * @type {ol.Collection.<ol.Feature>}
+   * Style function for use within the library.
+   * @type {ol.StyleFunction|undefined}
    * @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);
+  this.styleFunction_ = undefined;
 
-};
-goog.inherits(ol.interaction.Modify, ol.interaction.Pointer);
+  this.setStyle(options.style);
 
+  ol.events.listen(this.source_, ol.events.EventType.CHANGE,
+      this.handleSourceChange_, this);
 
-/**
- * @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);
 };
+ol.inherits(ol.source.ImageVector, ol.source.ImageCanvas);
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} evt Map browser event
+ * @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.interaction.Modify.prototype.willModifyFeatures_ = function(evt) {
-  if (!this.modified_) {
-    this.modified_ = true;
-    this.dispatchEvent(new ol.interaction.ModifyEvent(
-        ol.ModifyEventType.MODIFYSTART, this.features_, evt));
-  }
-};
+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_.getOverlaps(), this.renderBuffer_);
 
-/**
- * @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);
-};
-
+  this.source_.loadFeatures(extent, resolution, projection);
 
-/**
- * @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(
+  var loading = false;
+  this.source_.forEachFeatureInExtent(extent,
       /**
-       * @param {ol.interaction.SegmentDataType} node RTree node.
+       * @param {ol.Feature} feature Feature.
        */
-      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);
-};
-
+      function(feature) {
+        loading = loading ||
+            this.renderFeature_(feature, resolution, pixelRatio, replayGroup);
+      }, this);
+  replayGroup.finish();
 
-/**
- * @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);
+  if (loading) {
+    return null;
   }
-};
 
-
-/**
- * @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);
+  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, {});
 
-/**
- * @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);
-    }
-  }
-};
-
+  this.replayGroup_ = replayGroup;
 
-/**
- * @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);
-    }
-  }
+  return this.canvasContext_.canvas;
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiPolygon} geometry Geometry.
- * @private
+ * @inheritDoc
  */
-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
+ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function(
+    coordinate, resolution, rotation, hitTolerance, skippedFeatureUids, callback) {
+  if (!this.replayGroup_) {
+    return undefined;
+  } else {
+    /** @type {Object.<string, boolean>} */
+    var features = {};
+    return this.replayGroup_.forEachFeatureAtCoordinate(
+        coordinate, resolution, 0, hitTolerance, skippedFeatureUids,
+        /**
+         * @param {ol.Feature|ol.render.Feature} feature Feature.
+         * @return {?} Callback result.
+         */
+        function(feature) {
+          var key = ol.getUid(feature).toString();
+          if (!(key in features)) {
+            features[key] = true;
+            return callback(feature);
+          }
         });
-        this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-      }
-    }
   }
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.GeometryCollection} geometry Geometry.
- * @private
+ * Get a reference to the wrapped source.
+ * @return {ol.source.Vector} Source.
+ * @api
  */
-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]);
-  }
+ol.source.ImageVector.prototype.getSource = function() {
+  return this.source_;
 };
 
 
 /**
- * @param {ol.Coordinate} coordinates Coordinates.
- * @return {ol.Feature} Vertex feature.
- * @private
+ * 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.StyleFunction}
+ *     Layer style.
+ * @api
  */
-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;
+ol.source.ImageVector.prototype.getStyle = function() {
+  return this.style_;
 };
 
 
 /**
- * @param {ol.interaction.SegmentDataType} a
- * @param {ol.interaction.SegmentDataType} b
- * @return {number}
- * @private
+ * Get the style function.
+ * @return {ol.StyleFunction|undefined} Layer style function.
+ * @api
  */
-ol.interaction.Modify.compareIndexes_ = function(a, b) {
-  return a.index - b.index;
+ol.source.ImageVector.prototype.getStyleFunction = function() {
+  return this.styleFunction_;
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} evt Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.Modify}
+ * @param {ol.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.Size} size Size.
+ * @return {!ol.Transform} Transform.
  * @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;
-        }
+ol.source.ImageVector.prototype.getTransform_ = function(center, resolution, pixelRatio, size) {
+  var dx1 = size[0] / 2;
+  var dy1 = size[1] / 2;
+  var sx = pixelRatio / resolution;
+  var sy = -sx;
+  var dx2 = -center[0];
+  var dy2 = -center[1];
 
-        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_;
+  return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, 0, dx2, dy2);
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} evt Event.
- * @this {ol.interaction.Modify}
+ * Handle changes in image style state.
+ * @param {ol.events.Event} event Image style change event.
  * @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);
+ol.source.ImageVector.prototype.handleImageChange_ = function(event) {
+  this.changed();
 };
 
 
 /**
- * @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;
+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());
 };
 
 
 /**
- * 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
+ * @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.interaction.Modify.handleEvent = function(mapBrowserEvent) {
-  if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
-    return true;
+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);
   }
-
-  var handled;
-  if (!mapBrowserEvent.map.getView().getHints()[ol.ViewHint.INTERACTING] &&
-      mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE &&
-      !this.handlingDownUpSequence) {
-    this.handlePointerMove_(mapBrowserEvent);
+  if (!styles) {
+    return false;
   }
-  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;
-    }
+  var i, ii, loading = false;
+  if (!Array.isArray(styles)) {
+    styles = [styles];
   }
-
-  if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK) {
-    this.ignoreNextSingleClick_ = 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 ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) &&
-      !handled;
+  return loading;
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} evt Event.
- * @private
+ * 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.StyleFunction|undefined}
+ *     style Layer style.
+ * @api
  */
-ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) {
-  this.lastPixel_ = evt.pixel;
-  this.handlePointerAtPixel_(evt.pixel, evt.map);
+ol.source.ImageVector.prototype.setStyle = function(style) {
+  this.style_ = style !== undefined ? style : ol.style.Style.defaultFunction;
+  this.styleFunction_ = !style ?
+      undefined : ol.style.Style.createFunction(this.style_);
+  this.changed();
 };
 
+goog.provide('ol.renderer.webgl.ImageLayer');
+
+goog.require('ol');
+goog.require('ol.ViewHint');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.functions');
+goog.require('ol.renderer.webgl.Layer');
+goog.require('ol.source.ImageVector');
+goog.require('ol.transform');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Context');
+
+
+if (ol.ENABLE_WEBGL) {
+
+  /**
+   * @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) {
+
+    ol.renderer.webgl.Layer.call(this, mapRenderer, imageLayer);
+
+    /**
+     * The last rendered image.
+     * @private
+     * @type {?ol.ImageBase}
+     */
+    this.image_ = null;
+
+    /**
+     * @private
+     * @type {CanvasRenderingContext2D}
+     */
+    this.hitCanvasContext_ = null;
+
+    /**
+     * @private
+     * @type {?ol.Transform}
+     */
+    this.hitTransformationMatrix_ = null;
 
-/**
- * @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);
   };
+  ol.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer);
 
-  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];
+  /**
+   * @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, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE);
+  };
+
+
+  /**
+   * @inheritDoc
+   */
+  ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, 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, hitTolerance, 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 = /** @type {ol.layer.Image} */ (this.getLayer());
+    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) {
+          projection = sourceProjection;
+        }
       }
-      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;
+      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) {
+            /**
+             * @param {WebGLRenderingContext} gl GL.
+             * @param {WebGLTexture} texture Texture.
+             */
+            var postRenderFunction = function(gl, texture) {
+              if (!gl.isContextLost()) {
+                gl.deleteTexture(texture);
+              }
+            }.bind(null, gl, this.texture);
+            frameState.postRenderFunctions.push(
+              /** @type {ol.PostRenderFunction} */ (postRenderFunction)
+            );
+          }
         }
       }
-      this.vertexSegments_ = vertexSegments;
-      return;
     }
-  }
-  if (this.vertexFeature_) {
-    this.overlay_.getSource().removeFeature(this.vertexFeature_);
-    this.vertexFeature_ = null;
-  }
-};
 
+    if (image) {
+      var canvas = this.mapRenderer.getContext().getCanvas();
 
-/**
- * @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;
+      this.updateProjectionMatrix_(canvas.width, canvas.height,
+          pixelRatio, viewCenter, viewResolution, viewRotation,
+          image.getExtent());
+      this.hitTransformationMatrix_ = null;
 
-  while (vertex.length < geometry.getStride()) {
-    vertex.push(0);
-  }
+      // Translate and scale to flip the Y coord.
+      var texCoordMatrix = this.texCoordMatrix;
+      ol.transform.reset(texCoordMatrix);
+      ol.transform.scale(texCoordMatrix, 1, -1);
+      ol.transform.translate(texCoordMatrix, 0, -1);
 
-  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.image_ = image;
+      this.texture = texture;
 
-  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]);
+      this.updateAttributions(frameState.attributions, image.getAttributions());
+      this.updateLogos(frameState, imageSource);
+    }
 
-  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;
-};
+    return !!image;
+  };
 
 
-/**
- * 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;
+  /**
+   * @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;
+    ol.transform.reset(projectionMatrix);
+    ol.transform.scale(projectionMatrix,
+        pixelRatio * 2 / canvasExtentWidth,
+        pixelRatio * 2 / canvasExtentHeight);
+    ol.transform.rotate(projectionMatrix, -viewRotation);
+    ol.transform.translate(projectionMatrix,
+        imageExtent[0] - viewCenter[0],
+        imageExtent[1] - viewCenter[1]);
+    ol.transform.scale(projectionMatrix,
+        (imageExtent[2] - imageExtent[0]) / 2,
+        (imageExtent[3] - imageExtent[1]) / 2);
+    ol.transform.translate(projectionMatrix, 1, 1);
+
+  };
+
+
+  /**
+   * @inheritDoc
+   */
+  ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) {
+    var hasFeature = this.forEachFeatureAtCoordinate(
+        coordinate, frameState, 0, ol.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 (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 (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 = ol.transform.apply(
+          frameState.pixelToCoordinateTransform, pixel.slice());
+      var hasFeature = this.forEachFeatureAtCoordinate(
+          coordinate, frameState, 0, ol.functions.TRUE, this);
+
+      if (hasFeature) {
+        return callback.call(thisArg, this.getLayer(), null);
+      } else {
+        return undefined;
       }
+    } else {
+      var imageSize =
+          [this.image_.getImage().width, this.image_.getImage().height];
 
-      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.hitTransformationMatrix_) {
+        this.hitTransformationMatrix_ = this.getHitTransformationMatrix_(
+            frameState.size, imageSize);
+      }
 
-        if (this.vertexFeature_) {
-          this.overlay_.getSource().removeFeature(this.vertexFeature_);
-          this.vertexFeature_ = null;
-        }
+      var pixelOnFrameBuffer = ol.transform.apply(
+          this.hitTransformationMatrix_, pixel.slice());
+
+      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(),  imageData);
+      } else {
+        return undefined;
       }
     }
-  }
-  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;
-};
+  /**
+   * The transformation matrix to get the pixel on the image for a
+   * pixel on the map.
+   * @param {ol.Size} mapSize The map size.
+   * @param {ol.Size} imageSize The image size.
+   * @return {ol.Transform} The transformation matrix.
+   * @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 mapCoordTransform = ol.transform.create();
+    ol.transform.translate(mapCoordTransform, -1, -1);
+    ol.transform.scale(mapCoordTransform, 2 / mapSize[0], 2 / mapSize[1]);
+    ol.transform.translate(mapCoordTransform, 0, mapSize[1]);
+    ol.transform.scale(mapCoordTransform, 1, -1);
 
+    // the second matrix is the inverse of the projection matrix used in the
+    // shader for drawing
+    var projectionMatrixInv = ol.transform.invert(this.projectionMatrix.slice());
 
-/**
- * @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;
-    }
-  });
-};
+    // the third matrix scales to the image dimensions and flips the y-axis again
+    var transform = ol.transform.create();
+    ol.transform.translate(transform, 0, imageSize[1]);
+    ol.transform.scale(transform, 1, -1);
+    ol.transform.scale(transform, imageSize[0] / 2, imageSize[1] / 2);
+    ol.transform.translate(transform, 1, 1);
+
+    ol.transform.multiply(transform, projectionMatrixInv);
+    ol.transform.multiply(transform, mapCoordTransform);
+
+    return transform;
+  };
+
+}
+
+goog.provide('ol.layer.Image');
+
+goog.require('ol');
+goog.require('ol.layer.Layer');
+goog.require('ol.renderer.Type');
+goog.require('ol.renderer.canvas.ImageLayer');
+goog.require('ol.renderer.webgl.ImageLayer');
 
 
 /**
- * @return {ol.style.StyleFunction} Styles.
+ * @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
  */
-ol.interaction.Modify.getDefaultStyleFunction = function() {
-  var style = ol.style.createDefaultEditingStyles();
-  return function(feature, resolution) {
-    return style[ol.geom.GeometryType.POINT];
-  };
+ol.layer.Image = function(opt_options) {
+  var options = opt_options ? opt_options : {};
+  ol.layer.Layer.call(this,  /** @type {olx.layer.LayerOptions} */ (options));
 };
-
-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');
+ol.inherits(ol.layer.Image, ol.layer.Layer);
 
 
 /**
- * @enum {string}
+ * @inheritDoc
  */
-ol.interaction.SelectEventType = {
-  /**
-   * Triggered when feature(s) has been (de)selected.
-   * @event ol.interaction.SelectEvent#select
-   * @api
-   */
-  SELECT: 'select'
+ol.layer.Image.prototype.createRenderer = function(mapRenderer) {
+  var renderer = null;
+  var type = mapRenderer.getType();
+  if (ol.ENABLE_CANVAS && type === ol.renderer.Type.CANVAS) {
+    renderer = new ol.renderer.canvas.ImageLayer(this);
+  } else if (ol.ENABLE_WEBGL && type === ol.renderer.Type.WEBGL) {
+    renderer = new ol.renderer.webgl.ImageLayer(/** @type {ol.renderer.webgl.Map} */ (mapRenderer), this);
+  }
+  return renderer;
 };
 
 
 /**
- * A function that takes an {@link ol.Feature} and an {@link ol.layer.Layer}
- * and returns `true` if the feature may be selected or `false` otherwise.
- * @typedef {function(ol.Feature, ol.layer.Layer): boolean}
+ * Return the associated {@link ol.source.Image source} of the image layer.
+ * @function
+ * @return {ol.source.Image} Source.
  * @api
  */
-ol.interaction.SelectFilterFunction;
-
+ol.layer.Image.prototype.getSource;
 
+goog.provide('ol.layer.TileProperty');
 
 /**
- * @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
+ * @enum {string}
  */
-ol.interaction.SelectEvent =
-    function(type, selected, deselected, mapBrowserEvent) {
-  goog.base(this, type);
-
-  /**
-   * Selected features array.
-   * @type {Array.<ol.Feature>}
-   * @api
-   */
-  this.selected = selected;
+ol.layer.TileProperty = {
+  PRELOAD: 'preload',
+  USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError'
+};
 
-  /**
-   * Deselected features array.
-   * @type {Array.<ol.Feature>}
-   * @api
-   */
-  this.deselected = deselected;
+// FIXME find correct globalCompositeOperation
 
-  /**
-   * Associated {@link ol.MapBrowserEvent}.
-   * @type {ol.MapBrowserEvent}
-   * @api
-   */
-  this.mapBrowserEvent = mapBrowserEvent;
-};
-goog.inherits(ol.interaction.SelectEvent, goog.events.Event);
+goog.provide('ol.renderer.canvas.TileLayer');
 
+goog.require('ol');
+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.renderer.canvas.IntermediateCanvas');
+goog.require('ol.transform');
 
 
 /**
- * @classdesc
- * Interaction for selecting vector features. By default, selected features are
- * styled differently, so this interaction can be used for visual highlighting,
- * as well as selecting features for other actions, such as modification or
- * output. There are three ways of controlling which features are selected:
- * using the browser event as defined by the `condition` and optionally the
- * `toggle`, `add`/`remove`, and `multi` options; a `layers` filter; and a
- * further feature filter using the `filter` option.
- *
  * @constructor
- * @extends {ol.interaction.Interaction}
- * @param {olx.interaction.SelectOptions=} opt_options Options.
- * @fires ol.interaction.SelectEvent
- * @api stable
+ * @extends {ol.renderer.canvas.IntermediateCanvas}
+ * @param {ol.layer.Tile|ol.layer.VectorTile} tileLayer Tile layer.
  */
-ol.interaction.Select = function(opt_options) {
-
-  goog.base(this, {
-    handleEvent: ol.interaction.Select.handleEvent
-  });
+ol.renderer.canvas.TileLayer = function(tileLayer) {
 
-  var options = opt_options ? opt_options : {};
+  ol.renderer.canvas.IntermediateCanvas.call(this, tileLayer);
 
   /**
-   * @private
-   * @type {ol.events.ConditionType}
+   * @protected
+   * @type {CanvasRenderingContext2D}
    */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.singleClick;
+  this.context = this.context === null ? null :  ol.dom.createCanvasContext2D();
 
   /**
    * @private
-   * @type {ol.events.ConditionType}
+   * @type {number}
    */
-  this.addCondition_ = options.addCondition ?
-      options.addCondition : ol.events.condition.never;
+  this.oversampling_;
 
   /**
    * @private
-   * @type {ol.events.ConditionType}
+   * @type {ol.Extent}
    */
-  this.removeCondition_ = options.removeCondition ?
-      options.removeCondition : ol.events.condition.never;
+  this.renderedExtent_ = null;
 
   /**
-   * @private
-   * @type {ol.events.ConditionType}
+   * @protected
+   * @type {number}
    */
-  this.toggleCondition_ = options.toggleCondition ?
-      options.toggleCondition : ol.events.condition.shiftKeyOnly;
+  this.renderedRevision;
 
   /**
-   * @private
-   * @type {boolean}
+   * @protected
+   * @type {!Array.<ol.Tile>}
    */
-  this.multi_ = options.multi ? options.multi : false;
+  this.renderedTiles = [];
 
   /**
-   * @private
-   * @type {ol.interaction.SelectFilterFunction}
+   * @protected
+   * @type {ol.Extent}
    */
-  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;
-  }
+  this.tmpExtent = ol.extent.createEmpty();
 
   /**
    * @private
-   * @type {function(ol.layer.Layer): boolean}
+   * @type {ol.TileRange}
    */
-  this.layerFilter_ = layerFilter;
+  this.tmpTileRange_ = new ol.TileRange(0, 0, 0, 0);
 
   /**
-   * An association between selected feature (key)
-   * and layer (value)
    * @private
-   * @type {Object.<number, ol.layer.Layer>}
+   * @type {ol.Transform}
    */
-  this.featureLayerAssociation_ = {};
+  this.imageTransform_ = ol.transform.create();
 
   /**
-   * @private
-   * @type {ol.layer.Vector}
+   * @protected
+   * @type {number}
    */
-  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);
+  this.zDirection = 0;
 
 };
-goog.inherits(ol.interaction.Select, ol.interaction.Interaction);
+ol.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.IntermediateCanvas);
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @param {ol.layer.Layer} layer Layer.
  * @private
+ * @param {ol.Tile} tile Tile.
+ * @return {boolean} Tile is drawable.
  */
-ol.interaction.Select.prototype.addFeatureLayerAssociation_ =
-    function(feature, layer) {
-  var key = goog.getUid(feature);
-  this.featureLayerAssociation_[key] = layer;
+ol.renderer.canvas.TileLayer.prototype.isDrawableTile_ = function(tile) {
+  var tileState = tile.getState();
+  var useInterimTilesOnError = this.getLayer().getUseInterimTilesOnError();
+  return tileState == ol.TileState.LOADED ||
+      tileState == ol.TileState.EMPTY ||
+      tileState == ol.TileState.ERROR && !useInterimTilesOnError;
 };
 
-
 /**
- * Get the selected features.
- * @return {ol.Collection.<ol.Feature>} Features collection.
- * @api stable
+ * @inheritDoc
  */
-ol.interaction.Select.prototype.getFeatures = function() {
-  return this.featureOverlay_.getSource().getFeaturesCollection();
-};
-
+ol.renderer.canvas.TileLayer.prototype.prepareFrame = function(frameState, layerState) {
 
-/**
- * Returns the associated {@link ol.layer.Vector vectorlayer} of
- * the (last) selected feature.
- * @param {ol.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]);
-};
+  var pixelRatio = frameState.pixelRatio;
+  var size = frameState.size;
+  var viewState = frameState.viewState;
+  var projection = viewState.projection;
+  var viewResolution = viewState.resolution;
+  var viewCenter = viewState.center;
 
+  var tileLayer = this.getLayer();
+  var tileSource = /** @type {ol.source.Tile} */ (tileLayer.getSource());
+  var sourceRevision = tileSource.getRevision();
+  var tileGrid = tileSource.getTileGridForProjection(projection);
+  var z = tileGrid.getZForResolution(viewResolution, this.zDirection);
+  var tileResolution = tileGrid.getResolution(z);
+  var oversampling = Math.round(viewResolution / tileResolution) || 1;
+  var extent = frameState.extent;
 
-/**
- * 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;
+  if (layerState.extent !== undefined) {
+    extent = ol.extent.getIntersection(extent, layerState.extent);
   }
-  var add = this.addCondition_(mapBrowserEvent);
-  var remove = this.removeCondition_(mapBrowserEvent);
-  var toggle = this.toggleCondition_(mapBrowserEvent);
-  var set = !add && !remove && !toggle;
-  var map = mapBrowserEvent.map;
-  var features = this.featureOverlay_.getSource().getFeaturesCollection();
-  var /** @type {!Array.<ol.Feature>} */ deselected = [];
-  var /** @type {!Array.<ol.Feature>} */ selected = [];
-  var change = false;
-  if (set) {
-    // Replace the currently selected feature(s) with the feature(s) at the
-    // pixel, or clear the selected feature(s) if there is no feature at
-    // the pixel.
-    map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
-        /**
-         * @param {ol.Feature} feature Feature.
-         * @param {ol.layer.Layer} layer Layer.
-         */
-        function(feature, layer) {
-          if (this.filter_(feature, layer)) {
-            selected.push(feature);
-            this.addFeatureLayerAssociation_(feature, layer);
-            return !this.multi_;
+  if (ol.extent.isEmpty(extent)) {
+    // Return false to prevent the rendering of the layer.
+    return false;
+  }
+
+  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
+      extent, tileResolution);
+  var imageExtent = tileGrid.getTileRangeExtent(z, tileRange);
+
+  var tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio);
+
+  /**
+   * @type {Object.<number, Object.<string, ol.Tile>>}
+   */
+  var tilesToDrawByZ = {};
+  tilesToDrawByZ[z] = {};
+
+  var findLoadedTiles = this.createLoadedTileFinder(
+      tileSource, projection, tilesToDrawByZ);
+
+  var tmpExtent = this.tmpExtent;
+  var tmpTileRange = this.tmpTileRange_;
+  var newTiles = false;
+  var tile, 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);
+      // When useInterimTilesOnError is false, we consider the error tile as loaded.
+      if (tile.getState() == ol.TileState.ERROR && !this.getLayer().getUseInterimTilesOnError()) {
+        tile.setState(ol.TileState.LOADED);
+      }
+      if (!this.isDrawableTile_(tile)) {
+        tile = tile.getInterimTile();
+      }
+      if (this.isDrawableTile_(tile)) {
+        if (tile.getState() == ol.TileState.LOADED) {
+          tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
+          if (!newTiles && this.renderedTiles.indexOf(tile) == -1) {
+            newTiles = true;
           }
-        }, 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();
+        }
+        continue;
       }
-      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);
+
+      var fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
+          tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
+      if (!fullyLoaded) {
+        var childTileRange = tileGrid.getTileCoordChildTileRange(
+            tile.tileCoord, tmpTileRange, tmpExtent);
+        if (childTileRange) {
+          findLoadedTiles(z + 1, childTileRange);
         }
       }
+
     }
-  } else {
-    // Modify the currently selected feature(s).
-    map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
-        /**
-         * @param {ol.Feature} feature Feature.
-         * @param {ol.layer.Layer} layer Layer.
-         */
-        function(feature, layer) {
-          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]);
+  }
+
+  var renderedResolution = tileResolution * pixelRatio / tilePixelRatio * oversampling;
+  var hints = frameState.viewHints;
+  var animatingOrInteracting = hints[ol.ViewHint.ANIMATING] || hints[ol.ViewHint.INTERACTING];
+  if (!(this.renderedResolution && Date.now() - frameState.time > 16 && animatingOrInteracting) && (
+        newTiles ||
+        !(this.renderedExtent_ && ol.extent.containsExtent(this.renderedExtent_, extent)) ||
+        this.renderedRevision != sourceRevision ||
+        oversampling != this.oversampling_ ||
+        !animatingOrInteracting && renderedResolution != this.renderedResolution
+      )) {
+
+    var context = this.context;
+    if (context) {
+      var tilePixelSize = tileSource.getTilePixelSize(z, pixelRatio, projection);
+      var width = Math.round(tileRange.getWidth() * tilePixelSize[0] / oversampling);
+      var height = Math.round(tileRange.getHeight() * tilePixelSize[1] / oversampling);
+      var canvas = context.canvas;
+      if (canvas.width != width || canvas.height != height) {
+        this.oversampling_ = oversampling;
+        canvas.width = width;
+        canvas.height = height;
+      } else {
+        context.clearRect(0, 0, width, height);
+        oversampling = this.oversampling_;
+      }
     }
-    features.extend(selected);
-    if (selected.length > 0 || deselected.length > 0) {
-      change = true;
+
+    this.renderedTiles.length = 0;
+    /** @type {Array.<number>} */
+    var zs = Object.keys(tilesToDrawByZ).map(Number);
+    zs.sort(ol.array.numberSafeCompareFunction);
+    var currentResolution, currentScale, currentTilePixelSize, currentZ, i, ii;
+    var tileExtent, tileGutter, tilesToDraw, w, h;
+    for (i = 0, ii = zs.length; i < ii; ++i) {
+      currentZ = zs[i];
+      currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection);
+      currentResolution = tileGrid.getResolution(currentZ);
+      currentScale = currentResolution / tileResolution;
+      tileGutter = tilePixelRatio * tileSource.getGutter(projection);
+      tilesToDraw = tilesToDrawByZ[currentZ];
+      for (var tileCoordKey in tilesToDraw) {
+        tile = tilesToDraw[tileCoordKey];
+        tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent);
+        x = (tileExtent[0] - imageExtent[0]) / tileResolution * tilePixelRatio / oversampling;
+        y = (imageExtent[3] - tileExtent[3]) / tileResolution * tilePixelRatio / oversampling;
+        w = currentTilePixelSize[0] * currentScale / oversampling;
+        h = currentTilePixelSize[1] * currentScale / oversampling;
+        this.drawTileImage(tile, frameState, layerState, x, y, w, h, tileGutter);
+        this.renderedTiles.push(tile);
+      }
     }
+
+    this.renderedRevision = sourceRevision;
+    this.renderedResolution = tileResolution * pixelRatio / tilePixelRatio * oversampling;
+    this.renderedExtent_ = imageExtent;
   }
-  if (change) {
-    this.dispatchEvent(
-        new ol.interaction.SelectEvent(ol.interaction.SelectEventType.SELECT,
-            selected, deselected, mapBrowserEvent));
-  }
-  return ol.events.condition.pointerMove(mapBrowserEvent);
+
+  var scale = this.renderedResolution / viewResolution;
+  var transform = ol.transform.compose(this.imageTransform_,
+      pixelRatio * size[0] / 2, pixelRatio * size[1] / 2,
+      scale, scale,
+      0,
+      (this.renderedExtent_[0] - viewCenter[0]) / this.renderedResolution * pixelRatio,
+      (viewCenter[1] - this.renderedExtent_[3]) / this.renderedResolution * pixelRatio);
+  ol.transform.compose(this.coordinateToCanvasPixelTransform,
+      pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5],
+      pixelRatio / viewResolution, -pixelRatio / viewResolution,
+      0,
+      -viewCenter[0], -viewCenter[1]);
+
+
+  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 this.renderedTiles.length > 0;
 };
 
 
 /**
- * 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 (!goog.isNull(currentMap)) {
-    selectedFeatures.forEach(currentMap.unskipFeature, currentMap);
-  }
-  goog.base(this, 'setMap', map);
-  this.featureOverlay_.setMap(map);
-  if (!goog.isNull(map)) {
-    selectedFeatures.forEach(map.skipFeature, map);
+ * @param {ol.Tile} tile Tile.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.LayerState} layerState Layer state.
+ * @param {number} x Left of the tile.
+ * @param {number} y Top of the tile.
+ * @param {number} w Width of the tile.
+ * @param {number} h Height of the tile.
+ * @param {number} gutter Tile gutter.
+ */
+ol.renderer.canvas.TileLayer.prototype.drawTileImage = function(tile, frameState, layerState, x, y, w, h, gutter) {
+  if (!this.getLayer().getSource().getOpaque(frameState.viewState.projection)) {
+    this.context.clearRect(x, y, w, h);
+  }
+  var image = tile.getImage();
+  if (image) {
+    this.context.drawImage(image, gutter, gutter,
+        image.width - 2 * gutter, image.height - 2 * gutter, x, y, w, h);
   }
 };
 
 
 /**
- * @return {ol.style.StyleFunction} Styles.
+ * @inheritDoc
  */
-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()];
-  };
+ol.renderer.canvas.TileLayer.prototype.getImage = function() {
+  var context = this.context;
+  return context ? context.canvas : null;
 };
 
 
 /**
- * @param {ol.CollectionEvent} evt Event.
- * @private
+ * @function
+ * @return {ol.layer.Tile|ol.layer.VectorTile}
  */
-ol.interaction.Select.prototype.addFeature_ = function(evt) {
-  var feature = evt.element;
-  var map = this.getMap();
-  goog.asserts.assertInstanceof(feature, ol.Feature,
-      'feature should be an ol.Feature');
-  if (!goog.isNull(map)) {
-    map.skipFeature(feature);
-  }
-};
+ol.renderer.canvas.TileLayer.prototype.getLayer;
 
 
 /**
- * @param {ol.CollectionEvent} evt Event.
- * @private
+ * @inheritDoc
  */
-ol.interaction.Select.prototype.removeFeature_ = function(evt) {
-  var feature = evt.element;
-  var map = this.getMap();
-  goog.asserts.assertInstanceof(feature, ol.Feature,
-      'feature should be an ol.Feature');
-  if (!goog.isNull(map)) {
-    map.unskipFeature(feature);
-  }
+ol.renderer.canvas.TileLayer.prototype.getImageTransform = function() {
+  return this.imageTransform_;
 };
 
+// This file is automatically generated, do not edit
+/* eslint openlayers-internal/no-missing-requires: 0 */
+goog.provide('ol.renderer.webgl.tilelayershader');
+
+goog.require('ol');
+goog.require('ol.webgl.Fragment');
+goog.require('ol.webgl.Vertex');
 
-/**
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.interaction.Select.prototype.removeFeatureLayerAssociation_ =
-    function(feature) {
-  var key = goog.getUid(feature);
-  delete this.featureLayerAssociation_[key];
-};
+if (ol.ENABLE_WEBGL) {
+
+  /**
+   * @constructor
+   * @extends {ol.webgl.Fragment}
+   * @struct
+   */
+  ol.renderer.webgl.tilelayershader.Fragment = function() {
+    ol.webgl.Fragment.call(this, ol.renderer.webgl.tilelayershader.Fragment.SOURCE);
+  };
+  ol.inherits(ol.renderer.webgl.tilelayershader.Fragment, ol.webgl.Fragment);
+
+
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.renderer.webgl.tilelayershader.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.tilelayershader.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.tilelayershader.Fragment.SOURCE = ol.DEBUG_WEBGL ?
+      ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE :
+      ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE;
 
-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');
+  ol.renderer.webgl.tilelayershader.fragment = new ol.renderer.webgl.tilelayershader.Fragment();
+
+
+  /**
+   * @constructor
+   * @extends {ol.webgl.Vertex}
+   * @struct
+   */
+  ol.renderer.webgl.tilelayershader.Vertex = function() {
+    ol.webgl.Vertex.call(this, ol.renderer.webgl.tilelayershader.Vertex.SOURCE);
+  };
+  ol.inherits(ol.renderer.webgl.tilelayershader.Vertex, ol.webgl.Vertex);
+
+
+  /**
+   * @const
+   * @type {string}
+   */
+  ol.renderer.webgl.tilelayershader.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.tilelayershader.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.tilelayershader.Vertex.SOURCE = ol.DEBUG_WEBGL ?
+      ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE :
+      ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE;
+
+
+  ol.renderer.webgl.tilelayershader.vertex = new ol.renderer.webgl.tilelayershader.Vertex();
+
+
+  /**
+   * @constructor
+   * @param {WebGLRenderingContext} gl GL.
+   * @param {WebGLProgram} program Program.
+   * @struct
+   */
+  ol.renderer.webgl.tilelayershader.Locations = function(gl, program) {
+
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_texture = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_texture' : 'e');
+
+    /**
+     * @type {WebGLUniformLocation}
+     */
+    this.u_tileOffset = gl.getUniformLocation(
+        program, ol.DEBUG_WEBGL ? 'u_tileOffset' : 'd');
+
+    /**
+     * @type {number}
+     */
+    this.a_position = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? 'a_position' : 'b');
+
+    /**
+     * @type {number}
+     */
+    this.a_texCoord = gl.getAttribLocation(
+        program, ol.DEBUG_WEBGL ? '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('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.TileState');
+goog.require('ol.TileRange');
+goog.require('ol.array');
 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');
+goog.require('ol.math');
+goog.require('ol.renderer.webgl.Layer');
+goog.require('ol.renderer.webgl.tilelayershader');
+goog.require('ol.size');
+goog.require('ol.transform');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Buffer');
 
 
+if (ol.ENABLE_WEBGL) {
 
-/**
- * @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) {
+  /**
+   * @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) {
+
+    ol.renderer.webgl.Layer.call(this, mapRenderer, tileLayer);
+
+    /**
+     * @private
+     * @type {ol.webgl.Fragment}
+     */
+    this.fragmentShader_ = ol.renderer.webgl.tilelayershader.fragment;
+
+    /**
+     * @private
+     * @type {ol.webgl.Vertex}
+     */
+    this.vertexShader_ = ol.renderer.webgl.tilelayershader.vertex;
+
+    /**
+     * @private
+     * @type {ol.renderer.webgl.tilelayershader.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;
 
-  goog.base(this, {
-    handleEvent: ol.interaction.Snap.handleEvent_,
-    handleDownEvent: goog.functions.TRUE,
-    handleUpEvent: ol.interaction.Snap.handleUpEvent_
-  });
+    /**
+     * @private
+     * @type {ol.Size}
+     */
+    this.tmpSize_ = [0, 0];
 
-  var options = opt_options ? opt_options : {};
+  };
+  ol.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);
 
-  /**
-   * @type {ol.source.Vector}
-   * @private
-   */
-  this.source_ = options.source ? options.source : null;
 
   /**
-   * @type {ol.Collection.<ol.Feature>}
-   * @private
+   * @inheritDoc
    */
-  this.features_ = options.features ? options.features : null;
+  ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
+    var context = this.mapRenderer.getContext();
+    context.deleteBuffer(this.renderArrayBuffer_);
+    ol.renderer.webgl.Layer.prototype.disposeInternal.call(this);
+  };
 
-  /**
-   * @type {Array.<goog.events.Key>}
-   * @private
-   */
-  this.featuresListenerKeys_ = [];
 
   /**
-   * @type {Object.<number, goog.events.Key>}
-   * @private
+   * @inheritDoc
    */
-  this.geometryChangeListenerKeys_ = {};
+  ol.renderer.webgl.TileLayer.prototype.createLoadedTileFinder = function(source, projection, tiles) {
+    var mapRenderer = this.mapRenderer;
 
-  /**
-   * @type {Object.<number, goog.events.Key>}
-   * @private
-   */
-  this.geometryModifyListenerKeys_ = {};
+    return (
+        /**
+         * @param {number} zoom Zoom level.
+         * @param {ol.TileRange} tileRange Tile range.
+         * @return {boolean} The tile range is fully loaded.
+         */
+        function(zoom, tileRange) {
+          function callback(tile) {
+            var loaded = mapRenderer.isTileTextureLoaded(tile);
+            if (loaded) {
+              if (!tiles[zoom]) {
+                tiles[zoom] = {};
+              }
+              tiles[zoom][tile.tileCoord.toString()] = tile;
+            }
+            return loaded;
+          }
+          return source.forEachLoadedTile(projection, zoom, tileRange, callback);
+        });
+  };
 
-  /**
-   * 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
+   * @inheritDoc
    */
-  this.pendingFeatures_ = {};
+  ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
+    ol.renderer.webgl.Layer.prototype.handleWebGLContextLost.call(this);
+    this.locations_ = null;
+  };
 
-  /**
-   * Used for distance sorting in sortByDistance_
-   * @type {ol.Coordinate}
-   * @private
-   */
-  this.pixelCoordinate_ = null;
 
   /**
-   * @type {number}
-   * @private
+   * @inheritDoc
    */
-  this.pixelTolerance_ = options.pixelTolerance !== undefined ?
-      options.pixelTolerance : 10;
+  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 = /** @type {ol.layer.Tile} */ (this.getLayer());
+    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.getTilePixelRatio(pixelRatio) * tileSource.getGutter(projection);
+
+    var center = viewState.center;
+    var 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(ol.webgl.COLOR_BUFFER_BIT);
+      gl.disable(ol.webgl.BLEND);
+
+      var program = context.getProgram(this.fragmentShader_, this.vertexShader_);
+      context.useProgram(program);
+      if (!this.locations_) {
+        // eslint-disable-next-line openlayers-internal/no-missing-requires
+        this.locations_ = new ol.renderer.webgl.tilelayershader.Locations(gl, program);
+      }
+
+      context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.renderArrayBuffer_);
+      gl.enableVertexAttribArray(this.locations_.a_position);
+      gl.vertexAttribPointer(
+          this.locations_.a_position, 2, ol.webgl.FLOAT, false, 16, 0);
+      gl.enableVertexAttribArray(this.locations_.a_texCoord);
+      gl.vertexAttribPointer(
+          this.locations_.a_texCoord, 2, ol.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 = tile.getInterimTile();
+          }
+          tileState = tile.getState();
+          if (tileState == ol.TileState.LOADED) {
+            if (mapRenderer.isTileTextureLoaded(tile)) {
+              tilesToDrawByZ[z][tile.tileCoord.toString()] = 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 = new Float32Array(4);
+      var i, ii, tileKey, tilesToDraw;
+      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);
+          u_tileOffset[0] = 2 * (tileExtent[2] - tileExtent[0]) /
+              framebufferExtentDimension;
+          u_tileOffset[1] = 2 * (tileExtent[3] - tileExtent[1]) /
+              framebufferExtentDimension;
+          u_tileOffset[2] = 2 * (tileExtent[0] - framebufferExtent[0]) /
+              framebufferExtentDimension - 1;
+          u_tileOffset[3] = 2 * (tileExtent[1] - framebufferExtent[1]) /
+              framebufferExtentDimension - 1;
+          gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset);
+          mapRenderer.bindTileTexture(tile, tilePixelSize,
+              tileGutter * pixelRatio, ol.webgl.LINEAR, ol.webgl.LINEAR);
+          gl.drawArrays(ol.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;
+    ol.transform.reset(texCoordMatrix);
+    ol.transform.translate(texCoordMatrix,
+        (Math.round(center[0] / tileResolution) * tileResolution - framebufferExtent[0]) /
+            (framebufferExtent[2] - framebufferExtent[0]),
+        (Math.round(center[1] / tileResolution) * tileResolution - framebufferExtent[1]) /
+            (framebufferExtent[3] - framebufferExtent[1]));
+    if (viewState.rotation !== 0) {
+      ol.transform.rotate(texCoordMatrix, viewState.rotation);
+    }
+    ol.transform.scale(texCoordMatrix,
+        frameState.size[0] * viewState.resolution /
+            (framebufferExtent[2] - framebufferExtent[0]),
+        frameState.size[1] * viewState.resolution /
+            (framebufferExtent[3] - framebufferExtent[1]));
+    ol.transform.translate(texCoordMatrix, -0.5, -0.5);
+
+    return true;
+  };
+
 
   /**
-   * @type {function(ol.interaction.Snap.SegmentDataType, ol.interaction.Snap.SegmentDataType): number}
-   * @private
+   * @inheritDoc
    */
-  this.sortByDistance_ = goog.bind(ol.interaction.Snap.sortByDistance, this);
+  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]];
 
-  /**
-  * Segment RTree for each layer
-  * @type {ol.structs.RBush.<ol.interaction.Snap.SegmentDataType>}
-  * @private
-  */
-  this.rBush_ = new ol.structs.RBush();
+    var pixelOnFrameBufferScaled = ol.transform.apply(
+        this.texCoordMatrix, pixelOnMapScaled.slice());
+    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);
 
-  /**
-  * @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_
+    if (imageData[3] > 0) {
+      return callback.call(thisArg, this.getLayer(), imageData);
+    } else {
+      return undefined;
+    }
   };
-};
-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);
+goog.provide('ol.layer.Tile');
 
-    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);
-    }
-  }
-};
+goog.require('ol');
+goog.require('ol.layer.Layer');
+goog.require('ol.layer.TileProperty');
+goog.require('ol.obj');
+goog.require('ol.renderer.Type');
+goog.require('ol.renderer.canvas.TileLayer');
+goog.require('ol.renderer.webgl.TileLayer');
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @private
+ * @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
  */
-ol.interaction.Snap.prototype.forEachFeatureAdd_ = function(feature) {
-  this.addFeature(feature);
-};
+ol.layer.Tile = function(opt_options) {
+  var options = opt_options ? opt_options : {};
 
+  var baseOptions = ol.obj.assign({}, options);
 
-/**
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.interaction.Snap.prototype.forEachFeatureRemove_ = function(feature) {
-  this.removeFeature(feature);
+  delete baseOptions.preload;
+  delete baseOptions.useInterimTilesOnError;
+  ol.layer.Layer.call(this,  /** @type {olx.layer.LayerOptions} */ (baseOptions));
+
+  this.setPreload(options.preload !== undefined ? options.preload : 0);
+  this.setUseInterimTilesOnError(options.useInterimTilesOnError !== undefined ?
+      options.useInterimTilesOnError : true);
 };
+ol.inherits(ol.layer.Tile, ol.layer.Layer);
 
 
 /**
- * @return {ol.Collection.<ol.Feature>|Array.<ol.Feature>}
- * @private
+ * @inheritDoc
  */
-ol.interaction.Snap.prototype.getFeatures_ = function() {
-  var features;
-  if (this.features_) {
-    features = this.features_;
-  } else if (this.source_) {
-    features = this.source_.getFeatures();
+ol.layer.Tile.prototype.createRenderer = function(mapRenderer) {
+  var renderer = null;
+  var type = mapRenderer.getType();
+  if (ol.ENABLE_CANVAS && type === ol.renderer.Type.CANVAS) {
+    renderer = new ol.renderer.canvas.TileLayer(this);
+  } else if (ol.ENABLE_WEBGL && type === ol.renderer.Type.WEBGL) {
+    renderer = new ol.renderer.webgl.TileLayer(/** @type {ol.renderer.webgl.Map} */ (mapRenderer), this);
   }
-  goog.asserts.assert(features !== undefined, 'features should be defined');
-  return features;
+  return renderer;
 };
 
 
 /**
- * @param {ol.source.VectorEvent|ol.CollectionEvent} evt Event.
- * @private
+ * 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.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);
+ol.layer.Tile.prototype.getPreload = function() {
+  return /** @type {number} */ (this.get(ol.layer.TileProperty.PRELOAD));
 };
 
 
 /**
- * @param {ol.source.VectorEvent|ol.CollectionEvent} evt Event.
- * @private
+ * Return the associated {@link ol.source.Tile tilesource} of the layer.
+ * @function
+ * @return {ol.source.Tile} Source.
+ * @api
  */
-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);
-};
+ol.layer.Tile.prototype.getSource;
 
 
 /**
- * @param {goog.events.Event} evt Event.
- * @private
+ * 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.interaction.Snap.prototype.handleGeometryChange_ = function(evt) {
-  var feature = evt.currentTarget;
-  goog.asserts.assertInstanceof(feature, ol.Feature);
-  this.removeFeature(feature, true);
-  this.addFeature(feature, true);
+ol.layer.Tile.prototype.setPreload = function(preload) {
+  this.set(ol.layer.TileProperty.PRELOAD, preload);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature which geometry was modified.
- * @param {goog.events.Event} evt Event.
- * @private
+ * Whether we use interim tiles on error.
+ * @return {boolean} Use interim tiles on error.
+ * @observable
+ * @api
  */
-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);
-  }
+ol.layer.Tile.prototype.getUseInterimTilesOnError = function() {
+  return /** @type {boolean} */ (
+      this.get(ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR));
 };
 
 
 /**
- * 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`.
+ * Set whether we use interim tiles on error.
+ * @param {boolean} useInterimTilesOnError Use interim tiles on error.
+ * @observable
  * @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];
-    }
-  }
+ol.layer.Tile.prototype.setUseInterimTilesOnError = function(useInterimTilesOnError) {
+  this.set(
+      ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
 };
 
+goog.provide('ol.layer.VectorTileRenderType');
 
 /**
- * @inheritDoc
+ * @enum {string}
+ * Render mode for vector tiles:
+ *  * `'image'`: Vector tiles are rendered as images. Great performance, but
+ *    point symbols and texts are always rotated with the view and pixels are
+ *    scaled during zoom animations.
+ *  * `'hybrid'`: Polygon and line elements are rendered as images, so pixels
+ *    are scaled during zoom animations. Point symbols and texts are accurately
+ *    rendered as vectors and can stay upright on rotated views.
+ *  * `'vector'`: Vector tiles are rendered as vectors. Most accurate rendering
+ *    even during animations, but slower performance than the other options.
+ * @api
  */
-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);
-  }
+ol.layer.VectorTileRenderType = {
+  IMAGE: 'image',
+  HYBRID: 'hybrid',
+  VECTOR: 'vector'
+};
 
-  goog.base(this, 'setMap', map);
+goog.provide('ol.renderer.canvas.VectorTileLayer');
 
-  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);
-  }
-};
+goog.require('ol');
+goog.require('ol.TileState');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.proj.Units');
+goog.require('ol.layer.VectorTileRenderType');
+goog.require('ol.render.ReplayType');
+goog.require('ol.render.canvas');
+goog.require('ol.render.canvas.ReplayGroup');
+goog.require('ol.render.replay');
+goog.require('ol.renderer.canvas.TileLayer');
+goog.require('ol.renderer.vector');
+goog.require('ol.transform');
 
 
 /**
- * @inheritDoc
+ * @constructor
+ * @extends {ol.renderer.canvas.TileLayer}
+ * @param {ol.layer.VectorTile} layer VectorTile layer.
  */
-ol.interaction.Snap.prototype.shouldStopEvent = goog.functions.FALSE;
+ol.renderer.canvas.VectorTileLayer = function(layer) {
+
+  /**
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context = null;
 
+  ol.renderer.canvas.TileLayer.call(this, layer);
 
-/**
- * @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) {
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.dirty_ = false;
 
-  var lowerLeft = map.getCoordinateFromPixel(
-      [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);
-  var upperRight = map.getCoordinateFromPixel(
-      [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);
-  var box = ol.extent.boundingExtent([lowerLeft, upperRight]);
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedLayerRevision_;
+
+  /**
+   * @private
+   * @type {ol.Transform}
+   */
+  this.tmpTransform_ = ol.transform.create();
+
+  // Use lower resolution for pure vector rendering. Closest resolution otherwise.
+  this.zDirection =
+      layer.getRenderMode() == ol.layer.VectorTileRenderType.VECTOR ? 1 : 0;
 
-  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
-  });
 };
+ol.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer);
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @private
+ * @const
+ * @type {!Object.<string, Array.<ol.render.ReplayType>>}
  */
-ol.interaction.Snap.prototype.updateFeature_ = function(feature) {
-  this.removeFeature(feature, false);
-  this.addFeature(feature, false);
+ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS = {
+  'image': ol.render.replay.ORDER,
+  'hybrid': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.LINE_STRING]
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.GeometryCollection} geometry Geometry.
- * @private
+ * @const
+ * @type {!Object.<string, Array.<ol.render.ReplayType>>}
  */
-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]);
-  }
+ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS = {
+  'hybrid': [ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT],
+  'vector': ol.render.replay.ORDER
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.LineString} geometry Geometry.
- * @private
+ * @inheritDoc
  */
-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);
+ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = function(frameState, layerState) {
+  var layer = this.getLayer();
+  var layerRevision = layer.getRevision();
+  if (this.renderedLayerRevision_ != layerRevision) {
+    this.renderedTiles.length = 0;
+    var renderMode = layer.getRenderMode();
+    if (!this.context && renderMode != ol.layer.VectorTileRenderType.VECTOR) {
+      this.context = ol.dom.createCanvasContext2D();
+    }
+    if (this.context && renderMode == ol.layer.VectorTileRenderType.VECTOR) {
+      this.context = null;
+    }
   }
+  this.renderedLayerRevision_ = layerRevision;
+  return ol.renderer.canvas.TileLayer.prototype.prepareFrame.apply(this, arguments);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiLineString} geometry Geometry.
+ * @param {ol.VectorImageTile} tile Tile.
+ * @param {olx.FrameState} frameState Frame state.
  * @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);
+ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(
+    tile, frameState) {
+  var layer = this.getLayer();
+  var pixelRatio = frameState.pixelRatio;
+  var projection = frameState.viewState.projection;
+  var revision = layer.getRevision();
+  var renderOrder = /** @type {ol.RenderOrderFunction} */
+      (layer.getRenderOrder()) || null;
+
+  var replayState = tile.getReplayState();
+  if (!replayState.dirty && replayState.renderedRevision == revision &&
+      replayState.renderedRenderOrder == renderOrder) {
+    return;
+  }
+
+  for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
+    var sourceTile = tile.getTile(tile.tileKeys[t]);
+    sourceTile.replayGroup = null;
+    replayState.dirty = false;
+
+    var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
+    var sourceTileGrid = source.getTileGrid();
+    var sourceTileCoord = sourceTile.tileCoord;
+    var tileProjection = sourceTile.getProjection();
+    var tileGrid = source.getTileGridForProjection(projection);
+    var resolution = tileGrid.getResolution(tile.tileCoord[0]);
+    var sourceTileResolution = sourceTileGrid.getResolution(sourceTile.tileCoord[0]);
+    var tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
+    var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
+    var sharedExtent = ol.extent.getIntersection(tileExtent, sourceTileExtent);
+    var extent, reproject, tileResolution;
+    if (tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS) {
+      var tilePixelRatio = tileResolution = source.getTilePixelRatio();
+      var transform = ol.transform.compose(this.tmpTransform_,
+          0, 0,
+          1 / sourceTileResolution * tilePixelRatio, -1 / sourceTileResolution * tilePixelRatio,
+          0,
+          -sourceTileExtent[0], -sourceTileExtent[3]);
+      extent = (ol.transform.apply(transform, [sharedExtent[0], sharedExtent[3]])
+          .concat(ol.transform.apply(transform, [sharedExtent[2], sharedExtent[1]])));
+    } else {
+      tileResolution = resolution;
+      extent = sharedExtent;
+      if (!ol.proj.equivalent(projection, tileProjection)) {
+        reproject = true;
+        sourceTile.setProjection(projection);
+      }
+    }
+    replayState.dirty = false;
+    var replayGroup = new ol.render.canvas.ReplayGroup(0, extent,
+        tileResolution, source.getOverlaps(), layer.getRenderBuffer());
+    var squaredTolerance = ol.renderer.vector.getSquaredTolerance(
+        tileResolution, pixelRatio);
+
+    /**
+     * @param {ol.Feature|ol.render.Feature} feature Feature.
+     * @this {ol.renderer.canvas.VectorTileLayer}
+     */
+    var renderFeature = function(feature) {
+      var styles;
+      var styleFunction = feature.getStyleFunction();
+      if (styleFunction) {
+        styles = styleFunction.call(/** @type {ol.Feature} */ (feature), resolution);
+      } else {
+        styleFunction = layer.getStyleFunction();
+        if (styleFunction) {
+          styles = styleFunction(feature, resolution);
+        }
+      }
+      if (styles) {
+        if (!Array.isArray(styles)) {
+          styles = [styles];
+        }
+        var dirty = this.renderFeature(feature, squaredTolerance, styles,
+            replayGroup);
+        this.dirty_ = this.dirty_ || dirty;
+        replayState.dirty = replayState.dirty || dirty;
+      }
+    };
+
+    var features = sourceTile.getFeatures();
+    if (renderOrder && renderOrder !== replayState.renderedRenderOrder) {
+      features.sort(renderOrder);
+    }
+    var feature;
+    for (var i = 0, ii = features.length; i < ii; ++i) {
+      feature = features[i];
+      if (reproject) {
+        feature.getGeometry().transform(tileProjection, projection);
+      }
+      renderFeature.call(this, feature);
     }
+    replayGroup.finish();
+    sourceTile.setReplayGroup(tile.tileCoord.toString(), replayGroup);
   }
+  replayState.renderedRevision = revision;
+  replayState.renderedRenderOrder = renderOrder;
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiPoint} geometry Geometry.
- * @private
+ * @inheritDoc
  */
-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);
+ol.renderer.canvas.VectorTileLayer.prototype.drawTileImage = function(
+    tile, frameState, layerState, x, y, w, h, gutter) {
+  var vectorImageTile = /** @type {ol.VectorImageTile} */ (tile);
+  this.createReplayGroup_(vectorImageTile, frameState);
+  if (this.context) {
+    this.renderTileImage_(vectorImageTile, frameState, layerState);
+    ol.renderer.canvas.TileLayer.prototype.drawTileImage.apply(this, arguments);
   }
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiPolygon} geometry Geometry.
- * @private
+ * @inheritDoc
  */
-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);
+ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) {
+  var resolution = frameState.viewState.resolution;
+  var rotation = frameState.viewState.rotation;
+  hitTolerance = hitTolerance == undefined ? 0 : hitTolerance;
+  var layer = this.getLayer();
+  /** @type {Object.<string, boolean>} */
+  var features = {};
+
+  /** @type {Array.<ol.VectorImageTile>} */
+  var renderedTiles = this.renderedTiles;
+
+  var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
+  var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
+  var sourceTileGrid = source.getTileGrid();
+  var bufferedExtent, found, tileSpaceCoordinate;
+  var i, ii, origin, replayGroup;
+  var tile, tileCoord, tileExtent, tilePixelRatio, tileResolution;
+  for (i = 0, ii = renderedTiles.length; i < ii; ++i) {
+    tile = renderedTiles[i];
+    tileCoord = tile.tileCoord;
+    tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent);
+    bufferedExtent = ol.extent.buffer(tileExtent, hitTolerance * resolution, bufferedExtent);
+    if (!ol.extent.containsCoordinate(bufferedExtent, coordinate)) {
+      continue;
+    }
+    for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
+      var sourceTile = tile.getTile(tile.tileKeys[t]);
+      if (sourceTile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) {
+        var sourceTileCoord = sourceTile.tileCoord;
+        var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord, this.tmpExtent);
+        origin = ol.extent.getTopLeft(sourceTileExtent);
+        tilePixelRatio = source.getTilePixelRatio();
+        tileResolution = sourceTileGrid.getResolution(sourceTileCoord[0]) / tilePixelRatio;
+        tileSpaceCoordinate = [
+          (coordinate[0] - origin[0]) / tileResolution,
+          (origin[1] - coordinate[1]) / tileResolution
+        ];
+        resolution = tilePixelRatio;
+      } else {
+        tileSpaceCoordinate = coordinate;
       }
+      replayGroup = sourceTile.getReplayGroup(tile.tileCoord);
+      found = found || replayGroup.forEachFeatureAtCoordinate(
+          tileSpaceCoordinate, resolution, rotation, hitTolerance, {},
+          /**
+           * @param {ol.Feature|ol.render.Feature} feature Feature.
+           * @return {?} Callback result.
+           */
+          function(feature) {
+            var key = ol.getUid(feature).toString();
+            if (!(key in features)) {
+              features[key] = true;
+              return callback.call(thisArg, feature, layer);
+            }
+          });
     }
   }
+  return found;
 };
 
 
 /**
- * @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.
+ * @param {ol.VectorTile} tile Tile.
+ * @param {olx.FrameState} frameState Frame state.
+ * @return {ol.Transform} transform Transform.
  * @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);
-    }
+ol.renderer.canvas.VectorTileLayer.prototype.getReplayTransform_ = function(tile, frameState) {
+  if (tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) {
+    var layer = this.getLayer();
+    var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
+    var tileGrid = source.getTileGrid();
+    var tileCoord = tile.tileCoord;
+    var tileResolution =
+        tileGrid.getResolution(tileCoord[0]) / source.getTilePixelRatio();
+    var viewState = frameState.viewState;
+    var pixelRatio = frameState.pixelRatio;
+    var renderResolution = viewState.resolution / pixelRatio;
+    var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent);
+    var center = viewState.center;
+    var origin = ol.extent.getTopLeft(tileExtent);
+    var size = frameState.size;
+    var offsetX = Math.round(pixelRatio * size[0] / 2);
+    var offsetY = Math.round(pixelRatio * size[1] / 2);
+    return ol.transform.compose(this.tmpTransform_,
+        offsetX, offsetY,
+        tileResolution / renderResolution, tileResolution / renderResolution,
+        viewState.rotation,
+        (origin[0] - center[0]) / tileResolution,
+        (center[1] - origin[1]) / tileResolution);
+  } else {
+    return this.getTransform(frameState, 0);
   }
 };
 
 
 /**
- * @typedef {{
- *     snapped: {boolean},
- *     vertex: (ol.Coordinate|null),
- *     vertexPixel: (ol.Pixel|null)
- * }}
- */
-ol.interaction.Snap.ResultType;
-
-
-/**
- * @typedef {{
- *     feature: ol.Feature,
- *     segment: Array.<ol.Coordinate>
- * }}
+ * Handle changes in image style state.
+ * @param {ol.events.Event} event Image style change event.
+ * @private
  */
-ol.interaction.Snap.SegmentDataType;
+ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function(event) {
+  this.renderIfReadyAndVisible();
+};
 
 
 /**
- * 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
+ * @inheritDoc
  */
-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;
+ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, frameState, layerState) {
+  var layer = this.getLayer();
+  var source = layer.getSource();
+  var renderMode = layer.getRenderMode();
+  var replays = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode];
+  if (replays) {
+    var pixelRatio = frameState.pixelRatio;
+    var rotation = frameState.viewState.rotation;
+    var size = frameState.size;
+    var offsetX = Math.round(pixelRatio * size[0] / 2);
+    var offsetY = Math.round(pixelRatio * size[1] / 2);
+    var tiles = this.renderedTiles;
+    var tilePixelRatio = layer.getSource().getTilePixelRatio();
+    var sourceTileGrid = source.getTileGrid();
+    var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
+    var clips = [];
+    var zs = [];
+    for (var i = tiles.length - 1; i >= 0; --i) {
+      var tile = /** @type {ol.VectorImageTile} */ (tiles[i]);
+      if (tile.getState() == ol.TileState.ABORT) {
+        continue;
+      }
+      var tileCoord = tile.tileCoord;
+      var worldOffset = tileGrid.getTileCoordExtent(tileCoord)[0] -
+          tileGrid.getTileCoordExtent(tile.wrappedTileCoord)[0];
+      for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
+        var sourceTile = tile.getTile(tile.tileKeys[t]);
+        var currentZ = sourceTile.tileCoord[0];
+        var sourceResolution = sourceTileGrid.getResolution(currentZ);
+        var transform = this.getReplayTransform_(sourceTile, frameState);
+        ol.transform.translate(transform, worldOffset * tilePixelRatio / sourceResolution, 0);
+        var replayGroup = sourceTile.getReplayGroup(tileCoord.toString());
+        var currentClip = replayGroup.getClipCoords(transform);
+        context.save();
+        context.globalAlpha = layerState.opacity;
+        ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY);
+        // Create a clip mask for regions in this low resolution tile that are
+        // already filled by a higher resolution tile
+        for (var j = 0, jj = clips.length; j < jj; ++j) {
+          var clip = clips[j];
+          if (currentZ < zs[j]) {
+            context.beginPath();
+            // counter-clockwise (outer ring) for current tile
+            context.moveTo(currentClip[0], currentClip[1]);
+            context.lineTo(currentClip[2], currentClip[3]);
+            context.lineTo(currentClip[4], currentClip[5]);
+            context.lineTo(currentClip[6], currentClip[7]);
+            // clockwise (inner ring) for higher resolution tile
+            context.moveTo(clip[6], clip[7]);
+            context.lineTo(clip[4], clip[5]);
+            context.lineTo(clip[2], clip[3]);
+            context.lineTo(clip[0], clip[1]);
+            context.clip();
+          }
+        }
+        replayGroup.replay(context, pixelRatio, transform, rotation, {}, replays);
+        context.restore();
+        clips.push(currentClip);
+        zs.push(currentZ);
+      }
+    }
   }
-  return ol.interaction.Pointer.handleEvent.call(this, evt);
+  ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments);
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} evt Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Snap}
- * @private
+ * @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.interaction.Snap.handleUpEvent_ = function(evt) {
-  var featuresToUpdate = goog.object.getValues(this.pendingFeatures_);
-  if (featuresToUpdate.length) {
-    featuresToUpdate.forEach(this.updateFeature_, this);
-    this.pendingFeatures_ = {};
+ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, squaredTolerance, styles, replayGroup) {
+  if (!styles) {
+    return false;
   }
-  return false;
+  var loading = false;
+  if (Array.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;
 };
 
 
 /**
- * Sort segments by distance, helper function
- * @param {ol.interaction.Snap.SegmentDataType} a
- * @param {ol.interaction.Snap.SegmentDataType} b
- * @return {number}
- * @this {ol.interaction.Snap}
+ * @param {ol.VectorImageTile} tile Tile.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.LayerState} layerState Layer state.
+ * @private
  */
-ol.interaction.Snap.sortByDistance = function(a, b) {
-  return ol.coordinate.squaredDistanceToSegment(
-      this.pixelCoordinate_, a.segment) -
-      ol.coordinate.squaredDistanceToSegment(
-      this.pixelCoordinate_, b.segment);
+ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function(
+    tile, frameState, layerState) {
+  var layer = this.getLayer();
+  var replayState = tile.getReplayState();
+  var revision = layer.getRevision();
+  var replays = ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS[layer.getRenderMode()];
+  if (replays && replayState.renderedTileRevision !== revision) {
+    replayState.renderedTileRevision = revision;
+    var tileCoord = tile.wrappedTileCoord;
+    var z = tileCoord[0];
+    var pixelRatio = frameState.pixelRatio;
+    var source = layer.getSource();
+    var sourceTileGrid = source.getTileGrid();
+    var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
+    var resolution = tileGrid.getResolution(z);
+    var tilePixelRatio = source.getTilePixelRatio();
+    var context = tile.getContext();
+    var size = source.getTilePixelSize(z, pixelRatio, frameState.viewState.projection);
+    context.canvas.width = size[0];
+    context.canvas.height = size[1];
+    var tileExtent = tileGrid.getTileCoordExtent(tileCoord);
+    for (var i = 0, ii = tile.tileKeys.length; i < ii; ++i) {
+      var sourceTile = tile.getTile(tile.tileKeys[i]);
+      var sourceTileCoord = sourceTile.tileCoord;
+      var pixelScale = pixelRatio / resolution;
+      var transform = ol.transform.reset(this.tmpTransform_);
+      if (sourceTile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) {
+        var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord, this.tmpExtent);
+        var sourceResolution = sourceTileGrid.getResolution(sourceTileCoord[0]);
+        var renderPixelRatio = pixelRatio / tilePixelRatio * sourceResolution / resolution;
+        ol.transform.scale(transform, renderPixelRatio, renderPixelRatio);
+        var offsetX = (sourceTileExtent[0] - tileExtent[0]) / sourceResolution * tilePixelRatio;
+        var offsetY = (tileExtent[3] - sourceTileExtent[3]) / sourceResolution * tilePixelRatio;
+        ol.transform.translate(transform, Math.round(offsetX), Math.round(offsetY));
+      } else {
+        ol.transform.scale(transform, pixelScale, -pixelScale);
+        ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]);
+      }
+      var replayGroup = sourceTile.getReplayGroup(tile.tileCoord.toString());
+      replayGroup.replay(context, pixelRatio, transform, 0, {}, replays);
+    }
+  }
 };
 
-goog.provide('ol.interaction.Translate');
-
-goog.require('ol.array');
-goog.require('ol.interaction.Pointer');
+goog.provide('ol.layer.VectorTile');
 
+goog.require('ol');
+goog.require('ol.asserts');
+goog.require('ol.layer.TileProperty');
+goog.require('ol.layer.Vector');
+goog.require('ol.layer.VectorTileRenderType');
+goog.require('ol.obj');
+goog.require('ol.renderer.Type');
+goog.require('ol.renderer.canvas.VectorTileLayer');
 
 
 /**
  * @classdesc
- * Interaction for translating (moving) features.
+ * 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.interaction.Pointer}
- * @param {olx.interaction.TranslateOptions} options Options.
+ * @extends {ol.layer.Vector}
+ * @param {olx.layer.VectorTileOptions=} opt_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_
-  });
-
+ol.layer.VectorTile = function(opt_options) {
+  var options = opt_options ? opt_options : {};
 
-  /**
-   * @type {string|undefined}
-   * @private
-   */
-  this.previousCursor_ = undefined;
+  var baseOptions = ol.obj.assign({}, options);
 
+  delete baseOptions.preload;
+  delete baseOptions.useInterimTilesOnError;
+  ol.layer.Vector.call(this,  /** @type {olx.layer.VectorOptions} */ (baseOptions));
 
-  /**
-   * The last position we translated to.
-   * @type {ol.Coordinate}
-   * @private
-   */
-  this.lastCoordinate_ = null;
+  this.setPreload(options.preload ? options.preload : 0);
+  this.setUseInterimTilesOnError(options.useInterimTilesOnError ?
+      options.useInterimTilesOnError : true);
 
+  ol.asserts.assert(options.renderMode == undefined ||
+      options.renderMode == ol.layer.VectorTileRenderType.IMAGE ||
+      options.renderMode == ol.layer.VectorTileRenderType.HYBRID ||
+      options.renderMode == ol.layer.VectorTileRenderType.VECTOR,
+      28); // `renderMode` must be `'image'`, `'hybrid'` or `'vector'`
 
   /**
-   * @type {ol.Collection.<ol.Feature>}
    * @private
+   * @type {ol.layer.VectorTileRenderType|string}
    */
-  this.features_ = options.features !== undefined ? options.features : null;
+  this.renderMode_ = options.renderMode || ol.layer.VectorTileRenderType.HYBRID;
 
-  /**
-   * @type {ol.Feature}
-   * @private
-   */
-  this.lastFeature_ = null;
 };
-goog.inherits(ol.interaction.Translate, ol.interaction.Pointer);
+ol.inherits(ol.layer.VectorTile, ol.layer.Vector);
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.Translate}
- * @private
+ * @inheritDoc
  */
-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);
-    return true;
+ol.layer.VectorTile.prototype.createRenderer = function(mapRenderer) {
+  var renderer = null;
+  var type = mapRenderer.getType();
+  if (ol.ENABLE_CANVAS && type === ol.renderer.Type.CANVAS) {
+    renderer = new ol.renderer.canvas.VectorTileLayer(this);
   }
-  return false;
+  return renderer;
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Translate}
- * @private
+ * 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.interaction.Translate.handleUpEvent_ = function(event) {
-  if (this.lastCoordinate_) {
-    this.lastCoordinate_ = null;
-    ol.interaction.Translate.handleMoveEvent_.call(this, event);
-    return true;
-  }
-  return false;
+ol.layer.VectorTile.prototype.getPreload = function() {
+  return /** @type {number} */ (this.get(ol.layer.TileProperty.PRELOAD));
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @this {ol.interaction.Translate}
- * @private
+ * @return {ol.layer.VectorTileRenderType|string} The render mode.
  */
-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];
+ol.layer.VectorTile.prototype.getRenderMode = function() {
+  return this.renderMode_;
+};
 
-    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;
-  }
+/**
+ * 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.TileProperty.USE_INTERIM_TILES_ON_ERROR));
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} event Event.
- * @this {ol.interaction.Translate}
- * @private
+ * 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.interaction.Translate.handleMoveEvent_ = function(event)
-    {
-  var elem = event.map.getTargetElement();
-  var intersectingFeature = event.map.forEachFeatureAtPixel(event.pixel,
-      function(feature) {
-        return feature;
-      });
+ol.layer.VectorTile.prototype.setPreload = function(preload) {
+  this.set(ol.layer.TileProperty.PRELOAD, preload);
+};
 
-  if (intersectingFeature) {
-    var isSelected = false;
 
-    if (this.features_ &&
-        ol.array.includes(this.features_.getArray(), intersectingFeature)) {
-      isSelected = true;
-    }
+/**
+ * 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);
+};
 
-    this.previousCursor_ = elem.style.cursor;
+goog.provide('ol.net');
 
-    // WebKit browsers don't support the grab icons without a prefix
-    elem.style.cursor = this.lastCoordinate_ ?
-        '-webkit-grabbing' : (isSelected ? '-webkit-grab' : 'pointer');
+goog.require('ol');
 
-    // 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;
-  }
+/**
+ * Simple JSONP helper. Supports error callbacks and a custom callback param.
+ * The error callback will be called when no JSONP is executed after 10 seconds.
+ *
+ * @param {string} url Request url. A 'callback' query parameter will be
+ *     appended.
+ * @param {Function} callback Callback on success.
+ * @param {function()=} opt_errback Callback on error.
+ * @param {string=} opt_callbackParam Custom query parameter for the JSONP
+ *     callback. Default is 'callback'.
+ */
+ol.net.jsonp = function(url, callback, opt_errback, opt_callbackParam) {
+  var script = document.createElement('script');
+  var key = 'olc_' + ol.getUid(callback);
+  function cleanup() {
+    delete window[key];
+    script.parentNode.removeChild(script);
+  }
+  script.async = true;
+  script.src = url + (url.indexOf('?') == -1 ? '?' : '&') +
+      (opt_callbackParam || 'callback') + '=' + key;
+  var timer = setTimeout(function() {
+    cleanup();
+    if (opt_errback) {
+      opt_errback();
+    }
+  }, 10000);
+  window[key] = function(data) {
+    clearTimeout(timer);
+    cleanup();
+    callback(data);
+  };
+  document.getElementsByTagName('head')[0].appendChild(script);
 };
 
+goog.provide('ol.proj.common');
 
-/**
- * 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;
-      });
+goog.require('ol.proj');
 
-  if (this.features_ &&
-      ol.array.includes(this.features_.getArray(), intersectingFeature)) {
-    found = intersectingFeature;
-  }
 
-  return found;
-};
+/**
+ * Deprecated.  Transforms between EPSG:4326 and EPSG:3857 are now included by
+ * default.  There is no need to call this function in application code and it
+ * will be removed in a future major release.
+ * @deprecated This function is no longer necessary.
+ * @api
+ */
+ol.proj.common.add = ol.proj.addCommon;
 
-goog.provide('ol.layer.Heatmap');
+goog.provide('ol.render');
 
-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');
+goog.require('ol.has');
+goog.require('ol.transform');
+goog.require('ol.render.canvas.Immediate');
 
 
 /**
- * @enum {string}
+ * 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.drawPolygon(
+ *     new ol.geom.Polygon([[[0, 0], [100, 100], [100, 0], [0, 0]]]));
+ * ```
+ *
+ * @param {CanvasRenderingContext2D} context Canvas context.
+ * @param {olx.render.ToContextOptions=} opt_options Options.
+ * @return {ol.render.canvas.Immediate} Canvas Immediate.
+ * @api
  */
-ol.layer.HeatmapLayerProperty = {
-  BLUR: 'blur',
-  GRADIENT: 'gradient',
-  RADIUS: 'radius'
+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.transform.scale(ol.transform.create(), pixelRatio, pixelRatio);
+  return new ol.render.canvas.Immediate(context, pixelRatio, extent, transform,
+      0);
 };
 
+goog.provide('ol.reproj.Tile');
+
+goog.require('ol');
+goog.require('ol.Tile');
+goog.require('ol.TileState');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
+goog.require('ol.math');
+goog.require('ol.reproj');
+goog.require('ol.reproj.Triangulation');
 
 
 /**
  * @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.
+ * Class encapsulating single reprojected tile.
+ * See {@link ol.source.TileImage}.
  *
  * @constructor
- * @extends {ol.layer.Vector}
- * @fires ol.render.Event
- * @param {olx.layer.HeatmapOptions=} opt_options Options.
- * @api
+ * @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 {number} gutter Gutter of the source tiles.
+ * @param {ol.ReprojTileFunctionType} 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.layer.Heatmap = function(opt_options) {
-  var options = opt_options ? opt_options : {};
-
-  var baseOptions = goog.object.clone(options);
+ol.reproj.Tile = function(sourceProj, sourceTileGrid,
+    targetProj, targetTileGrid, tileCoord, wrappedTileCoord,
+    pixelRatio, gutter, getTileFunction,
+    opt_errorThreshold,
+    opt_renderEdges) {
+  ol.Tile.call(this, tileCoord, ol.TileState.IDLE);
 
-  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 {boolean}
+   */
+  this.renderEdges_ = opt_renderEdges !== undefined ? opt_renderEdges : false;
 
   /**
    * @private
-   * @type {Uint8ClampedArray}
+   * @type {number}
    */
-  this.gradient_ = null;
+  this.pixelRatio_ = pixelRatio;
 
   /**
    * @private
    * @type {number}
    */
-  this.shadow_ = options.shadow !== undefined ? options.shadow : 250;
+  this.gutter_ = gutter;
 
   /**
    * @private
-   * @type {string|undefined}
+   * @type {HTMLCanvasElement}
    */
-  this.circleImage_ = undefined;
+  this.canvas_ = null;
 
   /**
    * @private
-   * @type {Array.<Array.<ol.style.Style>>}
+   * @type {ol.tilegrid.TileGrid}
    */
-  this.styleCache_ = null;
+  this.sourceTileGrid_ = sourceTileGrid;
 
-  goog.events.listen(this,
-      ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.GRADIENT),
-      this.handleGradientChanged_, false, this);
+  /**
+   * @private
+   * @type {ol.tilegrid.TileGrid}
+   */
+  this.targetTileGrid_ = targetTileGrid;
 
-  this.setGradient(options.gradient ?
-      options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT);
+  /**
+   * @private
+   * @type {ol.TileCoord}
+   */
+  this.wrappedTileCoord_ = wrappedTileCoord ? wrappedTileCoord : tileCoord;
 
-  this.setBlur(options.blur !== undefined ? options.blur : 15);
+  /**
+   * @private
+   * @type {!Array.<ol.Tile>}
+   */
+  this.sourceTiles_ = [];
 
-  this.setRadius(options.radius !== undefined ? options.radius : 8);
+  /**
+   * @private
+   * @type {Array.<ol.EventsKey>}
+   */
+  this.sourcesListenerKeys_ = null;
 
-  goog.events.listen(this, [
-    ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.BLUR),
-    ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.RADIUS)
-  ], this.handleStyleChanged_, false, this);
+  /**
+   * @private
+   * @type {number}
+   */
+  this.sourceZ_ = 0;
 
-  this.handleStyleChanged_();
+  var targetExtent = targetTileGrid.getTileCoordExtent(this.wrappedTileCoord_);
+  var maxTargetExtent = this.targetTileGrid_.getExtent();
+  var maxSourceExtent = this.sourceTileGrid_.getExtent();
 
-  var weight = options.weight ? options.weight : 'weight';
-  var weightFunction;
-  if (goog.isString(weight)) {
-    weightFunction = function(feature) {
-      return feature.get(weight);
-    };
-  } else {
-    weightFunction = weight;
+  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;
   }
-  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;
+  var sourceProjExtent = sourceProj.getExtent();
+  if (sourceProjExtent) {
+    if (!maxSourceExtent) {
+      maxSourceExtent = sourceProjExtent;
+    } else {
+      maxSourceExtent = ol.extent.getIntersection(
+          maxSourceExtent, sourceProjExtent);
     }
-    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);
+  var targetResolution = targetTileGrid.getResolution(
+      this.wrappedTileCoord_[0]);
+
+  var targetCenter = ol.extent.getCenter(limitedTargetExtent);
+  var sourceResolution = ol.reproj.calculateSourceResolution(
+      sourceProj, targetProj, targetCenter, targetResolution);
+
+  if (!isFinite(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);
+    }
+  }
 
-  goog.events.listen(this, ol.render.EventType.RENDER,
-      this.handleRender_, false, this);
+  if (!ol.extent.getArea(sourceExtent)) {
+    this.state = ol.TileState.EMPTY;
+  } else {
+    var sourceRange = sourceTileGrid.getTileRangeForExtentAndZ(
+        sourceExtent, this.sourceZ_);
+
+    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.layer.Heatmap, ol.layer.Vector);
+ol.inherits(ol.reproj.Tile, ol.Tile);
 
 
 /**
- * @const
- * @type {Array.<string>}
+ * @inheritDoc
  */
-ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00'];
+ol.reproj.Tile.prototype.disposeInternal = function() {
+  if (this.state == ol.TileState.LOADING) {
+    this.unlistenSources_();
+  }
+  ol.Tile.prototype.disposeInternal.call(this);
+};
 
 
 /**
- * @param {Array.<string>} colors
- * @return {Uint8ClampedArray}
- * @private
+ * Get the HTML Canvas element for this tile.
+ * @return {HTMLCanvasElement} Canvas.
  */
-ol.layer.Heatmap.createGradient_ = function(colors) {
-  var width = 1;
-  var height = 256;
-  var context = ol.dom.createCanvasContext2D(width, height);
+ol.reproj.Tile.prototype.getImage = function() {
+  return this.canvas_;
+};
 
-  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);
+/**
+ * @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;
 
-  return context.getImageData(0, 0, width, height).data;
+  if (sources.length === 0) {
+    this.state = ol.TileState.ERROR;
+  } else {
+    var z = this.wrappedTileCoord_[0];
+    var size = this.targetTileGrid_.getTileSize(z);
+    var width = typeof size === 'number' ? size : size[0];
+    var height = typeof size === 'number' ? 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.gutter_, this.renderEdges_);
+
+    this.state = ol.TileState.LOADED;
+  }
+  this.changed();
 };
 
 
 /**
- * @return {string}
- * @private
+ * @inheritDoc
  */
-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();
+ol.reproj.Tile.prototype.load = function() {
+  if (this.state == ol.TileState.IDLE) {
+    this.state = ol.TileState.LOADING;
+    this.changed();
+
+    var leftToLoad = 0;
+
+    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 = ol.events.listen(tile, ol.events.EventType.CHANGE,
+            function(e) {
+              var state = tile.getState();
+              if (state == ol.TileState.LOADED ||
+                  state == ol.TileState.ERROR ||
+                  state == ol.TileState.EMPTY) {
+                ol.events.unlistenByKey(sourceListenKey);
+                leftToLoad--;
+                if (leftToLoad === 0) {
+                  this.unlistenSources_();
+                  this.reproject_();
+                }
+              }
+            }, 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) {
+      setTimeout(this.reproject_.bind(this), 0);
+    }
+  }
 };
 
 
 /**
- * Return the blur size in pixels.
- * @return {number} Blur size in pixels.
- * @api
- * @observable
+ * @private
  */
-ol.layer.Heatmap.prototype.getBlur = function() {
-  return /** @type {number} */ (this.get(ol.layer.HeatmapLayerProperty.BLUR));
+ol.reproj.Tile.prototype.unlistenSources_ = function() {
+  this.sourcesListenerKeys_.forEach(ol.events.unlistenByKey);
+  this.sourcesListenerKeys_ = null;
 };
 
+goog.provide('ol.TileUrlFunction');
+
+goog.require('ol.asserts');
+goog.require('ol.math');
+goog.require('ol.tilecoord');
+
 
 /**
- * Return the gradient colors as array of strings.
- * @return {Array.<string>} Colors.
- * @api
- * @observable
+ * @param {string} template Template.
+ * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
+ * @return {ol.TileUrlFunctionType} Tile URL function.
  */
-ol.layer.Heatmap.prototype.getGradient = function() {
-  return /** @type {Array.<string>} */ (
-      this.get(ol.layer.HeatmapLayerProperty.GRADIENT));
+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);
+                ol.asserts.assert(range, 55); // The {-y} placeholder requires a tile grid with extent
+                var y = range.getHeight() + tileCoord[2];
+                return y.toString();
+              });
+        }
+      });
 };
 
 
 /**
- * Return the size of the radius in pixels.
- * @return {number} Radius size in pixel.
- * @api
- * @observable
+ * @param {Array.<string>} templates Templates.
+ * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
+ * @return {ol.TileUrlFunctionType} Tile URL function.
  */
-ol.layer.Heatmap.prototype.getRadius = function() {
-  return /** @type {number} */ (this.get(ol.layer.HeatmapLayerProperty.RADIUS));
+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);
 };
 
 
 /**
- * @private
+ * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions.
+ * @return {ol.TileUrlFunctionType} Tile URL function.
  */
-ol.layer.Heatmap.prototype.handleGradientChanged_ = function() {
-  this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient());
+ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) {
+  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 = ol.math.modulo(h, tileUrlFunctions.length);
+          return tileUrlFunctions[index](tileCoord, pixelRatio, projection);
+        }
+      });
 };
 
 
 /**
- * @private
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {string|undefined} Tile URL.
  */
-ol.layer.Heatmap.prototype.handleStyleChanged_ = function() {
-  this.circleImage_ = this.createCircle_();
-  this.styleCache_ = new Array(256);
-  this.changed();
+ol.TileUrlFunction.nullTileUrlFunction = function(tileCoord, pixelRatio, projection) {
+  return undefined;
 };
 
 
 /**
- * @param {ol.render.Event} event Post compose event
- * @private
+ * @param {string} url URL.
+ * @return {Array.<string>} Array of urls.
  */
-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];
+ol.TileUrlFunction.expandUrl = function(url) {
+  var urls = [];
+  var match = /\{([a-z])-([a-z])\}/.exec(url);
+  if (match) {
+    // char range
+    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)));
     }
+    return urls;
   }
-  context.putImageData(image, 0, 0);
+  match = match = /\{(\d+)-(\d+)\}/.exec(url);
+  if (match) {
+    // number range
+    var stop = parseInt(match[2], 10);
+    for (var i = parseInt(match[1], 10); i <= stop; i++) {
+      urls.push(url.replace(match[0], i.toString()));
+    }
+    return urls;
+  }
+  urls.push(url);
+  return urls;
 };
 
+goog.provide('ol.TileCache');
+
+goog.require('ol');
+goog.require('ol.structs.LRUCache');
+
 
 /**
- * Set the blur size in pixels.
- * @param {number} blur Blur size in pixels.
- * @api
- * @observable
+ * @constructor
+ * @extends {ol.structs.LRUCache.<ol.Tile>}
+ * @param {number=} opt_highWaterMark High water mark.
+ * @struct
  */
-ol.layer.Heatmap.prototype.setBlur = function(blur) {
-  this.set(ol.layer.HeatmapLayerProperty.BLUR, blur);
+ol.TileCache = function(opt_highWaterMark) {
+
+  ol.structs.LRUCache.call(this);
+
+  /**
+   * @type {number}
+   */
+  this.highWaterMark = opt_highWaterMark !== undefined ? opt_highWaterMark : 2048;
+
 };
+ol.inherits(ol.TileCache, ol.structs.LRUCache);
 
 
 /**
- * Set the gradient colors as array of strings.
- * @param {Array.<string>} colors Gradient.
- * @api
- * @observable
+ * @return {boolean} Can expire cache.
  */
-ol.layer.Heatmap.prototype.setGradient = function(colors) {
-  this.set(ol.layer.HeatmapLayerProperty.GRADIENT, colors);
+ol.TileCache.prototype.canExpireCache = function() {
+  return this.getCount() > this.highWaterMark;
 };
 
 
 /**
- * Set the size of the radius in pixels.
- * @param {number} radius Radius size in pixel.
- * @api
- * @observable
+ * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
  */
-ol.layer.Heatmap.prototype.setRadius = function(radius) {
-  this.set(ol.layer.HeatmapLayerProperty.RADIUS, radius);
+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();
+    }
+  }
 };
 
-goog.provide('ol.raster.Operation');
-goog.provide('ol.raster.OperationType');
+goog.provide('ol.source.Tile');
+
+goog.require('ol');
+goog.require('ol.TileCache');
+goog.require('ol.TileState');
+goog.require('ol.events.Event');
+goog.require('ol.proj');
+goog.require('ol.size');
+goog.require('ol.source.Source');
+goog.require('ol.tilecoord');
+goog.require('ol.tilegrid');
 
 
 /**
- * Raster operation type. Supported values are `'pixel'` and `'image'`.
- * @enum {string}
+ * @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
+ * @abstract
+ * @extends {ol.source.Source}
+ * @param {ol.SourceTileOptions} options Tile source options.
  * @api
  */
-ol.raster.OperationType = {
-  PIXEL: 'pixel',
-  IMAGE: 'image'
+ol.source.Tile = function(options) {
+
+  ol.source.Source.call(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];
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.key_ = '';
+
 };
+ol.inherits(ol.source.Tile, ol.source.Source);
 
 
 /**
- * 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
+ * @return {boolean} Can expire cache.
  */
-ol.raster.Operation;
-
-goog.provide('ol.raster.Pixel');
+ol.source.Tile.prototype.canExpireCache = function() {
+  return this.tileCache.canExpireCache();
+};
 
 
 /**
- * An array of numbers representing pixel values.
- * @typedef {Array.<number>} ol.raster.Pixel
- * @api
+ * @param {ol.proj.Projection} projection Projection.
+ * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
  */
-ol.raster.Pixel;
+ol.source.Tile.prototype.expireCache = function(projection, usedTiles) {
+  var tileCache = this.getTileCacheForProjection(projection);
+  if (tileCache) {
+    tileCache.expireCache(usedTiles);
+  }
+};
 
-// 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.
- *
+ * @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;
+  }
 
-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');
+  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;
+};
 
 
 /**
- * The name of the property of goog.global under which the JavaScript
- * verification object is stored by the loaded script.
- * @private {string}
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {number} Gutter.
  */
-goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification';
+ol.source.Tile.prototype.getGutter = function(projection) {
+  return 0;
+};
 
 
 /**
- * The default length of time, in milliseconds, we are prepared to wait for a
- * load request to complete.
- * @type {number}
+ * Return the key to be used for all tiles in the source.
+ * @return {string} The key for all tiles.
+ * @protected
  */
-goog.net.jsloader.DEFAULT_TIMEOUT = 5000;
+ol.source.Tile.prototype.getKey = function() {
+  return this.key_;
+};
 
 
 /**
- * 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)
- * }}
+ * Set the value to be used as the key for all tiles in the source.
+ * @param {string} key The key for tiles.
+ * @protected
  */
-goog.net.jsloader.Options;
+ol.source.Tile.prototype.setKey = function(key) {
+  if (this.key_ !== key) {
+    this.key_ = key;
+    this.changed();
+  }
+};
 
 
 /**
- * Scripts (URIs) waiting to be loaded.
- * @private {!Array<string>}
+ * @param {number} z Z.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @return {string} Key.
+ * @protected
  */
-goog.net.jsloader.scriptsToLoad_ = [];
+ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY;
 
 
 /**
- * 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>}
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {boolean} Opaque.
  */
-goog.net.jsloader.scriptLoadingDeferred_;
+ol.source.Tile.prototype.getOpaque = function(projection) {
+  return this.opaque_;
+};
 
 
 /**
- * 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
+ * @inheritDoc
  */
-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_;
+ol.source.Tile.prototype.getResolutions = function() {
+  return this.tileGrid.getResolutions();
 };
 
 
 /**
- * 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.
+ * @abstract
+ * @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.
  */
-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.'));
-    }
-  });
+ol.source.Tile.prototype.getTile = function(z, x, y, pixelRatio, projection) {};
 
-  // Pass error to new deferred object.
-  sendDeferred.addErrback(function(error) {
-    if (goog.isDef(verifyObjs[verificationObjName])) {
-      delete verifyObjs[verificationObjName];
-    }
-    deferred.errback(error);
-  });
 
-  return deferred;
+/**
+ * Return the tile grid of the tile source.
+ * @return {ol.tilegrid.TileGrid} Tile grid.
+ * @api
+ */
+ol.source.Tile.prototype.getTileGrid = function() {
+  return this.tileGrid;
 };
 
 
 /**
- * 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
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {!ol.tilegrid.TileGrid} Tile grid.
  */
-goog.net.jsloader.getScriptParentElement_ = function(doc) {
-  var headElements = doc.getElementsByTagName(goog.dom.TagName.HEAD);
-  if (!headElements || goog.array.isEmpty(headElements)) {
-    return doc.documentElement;
+ol.source.Tile.prototype.getTileGridForProjection = function(projection) {
+  if (!this.tileGrid) {
+    return ol.tilegrid.getForProjection(projection);
   } else {
-    return headElements[0];
+    return this.tileGrid;
   }
 };
 
 
 /**
- * Cancels a given request.
- * @this {{script_: Element, timeout_: number}} The request context.
- * @private
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {ol.TileCache} Tile cache.
+ * @protected
  */
-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_);
-    }
+ol.source.Tile.prototype.getTileCacheForProjection = function(projection) {
+  var thisProj = this.getProjection();
+  if (thisProj && !ol.proj.equivalent(thisProj, projection)) {
+    return null;
+  } else {
+    return this.tileCache;
   }
 };
 
 
 /**
- * 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
+ * Get the tile pixel ratio for this source. Subclasses may override this
+ * method, which is meant to return a supported pixel ratio that matches the
+ * provided `opt_pixelRatio` as close as possible. When no `opt_pixelRatio` is
+ * provided, it is meant to return `this.tilePixelRatio_`.
+ * @param {number=} opt_pixelRatio Pixel ratio.
+ * @return {number} Tile pixel ratio.
  */
-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);
-  }
+ol.source.Tile.prototype.getTilePixelRatio = function(opt_pixelRatio) {
+  return this.tilePixelRatio_;
 };
 
 
 /**
- * Possible error codes for jsloader.
- * @enum {number}
+ * @param {number} z Z.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {ol.Size} Tile size.
  */
-goog.net.jsloader.ErrorCode = {
-  LOAD_ERROR: 0,
-  TIMEOUT: 1,
-  VERIFY_ERROR: 2,
-  VERIFY_OBJECT_ALREADY_EXISTS: 3
+ol.source.Tile.prototype.getTilePixelSize = function(z, pixelRatio, projection) {
+  var tileGrid = this.getTileGridForProjection(projection);
+  var tilePixelRatio = this.getTilePixelRatio(pixelRatio);
+  var tileSize = ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize);
+  if (tilePixelRatio == 1) {
+    return tileSize;
+  } else {
+    return ol.size.scale(tileSize, tilePixelRatio, this.tmpSize);
+  }
 };
 
 
-
 /**
- * A jsloader error.
- *
- * @param {goog.net.jsloader.ErrorCode} code The error code.
- * @param {string=} opt_message Additional message.
- * @constructor
- * @extends {goog.debug.Error}
- * @final
+ * Returns a tile coordinate wrapped around the x-axis. When the tile coordinate
+ * is outside the resolution and extent range of the tile grid, `null` will be
+ * returned.
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.proj.Projection=} opt_projection Projection.
+ * @return {ol.TileCoord} Tile coordinate to be passed to the tileUrlFunction or
+ *     null if no tile URL should be created for the passed `tileCoord`.
  */
-goog.net.jsloader.Error = function(code, opt_message) {
-  var msg = 'Jsloader error (code #' + code + ')';
-  if (opt_message) {
-    msg += ': ' + opt_message;
+ol.source.Tile.prototype.getTileCoordForTileUrlFunction = function(tileCoord, opt_projection) {
+  var projection = opt_projection !== undefined ?
+      opt_projection : this.getProjection();
+  var tileGrid = this.getTileGridForProjection(projection);
+  if (this.getWrapX() && projection.isGlobal()) {
+    tileCoord = ol.tilegrid.wrapX(tileGrid, tileCoord, projection);
   }
-  goog.net.jsloader.Error.base(this, 'constructor', msg);
-
-  /**
-   * The code for this error.
-   *
-   * @type {goog.net.jsloader.ErrorCode}
-   */
-  this.code = code;
+  return ol.tilecoord.withinExtentAndZ(tileCoord, tileGrid) ? tileCoord : null;
 };
-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+.
- *
+ * @inheritDoc
  */
+ol.source.Tile.prototype.refresh = function() {
+  this.tileCache.clear();
+  this.changed();
+};
 
-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
 
+/**
+ * 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;
 
 
 /**
- * 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".
+ * @classdesc
+ * Events emitted by {@link ol.source.Tile} instances are instances of this
+ * type.
  *
  * @constructor
- * @final
+ * @extends {ol.events.Event}
+ * @implements {oli.source.Tile.Event}
+ * @param {string} type Type.
+ * @param {ol.Tile} tile The tile.
  */
-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);
+ol.source.Tile.Event = function(type, tile) {
 
-  /**
-   * This is the callback parameter name that is added to the uri.
-   * @type {string}
-   * @private
-   */
-  this.callbackParamName_ = opt_callbackParamName ?
-      opt_callbackParamName : 'callback';
+  ol.events.Event.call(this, type);
 
   /**
-   * 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
+   * The tile related to the event.
+   * @type {ol.Tile}
+   * @api
    */
-  this.timeout_ = 5000;
+  this.tile = tile;
+
 };
+ol.inherits(ol.source.Tile.Event, ol.events.Event);
 
+goog.provide('ol.source.TileEventType');
 
 /**
- * The name of the property of goog.global under which the callback is
- * stored.
+ * @enum {string}
  */
-goog.net.Jsonp.CALLBACKS = '_callbacks_';
+ol.source.TileEventType = {
 
+  /**
+   * Triggered when a tile starts loading.
+   * @event ol.source.Tile.Event#tileloadstart
+   * @api
+   */
+  TILELOADSTART: 'tileloadstart',
 
-/**
- * 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;
+  /**
+   * Triggered when a tile finishes loading.
+   * @event ol.source.Tile.Event#tileloadend
+   * @api
+   */
+  TILELOADEND: 'tileloadend',
 
+  /**
+   * Triggered if tile loading results in an error.
+   * @event ol.source.Tile.Event#tileloaderror
+   * @api
+   */
+  TILELOADERROR: 'tileloaderror'
 
-/**
- * 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;
 };
 
+goog.provide('ol.source.UrlTile');
 
-/**
- * Returns the current timeout value, in milliseconds.
- *
- * @return {number} The timeout value.
- */
-goog.net.Jsonp.prototype.getRequestTimeout = function() {
-  return this.timeout_;
-};
+goog.require('ol');
+goog.require('ol.TileState');
+goog.require('ol.TileUrlFunction');
+goog.require('ol.source.Tile');
+goog.require('ol.source.TileEventType');
 
 
 /**
- * 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.
+ * @classdesc
+ * Base class for sources providing tiles divided into a tile grid over http.
  *
- * @return {!Object} A request descriptor that may be used to cancel this
- *     transmission, or null, if the message may not be cancelled.
+ * @constructor
+ * @abstract
+ * @fires ol.source.Tile.Event
+ * @extends {ol.source.Tile}
+ * @param {ol.SourceUrlTileOptions} options Image tile options.
  */
-goog.net.Jsonp.prototype.send = function(opt_payload,
-                                         opt_replyCallback,
-                                         opt_errorCallback,
-                                         opt_callbackParamValue) {
+ol.source.UrlTile = function(options) {
 
-  var payload = opt_payload || null;
-
-  var id = opt_callbackParamValue ||
-      '_' + (goog.net.Jsonp.scriptCounter_++).toString(36) +
-      goog.now().toString(36);
+  ol.source.Tile.call(this, {
+    attributions: options.attributions,
+    cacheSize: options.cacheSize,
+    extent: options.extent,
+    logo: options.logo,
+    opaque: options.opaque,
+    projection: options.projection,
+    state: options.state,
+    tileGrid: options.tileGrid,
+    tilePixelRatio: options.tilePixelRatio,
+    wrapX: options.wrapX
+  });
 
-  if (!goog.global[goog.net.Jsonp.CALLBACKS]) {
-    goog.global[goog.net.Jsonp.CALLBACKS] = {};
-  }
+  /**
+   * @protected
+   * @type {ol.TileLoadFunctionType}
+   */
+  this.tileLoadFunction = options.tileLoadFunction;
 
-  // 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);
-  }
+  /**
+   * @protected
+   * @type {ol.TileUrlFunctionType}
+   */
+  this.tileUrlFunction = this.fixedTileUrlFunction ?
+      this.fixedTileUrlFunction.bind(this) :
+      ol.TileUrlFunction.nullTileUrlFunction;
 
-  if (opt_replyCallback) {
-    var reply = goog.net.Jsonp.newReplyHandler_(id, opt_replyCallback);
-    goog.global[goog.net.Jsonp.CALLBACKS][id] = reply;
+  /**
+   * @protected
+   * @type {!Array.<string>|null}
+   */
+  this.urls = null;
 
-    uri.setParameterValues(this.callbackParamName_,
-                           goog.net.Jsonp.CALLBACKS + '.' + id);
+  if (options.urls) {
+    this.setUrls(options.urls);
+  } else if (options.url) {
+    this.setUrl(options.url);
   }
-
-  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);
-    }
+  if (options.tileUrlFunction) {
+    this.setTileUrlFunction(options.tileUrlFunction);
   }
-};
 
-
-/**
- * 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);
-    }
-  };
 };
+ol.inherits(ol.source.UrlTile, ol.source.Tile);
 
 
 /**
- * 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
+ * @type {ol.TileUrlFunctionType|undefined}
+ * @protected
  */
-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;
-};
-
+ol.source.UrlTile.prototype.fixedTileUrlFunction;
 
 /**
- * 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
+ * Return the tile load function of the source.
+ * @return {ol.TileLoadFunctionType} TileLoadFunction
+ * @api
  */
-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;
-    }
-  }
+ol.source.UrlTile.prototype.getTileLoadFunction = function() {
+  return this.tileLoadFunction;
 };
 
 
 /**
- * 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
+ * Return the tile URL function of the source.
+ * @return {ol.TileUrlFunctionType} TileUrlFunction
+ * @api
  */
-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;
+ol.source.UrlTile.prototype.getTileUrlFunction = function() {
+  return this.tileUrlFunction;
 };
 
 
-// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
-//
-// This class allows us (Google) to send data from non-Google and thus
-// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
-// anything sensitive, such as session or cookie specific data. Return
-// only data that you want parties external to Google to have. Also
-// NEVER use this method to send data from web pages to untrusted
-// servers, or redirects to unknown servers (www.google.com/cache,
-// /q=xx&btnl, /url, www.googlepages.com, etc.)
-//
-// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
-
-goog.provide('ol.TileUrlFunction');
-goog.provide('ol.TileUrlFunctionType');
-
-goog.require('goog.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)}
+ * 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.TileUrlFunctionType;
+ol.source.UrlTile.prototype.getUrls = function() {
+  return this.urls;
+};
 
 
 /**
- * @typedef {function(ol.TileCoord, ol.proj.Projection, ol.TileCoord=):
- *     ol.TileCoord}
+ * Handle tile change events.
+ * @param {ol.events.Event} event Event.
+ * @protected
  */
-ol.TileCoordTransformType;
+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.Tile.Event(ol.source.TileEventType.TILELOADSTART, tile));
+      break;
+    case ol.TileState.LOADED:
+      this.dispatchEvent(
+          new ol.source.Tile.Event(ol.source.TileEventType.TILELOADEND, tile));
+      break;
+    case ol.TileState.ERROR:
+      this.dispatchEvent(
+          new ol.source.Tile.Event(ol.source.TileEventType.TILELOADERROR, tile));
+      break;
+    default:
+      // pass
+  }
+};
 
 
 /**
- * @param {string} template Template.
- * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
- * @return {ol.TileUrlFunctionType} Tile URL function.
+ * Set the tile load function of the source.
+ * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
+ * @api
  */
-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();
-              });
-        }
-      });
+ol.source.UrlTile.prototype.setTileLoadFunction = function(tileLoadFunction) {
+  this.tileCache.clear();
+  this.tileLoadFunction = tileLoadFunction;
+  this.changed();
 };
 
 
 /**
- * @param {Array.<string>} templates Templates.
- * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
- * @return {ol.TileUrlFunctionType} Tile URL function.
+ * Set the tile URL function of the source.
+ * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
+ * @param {string=} opt_key Optional new tile key for the source.
+ * @api
  */
-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);
+ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction, opt_key) {
+  this.tileUrlFunction = tileUrlFunction;
+  if (typeof opt_key !== 'undefined') {
+    this.setKey(opt_key);
+  } else {
+    this.changed();
   }
-  return ol.TileUrlFunction.createFromTileUrlFunctions(tileUrlFunctions);
 };
 
 
 /**
- * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions.
- * @return {ol.TileUrlFunctionType} Tile URL function.
+ * Set the URL to use for requests.
+ * @param {string} url URL.
+ * @api
  */
-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);
-        }
-      });
+ol.source.UrlTile.prototype.setUrl = function(url) {
+  var urls = this.urls = ol.TileUrlFunction.expandUrl(url);
+  this.setTileUrlFunction(this.fixedTileUrlFunction ?
+      this.fixedTileUrlFunction.bind(this) :
+      ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), url);
 };
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {string|undefined} Tile URL.
+ * Set the URLs to use for requests.
+ * @param {Array.<string>} urls URLs.
+ * @api
  */
-ol.TileUrlFunction.nullTileUrlFunction =
-    function(tileCoord, pixelRatio, projection) {
-  return undefined;
+ol.source.UrlTile.prototype.setUrls = function(urls) {
+  this.urls = urls;
+  var key = urls.join('\n');
+  this.setTileUrlFunction(this.fixedTileUrlFunction ?
+      this.fixedTileUrlFunction.bind(this) :
+      ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), key);
 };
 
 
 /**
- * @param {string} url URL.
- * @return {Array.<string>} Array of urls.
+ * @inheritDoc
  */
-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);
+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);
   }
-  return urls;
 };
 
 goog.provide('ol.source.TileImage');
 
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
+goog.require('ol');
 goog.require('ol.ImageTile');
-goog.require('ol.TileCoord');
-goog.require('ol.TileLoadFunctionType');
+goog.require('ol.TileCache');
 goog.require('ol.TileState');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.TileUrlFunctionType');
-goog.require('ol.source.Tile');
-goog.require('ol.source.TileEvent');
-
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.proj');
+goog.require('ol.reproj.Tile');
+goog.require('ol.source.UrlTile');
+goog.require('ol.tilegrid');
 
 
 /**
@@ -111058,194 +72996,371 @@ goog.require('ol.source.TileEvent');
  * Base class for sources providing images divided into a tile grid.
  *
  * @constructor
- * @fires ol.source.TileEvent
- * @extends {ol.source.Tile}
+ * @fires ol.source.Tile.Event
+ * @extends {ol.source.UrlTile}
  * @param {olx.source.TileImageOptions} options Image tile options.
  * @api
  */
 ol.source.TileImage = function(options) {
 
-  goog.base(this, {
+  ol.source.UrlTile.call(this, {
     attributions: options.attributions,
+    cacheSize: options.cacheSize,
     extent: options.extent,
     logo: options.logo,
     opaque: options.opaque,
     projection: options.projection,
-    state: options.state !== undefined ?
-        /** @type {ol.source.State} */ (options.state) : undefined,
+    state: options.state,
     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 {ol.TileUrlFunctionType}
+   * @type {?string}
    */
-  this.tileUrlFunction = options.tileUrlFunction !== undefined ?
-      options.tileUrlFunction :
-      ol.TileUrlFunction.nullTileUrlFunction;
+  this.crossOrigin =
+      options.crossOrigin !== undefined ? options.crossOrigin : null;
 
   /**
    * @protected
-   * @type {?string}
+   * @type {function(new: ol.ImageTile, ol.TileCoord, ol.TileState, string,
+   *        ?string, ol.TileLoadFunctionType)}
    */
-  this.crossOrigin =
-      options.crossOrigin !== undefined ? options.crossOrigin : null;
+  this.tileClass = options.tileClass !== undefined ?
+      options.tileClass : ol.ImageTile;
 
   /**
    * @protected
-   * @type {ol.TileLoadFunctionType}
+   * @type {Object.<string, ol.TileCache>}
    */
-  this.tileLoadFunction = options.tileLoadFunction !== undefined ?
-      options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction;
+  this.tileCacheForProjection = {};
 
   /**
    * @protected
-   * @type {function(new: ol.ImageTile, ol.TileCoord, ol.TileState, string,
-   *        ?string, ol.TileLoadFunctionType)}
+   * @type {Object.<string, ol.tilegrid.TileGrid>}
    */
-  this.tileClass = options.tileClass !== undefined ?
-      options.tileClass : ol.ImageTile;
+  this.tileGridForProjection = {};
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.reprojectionErrorThreshold_ = options.reprojectionErrorThreshold;
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.renderReprojectionEdges_ = false;
 };
-goog.inherits(ol.source.TileImage, ol.source.Tile);
+ol.inherits(ol.source.TileImage, ol.source.UrlTile);
 
 
 /**
- * @param {ol.ImageTile} imageTile Image tile.
- * @param {string} src Source.
+ * @inheritDoc
  */
-ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) {
-  imageTile.getImage().src = src;
+ol.source.TileImage.prototype.canExpireCache = function() {
+  if (!ol.ENABLE_RASTER_REPROJECTION) {
+    return ol.source.UrlTile.prototype.canExpireCache.call(this);
+  }
+  if (this.tileCache.canExpireCache()) {
+    return true;
+  } else {
+    for (var key in this.tileCacheForProjection) {
+      if (this.tileCacheForProjection[key].canExpireCache()) {
+        return true;
+      }
+    }
+  }
+  return false;
 };
 
 
 /**
  * @inheritDoc
  */
-ol.source.TileImage.prototype.getTile =
-    function(z, x, y, pixelRatio, projection) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
-  if (this.tileCache.containsKey(tileCoordKey)) {
-    return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
+ol.source.TileImage.prototype.expireCache = function(projection, usedTiles) {
+  if (!ol.ENABLE_RASTER_REPROJECTION) {
+    ol.source.UrlTile.prototype.expireCache.call(this, projection, usedTiles);
+    return;
+  }
+  var usedTileCache = this.getTileCacheForProjection(projection);
+
+  this.tileCache.expireCache(this.tileCache == usedTileCache ? usedTiles : {});
+  for (var id in this.tileCacheForProjection) {
+    var tileCache = this.tileCacheForProjection[id];
+    tileCache.expireCache(tileCache == usedTileCache ? usedTiles : {});
+  }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.TileImage.prototype.getGutter = function(projection) {
+  if (ol.ENABLE_RASTER_REPROJECTION &&
+      this.getProjection() && projection &&
+      !ol.proj.equivalent(this.getProjection(), projection)) {
+    return 0;
   } else {
-    goog.asserts.assert(projection, 'argument projection is truthy');
-    var tileCoord = [z, x, y];
-    var urlTileCoord = this.getTileCoordForTileUrlFunction(
-        tileCoord, projection);
-    var tileUrl = !urlTileCoord ? undefined :
-        this.tileUrlFunction(urlTileCoord, pixelRatio, projection);
-    var tile = new this.tileClass(
-        tileCoord,
-        tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY,
-        tileUrl !== undefined ? tileUrl : '',
-        this.crossOrigin,
-        this.tileLoadFunction);
-    goog.events.listen(tile, goog.events.EventType.CHANGE,
-        this.handleTileChange_, false, this);
+    return this.getGutterInternal();
+  }
+};
 
-    this.tileCache.set(tileCoordKey, tile);
-    return tile;
+
+/**
+ * @protected
+ * @return {number} Gutter.
+ */
+ol.source.TileImage.prototype.getGutterInternal = function() {
+  return 0;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.TileImage.prototype.getOpaque = function(projection) {
+  if (ol.ENABLE_RASTER_REPROJECTION &&
+      this.getProjection() && projection &&
+      !ol.proj.equivalent(this.getProjection(), projection)) {
+    return false;
+  } else {
+    return ol.source.UrlTile.prototype.getOpaque.call(this, projection);
   }
 };
 
 
 /**
- * Return the tile load function of the source.
- * @return {ol.TileLoadFunctionType} TileLoadFunction
- * @api
+ * @inheritDoc
  */
-ol.source.TileImage.prototype.getTileLoadFunction = function() {
-  return this.tileLoadFunction;
+ol.source.TileImage.prototype.getTileGridForProjection = function(projection) {
+  if (!ol.ENABLE_RASTER_REPROJECTION) {
+    return ol.source.UrlTile.prototype.getTileGridForProjection.call(this, projection);
+  }
+  var thisProj = this.getProjection();
+  if (this.tileGrid &&
+      (!thisProj || ol.proj.equivalent(thisProj, projection))) {
+    return this.tileGrid;
+  } else {
+    var projKey = ol.getUid(projection).toString();
+    if (!(projKey in this.tileGridForProjection)) {
+      this.tileGridForProjection[projKey] =
+          ol.tilegrid.getForProjection(projection);
+    }
+    return /** @type {!ol.tilegrid.TileGrid} */ (this.tileGridForProjection[projKey]);
+  }
 };
 
 
 /**
- * Return the tile URL function of the source.
- * @return {ol.TileUrlFunctionType} TileUrlFunction
- * @api
+ * @inheritDoc
  */
-ol.source.TileImage.prototype.getTileUrlFunction = function() {
-  return this.tileUrlFunction;
+ol.source.TileImage.prototype.getTileCacheForProjection = function(projection) {
+  if (!ol.ENABLE_RASTER_REPROJECTION) {
+    return ol.source.UrlTile.prototype.getTileCacheForProjection.call(this, projection);
+  }
+  var thisProj = this.getProjection();
+  if (!thisProj || ol.proj.equivalent(thisProj, projection)) {
+    return this.tileCache;
+  } else {
+    var projKey = ol.getUid(projection).toString();
+    if (!(projKey in this.tileCacheForProjection)) {
+      this.tileCacheForProjection[projKey] = new ol.TileCache(this.tileCache.highWaterMark);
+    }
+    return this.tileCacheForProjection[projKey];
+  }
 };
 
 
 /**
- * Handle tile change events.
- * @param {goog.events.Event} event Event.
+ * @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.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;
+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;
+  ol.events.listen(tile, ol.events.EventType.CHANGE,
+      this.handleTileChange, 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, /** @type {!ol.proj.Projection} */ (projection));
+  } else {
+    var cache = this.getTileCacheForProjection(projection);
+    var tileCoord = [z, x, y];
+    var tile;
+    var tileCoordKey = this.getKeyZXY.apply(this, tileCoord);
+    if (cache.containsKey(tileCoordKey)) {
+      tile = /** @type {!ol.Tile} */ (cache.get(tileCoordKey));
+    }
+    var key = this.getKey();
+    if (tile && tile.key == key) {
+      return tile;
+    } else {
+      var sourceProjection = /** @type {!ol.proj.Projection} */ (this.getProjection());
+      var sourceTileGrid = this.getTileGridForProjection(sourceProjection);
+      var targetTileGrid = this.getTileGridForProjection(projection);
+      var wrappedTileCoord =
+          this.getTileCoordForTileUrlFunction(tileCoord, projection);
+      var newTile = new ol.reproj.Tile(
+          sourceProjection, sourceTileGrid,
+          projection, targetTileGrid,
+          tileCoord, wrappedTileCoord, this.getTilePixelRatio(pixelRatio),
+          this.getGutterInternal(),
+          function(z, x, y, pixelRatio) {
+            return this.getTileInternal(z, x, y, pixelRatio, sourceProjection);
+          }.bind(this), this.reprojectionErrorThreshold_,
+          this.renderReprojectionEdges_);
+      newTile.key = key;
+
+      if (tile) {
+        newTile.interimTile = tile;
+        cache.replace(tileCoordKey, newTile);
+      } else {
+        cache.set(tileCoordKey, newTile);
+      }
+      return newTile;
+    }
   }
 };
 
 
 /**
- * Set the tile load function of the source.
- * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
- * @api
+ * @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.setTileLoadFunction = function(tileLoadFunction) {
-  this.tileCache.clear();
-  this.tileLoadFunction = tileLoadFunction;
-  this.changed();
+ol.source.TileImage.prototype.getTileInternal = function(z, x, y, pixelRatio, projection) {
+  var tile = null;
+  var tileCoordKey = this.getKeyZXY(z, x, y);
+  var key = this.getKey();
+  if (!this.tileCache.containsKey(tileCoordKey)) {
+    tile = this.createTile_(z, x, y, pixelRatio, projection, key);
+    this.tileCache.set(tileCoordKey, tile);
+  } else {
+    tile = this.tileCache.get(tileCoordKey);
+    if (tile.key != key) {
+      // 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 interimTile = tile;
+      tile = this.createTile_(z, x, y, pixelRatio, projection, key);
+
+      //make the new tile the head of the list,
+      if (interimTile.getState() == ol.TileState.IDLE) {
+        //the old tile hasn't begun loading yet, and is now outdated, so we can simply discard it
+        tile.interimTile = interimTile.interimTile;
+      } else {
+        tile.interimTile = interimTile;
+      }
+      tile.refreshInterimChain();
+      this.tileCache.replace(tileCoordKey, tile);
+    }
+  }
+  return tile;
 };
 
 
 /**
- * Set the tile URL function of the source.
- * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
+ * Sets whether to render reprojection edges or not (usually for debugging).
+ * @param {boolean} render Render the edges.
  * @api
  */
-ol.source.TileImage.prototype.setTileUrlFunction = function(tileUrlFunction) {
-  // FIXME It should be possible to be more intelligent and avoid clearing the
-  // FIXME cache.  The tile URL function would need to be incorporated into the
-  // FIXME cache key somehow.
-  this.tileCache.clear();
-  this.tileUrlFunction = tileUrlFunction;
+ol.source.TileImage.prototype.setRenderReprojectionEdges = function(render) {
+  if (!ol.ENABLE_RASTER_REPROJECTION ||
+      this.renderReprojectionEdges_ == render) {
+    return;
+  }
+  this.renderReprojectionEdges_ = render;
+  for (var id in this.tileCacheForProjection) {
+    this.tileCacheForProjection[id].clear();
+  }
   this.changed();
 };
 
 
 /**
- * @inheritDoc
+ * 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.ProjectionLike} projection Projection.
+ * @param {ol.tilegrid.TileGrid} tilegrid Tile grid to use for the projection.
+ * @api
  */
-ol.source.TileImage.prototype.useTile = function(z, x, y) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
-  if (this.tileCache.containsKey(tileCoordKey)) {
-    this.tileCache.get(tileCoordKey);
+ol.source.TileImage.prototype.setTileGridForProjection = function(projection, tilegrid) {
+  if (ol.ENABLE_RASTER_REPROJECTION) {
+    var proj = ol.proj.get(projection);
+    if (proj) {
+      var projKey = ol.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');
 goog.require('ol.Attribution');
-goog.require('ol.TileRange');
 goog.require('ol.TileUrlFunction');
 goog.require('ol.extent');
+goog.require('ol.net');
 goog.require('ol.proj');
 goog.require('ol.source.State');
 goog.require('ol.source.TileImage');
 goog.require('ol.tilecoord');
-
+goog.require('ol.tilegrid');
 
 
 /**
@@ -111255,161 +73370,425 @@ goog.require('ol.tilecoord');
  * @constructor
  * @extends {ol.source.TileImage}
  * @param {olx.source.BingMapsOptions} options Bing Maps options.
- * @api stable
+ * @api
  */
 ol.source.BingMaps = function(options) {
 
-  goog.base(this, {
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.hidpi_ = options.hidpi !== undefined ? options.hidpi : false;
+
+  ol.source.TileImage.call(this, {
+    cacheSize: options.cacheSize,
     crossOrigin: 'anonymous',
     opaque: true,
     projection: ol.proj.get('EPSG:3857'),
+    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
     state: ol.source.State.LOADING,
     tileLoadFunction: options.tileLoadFunction,
+    tilePixelRatio: this.hidpi_ ? 2 : 1,
+    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;
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.apiKey_ = options.key;
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.imagerySet_ = options.imagerySet;
+
+  var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/' +
+      this.imagerySet_ +
+      '?uriScheme=https&include=ImageryProviders&key=' + this.apiKey_;
+
+  ol.net.jsonp(url, this.handleImageryMetadataResponse.bind(this), undefined,
+      'jsonp');
+
+};
+ol.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>'
+});
+
+
+/**
+ * Get the api key used for this source.
+ *
+ * @return {string} The api key.
+ * @api
+ */
+ol.source.BingMaps.prototype.getApiKey = function() {
+  return this.apiKey_;
+};
+
+
+/**
+ * Get the imagery set associated with this source.
+ *
+ * @return {string} The imagery set.
+ * @api
+ */
+ol.source.BingMaps.prototype.getImagerySet = function() {
+  return this.imagerySet_;
+};
+
+
+/**
+ * @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];
+  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.getTilePixelRatio()
+  });
+  this.tileGrid = tileGrid;
+
+  var culture = this.culture_;
+  var hidpi = this.hidpi_;
+  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) {
+              if (!tileCoord) {
+                return undefined;
+              } else {
+                ol.tilecoord.createOrUpdate(tileCoord[0], tileCoord[1],
+                    -tileCoord[2] - 1, quadKeyTileCoord);
+                var url = imageUrl;
+                if (hidpi) {
+                  url += '&dpi=d1&device=mobile';
+                }
+                return url.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);
+};
+
+goog.provide('ol.source.XYZ');
+
+goog.require('ol');
+goog.require('ol.source.TileImage');
+goog.require('ol.tilegrid');
+
+
+/**
+ * @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=} opt_options XYZ options.
+ * @api
+ */
+ol.source.XYZ = function(opt_options) {
+  var options = opt_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,
+        minZoom: options.minZoom,
+        tileSize: options.tileSize
+      });
+
+  ol.source.TileImage.call(this, {
+    attributions: options.attributions,
+    cacheSize: options.cacheSize,
+    crossOrigin: options.crossOrigin,
+    logo: options.logo,
+    opaque: options.opaque,
+    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
   });
 
+};
+ol.inherits(ol.source.XYZ, ol.source.TileImage);
+
+goog.provide('ol.source.CartoDB');
+
+goog.require('ol');
+goog.require('ol.obj');
+goog.require('ol.source.State');
+goog.require('ol.source.XYZ');
+
+
+/**
+ * @classdesc
+ * Layer source for the CartoDB tiles.
+ *
+ * @constructor
+ * @extends {ol.source.XYZ}
+ * @param {olx.source.CartoDBOptions} options CartoDB options.
+ * @api
+ */
+ol.source.CartoDB = function(options) {
+
+  /**
+   * @type {string}
+   * @private
+   */
+  this.account_ = options.account;
+
   /**
-   * @private
    * @type {string}
+   * @private
    */
-  this.culture_ = options.culture !== undefined ? options.culture : 'en-us';
+  this.mapId_ = options.map || '';
 
   /**
+   * @type {!Object}
    * @private
-   * @type {number}
    */
-  this.maxZoom_ = options.maxZoom !== undefined ? options.maxZoom : -1;
+  this.config_ = options.config || {};
+
+  /**
+   * @type {!Object.<string, CartoDBLayerInfo>}
+   * @private
+   */
+  this.templateCache_ = {};
 
-  var uri = new goog.Uri(
-      'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/' +
-      options.imagerySet);
+  ol.source.XYZ.call(this, {
+    attributions: options.attributions,
+    cacheSize: options.cacheSize,
+    crossOrigin: options.crossOrigin,
+    logo: options.logo,
+    maxZoom: options.maxZoom !== undefined ? options.maxZoom : 18,
+    minZoom: options.minZoom,
+    projection: options.projection,
+    state: ol.source.State.LOADING,
+    wrapX: options.wrapX
+  });
+  this.initializeMap_();
+};
+ol.inherits(ol.source.CartoDB, ol.source.XYZ);
 
-  var jsonp = new goog.net.Jsonp(uri, 'jsonp');
-  jsonp.send({
-    'include': 'ImageryProviders',
-    'uriScheme': 'https',
-    'key': options.key
-  }, goog.bind(this.handleImageryMetadataResponse, this));
 
+/**
+ * Returns the current config.
+ * @return {!Object} The current configuration.
+ * @api
+ */
+ol.source.CartoDB.prototype.getConfig = function() {
+  return this.config_;
 };
-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}
+ * Updates the carto db config.
+ * @param {Object} config a key-value lookup. Values will replace current values
+ *     in the config.
  * @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>'
-});
+ol.source.CartoDB.prototype.updateConfig = function(config) {
+  ol.obj.assign(this.config_, config);
+  this.initializeMap_();
+};
 
 
 /**
- * @param {BingMapsImageryMetadataResponse} response Response.
+ * Sets the CartoDB config
+ * @param {Object} config In the case of anonymous maps, a CartoDB configuration
+ *     object.
+ * If using named maps, a key-value lookup with the template parameters.
+ * @api
  */
-ol.source.BingMaps.prototype.handleImageryMetadataResponse =
-    function(response) {
+ol.source.CartoDB.prototype.setConfig = function(config) {
+  this.config_ = config || {};
+  this.initializeMap_();
+};
 
-  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);
+
+/**
+ * Issue a request to initialize the CartoDB map.
+ * @private
+ */
+ol.source.CartoDB.prototype.initializeMap_ = function() {
+  var paramHash = JSON.stringify(this.config_);
+  if (this.templateCache_[paramHash]) {
+    this.applyTemplate_(this.templateCache_[paramHash]);
     return;
   }
+  var mapUrl = 'https://' + this.account_ + '.cartodb.com/api/v1/map';
 
-  var brandLogoUri = response.brandLogoUri;
-  if (brandLogoUri.indexOf('https') == -1) {
-    brandLogoUri = brandLogoUri.replace('http', 'https');
+  if (this.mapId_) {
+    mapUrl += '/named/' + this.mapId_;
   }
-  //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));
-              }
-            });
-      }));
+  var client = new XMLHttpRequest();
+  client.addEventListener('load', this.handleInitResponse_.bind(this, paramHash));
+  client.addEventListener('error', this.handleInitError_.bind(this));
+  client.open('POST', mapUrl);
+  client.setRequestHeader('Content-type', 'application/json');
+  client.send(JSON.stringify(this.config_));
+};
 
-  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);
+/**
+ * Handle map initialization response.
+ * @param {string} paramHash a hash representing the parameter set that was used
+ *     for the request
+ * @param {Event} event Event.
+ * @private
+ */
+ol.source.CartoDB.prototype.handleInitResponse_ = function(paramHash, event) {
+  var client = /** @type {XMLHttpRequest} */ (event.target);
+  // status will be 0 for file:// urls
+  if (!client.status || client.status >= 200 && client.status < 300) {
+    var response;
+    try {
+      response = /** @type {CartoDBLayerInfo} */(JSON.parse(client.responseText));
+    } catch (err) {
+      this.setState(ol.source.State.ERROR);
+      return;
+    }
+    this.applyTemplate_(response);
+    this.templateCache_[paramHash] = response;
+    this.setState(ol.source.State.READY);
+  } else {
+    this.setState(ol.source.State.ERROR);
   }
+};
 
-  this.setLogo(brandLogoUri);
 
-  this.setState(ol.source.State.READY);
+/**
+ * @private
+ * @param {Event} event Event.
+ */
+ol.source.CartoDB.prototype.handleInitError_ = function(event) {
+  this.setState(ol.source.State.ERROR);
+};
 
+
+/**
+ * Apply the new tile urls returned by carto db
+ * @param {CartoDBLayerInfo} data Result of carto db call.
+ * @private
+ */
+ol.source.CartoDB.prototype.applyTemplate_ = function(data) {
+  var tilesUrl = 'https://' + data.cdn_url.https + '/' + this.account_ +
+      '/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png';
+  this.setUrl(tilesUrl);
 };
 
 // FIXME keep cluster cache by resolution ?
@@ -111417,62 +73796,86 @@ ol.source.BingMaps.prototype.handleImageryMetadataResponse =
 
 goog.provide('ol.source.Cluster');
 
-goog.require('goog.asserts');
-goog.require('goog.events.EventType');
-goog.require('goog.object');
+goog.require('ol');
+goog.require('ol.asserts');
 goog.require('ol.Feature');
 goog.require('ol.coordinate');
+goog.require('ol.events.EventType');
 goog.require('ol.extent');
 goog.require('ol.geom.Point');
 goog.require('ol.source.Vector');
 
 
-
 /**
  * @classdesc
- * Layer source to cluster vector data.
+ * Layer source to cluster vector data. Works out of the box with point
+ * geometries. For other geometry types, or if not all geometries should be
+ * considered for clustering, a custom `geometryFunction` can be defined.
  *
  * @constructor
- * @param {olx.source.ClusterOptions} options
+ * @param {olx.source.ClusterOptions} options Constructor options.
  * @extends {ol.source.Vector}
  * @api
  */
 ol.source.Cluster = function(options) {
-  goog.base(this, {
+  ol.source.Vector.call(this, {
     attributions: options.attributions,
     extent: options.extent,
     logo: options.logo,
-    projection: options.projection
+    projection: options.projection,
+    wrapX: options.wrapX
   });
 
   /**
    * @type {number|undefined}
-   * @private
+   * @protected
    */
-  this.resolution_ = undefined;
+  this.resolution = undefined;
 
   /**
    * @type {number}
-   * @private
+   * @protected
    */
-  this.distance_ = options.distance !== undefined ? options.distance : 20;
+  this.distance = options.distance !== undefined ? options.distance : 20;
 
   /**
    * @type {Array.<ol.Feature>}
-   * @private
+   * @protected
+   */
+  this.features = [];
+
+  /**
+   * @param {ol.Feature} feature Feature.
+   * @return {ol.geom.Point} Cluster calculation point.
+   * @protected
    */
-  this.features_ = [];
+  this.geometryFunction = options.geometryFunction || function(feature) {
+    var geometry = /** @type {ol.geom.Point} */ (feature.getGeometry());
+    ol.asserts.assert(geometry instanceof ol.geom.Point,
+        10); // The default `geometryFunction` can only handle `ol.geom.Point` geometries
+    return geometry;
+  };
 
   /**
    * @type {ol.source.Vector}
-   * @private
+   * @protected
    */
-  this.source_ = options.source;
+  this.source = options.source;
+
+  this.source.on(ol.events.EventType.CHANGE,
+      ol.source.Cluster.prototype.refresh, this);
+};
+ol.inherits(ol.source.Cluster, ol.source.Vector);
 
-  this.source_.on(goog.events.EventType.CHANGE,
-      ol.source.Cluster.prototype.onSourceChange_, this);
+
+/**
+ * Get the distance in pixels between clusters.
+ * @return {number} Distance.
+ * @api
+ */
+ol.source.Cluster.prototype.getDistance = function() {
+  return this.distance;
 };
-goog.inherits(ol.source.Cluster, ol.source.Vector);
 
 
 /**
@@ -111481,7 +73884,7 @@ goog.inherits(ol.source.Cluster, ol.source.Vector);
  * @api
  */
 ol.source.Cluster.prototype.getSource = function() {
-  return this.source_;
+  return this.source;
 };
 
 
@@ -111490,109 +73893,412 @@ ol.source.Cluster.prototype.getSource = function() {
  */
 ol.source.Cluster.prototype.loadFeatures = function(extent, resolution,
     projection) {
-  this.source_.loadFeatures(extent, resolution, projection);
-  if (resolution !== this.resolution_) {
+  this.source.loadFeatures(extent, resolution, projection);
+  if (resolution !== this.resolution) {
     this.clear();
-    this.resolution_ = resolution;
-    this.cluster_();
-    this.addFeatures(this.features_);
+    this.resolution = resolution;
+    this.cluster();
+    this.addFeatures(this.features);
   }
 };
 
 
+/**
+ * Set the distance in pixels between clusters.
+ * @param {number} distance The distance in pixels.
+ * @api
+ */
+ol.source.Cluster.prototype.setDistance = function(distance) {
+  this.distance = distance;
+  this.refresh();
+};
+
+
 /**
  * handle the source changing
- * @private
+ * @override
  */
-ol.source.Cluster.prototype.onSourceChange_ = function() {
+ol.source.Cluster.prototype.refresh = function() {
   this.clear();
-  this.cluster_();
-  this.addFeatures(this.features_);
-  this.changed();
+  this.cluster();
+  this.addFeatures(this.features);
+  ol.source.Vector.prototype.refresh.call(this);
 };
 
 
 /**
- * @private
+ * @protected
  */
-ol.source.Cluster.prototype.cluster_ = function() {
-  if (this.resolution_ === undefined) {
+ol.source.Cluster.prototype.cluster = function() {
+  if (this.resolution === undefined) {
     return;
   }
-  this.features_.length = 0;
+  this.features.length = 0;
   var extent = ol.extent.createEmpty();
-  var mapDistance = this.distance_ * this.resolution_;
-  var features = this.source_.getFeatures();
+  var mapDistance = this.distance * this.resolution;
+  var features = this.source.getFeatures();
 
   /**
-   * @type {Object.<string, boolean>}
+   * @type {!Object.<string, boolean>}
    */
   var clustered = {};
 
   for (var i = 0, ii = features.length; i < ii; i++) {
     var feature = features[i];
-    if (!goog.object.containsKey(clustered, goog.getUid(feature).toString())) {
-      var geometry = feature.getGeometry();
-      goog.asserts.assert(geometry instanceof ol.geom.Point,
-          'feature geometry is a ol.geom.Point instance');
-      var coordinates = geometry.getCoordinates();
-      ol.extent.createOrUpdateFromCoordinate(coordinates, extent);
-      ol.extent.buffer(extent, mapDistance, extent);
-
-      var neighbors = this.source_.getFeaturesInExtent(extent);
-      goog.asserts.assert(neighbors.length >= 1, 'at least one neighbor found');
-      neighbors = neighbors.filter(function(neighbor) {
-        var uid = goog.getUid(neighbor).toString();
-        if (!goog.object.containsKey(clustered, uid)) {
-          clustered[uid] = true;
-          return true;
-        } else {
-          return false;
-        }
-      });
-      this.features_.push(this.createCluster_(neighbors));
+    if (!(ol.getUid(feature).toString() in clustered)) {
+      var geometry = this.geometryFunction(feature);
+      if (geometry) {
+        var coordinates = geometry.getCoordinates();
+        ol.extent.createOrUpdateFromCoordinate(coordinates, extent);
+        ol.extent.buffer(extent, mapDistance, extent);
+
+        var neighbors = this.source.getFeaturesInExtent(extent);
+        neighbors = neighbors.filter(function(neighbor) {
+          var uid = ol.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
+ * @return {ol.Feature} The cluster feature.
+ * @protected
  */
-ol.source.Cluster.prototype.createCluster_ = function(features) {
-  var length = features.length;
+ol.source.Cluster.prototype.createCluster = function(features) {
   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);
+  for (var i = features.length - 1; i >= 0; --i) {
+    var geometry = this.geometryFunction(features[i]);
+    if (geometry) {
+      ol.coordinate.add(centroid, geometry.getCoordinates());
+    } else {
+      features.splice(i, 1);
+    }
   }
-  ol.coordinate.scale(centroid, 1 / length);
+  ol.coordinate.scale(centroid, 1 / features.length);
 
   var cluster = new ol.Feature(new ol.geom.Point(centroid));
   cluster.set('features', features);
   return cluster;
 };
 
-goog.provide('ol.source.ImageMapGuide');
+goog.provide('ol.uri');
+
+
+/**
+ * Appends query parameters to a URI.
+ *
+ * @param {string} uri The original URI, which may already have query data.
+ * @param {!Object} params An object where keys are URI-encoded parameter keys,
+ *     and the values are arbitrary types or arrays.
+ * @return {string} The new URI.
+ */
+ol.uri.appendParams = function(uri, params) {
+  var keyParams = [];
+  // Skip any null or undefined parameter values
+  Object.keys(params).forEach(function(k) {
+    if (params[k] !== null && params[k] !== undefined) {
+      keyParams.push(k + '=' + encodeURIComponent(params[k]));
+    }
+  });
+  var qs = keyParams.join('&');
+  // remove any trailing ? or &
+  uri = uri.replace(/[?&]$/, '');
+  // append ? or & depending on whether uri has existing parameters
+  uri = uri.indexOf('?') === -1 ? uri + '?' : uri + '&';
+  return uri + qs;
+};
 
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.object');
-goog.require('goog.uri.utils');
+goog.provide('ol.source.ImageArcGISRest');
+
+goog.require('ol');
 goog.require('ol.Image');
-goog.require('ol.ImageLoadFunctionType');
-goog.require('ol.ImageUrlFunction');
+goog.require('ol.asserts');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
 goog.require('ol.extent');
+goog.require('ol.obj');
 goog.require('ol.source.Image');
+goog.require('ol.uri');
+
+
+/**
+ * @classdesc
+ * Source for data from ArcGIS Rest services providing single, untiled images.
+ * Useful when underlying map service has labels.
+ *
+ * If underlying map service is not using labels,
+ * take advantage of ol image caching and use
+ * {@link ol.source.TileArcGISRest} data source.
+ *
+ * @constructor
+ * @fires ol.source.Image.Event
+ * @extends {ol.source.Image}
+ * @param {olx.source.ImageArcGISRestOptions=} opt_options Image ArcGIS Rest Options.
+ * @api
+ */
+ol.source.ImageArcGISRest = function(opt_options) {
+
+  var options = opt_options || {};
+
+  ol.source.Image.call(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 {boolean}
+   */
+  this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;
+
+  /**
+   * @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 {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;
+
+};
+ol.inherits(ol.source.ImageArcGISRest, 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
+ */
+ol.source.ImageArcGISRest.prototype.getParams = function() {
+  return this.params_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.ImageArcGISRest.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
+
+  if (this.url_ === undefined) {
+    return null;
+  }
+
+  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;
+  }
+
+  var params = {
+    'F': 'image',
+    'FORMAT': 'PNG32',
+    'TRANSPARENT': true
+  };
+  ol.obj.assign(params, this.params_);
+
+  extent = extent.slice();
+  var centerX = (extent[0] + extent[2]) / 2;
+  var centerY = (extent[1] + extent[3]) / 2;
+  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 imageResolution = resolution / pixelRatio;
+
+  // Compute an integer width and height.
+  var width = Math.ceil(ol.extent.getWidth(extent) / imageResolution);
+  var height = Math.ceil(ol.extent.getHeight(extent) / imageResolution);
+
+  // Modify the extent to match the integer width and height.
+  extent[0] = centerX - imageResolution * width / 2;
+  extent[2] = centerX + imageResolution * width / 2;
+  extent[1] = centerY - imageResolution * height / 2;
+  extent[3] = centerY + imageResolution * height / 2;
+
+  this.imageSize_[0] = width;
+  this.imageSize_[1] = height;
+
+  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();
+
+  ol.events.listen(this.image_, ol.events.EventType.CHANGE,
+      this.handleImageChange, this);
+
+  return this.image_;
+
+};
+
+
+/**
+ * Return the image load function of the source.
+ * @return {ol.ImageLoadFunctionType} The image load function.
+ * @api
+ */
+ol.source.ImageArcGISRest.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.ImageArcGISRest.prototype.getRequestUrl_ = function(extent, size, pixelRatio, projection, params) {
+  // ArcGIS Server only wants the numeric portion of the projection ID.
+  var srid = projection.getCode().split(':').pop();
+
+  params['SIZE'] = size[0] + ',' + size[1];
+  params['BBOX'] = extent.join(',');
+  params['BBOXSR'] = srid;
+  params['IMAGESR'] = srid;
+  params['DPI'] = Math.round(90 * pixelRatio);
+
+  var url = this.url_;
+
+  var modifiedUrl = url
+   .replace(/MapServer\/?$/, 'MapServer/export')
+   .replace(/ImageServer\/?$/, 'ImageServer/exportImage');
+  if (modifiedUrl == url) {
+    ol.asserts.assert(false, 50); // `options.featureTypes` should be an Array
+  }
+  return ol.uri.appendParams(modifiedUrl, params);
+};
+
+
+/**
+ * Return the URL used for this ArcGIS source.
+ * @return {string|undefined} URL.
+ * @api
+ */
+ol.source.ImageArcGISRest.prototype.getUrl = function() {
+  return this.url_;
+};
+
+
+/**
+ * Set the image load function of the source.
+ * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
+ * @api
+ */
+ol.source.ImageArcGISRest.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
+ */
+ol.source.ImageArcGISRest.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
+ */
+ol.source.ImageArcGISRest.prototype.updateParams = function(params) {
+  ol.obj.assign(this.params_, params);
+  this.image_ = null;
+  this.changed();
+};
+
+goog.provide('ol.source.ImageMapGuide');
 
+goog.require('ol');
+goog.require('ol.Image');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
+goog.require('ol.obj');
+goog.require('ol.source.Image');
+goog.require('ol.uri');
 
 
 /**
@@ -111600,14 +74306,14 @@ goog.require('ol.source.Image');
  * Source for images from Mapguide servers
  *
  * @constructor
- * @fires ol.source.ImageEvent
+ * @fires ol.source.Image.Event
  * @extends {ol.source.Image}
  * @param {olx.source.ImageMapGuideOptions} options Options.
- * @api stable
+ * @api
  */
 ol.source.ImageMapGuide = function(options) {
 
-  goog.base(this, {
+  ol.source.Image.call(this, {
     projection: options.projection,
     resolutions: options.resolutions
   });
@@ -111628,23 +74334,15 @@ ol.source.ImageMapGuide = function(options) {
 
   /**
    * @private
-   * @type {Object}
+   * @type {!Object}
    */
-  this.params_ = options.params !== undefined ? options.params : {};
-
-  var imageUrlFunction;
-  if (options.url !== undefined) {
-    imageUrlFunction = ol.ImageUrlFunction.createFromParamsFunction(
-        options.url, this.params_, goog.bind(this.getUrl, this));
-  } else {
-    imageUrlFunction = ol.ImageUrlFunction.nullImageUrlFunction;
-  }
+  this.params_ = options.params || {};
 
   /**
    * @private
-   * @type {ol.ImageUrlFunctionType}
+   * @type {string|undefined}
    */
-  this.imageUrlFunction_ = imageUrlFunction;
+  this.url_ = options.url;
 
   /**
    * @private
@@ -111692,14 +74390,14 @@ ol.source.ImageMapGuide = function(options) {
   this.renderedRevision_ = 0;
 
 };
-goog.inherits(ol.source.ImageMapGuide, ol.source.Image);
+ol.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
+ * @api
  */
 ol.source.ImageMapGuide.prototype.getParams = function() {
   return this.params_;
@@ -111709,8 +74407,7 @@ ol.source.ImageMapGuide.prototype.getParams = function() {
 /**
  * @inheritDoc
  */
-ol.source.ImageMapGuide.prototype.getImage =
-    function(extent, resolution, pixelRatio, projection) {
+ol.source.ImageMapGuide.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
   resolution = this.findNearestResolution(resolution);
   pixelRatio = this.hidpi_ ? pixelRatio : 1;
 
@@ -111731,13 +74428,14 @@ ol.source.ImageMapGuide.prototype.getImage =
   var height = ol.extent.getHeight(extent) / resolution;
   var size = [width * pixelRatio, height * pixelRatio];
 
-  var imageUrl = this.imageUrlFunction_(extent, size, projection);
-  if (imageUrl !== undefined) {
+  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);
+    ol.events.listen(image, ol.events.EventType.CHANGE,
+        this.handleImageChange, this);
   } else {
     image = null;
   }
@@ -111782,10 +74480,10 @@ ol.source.ImageMapGuide.getScale = function(extent, size, metersPerUnit, dpi) {
 /**
  * Update the user-provided params.
  * @param {Object} params Params.
- * @api stable
+ * @api
  */
 ol.source.ImageMapGuide.prototype.updateParams = function(params) {
-  goog.object.extend(this.params_, params);
+  ol.obj.assign(this.params_, params);
   this.changed();
 };
 
@@ -111798,8 +74496,7 @@ ol.source.ImageMapGuide.prototype.updateParams = function(params) {
  * @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) {
+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);
@@ -111816,8 +74513,8 @@ ol.source.ImageMapGuide.prototype.getUrl =
     'SETVIEWCENTERX': center[0],
     'SETVIEWCENTERY': center[1]
   };
-  goog.object.extend(baseParams, params);
-  return goog.uri.utils.appendParamsFromMap(baseUrl, baseParams);
+  ol.obj.assign(baseParams, params);
+  return ol.uri.appendParams(baseUrl, baseParams);
 };
 
 
@@ -111835,16 +74532,17 @@ ol.source.ImageMapGuide.prototype.setImageLoadFunction = function(
 
 goog.provide('ol.source.ImageStatic');
 
-goog.require('goog.events');
-goog.require('goog.events.EventType');
+goog.require('ol');
 goog.require('ol.Image');
-goog.require('ol.ImageLoadFunctionType');
+goog.require('ol.ImageState');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
 goog.require('ol.extent');
 goog.require('ol.proj');
 goog.require('ol.source.Image');
 
 
-
 /**
  * @classdesc
  * A layer source for displaying a single, static image.
@@ -111852,21 +74550,11 @@ goog.require('ol.source.Image');
  * @constructor
  * @extends {ol.source.Image}
  * @param {olx.source.ImageStaticOptions} options Options.
- * @api stable
+ * @api
  */
 ol.source.ImageStatic = function(options) {
-
-  var attributions = options.attributions !== undefined ?
-      options.attributions : null;
-
   var imageExtent = options.imageExtent;
 
-  var resolution, resolutions;
-  if (options.imageSize !== undefined) {
-    resolution = ol.extent.getHeight(imageExtent) / options.imageSize[1];
-    resolutions = [resolution];
-  }
-
   var crossOrigin = options.crossOrigin !== undefined ?
       options.crossOrigin : null;
 
@@ -111874,39 +74562,72 @@ ol.source.ImageStatic = function(options) {
       options.imageLoadFunction !== undefined ?
       options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
 
-  goog.base(this, {
-    attributions: attributions,
+  ol.source.Image.call(this, {
+    attributions: options.attributions,
     logo: options.logo,
-    projection: ol.proj.get(options.projection),
-    resolutions: resolutions
+    projection: ol.proj.get(options.projection)
   });
 
   /**
    * @private
    * @type {ol.Image}
    */
-  this.image_ = new ol.Image(imageExtent, resolution, 1, attributions,
+  this.image_ = new ol.Image(imageExtent, undefined, 1, this.getAttributions(),
       options.url, crossOrigin, imageLoadFunction);
-  goog.events.listen(this.image_, goog.events.EventType.CHANGE,
-      this.handleImageChange, false, this);
+
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.imageSize_ = options.imageSize ? options.imageSize : null;
+
+  ol.events.listen(this.image_, ol.events.EventType.CHANGE,
+      this.handleImageChange, this);
 
 };
-goog.inherits(ol.source.ImageStatic, ol.source.Image);
+ol.inherits(ol.source.ImageStatic, ol.source.Image);
 
 
 /**
  * @inheritDoc
  */
-ol.source.ImageStatic.prototype.getImage =
-    function(extent, resolution, pixelRatio, projection) {
+ol.source.ImageStatic.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
   if (ol.extent.intersects(extent, this.image_.getExtent())) {
     return this.image_;
   }
   return null;
 };
 
-goog.provide('ol.source.wms');
-goog.provide('ol.source.wms.ServerType');
+
+/**
+ * @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 context = ol.dom.createCanvasContext2D(targetWidth, imageHeight);
+      var canvas = context.canvas;
+      context.drawImage(image, 0, 0, imageWidth, imageHeight,
+          0, 0, canvas.width, canvas.height);
+      this.image_.setImage(canvas);
+    }
+  }
+  ol.source.Image.prototype.handleImageChange.call(this, evt);
+};
+
+goog.provide('ol.source.WMSServerType');
 
 
 /**
@@ -111914,9 +74635,8 @@ goog.provide('ol.source.wms.ServerType');
  *     `'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 = {
+ol.source.WMSServerType = {
   CARMENTA_SERVER: 'carmentaserver',
   GEOSERVER: 'geoserver',
   MAPSERVER: 'mapserver',
@@ -111927,21 +74647,18 @@ ol.source.wms.ServerType = {
 
 goog.provide('ol.source.ImageWMS');
 
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.object');
-goog.require('goog.string');
-goog.require('goog.uri.utils');
 goog.require('ol');
 goog.require('ol.Image');
-goog.require('ol.ImageLoadFunctionType');
+goog.require('ol.asserts');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
 goog.require('ol.extent');
+goog.require('ol.obj');
 goog.require('ol.proj');
 goog.require('ol.source.Image');
-goog.require('ol.source.wms');
-goog.require('ol.source.wms.ServerType');
-
+goog.require('ol.source.WMSServerType');
+goog.require('ol.string');
+goog.require('ol.uri');
 
 
 /**
@@ -111949,16 +74666,16 @@ goog.require('ol.source.wms.ServerType');
  * Source for WMS servers providing single, untiled images.
  *
  * @constructor
- * @fires ol.source.ImageEvent
+ * @fires ol.source.Image.Event
  * @extends {ol.source.Image}
  * @param {olx.source.ImageWMSOptions=} opt_options Options.
- * @api stable
+ * @api
  */
 ol.source.ImageWMS = function(opt_options) {
 
   var options = opt_options || {};
 
-  goog.base(this, {
+  ol.source.Image.call(this, {
     attributions: options.attributions,
     logo: options.logo,
     projection: options.projection,
@@ -111987,9 +74704,9 @@ ol.source.ImageWMS = function(opt_options) {
 
   /**
    * @private
-   * @type {Object}
+   * @type {!Object}
    */
-  this.params_ = options.params;
+  this.params_ = options.params || {};
 
   /**
    * @private
@@ -112000,10 +74717,10 @@ ol.source.ImageWMS = function(opt_options) {
 
   /**
    * @private
-   * @type {ol.source.wms.ServerType|undefined}
+   * @type {ol.source.WMSServerType|undefined}
    */
   this.serverType_ =
-      /** @type {ol.source.wms.ServerType|undefined} */ (options.serverType);
+      /** @type {ol.source.WMSServerType|undefined} */ (options.serverType);
 
   /**
    * @private
@@ -112036,7 +74753,7 @@ ol.source.ImageWMS = function(opt_options) {
   this.ratio_ = options.ratio !== undefined ? options.ratio : 1.5;
 
 };
-goog.inherits(ol.source.ImageWMS, ol.source.Image);
+ol.inherits(ol.source.ImageWMS, ol.source.Image);
 
 
 /**
@@ -112053,20 +74770,15 @@ ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_ = [101, 101];
  * constructed.
  * @param {ol.Coordinate} coordinate Coordinate.
  * @param {number} resolution Resolution.
- * @param {ol.proj.ProjectionLike} projection Projection.
+ * @param {ol.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
+ * @api
  */
-ol.source.ImageWMS.prototype.getGetFeatureInfoUrl =
-    function(coordinate, resolution, projection, params) {
-
-  goog.asserts.assert(!('VERSION' in params),
-      'key VERSION is not allowed in params');
-
+ol.source.ImageWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, params) {
   if (this.url_ === undefined) {
     return undefined;
   }
@@ -112083,7 +74795,7 @@ ol.source.ImageWMS.prototype.getGetFeatureInfoUrl =
     'TRANSPARENT': true,
     'QUERY_LAYERS': this.params_['LAYERS']
   };
-  goog.object.extend(baseParams, this.params_, params);
+  ol.obj.assign(baseParams, this.params_, params);
 
   var x = Math.floor((coordinate[0] - extent[0]) / resolution);
   var y = Math.floor((extent[3] - coordinate[1]) / resolution);
@@ -112100,7 +74812,7 @@ ol.source.ImageWMS.prototype.getGetFeatureInfoUrl =
  * 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
+ * @api
  */
 ol.source.ImageWMS.prototype.getParams = function() {
   return this.params_;
@@ -112110,8 +74822,7 @@ ol.source.ImageWMS.prototype.getParams = function() {
 /**
  * @inheritDoc
  */
-ol.source.ImageWMS.prototype.getImage =
-    function(extent, resolution, pixelRatio, projection) {
+ol.source.ImageWMS.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
 
   if (this.url_ === undefined) {
     return null;
@@ -112123,36 +74834,24 @@ ol.source.ImageWMS.prototype.getImage =
     pixelRatio = 1;
   }
 
-  extent = extent.slice();
-  var centerX = (extent[0] + extent[2]) / 2;
-  var centerY = (extent[1] + extent[3]) / 2;
-  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 imageResolution = resolution / pixelRatio;
 
-  // Compute an integer width and height.
-  var width = Math.ceil(ol.extent.getWidth(extent) / imageResolution);
-  var height = Math.ceil(ol.extent.getHeight(extent) / imageResolution);
-
-  // Modify the extent to match the integer width and height.
-  extent[0] = centerX - imageResolution * width / 2;
-  extent[2] = centerX + imageResolution * width / 2;
-  extent[1] = centerY - imageResolution * height / 2;
-  extent[3] = centerY + imageResolution * height / 2;
+  var center = ol.extent.getCenter(extent);
+  var viewWidth = Math.ceil(ol.extent.getWidth(extent) / imageResolution);
+  var viewHeight = Math.ceil(ol.extent.getHeight(extent) / imageResolution);
+  var viewExtent = ol.extent.getForViewAndSize(center, imageResolution, 0,
+      [viewWidth, viewHeight]);
+  var requestWidth = Math.ceil(this.ratio_ * ol.extent.getWidth(extent) / imageResolution);
+  var requestHeight = Math.ceil(this.ratio_ * ol.extent.getHeight(extent) / imageResolution);
+  var requestExtent = ol.extent.getForViewAndSize(center, imageResolution, 0,
+      [requestWidth, requestHeight]);
 
   var image = this.image_;
   if (image &&
       this.renderedRevision_ == this.getRevision() &&
       image.getResolution() == resolution &&
       image.getPixelRatio() == pixelRatio &&
-      ol.extent.containsExtent(image.getExtent(), extent)) {
+      ol.extent.containsExtent(image.getExtent(), viewExtent)) {
     return image;
   }
 
@@ -112163,21 +74862,21 @@ ol.source.ImageWMS.prototype.getImage =
     'FORMAT': 'image/png',
     'TRANSPARENT': true
   };
-  goog.object.extend(params, this.params_);
+  ol.obj.assign(params, this.params_);
 
-  this.imageSize_[0] = width;
-  this.imageSize_[1] = height;
+  this.imageSize_[0] = Math.round(ol.extent.getWidth(requestExtent) / imageResolution);
+  this.imageSize_[1] = Math.round(ol.extent.getHeight(requestExtent) / imageResolution);
 
-  var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio,
+  var url = this.getRequestUrl_(requestExtent, this.imageSize_, pixelRatio,
       projection, params);
 
-  this.image_ = new ol.Image(extent, resolution, pixelRatio,
+  this.image_ = new ol.Image(requestExtent, 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);
+  ol.events.listen(this.image_, ol.events.EventType.CHANGE,
+      this.handleImageChange, this);
 
   return this.image_;
 
@@ -112203,22 +74902,19 @@ ol.source.ImageWMS.prototype.getImageLoadFunction = function() {
  * @return {string} Request URL.
  * @private
  */
-ol.source.ImageWMS.prototype.getRequestUrl_ =
-    function(extent, size, pixelRatio, projection, params) {
+ol.source.ImageWMS.prototype.getRequestUrl_ = function(extent, size, pixelRatio, projection, params) {
 
-  goog.asserts.assert(this.url_ !== undefined, 'url is defined');
+  ol.asserts.assert(this.url_ !== undefined, 9); // `url` must be configured or set using `#setUrl()`
 
   params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();
 
   if (!('STYLES' in this.params_)) {
-    /* jshint -W053 */
-    params['STYLES'] = new String('');
-    /* jshint +W053 */
+    params['STYLES'] = '';
   }
 
   if (pixelRatio != 1) {
     switch (this.serverType_) {
-      case ol.source.wms.ServerType.GEOSERVER:
+      case ol.source.WMSServerType.GEOSERVER:
         var dpi = (90 * pixelRatio + 0.5) | 0;
         if ('FORMAT_OPTIONS' in params) {
           params['FORMAT_OPTIONS'] += ';dpi:' + dpi;
@@ -112226,15 +74922,15 @@ ol.source.ImageWMS.prototype.getRequestUrl_ =
           params['FORMAT_OPTIONS'] = 'dpi:' + dpi;
         }
         break;
-      case ol.source.wms.ServerType.MAPSERVER:
+      case ol.source.WMSServerType.MAPSERVER:
         params['MAP_RESOLUTION'] = 90 * pixelRatio;
         break;
-      case ol.source.wms.ServerType.CARMENTA_SERVER:
-      case ol.source.wms.ServerType.QGIS:
+      case ol.source.WMSServerType.CARMENTA_SERVER:
+      case ol.source.WMSServerType.QGIS:
         params['DPI'] = 90 * pixelRatio;
         break;
       default:
-        goog.asserts.fail('unknown serverType configured');
+        ol.asserts.assert(false, 8); // Unknown `serverType` configured
         break;
     }
   }
@@ -112251,14 +74947,14 @@ ol.source.ImageWMS.prototype.getRequestUrl_ =
   }
   params['BBOX'] = bbox.join(',');
 
-  return goog.uri.utils.appendParamsFromMap(this.url_, params);
+  return ol.uri.appendParams(/** @type {string} */ (this.url_), params);
 };
 
 
 /**
  * Return the URL used for this WMS source.
  * @return {string|undefined} URL.
- * @api stable
+ * @api
  */
 ol.source.ImageWMS.prototype.getUrl = function() {
   return this.url_;
@@ -112281,7 +74977,7 @@ ol.source.ImageWMS.prototype.setImageLoadFunction = function(
 /**
  * Set the URL to use for requests.
  * @param {string|undefined} url URL.
- * @api stable
+ * @api
  */
 ol.source.ImageWMS.prototype.setUrl = function(url) {
   if (url != this.url_) {
@@ -112295,10 +74991,10 @@ ol.source.ImageWMS.prototype.setUrl = function(url) {
 /**
  * Update the user-provided params.
  * @param {Object} params Params.
- * @api stable
+ * @api
  */
 ol.source.ImageWMS.prototype.updateParams = function(params) {
-  goog.object.extend(this.params_, params);
+  ol.obj.assign(this.params_, params);
   this.updateV13_();
   this.image_ = null;
   this.changed();
@@ -112309,122 +75005,17 @@ ol.source.ImageWMS.prototype.updateParams = function(params) {
  * @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.TileUrlFunction');
-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
-      });
-
-  /**
-   * @private
-   * @type {!Array.<string>|null}
-   */
-  this.urls_ = null;
-
-  goog.base(this, {
-    attributions: options.attributions,
-    crossOrigin: options.crossOrigin,
-    logo: options.logo,
-    projection: projection,
-    tileGrid: tileGrid,
-    tileLoadFunction: options.tileLoadFunction,
-    tilePixelRatio: options.tilePixelRatio,
-    tileUrlFunction: ol.TileUrlFunction.nullTileUrlFunction,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
-  });
-
-  if (options.tileUrlFunction !== undefined) {
-    this.setTileUrlFunction(options.tileUrlFunction);
-  } else if (options.urls !== undefined) {
-    this.setUrls(options.urls);
-  } else if (options.url !== undefined) {
-    this.setUrl(options.url);
-  }
-
-};
-goog.inherits(ol.source.XYZ, ol.source.TileImage);
-
-
-/**
- * Return the URLs used for this XYZ source.
- * When a tileUrlFunction is used instead of url or urls,
- * null will be returned.
- * @return {!Array.<string>|null} URLs.
- * @api
- */
-ol.source.XYZ.prototype.getUrls = function() {
-  return this.urls_;
-};
-
-
-/**
- * Set the URL to use for requests.
- * @param {string} url URL.
- * @api stable
- */
-ol.source.XYZ.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.
- */
-ol.source.XYZ.prototype.setUrls = function(urls) {
-  this.setTileUrlFunction(
-      ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid));
-  this.urls_ = urls;
+  var version = this.params_['VERSION'] || ol.DEFAULT_WMS_VERSION;
+  this.v13_ = ol.string.compareVersions(version, '1.3') >= 0;
 };
 
 goog.provide('ol.source.OSM');
 
+goog.require('ol');
 goog.require('ol.Attribution');
 goog.require('ol.source.XYZ');
 
 
-
 /**
  * @classdesc
  * Layer source for the OpenStreetMap tile server.
@@ -112432,7 +75023,7 @@ goog.require('ol.source.XYZ');
  * @constructor
  * @extends {ol.source.XYZ}
  * @param {olx.source.OSMOptions=} opt_options Open Street Map options.
- * @api stable
+ * @api
  */
 ol.source.OSM = function(opt_options) {
 
@@ -112451,18 +75042,20 @@ ol.source.OSM = function(opt_options) {
   var url = options.url !== undefined ?
       options.url : 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png';
 
-  goog.base(this, {
+  ol.source.XYZ.call(this, {
     attributions: attributions,
+    cacheSize: options.cacheSize,
     crossOrigin: crossOrigin,
-    opaque: true,
+    opaque: options.opaque !== undefined ? options.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);
+ol.inherits(ol.source.OSM, ol.source.XYZ);
 
 
 /**
@@ -112474,153 +75067,72 @@ goog.inherits(ol.source.OSM, ol.source.XYZ);
  */
 ol.source.OSM.ATTRIBUTION = new ol.Attribution({
   html: '&copy; ' +
-      '<a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> ' +
+      '<a href="https://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
+ * @fileoverview
+ * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, unusedLocalVariables, uselessCode, visibility}
  */
-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,
-    opaque: true,
-    tileLoadFunction: options.tileLoadFunction,
-    url: url
-  });
-
-};
-goog.inherits(ol.source.MapQuest, ol.source.XYZ);
+goog.provide('ol.ext.pixelworks.Processor');
 
+/** @typedef {function(*)} */
+ol.ext.pixelworks.Processor = function() {};
 
-/**
- * @const
- * @type {ol.Attribution}
- */
-ol.source.MapQuest.TILE_ATTRIBUTION = new ol.Attribution({
-  html: 'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a>'
-});
-
+(function() {(function (exports) {
+'use strict';
 
-/**
- * @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
-    ]
+var hasImageData = true;
+try {
+  new ImageData(10, 10);
+} catch (_) {
+  hasImageData = false;
+}
+var context = document.createElement('canvas').getContext('2d');
+function newImageData$1(data, width, height) {
+  if (hasImageData) {
+    return new ImageData(data, width, height);
+  } else {
+    var imageData = context.createImageData(width, height);
+    imageData.data.set(data);
+    return imageData;
   }
+}
+var newImageData_1 = newImageData$1;
+var util = {
+	newImageData: newImageData_1
 };
 
-
-/**
- * Get the layer of the source, either `osm`, `sat`, or `hyb`.
- * @return {string} Layer.
- * @api
- */
-ol.source.MapQuest.prototype.getLayer = function() {
-  return this.layer_;
-};
-
-goog.provide('ol.ext.pixelworks');
-/** @typedef {function(*)} */
-ol.ext.pixelworks;
-(function() {
-var exports = {};
-var module = {exports: exports};
-var define;
-/**
- * @fileoverview
- * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility}
- */
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pixelworks = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-var Processor = require('./processor');
-
-exports.Processor = Processor;
-
-},{"./processor":2}],2:[function(require,module,exports){
-/* eslint-disable dot-notation */
-
-/**
- * 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.
- */
+var newImageData = util.newImageData;
 function createMinion(operation) {
+  var workerHasImageData = true;
+  try {
+    new ImageData(10, 10);
+  } catch (_) {
+    workerHasImageData = false;
+  }
+  function newWorkerImageData(data, width, height) {
+    if (workerHasImageData) {
+      return new ImageData(data, width, height);
+    } else {
+      return {data: data, width: width, height: height};
+    }
+  }
   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(
+        images[b] = newWorkerImageData(
             new Uint8ClampedArray(buffers[b]), width, height);
       }
       output = operation(images, meta).data;
@@ -112650,56 +75162,33 @@ function createMinion(operation) {
     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 __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}});
+        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;
@@ -112725,17 +75214,6 @@ function Processor(config) {
   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,
@@ -112744,31 +75222,18 @@ Processor.prototype.process = function(inputs, meta, 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();
@@ -112807,12 +75272,6 @@ Processor.prototype._dispatch = function() {
     }
   }
 };
-
-/**
- * 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;
@@ -112823,11 +75282,6 @@ Processor.prototype._onWorkerMessage = function(index, event) {
     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;
@@ -112850,51 +75304,64 @@ Processor.prototype._resolveJob = function() {
   this._job = null;
   this._dataLookup = {};
   job.callback(null,
-      new ImageData(data, job.inputs[0].width, job.inputs[0].height), meta);
+      newImageData(data, job.inputs[0].width, job.inputs[0].height), meta);
   this._dispatch();
 };
+var processor = Processor;
 
-module.exports = Processor;
+var Processor_1 = processor;
+var index = {
+	Processor: Processor_1
+};
 
-},{}]},{},[1])(1)
-});
-ol.ext.pixelworks = module.exports;
-})();
+exports['default'] = index;
+exports.Processor = Processor_1;
+
+}((this.pixelworks = this.pixelworks || {})));}).call(ol.ext);
+
+goog.provide('ol.source.RasterOperationType');
+
+/**
+ * Raster operation type. Supported values are `'pixel'` and `'image'`.
+ * @enum {string}
+ */
+ol.source.RasterOperationType = {
+  PIXEL: 'pixel',
+  IMAGE: 'image'
+};
 
 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');
 goog.require('ol.ImageCanvas');
 goog.require('ol.TileQueue');
 goog.require('ol.dom');
-goog.require('ol.ext.pixelworks');
+goog.require('ol.events');
+goog.require('ol.events.Event');
+goog.require('ol.events.EventType');
+goog.require('ol.ext.pixelworks.Processor');
 goog.require('ol.extent');
 goog.require('ol.layer.Image');
 goog.require('ol.layer.Tile');
-goog.require('ol.raster.OperationType');
+goog.require('ol.obj');
 goog.require('ol.renderer.canvas.ImageLayer');
 goog.require('ol.renderer.canvas.TileLayer');
 goog.require('ol.source.Image');
+goog.require('ol.source.RasterOperationType');
 goog.require('ol.source.State');
 goog.require('ol.source.Tile');
-
+goog.require('ol.transform');
 
 
 /**
  * @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
+ * of {@link ol.RasterOperation} functions to transform input pixel values into
  * output pixel values.
  *
  * @constructor
  * @extends {ol.source.Image}
+ * @fires ol.source.Raster.Event
  * @param {olx.source.RasterOptions} options Options.
  * @api
  */
@@ -112908,10 +75375,10 @@ ol.source.Raster = function(options) {
 
   /**
    * @private
-   * @type {ol.raster.OperationType}
+   * @type {ol.source.RasterOperationType}
    */
   this.operationType_ = options.operationType !== undefined ?
-      options.operationType : ol.raster.OperationType.PIXEL;
+      options.operationType : ol.source.RasterOperationType.PIXEL;
 
   /**
    * @private
@@ -112926,36 +75393,32 @@ ol.source.Raster = function(options) {
   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);
+    ol.events.listen(this.renderers_[r], ol.events.EventType.CHANGE,
+        this.changed, 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));
+      function() {
+        return 1;
+      },
+      this.changed.bind(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];
+    layerStates[ol.getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
   }
 
   /**
-   * The most recently rendered state.
-   * @type {?ol.source.Raster.RenderedState}
+   * The most recently requested frame state.
+   * @type {olx.FrameState}
    * @private
    */
-  this.renderedState_ = null;
+  this.requestedFrameState_;
 
   /**
    * The most recently rendered image canvas.
@@ -112964,6 +75427,12 @@ ol.source.Raster = function(options) {
    */
   this.renderedImageCanvas_ = null;
 
+  /**
+   * The most recently rendered revision.
+   * @type {number}
+   */
+  this.renderedRevision_;
+
   /**
    * @private
    * @type {olx.FrameState}
@@ -112971,7 +75440,7 @@ ol.source.Raster = function(options) {
   this.frameState_ = {
     animate: false,
     attributions: {},
-    coordinateToPixelMatrix: goog.vec.Mat4.createNumber(),
+    coordinateToPixelTransform: ol.transform.create(),
     extent: null,
     focus: null,
     index: 0,
@@ -112979,7 +75448,7 @@ ol.source.Raster = function(options) {
     layerStatesArray: layerStatesArray,
     logos: {},
     pixelRatio: 1,
-    pixelToCoordinateMatrix: goog.vec.Mat4.createNumber(),
+    pixelToCoordinateTransform: ol.transform.create(),
     postRenderFunctions: [],
     size: [0, 0],
     skippedFeatureUids: {},
@@ -112993,19 +75462,19 @@ ol.source.Raster = function(options) {
     wantedTiles: {}
   };
 
-  goog.base(this, {});
+  ol.source.Image.call(this, {});
 
   if (options.operation !== undefined) {
     this.setOperation(options.operation, options.lib);
   }
 
 };
-goog.inherits(ol.source.Raster, ol.source.Image);
+ol.inherits(ol.source.Raster, ol.source.Image);
 
 
 /**
  * Set the operation.
- * @param {ol.raster.Operation} operation New operation.
+ * @param {ol.RasterOperation} operation New operation.
  * @param {Object=} opt_lib Functions that will be available to operations run
  *     in a worker.
  * @api
@@ -113013,7 +75482,7 @@ goog.inherits(ol.source.Raster, ol.source.Image);
 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,
+    imageOps: this.operationType_ === ol.source.RasterOperationType.IMAGE,
     queue: 1,
     lib: opt_lib,
     threads: this.threads_
@@ -113030,23 +75499,20 @@ ol.source.Raster.prototype.setOperation = function(operation, opt_lib) {
  * @return {olx.FrameState} The updated frame state.
  * @private
  */
-ol.source.Raster.prototype.updateFrameState_ =
-    function(extent, resolution, projection) {
+ol.source.Raster.prototype.updateFrameState_ = function(extent, resolution, projection) {
 
   var frameState = /** @type {olx.FrameState} */ (
-      goog.object.clone(this.frameState_));
+      ol.obj.assign({}, this.frameState_));
 
   frameState.viewState = /** @type {olx.ViewState} */ (
-      goog.object.clone(frameState.viewState));
+      ol.obj.assign({}, 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;
+  frameState.extent = extent.slice();
+  frameState.focus = center;
+  frameState.size[0] = Math.round(ol.extent.getWidth(extent) / resolution);
+  frameState.size[1] = Math.round(ol.extent.getHeight(extent) / resolution);
 
   var viewState = frameState.viewState;
   viewState.center = center;
@@ -113057,92 +75523,60 @@ ol.source.Raster.prototype.updateFrameState_ =
 
 
 /**
- * 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.
+ * Determine if all sources are ready.
+ * @return {boolean} All sources are ready.
  * @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);
+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;
 };
 
 
 /**
  * @inheritDoc
  */
-ol.source.Raster.prototype.getImage =
-    function(extent, resolution, pixelRatio, projection) {
-
+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);
+  this.requestedFrameState_ = frameState;
 
-  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;
-};
-
+  frameState.tileQueue.loadMoreTiles(16, 16);
 
-/**
- * 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;
+  // check if we can't reuse the existing ol.ImageCanvas
+  if (this.renderedImageCanvas_) {
+    var renderedResolution = this.renderedImageCanvas_.getResolution();
+    var renderedExtent = this.renderedImageCanvas_.getExtent();
+    if (resolution !== renderedResolution || !ol.extent.equals(extent, renderedExtent)) {
+      this.renderedImageCanvas_ = null;
     }
   }
-  return ready;
+
+  if (!this.renderedImageCanvas_ || this.getRevision() !== this.renderedRevision_) {
+    this.processSources_();
+  }
+
+  return this.renderedImageCanvas_;
 };
 
 
 /**
- * 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.
+ * Start processing source data.
  * @private
  */
-ol.source.Raster.prototype.composeFrame_ = function(frameState, callback) {
+ol.source.Raster.prototype.processSources_ = function() {
+  var frameState = this.requestedFrameState_;
   var len = this.renderers_.length;
   var imageDatas = new Array(len);
   for (var i = 0; i < len; ++i) {
@@ -113151,51 +75585,56 @@ ol.source.Raster.prototype.composeFrame_ = function(frameState, callback) {
     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.dispatchEvent(new ol.source.Raster.Event(
+      ol.source.Raster.EventType_.BEFOREOPERATIONS, frameState, data));
   this.worker_.process(imageDatas, data,
-      this.onWorkerComplete_.bind(this, frameState, callback));
-
-  frameState.tileQueue.loadMoreTiles(16, 16);
+      this.onWorkerComplete_.bind(this, frameState));
 };
 
 
 /**
  * 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);
+ol.source.Raster.prototype.onWorkerComplete_ = function(frameState, err, output, data) {
+  if (err || !output) {
     return;
   }
-  if (!output) {
-    // job aborted
+
+  // do nothing if extent or resolution changed
+  var extent = frameState.extent;
+  var resolution = frameState.viewState.resolution;
+  if (resolution !== this.requestedFrameState_.viewState.resolution ||
+      !ol.extent.equals(extent, this.requestedFrameState_.extent)) {
     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);
+  var context;
+  if (this.renderedImageCanvas_) {
+    context = this.renderedImageCanvas_.getImage().getContext('2d');
+  } else {
+    var width = Math.round(ol.extent.getWidth(extent) / resolution);
+    var height = Math.round(ol.extent.getHeight(extent) / resolution);
+    context = ol.dom.createCanvasContext2D(width, height);
+    this.renderedImageCanvas_ = new ol.ImageCanvas(
+        extent, resolution, 1, this.getAttributions(), context.canvas);
   }
+  context.putImageData(output, 0, 0);
+
+  this.changed();
+  this.renderedRevision_ = this.getRevision();
 
-  callback(null);
+  this.dispatchEvent(new ol.source.Raster.Event(
+      ol.source.Raster.EventType_.AFTEROPERATIONS, frameState, data));
 };
 
 
@@ -113203,45 +75642,28 @@ ol.source.Raster.prototype.onWorkerComplete_ =
  * 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.
+ * @param {ol.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) {
+  if (!renderer.prepareFrame(frameState, layerState)) {
     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_) {
+  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 {
-      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);
-      }
+      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);
   }
+  renderer.composeFrame(frameState, layerState, ol.source.Raster.context_);
+  return ol.source.Raster.context_.getImageData(0, 0, width, height);
 };
 
 
@@ -113256,7 +75678,7 @@ 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.
+ * @return {Array.<ol.LayerState>} The layer states.
  * @private
  */
 ol.source.Raster.getLayerStatesArray_ = function(renderers) {
@@ -113294,8 +75716,6 @@ ol.source.Raster.createRenderer_ = function(source) {
     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;
 };
@@ -113325,29 +75745,20 @@ ol.source.Raster.createTileRenderer_ = function(source) {
 };
 
 
-/**
- * @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}
+ * @extends {ol.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);
+ol.source.Raster.Event = function(type, frameState, data) {
+  ol.events.Event.call(this, type);
 
   /**
    * The raster extent.
@@ -113372,23 +75783,32 @@ ol.source.RasterEvent = function(type, frameState, data) {
   this.data = data;
 
 };
-goog.inherits(ol.source.RasterEvent, goog.events.Event);
+ol.inherits(ol.source.Raster.Event, ol.events.Event);
+
+
+/**
+ * @override
+ */
+ol.source.Raster.prototype.getImageInternal = function() {
+  return null; // not implemented
+};
 
 
 /**
  * @enum {string}
+ * @private
  */
-ol.source.RasterEventType = {
+ol.source.Raster.EventType_ = {
   /**
    * Triggered before operations are run.
-   * @event ol.source.RasterEvent#beforeoperations
+   * @event ol.source.Raster.Event#beforeoperations
    * @api
    */
   BEFOREOPERATIONS: 'beforeoperations',
 
   /**
    * Triggered after operations are run.
-   * @event ol.source.RasterEvent#afteroperations
+   * @event ol.source.Raster.Event#afteroperations
    * @api
    */
   AFTEROPERATIONS: 'afteroperations'
@@ -113396,16 +75816,65 @@ ol.source.RasterEventType = {
 
 goog.provide('ol.source.Stamen');
 
-goog.require('goog.asserts');
+goog.require('ol');
 goog.require('ol.Attribution');
 goog.require('ol.source.OSM');
 goog.require('ol.source.XYZ');
 
 
+/**
+ * @classdesc
+ * Layer source for the Stamen tile server.
+ *
+ * @constructor
+ * @extends {ol.source.XYZ}
+ * @param {olx.source.StamenOptions} options Stamen options.
+ * @api
+ */
+ol.source.Stamen = function(options) {
+  var i = options.layer.indexOf('-');
+  var provider = i == -1 ? options.layer : options.layer.slice(0, i);
+  var providerConfig = ol.source.Stamen.ProviderConfig[provider];
+
+  var layerConfig = ol.source.Stamen.LayerConfig[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;
+
+  ol.source.XYZ.call(this, {
+    attributions: ol.source.Stamen.ATTRIBUTIONS,
+    cacheSize: options.cacheSize,
+    crossOrigin: 'anonymous',
+    maxZoom: options.maxZoom != undefined ? options.maxZoom : providerConfig.maxZoom,
+    minZoom: options.minZoom != undefined ? options.minZoom : providerConfig.minZoom,
+    opaque: layerConfig.opaque,
+    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+    tileLoadFunction: options.tileLoadFunction,
+    url: url,
+    wrapX: options.wrapX
+  });
+};
+ol.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
+];
+
 /**
  * @type {Object.<string, {extension: string, opaque: boolean}>}
  */
-ol.source.StamenLayerConfig = {
+ol.source.Stamen.LayerConfig = {
   'terrain': {
     extension: 'jpg',
     opaque: true
@@ -113452,11 +75921,10 @@ ol.source.StamenLayerConfig = {
   }
 };
 
-
 /**
  * @type {Object.<string, {minZoom: number, maxZoom: number}>}
  */
-ol.source.StamenProviderConfig = {
+ol.source.Stamen.ProviderConfig = {
   'terrain': {
     minZoom: 4,
     maxZoom: 18
@@ -113466,82 +75934,21 @@ ol.source.StamenProviderConfig = {
     maxZoom: 20
   },
   'watercolor': {
-    minZoom: 3,
+    minZoom: 1,
     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,
-    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.TileUrlFunction');
 goog.require('ol.extent');
-goog.require('ol.proj');
+goog.require('ol.math');
+goog.require('ol.obj');
 goog.require('ol.size');
 goog.require('ol.source.TileImage');
 goog.require('ol.tilecoord');
-
+goog.require('ol.uri');
 
 
 /**
@@ -113562,35 +75969,25 @@ ol.source.TileArcGISRest = function(opt_options) {
 
   var options = opt_options || {};
 
-  var params = options.params !== undefined ? options.params : {};
-
-  goog.base(this, {
+  ol.source.TileImage.call(this, {
     attributions: options.attributions,
+    cacheSize: options.cacheSize,
     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
   });
 
-  var urls = options.urls;
-  if (urls === undefined && options.url !== undefined) {
-    urls = ol.TileUrlFunction.expandUrl(options.url);
-  }
-
   /**
    * @private
-   * @type {!Array.<string>}
+   * @type {!Object}
    */
-  this.urls_ = urls || [];
-
-  /**
-   * @private
-   * @type {Object}
-   */
-  this.params_ = params;
+  this.params_ = options.params || {};
 
   /**
    * @private
@@ -113598,8 +75995,23 @@ ol.source.TileArcGISRest = function(opt_options) {
    */
   this.tmpExtent_ = ol.extent.createEmpty();
 
+  this.setKey(this.getKeyForParams_());
+};
+ol.inherits(ol.source.TileArcGISRest, ol.source.TileImage);
+
+
+/**
+ * @private
+ * @return {string} The key for the current params.
+ */
+ol.source.TileArcGISRest.prototype.getKeyForParams_ = function() {
+  var i = 0;
+  var res = [];
+  for (var key in this.params_) {
+    res[i++] = key + '-' + this.params_[key];
+  }
+  return res.join('/');
 };
-goog.inherits(ol.source.TileArcGISRest, ol.source.TileImage);
 
 
 /**
@@ -113623,12 +76035,11 @@ ol.source.TileArcGISRest.prototype.getParams = function() {
  * @return {string|undefined} Request URL.
  * @private
  */
-ol.source.TileArcGISRest.prototype.getRequestUrl_ =
-    function(tileCoord, tileSize, tileExtent,
+ol.source.TileArcGISRest.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent,
         pixelRatio, projection, params) {
 
-  var urls = this.urls_;
-  if (urls.length === 0) {
+  var urls = this.urls;
+  if (!urls) {
     return undefined;
   }
 
@@ -113639,93 +76050,37 @@ ol.source.TileArcGISRest.prototype.getRequestUrl_ =
   params['BBOX'] = tileExtent.join(',');
   params['BBOXSR'] = srid;
   params['IMAGESR'] = srid;
-  params['DPI'] = Math.round(90 * pixelRatio);
+  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);
+    var index = ol.math.modulo(ol.tilecoord.hash(tileCoord), urls.length);
     url = urls[index];
   }
 
-  if (!goog.string.endsWith(url, '/')) {
-    url = url + '/';
-  }
-
-  // If a MapServer, use export. If an ImageServer, use exportImage.
-  if (goog.string.endsWith(url, 'MapServer/')) {
-    url = url + 'export';
-  }
-  else if (goog.string.endsWith(url, 'ImageServer/')) {
-    url = url + 'exportImage';
-  }
-  else {
-    goog.asserts.fail('Unknown Rest Service', url);
-  }
-
-  return goog.uri.utils.appendParamsFromMap(url, params);
-};
-
-
-/**
- * @param {number} z Z.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {ol.Size} Size.
- */
-ol.source.TileArcGISRest.prototype.getTilePixelSize =
-    function(z, pixelRatio, projection) {
-  var tileSize = goog.base(this, 'getTilePixelSize', z, pixelRatio, projection);
-  if (pixelRatio == 1) {
-    return tileSize;
-  } else {
-    return ol.size.scale(tileSize, pixelRatio, this.tmpSize);
-  }
-};
-
-
-/**
- * Return the URLs used for this ArcGIS source.
- * @return {!Array.<string>} URLs.
- * @api stable
- */
-ol.source.TileArcGISRest.prototype.getUrls = function() {
-  return this.urls_;
-};
-
-
-/**
- * Set the URL to use for requests.
- * @param {string|undefined} url URL.
- * @api stable
- */
-ol.source.TileArcGISRest.prototype.setUrl = function(url) {
-  var urls = url !== undefined ? ol.TileUrlFunction.expandUrl(url) : null;
-  this.setUrls(urls);
+  var modifiedUrl = url
+      .replace(/MapServer\/?$/, 'MapServer/export')
+      .replace(/ImageServer\/?$/, 'ImageServer/exportImage');
+  return ol.uri.appendParams(modifiedUrl, params);
 };
 
 
 /**
- * Set the URLs to use for requests.
- * @param {Array.<string>|undefined} urls URLs.
- * @api stable
+ * @inheritDoc
  */
-ol.source.TileArcGISRest.prototype.setUrls = function(urls) {
-  this.urls_ = urls || [];
-  this.changed();
+ol.source.TileArcGISRest.prototype.getTilePixelRatio = function(pixelRatio) {
+  return /** @type {number} */ (pixelRatio);
 };
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {string|undefined} Tile URL.
- * @private
+ * @inheritDoc
  */
-ol.source.TileArcGISRest.prototype.tileUrlFunction_ =
-    function(tileCoord, pixelRatio, projection) {
+ol.source.TileArcGISRest.prototype.fixedTileUrlFunction = function(tileCoord, pixelRatio, projection) {
 
   var tileGrid = this.getTileGrid();
   if (!tileGrid) {
@@ -113751,7 +76106,7 @@ ol.source.TileArcGISRest.prototype.tileUrlFunction_ =
     'FORMAT': 'PNG32',
     'TRANSPARENT': true
   };
-  goog.object.extend(baseParams, this.params_);
+  ol.obj.assign(baseParams, this.params_);
 
   return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
       pixelRatio, projection, baseParams);
@@ -113761,24 +76116,68 @@ ol.source.TileArcGISRest.prototype.tileUrlFunction_ =
 /**
  * Update the user-provided params.
  * @param {Object} params Params.
- * @api stable
+ * @api
  */
 ol.source.TileArcGISRest.prototype.updateParams = function(params) {
-  goog.object.extend(this.params_, params);
-  this.changed();
+  ol.obj.assign(this.params_, params);
+  this.setKey(this.getKeyForParams_());
 };
 
 goog.provide('ol.source.TileDebug');
 
+goog.require('ol');
 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');
 
 
+/**
+ * @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) {
+
+  ol.source.Tile.call(this, {
+    opaque: false,
+    projection: options.projection,
+    tileGrid: options.tileGrid,
+    wrapX: options.wrapX !== undefined ? options.wrapX : true
+  });
+
+};
+ol.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.source.TileDebug.Tile_} */ (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 ? '' :
+        this.getTileCoordForTileUrlFunction(textTileCoord).toString();
+    var tile = new ol.source.TileDebug.Tile_(tileCoord, tileSize, text);
+    this.tileCache.set(tileCoordKey, tile);
+    return tile;
+  }
+};
+
 
 /**
  * @constructor
@@ -113788,9 +76187,9 @@ goog.require('ol.tilecoord');
  * @param {string} text Text.
  * @private
  */
-ol.DebugTile_ = function(tileCoord, tileSize, text) {
+ol.source.TileDebug.Tile_ = function(tileCoord, tileSize, text) {
 
-  goog.base(this, tileCoord, ol.TileState.LOADED);
+  ol.Tile.call(this, tileCoord, ol.TileState.LOADED);
 
   /**
    * @private
@@ -113806,23 +76205,22 @@ ol.DebugTile_ = function(tileCoord, tileSize, text) {
 
   /**
    * @private
-   * @type {Object.<number, HTMLCanvasElement>}
+   * @type {HTMLCanvasElement}
    */
-  this.canvasByContext_ = {};
+  this.canvas_ = null;
 
 };
-goog.inherits(ol.DebugTile_, ol.Tile);
+ol.inherits(ol.source.TileDebug.Tile_, ol.Tile);
 
 
 /**
- * @inheritDoc
+ * Get the image element for this tile.
+ * @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];
+ol.source.TileDebug.Tile_.prototype.getImage = function() {
+  if (this.canvas_) {
+    return this.canvas_;
   } else {
-
     var tileSize = this.tileSize_;
     var context = ol.dom.createCanvasContext2D(tileSize[0], tileSize[1]);
 
@@ -113835,58 +76233,16 @@ ol.DebugTile_.prototype.getImage = function(opt_context) {
     context.font = '24px sans-serif';
     context.fillText(this.text_, tileSize[0] / 2, tileSize[1] / 2);
 
-    this.canvasByContext_[key] = context.canvas;
+    this.canvas_ = 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
+ * @override
  */
-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;
-  }
-};
+ol.source.TileDebug.Tile_.prototype.load = function() {};
 
 // FIXME check order of async callbacks
 
@@ -113895,18 +76251,17 @@ ol.source.TileDebug.prototype.getTile = function(z, x, y) {
  */
 
 goog.provide('ol.source.TileJSON');
-goog.provide('ol.tilejson');
 
-goog.require('goog.asserts');
-goog.require('goog.net.Jsonp');
+goog.require('ol');
 goog.require('ol.Attribution');
-goog.require('ol.TileRange');
 goog.require('ol.TileUrlFunction');
+goog.require('ol.asserts');
 goog.require('ol.extent');
+goog.require('ol.net');
 goog.require('ol.proj');
 goog.require('ol.source.State');
 goog.require('ol.source.TileImage');
-
+goog.require('ol.tilegrid');
 
 
 /**
@@ -113916,25 +76271,86 @@ goog.require('ol.source.TileImage');
  * @constructor
  * @extends {ol.source.TileImage}
  * @param {olx.source.TileJSONOptions} options TileJSON options.
- * @api stable
+ * @api
  */
 ol.source.TileJSON = function(options) {
 
-  goog.base(this, {
+  /**
+   * @type {TileJSON}
+   * @private
+   */
+  this.tileJSON_ = null;
+
+  ol.source.TileImage.call(this, {
     attributions: options.attributions,
+    cacheSize: options.cacheSize,
     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));
+  if (options.url) {
+    if (options.jsonp) {
+      ol.net.jsonp(options.url, this.handleTileJSONResponse.bind(this),
+          this.handleTileJSONError.bind(this));
+    } else {
+      var client = new XMLHttpRequest();
+      client.addEventListener('load', this.onXHRLoad_.bind(this));
+      client.addEventListener('error', this.onXHRError_.bind(this));
+      client.open('GET', options.url);
+      client.send();
+    }
+  } else if (options.tileJSON) {
+    this.handleTileJSONResponse(options.tileJSON);
+  } else {
+    ol.asserts.assert(false, 51); // Either `url` or `tileJSON` options must be provided
+  }
+
+};
+ol.inherits(ol.source.TileJSON, ol.source.TileImage);
+
+
+/**
+ * @private
+ * @param {Event} event The load event.
+ */
+ol.source.TileJSON.prototype.onXHRLoad_ = function(event) {
+  var client = /** @type {XMLHttpRequest} */ (event.target);
+  // status will be 0 for file:// urls
+  if (!client.status || client.status >= 200 && client.status < 300) {
+    var response;
+    try {
+      response = /** @type {TileJSON} */(JSON.parse(client.responseText));
+    } catch (err) {
+      this.handleTileJSONError();
+      return;
+    }
+    this.handleTileJSONResponse(response);
+  } else {
+    this.handleTileJSONError();
+  }
+};
+
 
+/**
+ * @private
+ * @param {Event} event The error event.
+ */
+ol.source.TileJSON.prototype.onXHRError_ = function(event) {
+  this.handleTileJSONError();
+};
+
+
+/**
+ * @return {TileJSON} The tilejson object.
+ * @api
+ */
+ol.source.TileJSON.prototype.getTileJSON = function() {
+  return this.tileJSON_;
 };
-goog.inherits(ol.source.TileJSON, ol.source.TileImage);
 
 
 /**
@@ -113953,9 +76369,6 @@ ol.source.TileJSON.prototype.handleTileJSONResponse = function(tileJSON) {
     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({
@@ -113986,7 +76399,7 @@ ol.source.TileJSON.prototype.handleTileJSONResponse = function(tileJSON) {
       })
     ]);
   }
-
+  this.tileJSON_ = tileJSON;
   this.setState(ol.source.State.READY);
 
 };
@@ -114001,20 +76414,20 @@ ol.source.TileJSON.prototype.handleTileJSONError = function() {
 
 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');
 goog.require('ol.Attribution');
 goog.require('ol.Tile');
 goog.require('ol.TileState');
 goog.require('ol.TileUrlFunction');
+goog.require('ol.asserts');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
 goog.require('ol.extent');
+goog.require('ol.net');
 goog.require('ol.proj');
 goog.require('ol.source.State');
 goog.require('ol.source.Tile');
-
+goog.require('ol.tilegrid');
 
 
 /**
@@ -114027,7 +76440,7 @@ goog.require('ol.source.Tile');
  * @api
  */
 ol.source.TileUTFGrid = function(options) {
-  goog.base(this, {
+  ol.source.Tile.call(this, {
     projection: ol.proj.get('EPSG:3857'),
     state: ol.source.State.LOADING
   });
@@ -114051,10 +76464,61 @@ ol.source.TileUTFGrid = function(options) {
    */
   this.template_ = undefined;
 
-  var request = new goog.net.Jsonp(options.url);
-  request.send(undefined, goog.bind(this.handleTileJSONResponse, this));
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.jsonp_ = options.jsonp || false;
+
+  if (options.url) {
+    if (this.jsonp_) {
+      ol.net.jsonp(options.url, this.handleTileJSONResponse.bind(this),
+          this.handleTileJSONError.bind(this));
+    } else {
+      var client = new XMLHttpRequest();
+      client.addEventListener('load', this.onXHRLoad_.bind(this));
+      client.addEventListener('error', this.onXHRError_.bind(this));
+      client.open('GET', options.url);
+      client.send();
+    }
+  } else if (options.tileJSON) {
+    this.handleTileJSONResponse(options.tileJSON);
+  } else {
+    ol.asserts.assert(false, 51); // Either `url` or `tileJSON` options must be provided
+  }
+};
+ol.inherits(ol.source.TileUTFGrid, ol.source.Tile);
+
+
+/**
+ * @private
+ * @param {Event} event The load event.
+ */
+ol.source.TileUTFGrid.prototype.onXHRLoad_ = function(event) {
+  var client = /** @type {XMLHttpRequest} */ (event.target);
+  // status will be 0 for file:// urls
+  if (!client.status || client.status >= 200 && client.status < 300) {
+    var response;
+    try {
+      response = /** @type {TileJSON} */(JSON.parse(client.responseText));
+    } catch (err) {
+      this.handleTileJSONError();
+      return;
+    }
+    this.handleTileJSONResponse(response);
+  } else {
+    this.handleTileJSONError();
+  }
+};
+
+
+/**
+ * @private
+ * @param {Event} event The error event.
+ */
+ol.source.TileUTFGrid.prototype.onXHRError_ = function(event) {
+  this.handleTileJSONError();
 };
-goog.inherits(ol.source.TileUTFGrid, ol.source.Tile);
 
 
 /**
@@ -114073,7 +76537,7 @@ ol.source.TileUTFGrid.prototype.getTemplate = function() {
  * in case of an error).
  * @param {ol.Coordinate} coordinate Coordinate.
  * @param {number} resolution Resolution.
- * @param {function(this: T, Object)} callback Callback.
+ * @param {function(this: T, *)} 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.
@@ -114085,14 +76549,14 @@ ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution = function(
   if (this.tileGrid) {
     var tileCoord = this.tileGrid.getTileCoordForCoordAndResolution(
         coordinate, resolution);
-    var tile = /** @type {!ol.source.TileUTFGridTile_} */(this.getTile(
+    var tile = /** @type {!ol.source.TileUTFGrid.Tile_} */(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() {
+      setTimeout(function() {
         callback.call(opt_this, null);
-      });
+      }, 0);
     } else {
       callback.call(opt_this, null);
     }
@@ -114100,6 +76564,14 @@ ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution = function(
 };
 
 
+/**
+ * @protected
+ */
+ol.source.TileUTFGrid.prototype.handleTileJSONError = function() {
+  this.setState(ol.source.State.ERROR);
+};
+
+
 /**
  * TODO: very similar to ol.source.TileJSON#handleTileJSONResponse
  * @protected
@@ -114117,9 +76589,6 @@ ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) {
     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({
@@ -114167,23 +76636,22 @@ ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) {
 /**
  * @inheritDoc
  */
-ol.source.TileUTFGrid.prototype.getTile =
-    function(z, x, y, pixelRatio, projection) {
+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_(
+    var tile = new ol.source.TileUTFGrid.Tile_(
         tileCoord,
         tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY,
         tileUrl !== undefined ? tileUrl : '',
         this.tileGrid.getTileCoordExtent(tileCoord),
-        this.preemptive_);
+        this.preemptive_,
+        this.jsonp_);
     this.tileCache.set(tileCoordKey, tile);
     return tile;
   }
@@ -114201,7 +76669,6 @@ ol.source.TileUTFGrid.prototype.useTile = function(z, x, y) {
 };
 
 
-
 /**
  * @constructor
  * @extends {ol.Tile}
@@ -114210,12 +76677,12 @@ ol.source.TileUTFGrid.prototype.useTile = function(z, x, y) {
  * @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).
+ * @param {boolean} jsonp Load the tile as a script.
  * @private
  */
-ol.source.TileUTFGridTile_ =
-    function(tileCoord, state, src, extent, preemptive) {
+ol.source.TileUTFGrid.Tile_ = function(tileCoord, state, src, extent, preemptive, jsonp) {
 
-  goog.base(this, tileCoord, state);
+  ol.Tile.call(this, tileCoord, state);
 
   /**
    * @private
@@ -114252,14 +76719,23 @@ ol.source.TileUTFGridTile_ =
    * @type {Object.<string, Object>|undefined}
    */
   this.data_ = null;
+
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.jsonp_ = jsonp;
+
 };
-goog.inherits(ol.source.TileUTFGridTile_, ol.Tile);
+ol.inherits(ol.source.TileUTFGrid.Tile_, ol.Tile);
 
 
 /**
- * @inheritDoc
+ * Get the image element for this tile.
+ * @return {Image} Image.
  */
-ol.source.TileUTFGridTile_.prototype.getImage = function(opt_context) {
+ol.source.TileUTFGrid.Tile_.prototype.getImage = function() {
   return null;
 };
 
@@ -114267,10 +76743,10 @@ ol.source.TileUTFGridTile_.prototype.getImage = function(opt_context) {
 /**
  * Synchronously returns data at given coordinate (if available).
  * @param {ol.Coordinate} coordinate Coordinate.
- * @return {Object}
+ * @return {*} The data.
  */
-ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) {
-  if (!this.grid_ || !this.keys_ || !this.data_) {
+ol.source.TileUTFGrid.Tile_.prototype.getData = function(coordinate) {
+  if (!this.grid_ || !this.keys_) {
     return null;
   }
   var xRelative = (coordinate[0] - this.extent_[0]) /
@@ -114280,7 +76756,7 @@ ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) {
 
   var row = this.grid_[Math.floor((1 - yRelative) * this.grid_.length)];
 
-  if (!goog.isString(row)) {
+  if (typeof row !== 'string') {
     return null;
   }
 
@@ -114293,7 +76769,16 @@ ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) {
   }
   code -= 32;
 
-  return (code in this.keys_) ? this.data_[this.keys_[code]] : null;
+  var data = null;
+  if (code in this.keys_) {
+    var id = this.keys_[code];
+    if (this.data_ && id in this.data_) {
+      data = this.data_[id];
+    } else {
+      data = id;
+    }
+  }
+  return data;
 };
 
 
@@ -114301,24 +76786,23 @@ ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) {
  * 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 {function(this: T, *)} 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) {
+ol.source.TileUTFGrid.Tile_.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) {
+    ol.events.listenOnce(this, ol.events.EventType.CHANGE, function(e) {
       callback.call(opt_this, this.getData(coordinate));
-    }, false, this);
+    }, this);
     this.loadInternal_();
   } else {
     if (opt_request === true) {
-      goog.async.nextTick(function() {
+      setTimeout(function() {
         callback.call(opt_this, this.getData(coordinate));
-      }, this);
+      }.bind(this), 0);
     } else {
       callback.call(opt_this, this.getData(coordinate));
     }
@@ -114329,7 +76813,7 @@ ol.source.TileUTFGridTile_.prototype.forDataAtCoordinate =
 /**
  * @inheritDoc
  */
-ol.source.TileUTFGridTile_.prototype.getKey = function() {
+ol.source.TileUTFGrid.Tile_.prototype.getKey = function() {
   return this.src_;
 };
 
@@ -114337,17 +76821,17 @@ ol.source.TileUTFGridTile_.prototype.getKey = function() {
 /**
  * @private
  */
-ol.source.TileUTFGridTile_.prototype.handleError_ = function() {
+ol.source.TileUTFGrid.Tile_.prototype.handleError_ = function() {
   this.state = ol.TileState.ERROR;
   this.changed();
 };
 
 
 /**
- * @param {!UTFGridJSON} json
+ * @param {!UTFGridJSON} json UTFGrid data.
  * @private
  */
-ol.source.TileUTFGridTile_.prototype.handleLoad_ = function(json) {
+ol.source.TileUTFGrid.Tile_.prototype.handleLoad_ = function(json) {
   this.grid_ = json.grid;
   this.keys_ = json.keys;
   this.data_ = json.data;
@@ -114360,819 +76844,1079 @@ ol.source.TileUTFGridTile_.prototype.handleLoad_ = function(json) {
 /**
  * @private
  */
-ol.source.TileUTFGridTile_.prototype.loadInternal_ = function() {
+ol.source.TileUTFGrid.Tile_.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));
+    if (this.jsonp_) {
+      ol.net.jsonp(this.src_, this.handleLoad_.bind(this),
+          this.handleError_.bind(this));
+    } else {
+      var client = new XMLHttpRequest();
+      client.addEventListener('load', this.onXHRLoad_.bind(this));
+      client.addEventListener('error', this.onXHRError_.bind(this));
+      client.open('GET', this.src_);
+      client.send();
+    }
   }
 };
 
 
 /**
- * Load not yet loaded URI.
+ * @private
+ * @param {Event} event The load event.
+ */
+ol.source.TileUTFGrid.Tile_.prototype.onXHRLoad_ = function(event) {
+  var client = /** @type {XMLHttpRequest} */ (event.target);
+  // status will be 0 for file:// urls
+  if (!client.status || client.status >= 200 && client.status < 300) {
+    var response;
+    try {
+      response = /** @type {!UTFGridJSON} */(JSON.parse(client.responseText));
+    } catch (err) {
+      this.handleError_();
+      return;
+    }
+    this.handleLoad_(response);
+  } else {
+    this.handleError_();
+  }
+};
+
+
+/**
+ * @private
+ * @param {Event} event The error event.
+ */
+ol.source.TileUTFGrid.Tile_.prototype.onXHRError_ = function(event) {
+  this.handleError_();
+};
+
+
+/**
+ * @override
  */
-ol.source.TileUTFGridTile_.prototype.load = function() {
+ol.source.TileUTFGrid.Tile_.prototype.load = function() {
   if (this.preemptive_) {
     this.loadInternal_();
   }
 };
 
-goog.provide('ol.source.TileVector');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.object');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.featureloader');
-goog.require('ol.source.State');
-goog.require('ol.source.Vector');
-goog.require('ol.tilecoord');
-goog.require('ol.tilegrid.TileGrid');
+// 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('ol');
+goog.require('ol.asserts');
+goog.require('ol.extent');
+goog.require('ol.obj');
+goog.require('ol.math');
+goog.require('ol.proj');
+goog.require('ol.size');
+goog.require('ol.source.TileImage');
+goog.require('ol.source.WMSServerType');
+goog.require('ol.tilecoord');
+goog.require('ol.string');
+goog.require('ol.uri');
 
 /**
  * @classdesc
- * A vector source in one of the supported formats, where the data is divided
- * into tiles in a fixed grid pattern.
+ * Layer source for tile data from WMS servers.
  *
  * @constructor
- * @extends {ol.source.Vector}
- * @param {olx.source.TileVectorOptions} options Options.
+ * @extends {ol.source.TileImage}
+ * @param {olx.source.TileWMSOptions=} opt_options Tile WMS options.
  * @api
  */
-ol.source.TileVector = function(options) {
+ol.source.TileWMS = function(opt_options) {
+
+  var options = opt_options || {};
 
-  goog.base(this, {
+  var params = options.params || {};
+
+  var transparent = 'TRANSPARENT' in params ? params['TRANSPARENT'] : true;
+
+  ol.source.TileImage.call(this, {
     attributions: options.attributions,
+    cacheSize: options.cacheSize,
+    crossOrigin: options.crossOrigin,
     logo: options.logo,
-    projection: undefined,
-    state: ol.source.State.READY,
-    wrapX: options.wrapX
+    opaque: !transparent,
+    projection: options.projection,
+    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+    tileGrid: options.tileGrid,
+    tileLoadFunction: options.tileLoadFunction,
+    url: options.url,
+    urls: options.urls,
+    wrapX: options.wrapX !== undefined ? options.wrapX : true
   });
 
   /**
    * @private
-   * @type {ol.format.Feature|undefined}
+   * @type {number}
    */
-  this.format_ = options.format !== undefined ? options.format : null;
+  this.gutter_ = options.gutter !== undefined ? options.gutter : 0;
 
   /**
    * @private
-   * @type {ol.tilegrid.TileGrid}
+   * @type {!Object}
    */
-  this.tileGrid_ = options.tileGrid;
+  this.params_ = params;
 
   /**
    * @private
-   * @type {ol.TileUrlFunctionType}
+   * @type {boolean}
    */
-  this.tileUrlFunction_ = ol.TileUrlFunction.nullTileUrlFunction;
+  this.v13_ = true;
 
   /**
    * @private
-   * @type {?ol.TileVectorLoadFunctionType}
+   * @type {ol.source.WMSServerType|undefined}
    */
-  this.tileLoadFunction_ = options.tileLoadFunction !== undefined ?
-      options.tileLoadFunction : null;
+  this.serverType_ =
+      /** @type {ol.source.WMSServerType|undefined} */ (options.serverType);
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;
 
-  goog.asserts.assert(this.format_ || this.tileLoadFunction_,
-      'Either format or tileLoadFunction are required');
+  /**
+   * @private
+   * @type {string}
+   */
+  this.coordKeyPrefix_ = '';
+  this.resetCoordKeyPrefix_();
 
   /**
    * @private
-   * @type {Object.<string, Array.<ol.Feature>>}
+   * @type {ol.Extent}
    */
-  this.tiles_ = {};
+  this.tmpExtent_ = ol.extent.createEmpty();
 
-  if (options.tileUrlFunction !== undefined) {
-    this.setTileUrlFunction(options.tileUrlFunction);
-  } else if (options.urls !== undefined) {
-    this.setUrls(options.urls);
-  } else if (options.url !== undefined) {
-    this.setUrl(options.url);
-  }
+  this.updateV13_();
+  this.setKey(this.getKeyForParams_());
 
 };
-goog.inherits(ol.source.TileVector, ol.source.Vector);
+ol.inherits(ol.source.TileWMS, ol.source.TileImage);
 
 
 /**
- * @inheritDoc
+ * 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.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
  */
-ol.source.TileVector.prototype.addFeature = goog.abstractMethod;
+ol.source.TileWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, 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']
+  };
+  ol.obj.assign(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.TileVector.prototype.addFeatures = goog.abstractMethod;
+ol.source.TileWMS.prototype.getGutterInternal = function() {
+  return this.gutter_;
+};
 
 
 /**
  * @inheritDoc
  */
-ol.source.TileVector.prototype.clear = function() {
-  goog.object.clear(this.tiles_);
+ol.source.TileWMS.prototype.getKeyZXY = function(z, x, y) {
+  return this.coordKeyPrefix_ + ol.source.TileImage.prototype.getKeyZXY.call(this, z, x, y);
 };
 
 
 /**
- * @inheritDoc
+ * 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.TileVector.prototype.forEachFeature = goog.abstractMethod;
+ol.source.TileWMS.prototype.getParams = function() {
+  return this.params_;
+};
 
 
 /**
- * Iterate through all features whose geometries contain the provided
- * coordinate at the provided resolution, 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 {number} resolution Resolution.
- * @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
+ * @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.TileVector.prototype.forEachFeatureAtCoordinateAndResolution =
-    function(coordinate, resolution, callback, opt_this) {
+ol.source.TileWMS.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent,
+        pixelRatio, projection, params) {
 
-  var tileGrid = this.tileGrid_;
-  var tiles = this.tiles_;
-  var tileCoord = tileGrid.getTileCoordForCoordAndResolution(coordinate,
-      resolution);
+  var urls = this.urls;
+  if (!urls) {
+    return undefined;
+  }
 
-  var tileKey = this.getTileKeyZXY_(tileCoord[0], tileCoord[1], tileCoord[2]);
-  var features = tiles[tileKey];
-  if (features !== undefined) {
-    var i, ii;
-    for (i = 0, ii = features.length; i < ii; ++i) {
-      var feature = features[i];
-      var geometry = feature.getGeometry();
-      goog.asserts.assert(geometry, 'feature geometry is defined and not null');
-      if (geometry.containsCoordinate(coordinate)) {
-        var result = callback.call(opt_this, feature);
-        if (result) {
-          return result;
+  params['WIDTH'] = tileSize[0];
+  params['HEIGHT'] = tileSize[1];
+
+  params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();
+
+  if (!('STYLES' in this.params_)) {
+    params['STYLES'] = '';
+  }
+
+  if (pixelRatio != 1) {
+    switch (this.serverType_) {
+      case ol.source.WMSServerType.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.WMSServerType.MAPSERVER:
+        params['MAP_RESOLUTION'] = 90 * pixelRatio;
+        break;
+      case ol.source.WMSServerType.CARMENTA_SERVER:
+      case ol.source.WMSServerType.QGIS:
+        params['DPI'] = 90 * pixelRatio;
+        break;
+      default:
+        ol.asserts.assert(false, 52); // Unknown `serverType` configured
+        break;
     }
   }
-  return undefined;
+
+  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 = ol.math.modulo(ol.tilecoord.hash(tileCoord), urls.length);
+    url = urls[index];
+  }
+  return ol.uri.appendParams(url, params);
 };
 
 
 /**
  * @inheritDoc
  */
-ol.source.TileVector.prototype.forEachFeatureInExtent = goog.abstractMethod;
+ol.source.TileWMS.prototype.getTilePixelRatio = function(pixelRatio) {
+  return (!this.hidpi_ || this.serverType_ === undefined) ? 1 :
+      /** @type {number} */ (pixelRatio);
+};
 
 
 /**
- * @inheritDoc
+ * @private
  */
-ol.source.TileVector.prototype.forEachFeatureInExtentAtResolution =
-    function(extent, resolution, f, opt_this) {
-  var tileGrid = this.tileGrid_;
-  var tiles = this.tiles_;
-  var z = tileGrid.getZForResolution(resolution);
-  var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
-  var x, y;
-  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
-      var tileKey = this.getTileKeyZXY_(z, x, y);
-      var features = tiles[tileKey];
-      if (features !== undefined) {
-        var i, ii;
-        for (i = 0, ii = features.length; i < ii; ++i) {
-          var result = f.call(opt_this, features[i]);
-          if (result) {
-            return result;
-          }
-        }
-      }
+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];
     }
   }
-  return undefined;
+
+  this.coordKeyPrefix_ = res.join('#');
 };
 
 
 /**
- * @inheritDoc
+ * @private
+ * @return {string} The key for the current params.
  */
-ol.source.TileVector.prototype.getClosestFeatureToCoordinate =
-    goog.abstractMethod;
+ol.source.TileWMS.prototype.getKeyForParams_ = function() {
+  var i = 0;
+  var res = [];
+  for (var key in this.params_) {
+    res[i++] = key + '-' + this.params_[key];
+  }
+  return res.join('/');
+};
 
 
 /**
  * @inheritDoc
  */
-ol.source.TileVector.prototype.getExtent = goog.abstractMethod;
+ol.source.TileWMS.prototype.fixedTileUrlFunction = 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
+  };
+  ol.obj.assign(baseParams, this.params_);
+
+  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
+      pixelRatio, projection, baseParams);
+};
 
 /**
- * Return the features of the TileVector source.
  * @inheritDoc
- * @api
  */
-ol.source.TileVector.prototype.getFeatures = function() {
-  var tiles = this.tiles_;
-  var features = [];
-  var tileKey;
-  for (tileKey in tiles) {
-    goog.array.extend(features, tiles[tileKey]);
-  }
-  return features;
+ol.source.TileWMS.prototype.setUrls = function(urls) {
+  ol.source.TileImage.prototype.setUrls.call(this, urls);
+  this.resetCoordKeyPrefix_();
 };
 
 
 /**
- * Get all features whose geometry intersects the provided coordinate for the
- * provided resolution.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} resolution Resolution.
- * @return {Array.<ol.Feature>} Features.
+ * Update the user-provided params.
+ * @param {Object} params Params.
  * @api
  */
-ol.source.TileVector.prototype.getFeaturesAtCoordinateAndResolution =
-    function(coordinate, resolution) {
-  var features = [];
-  this.forEachFeatureAtCoordinateAndResolution(coordinate, resolution,
-      /**
-       * @param {ol.Feature} feature Feature.
-       */
-      function(feature) {
-        features.push(feature);
-      });
-  return features;
+ol.source.TileWMS.prototype.updateParams = function(params) {
+  ol.obj.assign(this.params_, params);
+  this.resetCoordKeyPrefix_();
+  this.updateV13_();
+  this.setKey(this.getKeyForParams_());
 };
 
 
 /**
- * @inheritDoc
+ * @private
  */
-ol.source.TileVector.prototype.getFeaturesInExtent = goog.abstractMethod;
+ol.source.TileWMS.prototype.updateV13_ = function() {
+  var version = this.params_['VERSION'] || ol.DEFAULT_WMS_VERSION;
+  this.v13_ = ol.string.compareVersions(version, '1.3') >= 0;
+};
+
+goog.provide('ol.VectorImageTile');
+
+goog.require('ol');
+goog.require('ol.Tile');
+goog.require('ol.TileState');
+goog.require('ol.array');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.extent');
+goog.require('ol.events.EventType');
+goog.require('ol.featureloader');
 
 
 /**
- * Handles x-axis wrapping and returns a tile coordinate transformed from the
- * internal tile scheme to the tile grid's tile scheme. When the tile coordinate
- * is outside the resolution and extent range of the tile grid, `null` will be
- * returned.
+ * @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.TileCoord} urlTileCoord Wrapped tile coordinate for source urls.
+ * @param {ol.TileUrlFunctionType} tileUrlFunction Tile url function.
+ * @param {ol.tilegrid.TileGrid} sourceTileGrid Tile grid of the source.
+ * @param {ol.tilegrid.TileGrid} tileGrid Tile grid of the renderer.
+ * @param {Object.<string,ol.VectorTile>} sourceTiles Source tiles.
+ * @param {number} pixelRatio Pixel ratio.
  * @param {ol.proj.Projection} projection Projection.
- * @return {ol.TileCoord} Tile coordinate to be passed to the tileUrlFunction or
- *     null if no tile URL should be created for the passed `tileCoord`.
+ * @param {function(new: ol.VectorTile, ol.TileCoord, ol.TileState, string,
+ *     ol.format.Feature, ol.TileLoadFunctionType)} tileClass Class to
+ *     instantiate for source tiles.
+ * @param {function(this: ol.source.VectorTile, ol.events.Event)} handleTileChange
+ *     Function to call when a source tile's state changes.
  */
-ol.source.TileVector.prototype.getTileCoordForTileUrlFunction =
-    function(tileCoord, projection) {
-  var tileGrid = this.tileGrid_;
-  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;
-};
+ol.VectorImageTile = function(tileCoord, state, src, format, tileLoadFunction,
+    urlTileCoord, tileUrlFunction, sourceTileGrid, tileGrid, sourceTiles,
+    pixelRatio, projection, tileClass, handleTileChange) {
 
+  ol.Tile.call(this, tileCoord, state);
 
-/**
- * @param {number} z Z.
- * @param {number} x X.
- * @param {number} y Y.
- * @private
- * @return {string} Tile key.
- */
-ol.source.TileVector.prototype.getTileKeyZXY_ = function(z, x, y) {
-  return z + '/' + x + '/' + y;
-};
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context_ = null;
 
+  /**
+   * @private
+   * @type {ol.FeatureLoader}
+   */
+  this.loader_;
 
-/**
- * @inheritDoc
- */
-ol.source.TileVector.prototype.loadFeatures =
-    function(extent, resolution, projection) {
-  var tileGrid = this.tileGrid_;
-  var tileUrlFunction = this.tileUrlFunction_;
-  var tiles = this.tiles_;
-  var z = tileGrid.getZForResolution(resolution);
-  var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
-  var tileCoord = [z, 0, 0];
-  var x, y;
   /**
-   * @param {string} tileKey Tile key.
-   * @param {Array.<ol.Feature>} features Features.
-   * @this {ol.source.TileVector}
+   * @private
+   * @type {ol.TileReplayState}
    */
-  function success(tileKey, features) {
-    tiles[tileKey] = features;
-    this.changed();
-  }
-  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
-      var tileKey = this.getTileKeyZXY_(z, x, y);
-      if (!(tileKey in tiles)) {
-        tileCoord[1] = x;
-        tileCoord[2] = y;
-        var urlTileCoord = this.getTileCoordForTileUrlFunction(
-            tileCoord, projection);
-        var url = !urlTileCoord ? undefined :
-            tileUrlFunction(urlTileCoord, 1, projection);
-        if (url !== undefined) {
-          tiles[tileKey] = [];
-          var tileSuccess = goog.partial(success, tileKey);
-          if (this.tileLoadFunction_) {
-            this.tileLoadFunction_(url, goog.bind(tileSuccess, this));
-          } else {
-            var loader = ol.featureloader.loadFeaturesXhr(url,
-                /** @type {ol.format.Feature} */ (this.format_), tileSuccess);
-            loader.call(this, extent, resolution, projection);
-          }
+  this.replayState_ = {
+    dirty: false,
+    renderedRenderOrder: null,
+    renderedRevision: -1,
+    renderedTileRevision: -1
+  };
+
+  /**
+   * @private
+   * @type {Object.<string,ol.VectorTile>}
+   */
+  this.sourceTiles_ = sourceTiles;
+
+  /**
+   * Keys of source tiles used by this tile. Use with {@link #getTile}.
+   * @type {Array.<string>}
+   */
+  this.tileKeys = [];
+
+  /**
+   * @type {string}
+   */
+  this.src_ = src;
+
+  /**
+   * @type {ol.TileCoord}
+   */
+  this.wrappedTileCoord = urlTileCoord;
+
+  /**
+   * @type {Array.<ol.EventsKey>}
+   */
+  this.loadListenerKeys_ = [];
+
+  /**
+   * @type {Array.<ol.EventsKey>}
+   */
+  this.sourceTileListenerKeys_ = [];
+
+  if (urlTileCoord) {
+    var extent = tileGrid.getTileCoordExtent(urlTileCoord);
+    var resolution = tileGrid.getResolution(tileCoord[0]);
+    var sourceZ = sourceTileGrid.getZForResolution(resolution);
+    sourceTileGrid.forEachTileCoord(extent, sourceZ, function(sourceTileCoord) {
+      var sharedExtent = ol.extent.getIntersection(extent,
+          sourceTileGrid.getTileCoordExtent(sourceTileCoord));
+      if (ol.extent.getWidth(sharedExtent) / resolution >= 0.5 &&
+          ol.extent.getHeight(sharedExtent) / resolution >= 0.5) {
+        // only include source tile if overlap is at least 1 pixel
+        var sourceTileKey = sourceTileCoord.toString();
+        var sourceTile = sourceTiles[sourceTileKey];
+        if (!sourceTile) {
+          var tileUrl = tileUrlFunction(sourceTileCoord, pixelRatio, projection);
+          sourceTile = sourceTiles[sourceTileKey] = new tileClass(sourceTileCoord,
+              tileUrl == undefined ? ol.TileState.EMPTY : ol.TileState.IDLE,
+              tileUrl == undefined ? '' : tileUrl,
+              format, tileLoadFunction);
+          this.sourceTileListenerKeys_.push(
+              ol.events.listen(sourceTile, ol.events.EventType.CHANGE, handleTileChange));
         }
+        sourceTile.consumers++;
+        this.tileKeys.push(sourceTileKey);
       }
-    }
+    }.bind(this));
   }
+
 };
+ol.inherits(ol.VectorImageTile, ol.Tile);
 
 
 /**
  * @inheritDoc
  */
-ol.source.TileVector.prototype.removeFeature = goog.abstractMethod;
+ol.VectorImageTile.prototype.disposeInternal = function() {
+  for (var i = 0, ii = this.tileKeys.length; i < ii; ++i) {
+    var sourceTileKey = this.tileKeys[i];
+    var sourceTile = this.getTile(sourceTileKey);
+    sourceTile.consumers--;
+    if (sourceTile.consumers == 0) {
+      delete this.sourceTiles_[sourceTileKey];
+      sourceTile.dispose();
+    }
+  }
+  this.tileKeys.length = 0;
+  this.sourceTiles_ = null;
+  if (this.state == ol.TileState.LOADING) {
+    this.loadListenerKeys_.forEach(ol.events.unlistenByKey);
+    this.loadListenerKeys_.length = 0;
+  }
+  if (this.interimTile) {
+    this.interimTile.dispose();
+  }
+  this.state = ol.TileState.ABORT;
+  this.changed();
+  this.sourceTileListenerKeys_.forEach(ol.events.unlistenByKey);
+  this.sourceTileListenerKeys_.length = 0;
+  ol.Tile.prototype.disposeInternal.call(this);
+};
 
 
 /**
- * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
+ * @return {CanvasRenderingContext2D} The rendering context.
  */
-ol.source.TileVector.prototype.setTileUrlFunction = function(tileUrlFunction) {
-  this.tileUrlFunction_ = tileUrlFunction;
-  this.changed();
+ol.VectorImageTile.prototype.getContext = function() {
+  if (!this.context_) {
+    this.context_ = ol.dom.createCanvasContext2D();
+  }
+  return this.context_;
 };
 
 
 /**
- * @param {string} url URL.
+ * Get the Canvas for this tile.
+ * @return {HTMLCanvasElement} Canvas.
  */
-ol.source.TileVector.prototype.setUrl = function(url) {
-  this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates(
-      ol.TileUrlFunction.expandUrl(url), this.tileGrid_));
+ol.VectorImageTile.prototype.getImage = function() {
+  return this.replayState_.renderedTileRevision == -1 ?
+      null : this.context_.canvas;
 };
 
 
 /**
- * @param {Array.<string>} urls URLs.
+ * @return {ol.TileReplayState} The replay state.
  */
-ol.source.TileVector.prototype.setUrls = function(urls) {
-  this.setTileUrlFunction(
-      ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid_));
+ol.VectorImageTile.prototype.getReplayState = function() {
+  return this.replayState_;
 };
 
-// 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');
+/**
+ * @inheritDoc
+ */
+ol.VectorImageTile.prototype.getKey = function() {
+  return this.tileKeys.join('/') + '/' + this.src_;
+};
 
-goog.require('goog.asserts');
-goog.require('goog.math');
-goog.require('goog.object');
-goog.require('goog.string');
-goog.require('goog.uri.utils');
-goog.require('ol');
-goog.require('ol.TileCoord');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.extent');
-goog.require('ol.proj');
-goog.require('ol.size');
-goog.require('ol.source.TileImage');
-goog.require('ol.source.wms');
-goog.require('ol.source.wms.ServerType');
-goog.require('ol.tilecoord');
 
+/**
+ * @param {string} tileKey Key (tileCoord) of the source tile.
+ * @return {ol.VectorTile} Source tile.
+ */
+ol.VectorImageTile.prototype.getTile = function(tileKey) {
+  return this.sourceTiles_[tileKey];
+};
 
 
 /**
- * @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
+ * @inheritDoc
  */
-ol.source.TileWMS = function(opt_options) {
+ol.VectorImageTile.prototype.load = function() {
+  var leftToLoad = 0;
+  var errors = false;
+  if (this.state == ol.TileState.IDLE) {
+    this.setState(ol.TileState.LOADING);
+  }
+  if (this.state == ol.TileState.LOADING) {
+    this.tileKeys.forEach(function(sourceTileKey) {
+      var sourceTile = this.getTile(sourceTileKey);
+      if (sourceTile.state == ol.TileState.IDLE) {
+        sourceTile.setLoader(this.loader_);
+        sourceTile.load();
+      } else if (sourceTile.state == ol.TileState.ERROR) {
+        errors = true;
+      } else if (sourceTile.state == ol.TileState.EMPTY) {
+        ol.array.remove(this.tileKeys, sourceTileKey);
+      }
+      if (sourceTile.state == ol.TileState.LOADING) {
+        var key = ol.events.listen(sourceTile, ol.events.EventType.CHANGE, function(e) {
+          var state = sourceTile.getState();
+          if (state == ol.TileState.LOADED ||
+              state == ol.TileState.ERROR) {
+            --leftToLoad;
+            ol.events.unlistenByKey(key);
+            ol.array.remove(this.loadListenerKeys_, key);
+            if (state == ol.TileState.ERROR) {
+              ol.array.remove(this.tileKeys, sourceTileKey);
+              errors = true;
+            }
+            if (leftToLoad == 0) {
+              this.setState(this.tileKeys.length > 0 ?
+                  ol.TileState.LOADED : ol.TileState.ERROR);
+            }
+          }
+        }.bind(this));
+        this.loadListenerKeys_.push(key);
+        ++leftToLoad;
+      }
+    }.bind(this));
+  }
+  if (leftToLoad == 0) {
+    setTimeout(function() {
+      this.setState(this.tileKeys.length > 0 ?
+          ol.TileState.LOADED :
+          (errors ? ol.TileState.ERROR : ol.TileState.EMPTY));
+    }.bind(this), 0);
+  }
+};
 
-  var options = opt_options || {};
 
-  var params = options.params !== undefined ? options.params : {};
+/**
+ * Sets the loader for a tile.
+ * @param {ol.VectorTile} tile Vector tile.
+ * @param {string} url URL.
+ */
+ol.VectorImageTile.defaultLoadFunction = function(tile, url) {
+  var loader = ol.featureloader.loadFeaturesXhr(
+      url, tile.getFormat(), tile.onLoad_.bind(tile), tile.onError_.bind(tile));
 
-  var transparent = goog.object.get(params, 'TRANSPARENT', true);
+  tile.setLoader(loader);
+};
 
-  goog.base(this, {
-    attributions: options.attributions,
-    crossOrigin: options.crossOrigin,
-    logo: options.logo,
-    opaque: !transparent,
-    projection: options.projection,
-    tileGrid: options.tileGrid,
-    tileLoadFunction: options.tileLoadFunction,
-    tileUrlFunction: goog.bind(this.tileUrlFunction_, this),
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
-  });
+goog.provide('ol.VectorTile');
 
-  var urls = options.urls;
-  if (urls === undefined && options.url !== undefined) {
-    urls = ol.TileUrlFunction.expandUrl(options.url);
-  }
+goog.require('ol');
+goog.require('ol.Tile');
+goog.require('ol.TileState');
 
-  /**
-   * @private
-   * @type {!Array.<string>}
-   */
-  this.urls_ = urls || [];
+
+/**
+ * @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.
+ */
+ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) {
+
+  ol.Tile.call(this, tileCoord, state);
 
   /**
-   * @private
    * @type {number}
    */
-  this.gutter_ = options.gutter !== undefined ? options.gutter : 0;
+  this.consumers = 0;
 
   /**
    * @private
-   * @type {Object}
+   * @type {ol.format.Feature}
    */
-  this.params_ = params;
+  this.format_ = format;
 
   /**
    * @private
-   * @type {boolean}
+   * @type {Array.<ol.Feature>}
    */
-  this.v13_ = true;
+  this.features_ = null;
 
   /**
    * @private
-   * @type {ol.source.wms.ServerType|undefined}
+   * @type {ol.FeatureLoader}
    */
-  this.serverType_ =
-      /** @type {ol.source.wms.ServerType|undefined} */ (options.serverType);
+  this.loader_;
 
   /**
+   * Data projection
    * @private
-   * @type {boolean}
+   * @type {ol.proj.Projection}
    */
-  this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;
+  this.projection_;
+
+  this.replayGroups_ = {};
 
   /**
    * @private
-   * @type {string}
+   * @type {ol.TileLoadFunctionType}
    */
-  this.coordKeyPrefix_ = '';
-  this.resetCoordKeyPrefix_();
+  this.tileLoadFunction_ = tileLoadFunction;
 
   /**
    * @private
-   * @type {ol.Extent}
+   * @type {string}
    */
-  this.tmpExtent_ = ol.extent.createEmpty();
-
-  this.updateV13_();
+  this.url_ = src;
 
 };
-goog.inherits(ol.source.TileWMS, ol.source.TileImage);
+ol.inherits(ol.VectorTile, ol.Tile);
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.source.TileWMS.prototype.getGetFeatureInfoUrl =
-    function(coordinate, resolution, projection, params) {
-
-  goog.asserts.assert(!('VERSION' in params),
-      'key VERSION is not allowed in params');
+ol.VectorTile.prototype.disposeInternal = function() {
+  this.features_ = null;
+  this.replayGroups_ = {};
+  this.state = ol.TileState.ABORT;
+  this.changed();
+  ol.Tile.prototype.disposeInternal.call(this);
+};
 
-  var projectionObj = ol.proj.get(projection);
 
-  var tileGrid = this.getTileGrid();
-  if (!tileGrid) {
-    tileGrid = this.getTileGridForProjection(projectionObj);
-  }
+/**
+ * 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_;
+};
 
-  var tileCoord = tileGrid.getTileCoordForCoordAndResolution(
-      coordinate, resolution);
 
-  if (tileGrid.getResolutions().length <= tileCoord[0]) {
-    return undefined;
-  }
+/**
+ * Get the features for this tile. Geometries will be in the projection returned
+ * by {@link #getProjection}.
+ * @return {Array.<ol.Feature|ol.render.Feature>} Features.
+ * @api
+ */
+ol.VectorTile.prototype.getFeatures = function() {
+  return this.features_;
+};
 
-  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);
-  }
+/**
+ * @inheritDoc
+ */
+ol.VectorTile.prototype.getKey = function() {
+  return this.url_;
+};
 
-  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);
+/**
+ * Get the feature projection of features returned by {@link #getFeatures}.
+ * @return {ol.proj.Projection} Feature projection.
+ * @api
+ */
+ol.VectorTile.prototype.getProjection = function() {
+  return this.projection_;
+};
 
-  baseParams[this.v13_ ? 'I' : 'X'] = x;
-  baseParams[this.v13_ ? 'J' : 'Y'] = y;
 
-  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
-      1, projectionObj, baseParams);
+ol.VectorTile.prototype.getReplayGroup = function(key) {
+  return this.replayGroups_[key];
 };
 
 
 /**
  * @inheritDoc
  */
-ol.source.TileWMS.prototype.getGutter = function() {
-  return this.gutter_;
+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, null);
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Handler for successful tile load.
+ * @param {Array.<ol.Feature>} features The loaded features.
+ * @param {ol.proj.Projection} dataProjection Data projection.
  */
-ol.source.TileWMS.prototype.getKeyZXY = function(z, x, y) {
-  return this.coordKeyPrefix_ + goog.base(this, 'getKeyZXY', z, x, y);
+ol.VectorTile.prototype.onLoad_ = function(features, dataProjection) {
+  this.setProjection(dataProjection);
+  this.setFeatures(features);
 };
 
 
 /**
- * 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
+ * Handler for tile load errors.
  */
-ol.source.TileWMS.prototype.getParams = function() {
-  return this.params_;
+ol.VectorTile.prototype.onError_ = function() {
+  this.setState(ol.TileState.ERROR);
 };
 
 
 /**
- * @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
+ * @param {Array.<ol.Feature>} features Features.
+ * @api
  */
-ol.source.TileWMS.prototype.getRequestUrl_ =
-    function(tileCoord, tileSize, tileExtent,
-        pixelRatio, projection, params) {
-
-  var urls = this.urls_;
-  if (urls.length === 0) {
-    return undefined;
-  }
-
-  params['WIDTH'] = tileSize[0];
-  params['HEIGHT'] = tileSize[1];
-
-  params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();
+ol.VectorTile.prototype.setFeatures = function(features) {
+  this.features_ = features;
+  this.setState(ol.TileState.LOADED);
+};
 
-  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;
-    }
-  }
+/**
+ * Set the projection of the features that were added with {@link #setFeatures}.
+ * @param {ol.proj.Projection} projection Feature projection.
+ * @api
+ */
+ol.VectorTile.prototype.setProjection = function(projection) {
+  this.projection_ = projection;
+};
 
-  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);
+ol.VectorTile.prototype.setReplayGroup = function(key, replayGroup) {
+  this.replayGroups_[key] = replayGroup;
 };
 
 
 /**
- * @param {number} z Z.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {ol.Size} Size.
+ * Set the feature loader for reading this tile's features.
+ * @param {ol.FeatureLoader} loader Feature loader.
+ * @api
  */
-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);
-  }
+ol.VectorTile.prototype.setLoader = function(loader) {
+  this.loader_ = loader;
 };
 
+goog.provide('ol.source.VectorTile');
 
-/**
- * Return the URLs used for this WMS source.
- * @return {!Array.<string>} URLs.
- * @api stable
- */
-ol.source.TileWMS.prototype.getUrls = function() {
-  return this.urls_;
-};
+goog.require('ol');
+goog.require('ol.TileState');
+goog.require('ol.VectorImageTile');
+goog.require('ol.VectorTile');
+goog.require('ol.proj');
+goog.require('ol.size');
+goog.require('ol.tilegrid');
+goog.require('ol.source.UrlTile');
 
 
 /**
- * @private
+ * @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.Tile.Event
+ * @extends {ol.source.UrlTile}
+ * @param {olx.source.VectorTileOptions} options Vector tile options.
+ * @api
  */
-ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() {
-  var i = 0;
-  var res = [];
+ol.source.VectorTile = function(options) {
 
-  var j, jj;
-  for (j = 0, jj = this.urls_.length; j < jj; ++j) {
-    res[i++] = this.urls_[j];
-  }
+  ol.source.UrlTile.call(this, {
+    attributions: options.attributions,
+    cacheSize: options.cacheSize !== undefined ? options.cacheSize : 128,
+    extent: options.extent,
+    logo: options.logo,
+    opaque: false,
+    projection: options.projection,
+    state: options.state,
+    tileGrid: options.tileGrid,
+    tileLoadFunction: options.tileLoadFunction ?
+        options.tileLoadFunction : ol.VectorImageTile.defaultLoadFunction,
+    tileUrlFunction: options.tileUrlFunction,
+    tilePixelRatio: options.tilePixelRatio,
+    url: options.url,
+    urls: options.urls,
+    wrapX: options.wrapX === undefined ? true : options.wrapX
+  });
 
-  var key;
-  for (key in this.params_) {
-    res[i++] = key + '-' + this.params_[key];
+  /**
+   * @private
+   * @type {ol.format.Feature}
+   */
+  this.format_ = options.format ? options.format : null;
+
+  /**
+   * @private
+   * @type {Object.<string,ol.VectorTile>}
+   */
+  this.sourceTiles_ = {};
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.overlaps_ = options.overlaps == undefined ? true : options.overlaps;
+
+  /**
+   * @protected
+   * @type {function(new: ol.VectorTile, ol.TileCoord, ol.TileState, string,
+   *        ol.format.Feature, ol.TileLoadFunctionType)}
+   */
+  this.tileClass = options.tileClass ? options.tileClass : ol.VectorTile;
+
+  /**
+   * @private
+   * @type {Object.<string,ol.tilegrid.TileGrid>}
+   */
+  this.tileGrids_ = {};
+
+  if (!this.tileGrid) {
+    this.tileGrid = this.getTileGridForProjection(ol.proj.get(options.projection || 'EPSG:3857'));
   }
 
-  this.coordKeyPrefix_ = res.join('#');
 };
+ol.inherits(ol.source.VectorTile, ol.source.UrlTile);
 
 
 /**
- * Set the URL to use for requests.
- * @param {string|undefined} url URL.
- * @api stable
+ * @return {boolean} The source can have overlapping geometries.
  */
-ol.source.TileWMS.prototype.setUrl = function(url) {
-  var urls = url !== undefined ? ol.TileUrlFunction.expandUrl(url) : null;
-  this.setUrls(urls);
+ol.source.VectorTile.prototype.getOverlaps = function() {
+  return this.overlaps_;
 };
 
 
 /**
- * Set the URLs to use for requests.
- * @param {Array.<string>|undefined} urls URLs.
- * @api stable
+ * @inheritDoc
  */
-ol.source.TileWMS.prototype.setUrls = function(urls) {
-  this.urls_ = urls || [];
-  this.resetCoordKeyPrefix_();
-  this.changed();
+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 {
+    var tileCoord = [z, x, y];
+    var urlTileCoord = this.getTileCoordForTileUrlFunction(
+        tileCoord, projection);
+    var tileUrl = urlTileCoord ?
+        this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined;
+    var tile = new ol.VectorImageTile(
+        tileCoord,
+        tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY,
+        tileUrl !== undefined ? tileUrl : '',
+        this.format_, this.tileLoadFunction, urlTileCoord, this.tileUrlFunction,
+        this.tileGrid, this.getTileGridForProjection(projection),
+        this.sourceTiles_, pixelRatio, projection, this.tileClass,
+        this.handleTileChange.bind(this));
+
+    this.tileCache.set(tileCoordKey, tile);
+    return tile;
+  }
 };
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {string|undefined} Tile URL.
- * @private
+ * @inheritDoc
  */
-ol.source.TileWMS.prototype.tileUrlFunction_ =
-    function(tileCoord, pixelRatio, projection) {
-
-  var tileGrid = this.getTileGrid();
+ol.source.VectorTile.prototype.getTileGridForProjection = function(projection) {
+  var code = projection.getCode();
+  var tileGrid = this.tileGrids_[code];
   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);
+    // A tile grid that matches the tile size of the source tile grid is more
+    // likely to have 1:1 relationships between source tiles and rendered tiles.
+    var sourceTileGrid = this.tileGrid;
+    tileGrid = this.tileGrids_[code] = ol.tilegrid.createForProjection(projection, undefined,
+        sourceTileGrid ? sourceTileGrid.getTileSize(sourceTileGrid.getMinZoom()) : undefined);
   }
+  return tileGrid;
+};
 
-  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);
+/**
+ * @inheritDoc
+ */
+ol.source.VectorTile.prototype.getTilePixelRatio = function(opt_pixelRatio) {
+  return opt_pixelRatio == undefined ?
+      ol.source.UrlTile.prototype.getTilePixelRatio.call(this, opt_pixelRatio) :
+      opt_pixelRatio;
 };
 
 
 /**
- * Update the user-provided params.
- * @param {Object} params Params.
- * @api stable
+ * @inheritDoc
  */
-ol.source.TileWMS.prototype.updateParams = function(params) {
-  goog.object.extend(this.params_, params);
-  this.resetCoordKeyPrefix_();
-  this.updateV13_();
-  this.changed();
+ol.source.VectorTile.prototype.getTilePixelSize = function(z, pixelRatio, projection) {
+  var tileSize = ol.size.toSize(this.getTileGridForProjection(projection).getTileSize(z));
+  return [Math.round(tileSize[0] * pixelRatio), Math.round(tileSize[1] * pixelRatio)];
 };
 
+goog.provide('ol.source.WMTSRequestEncoding');
 
 /**
- * @private
+ * Request encoding. One of 'KVP', 'REST'.
+ * @enum {string}
  */
-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;
+ol.source.WMTSRequestEncoding = {
+  KVP: 'KVP',  // see spec §8
+  REST: 'REST' // see spec §10
 };
 
 goog.provide('ol.tilegrid.WMTS');
 
-goog.require('goog.array');
-goog.require('goog.asserts');
+goog.require('ol');
+goog.require('ol.array');
 goog.require('ol.proj');
 goog.require('ol.tilegrid.TileGrid');
 
 
-
 /**
  * @classdesc
  * Set the grid pattern for sources accessing WMTS tiled-image servers.
@@ -115184,20 +77928,14 @@ goog.require('ol.tilegrid.TileGrid');
  * @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?
+  // FIXME: should the matrixIds become optional?
 
-  goog.base(this, {
+  ol.tilegrid.TileGrid.call(this, {
     extent: options.extent,
     origin: options.origin,
     origins: options.origins,
@@ -115206,9 +77944,8 @@ ol.tilegrid.WMTS = function(options) {
     tileSizes: options.tileSizes,
     sizes: options.sizes
   });
-
 };
-goog.inherits(ol.tilegrid.WMTS, ol.tilegrid.TileGrid);
+ol.inherits(ol.tilegrid.WMTS, ol.tilegrid.TileGrid);
 
 
 /**
@@ -115216,8 +77953,6 @@ goog.inherits(ol.tilegrid.WMTS, ol.tilegrid.TileGrid);
  * @return {string} MatrixId..
  */
 ol.tilegrid.WMTS.prototype.getMatrixId = function(z) {
-  goog.asserts.assert(0 <= z && z < this.matrixIds_.length,
-      'attempted to retrive matrixId for illegal z (%s)', z);
   return this.matrixIds_[z];
 };
 
@@ -115233,16 +77968,19 @@ ol.tilegrid.WMTS.prototype.getMatrixIds = function() {
 
 
 /**
- * Create a tile grid from a WMTS capabilities matrix set.
+ * Create a tile grid from a WMTS capabilities matrix set and an
+ * optional TileMatrixSetLimits.
  * @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.
+ * @param {Array.<Object>=} opt_matrixLimits An optional object representing
+ *     the available matrices for tileGrid.
  * @return {ol.tilegrid.WMTS} WMTS tileGrid instance.
  * @api
  */
-ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet =
-    function(matrixSet, opt_extent) {
+ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = function(matrixSet, opt_extent,
+ opt_matrixLimits) {
 
   /** @type {!Array.<number>} */
   var resolutions = [];
@@ -115255,6 +77993,8 @@ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet =
   /** @type {!Array.<ol.Size>} */
   var sizes = [];
 
+  var matrixLimits = opt_matrixLimits !== undefined ? opt_matrixLimits : [];
+
   var supportedCRSPropName = 'SupportedCRS';
   var matrixIdsPropName = 'TileMatrix';
   var identifierPropName = 'Identifier';
@@ -115270,26 +78010,41 @@ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet =
   // swap origin x and y coordinates if axis orientation is lat/long
   var switchOriginXY = projection.getAxisOrientation().substr(0, 2) == 'ne';
 
-  goog.array.sort(matrixSet[matrixIdsPropName], function(a, b) {
+  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]]);
+
+    var matrixAvailable;
+    // use of matrixLimits to filter TileMatrices from GetCapabilities
+    // TileMatrixSet from unavailable matrix levels.
+    if (matrixLimits.length > 0) {
+      matrixAvailable = ol.array.find(matrixLimits,
+          function(elt_ml, index_ml, array_ml) {
+            return elt[identifierPropName] == elt_ml[matrixIdsPropName];
+          });
     } else {
-      origins.push(elt[topLeftCornerPropName]);
+      matrixAvailable = true;
+    }
+
+    if (matrixAvailable) {
+      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']]);
     }
-    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({
@@ -115303,31 +78058,17 @@ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet =
 };
 
 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');
 goog.require('ol.TileUrlFunction');
-goog.require('ol.TileUrlFunctionType');
 goog.require('ol.array');
 goog.require('ol.extent');
+goog.require('ol.obj');
 goog.require('ol.proj');
 goog.require('ol.source.TileImage');
+goog.require('ol.source.WMTSRequestEncoding');
 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
-};
-
+goog.require('ol.uri');
 
 
 /**
@@ -115337,7 +78078,7 @@ ol.source.WMTSRequestEncoding = {
  * @constructor
  * @extends {ol.source.TileImage}
  * @param {olx.source.WMTSOptions} options WMTS options.
- * @api stable
+ * @api
  */
 ol.source.WMTS = function(options) {
 
@@ -115357,17 +78098,10 @@ ol.source.WMTS = function(options) {
 
   /**
    * @private
-   * @type {Object}
+   * @type {!Object}
    */
   this.dimensions_ = options.dimensions !== undefined ? options.dimensions : {};
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.coordKeyPrefix_ = '';
-  this.resetCoordKeyPrefix_();
-
   /**
    * @private
    * @type {string}
@@ -115391,12 +78125,6 @@ ol.source.WMTS = function(options) {
     urls = ol.TileUrlFunction.expandUrl(options.url);
   }
 
-  /**
-   * @private
-   * @type {!Array.<string>}
-   */
-  this.urls_ = urls || [];
-
   // FIXME: should we guess this requestEncoding from options.url(s)
   //        structure? that would mean KVP only if a template is not provided.
 
@@ -115423,7 +78151,7 @@ ol.source.WMTS = function(options) {
   };
 
   if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
-    goog.object.extend(context, {
+    ol.obj.assign(context, {
       'Service': 'WMTS',
       'Request': 'GetTile',
       'Version': this.version_,
@@ -115444,7 +78172,7 @@ ol.source.WMTS = function(options) {
     // special template params
 
     template = (requestEncoding == ol.source.WMTSRequestEncoding.KVP) ?
-        goog.uri.utils.appendParamsFromMap(template, context) :
+        ol.uri.appendParams(template, context) :
         template.replace(/\{(\w+?)\}/g, function(m, p) {
           return (p.toLowerCase() in context) ? context[p.toLowerCase()] : m;
         });
@@ -115465,10 +78193,10 @@ ol.source.WMTS = function(options) {
               'TileCol': tileCoord[1],
               'TileRow': -tileCoord[2] - 1
             };
-            goog.object.extend(localContext, dimensions);
+            ol.obj.assign(localContext, dimensions);
             var url = template;
             if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
-              url = goog.uri.utils.appendParamsFromMap(url, localContext);
+              url = ol.uri.appendParams(url, localContext);
             } else {
               url = url.replace(/\{(\w+?)\}/g, function(m, p) {
                 return localContext[p];
@@ -115479,33 +78207,38 @@ ol.source.WMTS = function(options) {
         });
   }
 
-  var tileUrlFunction = this.urls_.length > 0 ?
+  var tileUrlFunction = (urls && urls.length > 0) ?
       ol.TileUrlFunction.createFromTileUrlFunctions(
-          this.urls_.map(createFromWMTSTemplate)) :
+          urls.map(createFromWMTSTemplate)) :
       ol.TileUrlFunction.nullTileUrlFunction;
 
-  goog.base(this, {
+  ol.source.TileImage.call(this, {
     attributions: options.attributions,
+    cacheSize: options.cacheSize,
     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
   });
 
+  this.setKey(this.getKeyForDimensions_());
+
 };
-goog.inherits(ol.source.WMTS, ol.source.TileImage);
+ol.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.
+ * @return {!Object} Dimensions.
  * @api
  */
 ol.source.WMTS.prototype.getDimensions = function() {
@@ -115523,14 +78256,6 @@ ol.source.WMTS.prototype.getFormat = function() {
 };
 
 
-/**
- * @inheritDoc
- */
-ol.source.WMTS.prototype.getKeyZXY = function(z, x, y) {
-  return this.coordKeyPrefix_ + goog.base(this, 'getKeyZXY', z, x, y);
-};
-
-
 /**
  * Return the layer of the WMTS source.
  * @return {string} Layer.
@@ -115571,16 +78296,6 @@ ol.source.WMTS.prototype.getStyle = function() {
 };
 
 
-/**
- * Return the URLs used for this WMTS source.
- * @return {!Array.<string>} URLs.
- * @api
- */
-ol.source.WMTS.prototype.getUrls = function() {
-  return this.urls_;
-};
-
-
 /**
  * Return the version of the WMTS source.
  * @return {string} Version.
@@ -115593,14 +78308,15 @@ ol.source.WMTS.prototype.getVersion = function() {
 
 /**
  * @private
+ * @return {string} The key for the current dimensions.
  */
-ol.source.WMTS.prototype.resetCoordKeyPrefix_ = function() {
+ol.source.WMTS.prototype.getKeyForDimensions_ = function() {
   var i = 0;
   var res = [];
   for (var key in this.dimensions_) {
     res[i++] = key + '-' + this.dimensions_[key];
   }
-  this.coordKeyPrefix_ = res.join('/');
+  return res.join('/');
 };
 
 
@@ -115610,9 +78326,8 @@ ol.source.WMTS.prototype.resetCoordKeyPrefix_ = function() {
  * @api
  */
 ol.source.WMTS.prototype.updateDimensions = function(dimensions) {
-  goog.object.extend(this.dimensions_, dimensions);
-  this.resetCoordKeyPrefix_();
-  this.changed();
+  ol.obj.assign(this.dimensions_, dimensions);
+  this.setKey(this.getKeyForDimensions_());
 };
 
 
@@ -115623,53 +78338,51 @@ ol.source.WMTS.prototype.updateDimensions = function(dimensions) {
  *                  the layer will apply if not provided.
  *
  * Required config properties:
- * layer - {String} The layer identifier.
+ *  - 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.
+ *  - 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.
+ *  - crossOrigin - {string|null|undefined} Cross origin. Default is `undefined`.
+ * @return {?olx.source.WMTSOptions} WMTS source options object or `null` if the layer was not found.
  * @api
  */
 ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) {
-
-  /* jshint -W069 */
-
-  // 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) {
+  var l = ol.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');
+  if (l === null) {
+    return null;
+  }
   var tileMatrixSets = wmtsCap['Contents']['TileMatrixSet'];
-  var idx, matrixSet;
+  var idx, matrixSet, matrixLimits;
   if (l['TileMatrixSetLink'].length > 1) {
     if ('projection' in config) {
-      idx = goog.array.findIndex(l['TileMatrixSetLink'],
+      idx = ol.array.findIndex(l['TileMatrixSetLink'],
           function(elt, index, array) {
-            var tileMatrixSet = goog.array.find(tileMatrixSets, function(el) {
+            var tileMatrixSet = ol.array.find(tileMatrixSets, function(el) {
               return el['Identifier'] == elt['TileMatrixSet'];
             });
-            return tileMatrixSet['SupportedCRS'].replace(
-                /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3'
-                   ) == config['projection'];
+            var supportedCRS = tileMatrixSet['SupportedCRS'].replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3');
+            var proj1 = ol.proj.get(supportedCRS);
+            var proj2 = ol.proj.get(config['projection']);
+            if (proj1 && proj2) {
+              return ol.proj.equivalent(proj1, proj2);
+            } else {
+              return supportedCRS == config['projection'];
+            }
           });
     } else {
-      idx = goog.array.findIndex(l['TileMatrixSetLink'],
+      idx = ol.array.findIndex(l['TileMatrixSetLink'],
           function(elt, index, array) {
             return elt['TileMatrixSet'] == config['matrixSet'];
           });
@@ -115682,14 +78395,14 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) {
   }
   matrixSet = /** @type {string} */
       (l['TileMatrixSetLink'][idx]['TileMatrixSet']);
-
-  goog.asserts.assert(matrixSet, 'TileMatrixSet must not be null');
+  matrixLimits = /** @type {Array.<Object>} */
+      (l['TileMatrixSetLink'][idx]['TileMatrixSetLimits']);
 
   var format = /** @type {string} */ (l['Format'][0]);
   if ('format' in config) {
     format = config['format'];
   }
-  idx = goog.array.findIndex(l['Style'], function(elt, index, array) {
+  idx = ol.array.findIndex(l['Style'], function(elt, index, array) {
     if ('style' in config) {
       return elt['Title'] == config['style'];
     } else {
@@ -115705,24 +78418,18 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) {
   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['values'], value),
-            'default value contained in values');
-      } else {
-        value = elt['values'][0];
+      var value = elt['Default'];
+      if (value === undefined) {
+        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) {
+  var matrixSetObj = ol.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) {
@@ -115751,45 +78458,44 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) {
   }
 
   var tileGrid = ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet(
-      matrixSetObj, extent);
+      matrixSetObj, extent, matrixLimits);
 
   /** @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 {
+  if ('OperationsMetadata' in wmtsCap && 'GetTile' in wmtsCap['OperationsMetadata']) {
     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 constraint = ol.array.find(gets[i]['Constraint'], function(element) {
+        return element['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']));
+
+      if (requestEncoding === '') {
+        // requestEncoding not provided, use the first encoding from the list
+        requestEncoding = encodings[0];
+      }
+      if (requestEncoding === ol.source.WMTSRequestEncoding.KVP) {
+        if (ol.array.includes(encodings, ol.source.WMTSRequestEncoding.KVP)) {
+          urls.push(/** @type {string} */ (gets[i]['href']));
+        }
+      } else {
+        break;
       }
     }
   }
-  goog.asserts.assert(urls.length > 0, 'At least one URL found');
+  if (urls.length === 0) {
+    requestEncoding = ol.source.WMTSRequestEncoding.REST;
+    l['ResourceURL'].forEach(function(element) {
+      if (element['resourceType'] === 'tile') {
+        format = element['format'];
+        urls.push(/** @type {string} */ (element['template']));
+      }
+    });
+  }
 
   return {
     urls: urls,
@@ -115801,37 +78507,24 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) {
     tileGrid: tileGrid,
     style: style,
     dimensions: dimensions,
-    wrapX: wrapX
+    wrapX: wrapX,
+    crossOrigin: config['crossOrigin']
   };
-
-  /* jshint +W069 */
-
 };
 
 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.TileUrlFunction');
+goog.require('ol.asserts');
 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.
@@ -115839,7 +78532,7 @@ ol.source.ZoomifyTierSizeCalculation = {
  * @constructor
  * @extends {ol.source.TileImage}
  * @param {olx.source.ZoomifyOptions=} opt_options Options.
- * @api stable
+ * @api
  */
 ol.source.Zoomify = function(opt_options) {
 
@@ -115848,7 +78541,7 @@ ol.source.Zoomify = function(opt_options) {
   var size = options.size;
   var tierSizeCalculation = options.tierSizeCalculation !== undefined ?
       options.tierSizeCalculation :
-      ol.source.ZoomifyTierSizeCalculation.DEFAULT;
+      ol.source.Zoomify.TierSizeCalculation_.DEFAULT;
 
   var imageWidth = size[0];
   var imageHeight = size[1];
@@ -115856,7 +78549,7 @@ ol.source.Zoomify = function(opt_options) {
   var tileSize = ol.DEFAULT_TILE_SIZE;
 
   switch (tierSizeCalculation) {
-    case ol.source.ZoomifyTierSizeCalculation.DEFAULT:
+    case ol.source.Zoomify.TierSizeCalculation_.DEFAULT:
       while (imageWidth > tileSize || imageHeight > tileSize) {
         tierSizeInTiles.push([
           Math.ceil(imageWidth / tileSize),
@@ -115865,7 +78558,7 @@ ol.source.Zoomify = function(opt_options) {
         tileSize += tileSize;
       }
       break;
-    case ol.source.ZoomifyTierSizeCalculation.TRUNCATED:
+    case ol.source.Zoomify.TierSizeCalculation_.TRUNCATED:
       var width = imageWidth;
       var height = imageHeight;
       while (width > tileSize || height > tileSize) {
@@ -115878,7 +78571,7 @@ ol.source.Zoomify = function(opt_options) {
       }
       break;
     default:
-      goog.asserts.fail();
+      ol.asserts.assert(false, 53); // Unknown `tierSizeCalculation` configured
       break;
   }
 
@@ -115902,357 +78595,132 @@ ol.source.Zoomify = function(opt_options) {
     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,
-    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.dom');
-goog.require('goog.dom.TagName');
-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_;
+  var url = options.url;
+  if (url && url.indexOf('{TileGroup}') == -1) {
+    url += '{TileGroup}/{z}-{x}-{y}.jpg';
+  }
+  var urls = ol.TileUrlFunction.expandUrl(url);
 
   /**
-   * @private
-   * @type {Array.<ol.style.Atlas>}
+   * @param {string} template Template.
+   * @return {ol.TileUrlFunctionType} Tile URL function.
    */
-  this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)];
-};
+  function createFromTemplate(template) {
 
+    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 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;
+          var localContext = {
+            'z': tileCoordZ,
+            'x': tileCoordX,
+            'y': tileCoordY,
+            'TileGroup': 'TileGroup' + tileGroup
+          };
+          return template.replace(/\{(\w+?)\}/g, function(m, p) {
+            return localContext[p];
+          });
+        }
+      });
+  }
 
-/**
- * @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);
+  var tileUrlFunction = ol.TileUrlFunction.createFromTileUrlFunctions(urls.map(createFromTemplate));
 
-  if (!info) {
-    return null;
-  }
-  /** @type {?ol.style.AtlasInfo} */
-  var hitInfo = this.getInfo_(this.hitAtlases_, id);
-  goog.asserts.assert(hitInfo, 'hitInfo must not be null');
+  ol.source.TileImage.call(this, {
+    attributions: options.attributions,
+    cacheSize: options.cacheSize,
+    crossOrigin: options.crossOrigin,
+    logo: options.logo,
+    projection: options.projection,
+    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+    tileClass: ol.source.Zoomify.Tile_,
+    tileGrid: tileGrid,
+    tileUrlFunction: tileUrlFunction
+  });
 
-  return this.mergeInfos_(info, hitInfo);
 };
+ol.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
- * @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;
-};
+ol.source.Zoomify.Tile_ = function(
+    tileCoord, state, src, crossOrigin, tileLoadFunction) {
 
+  ol.ImageTile.call(this, tileCoord, state, src, crossOrigin, tileLoadFunction);
+
+  /**
+   * @private
+   * @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement}
+   */
+  this.zoomifyImage_ = 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
-  });
 };
+ol.inherits(ol.source.Zoomify.Tile_, ol.ImageTile);
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-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;
+ol.source.Zoomify.Tile_.prototype.getImage = function() {
+  if (this.zoomifyImage_) {
+    return this.zoomifyImage_;
   }
-
-  /** @type {?ol.style.AtlasInfo} */
-  var info = this.add_(false,
-      id, width, height, renderCallback, opt_this);
-  if (!info) {
-    return null;
+  var tileSize = ol.DEFAULT_TILE_SIZE;
+  var image = ol.ImageTile.prototype.getImage.call(this);
+  if (this.state == ol.TileState.LOADED) {
+    if (image.width == tileSize && image.height == tileSize) {
+      this.zoomifyImage_ = image;
+      return image;
+    } else {
+      var context = ol.dom.createCanvasContext2D(tileSize, tileSize);
+      context.drawImage(image, 0, 0);
+      this.zoomifyImage_ = context.canvas;
+      return context.canvas;
+    }
+  } else {
+    return image;
   }
-
-  // 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);
 };
 
 
 /**
+ * @enum {string}
  * @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');
+ol.source.Zoomify.TierSizeCalculation_ = {
+  DEFAULT: 'default',
+  TRUNCATED: 'truncated'
 };
 
+goog.provide('ol.style.Atlas');
 
-/**
- * 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;
-
+goog.require('ol.dom');
 
 
 /**
@@ -116282,41 +78750,36 @@ ol.style.Atlas = function(size, space) {
 
   /**
    * @private
-   * @type {Array.<ol.style.Atlas.Block>}
+   * @type {Array.<ol.AtlasBlock>}
    */
   this.emptyBlocks_ = [{x: 0, y: 0, width: size, height: size}];
 
   /**
    * @private
-   * @type {Object.<string, ol.style.AtlasInfo>}
+   * @type {Object.<string, ol.AtlasInfo>}
    */
   this.entries_ = {};
 
   /**
    * @private
-   * @type {HTMLCanvasElement}
+   * @type {CanvasRenderingContext2D}
    */
-  this.canvas_ = /** @type {HTMLCanvasElement} */
-      (goog.dom.createElement(goog.dom.TagName.CANVAS));
-  this.canvas_.width = size;
-  this.canvas_.height = size;
+  this.context_ = ol.dom.createCanvasContext2D(size, size);
 
   /**
    * @private
-   * @type {CanvasRenderingContext2D}
+   * @type {HTMLCanvasElement}
    */
-  this.context_ = /** @type {CanvasRenderingContext2D} */
-      (this.canvas_.getContext('2d'));
+  this.canvas_ = this.context_.canvas;
 };
 
 
 /**
  * @param {string} id The identifier of the entry to check.
- * @return {?ol.style.AtlasInfo}
+ * @return {?ol.AtlasInfo} The atlas info.
  */
 ol.style.Atlas.prototype.get = function(id) {
-  return /** @type {?ol.style.AtlasInfo} */ (
-      goog.object.get(this.entries_, id, null));
+  return this.entries_[id] || null;
 };
 
 
@@ -116328,671 +78791,332 @@ ol.style.Atlas.prototype.get = function(id) {
  *    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.
+ * @return {?ol.AtlasInfo} The position and atlas image for the entry.
  */
-ol.style.Atlas.prototype.add =
-    function(id, width, height, renderCallback, opt_this) {
+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('goog.dom');
-goog.require('goog.dom.TagName');
-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_;
-};
+      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_);
 
-/**
- * Get the stroke style for the shape.
- * @return {ol.style.Stroke} Stroke style.
- * @api
- */
-ol.style.RegularShape.prototype.getStroke = function() {
-  return this.stroke_;
-};
+      // split the block after the insertion, either horizontally or vertically
+      this.split_(i, block, width + this.space_, height + this.space_);
 
+      return entry;
+    }
+  }
 
-/**
- * @inheritDoc
- */
-ol.style.RegularShape.prototype.listenImageChange = ol.nullFunction;
+  // there is no space for the new entry in this atlas
+  return null;
+};
 
 
 /**
- * @inheritDoc
+ * @private
+ * @param {number} index The index of the block.
+ * @param {ol.AtlasBlock} 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.RegularShape.prototype.load = ol.nullFunction;
+ol.style.Atlas.prototype.split_ = function(index, block, width, height) {
+  var deltaWidth = block.width - width;
+  var deltaHeight = block.height - height;
 
+  /** @type {ol.AtlasBlock} */
+  var newBlock1;
+  /** @type {ol.AtlasBlock} */
+  var newBlock2;
 
-/**
- * @inheritDoc
- */
-ol.style.RegularShape.prototype.unlistenImageChange = ol.nullFunction;
+  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
+    };
 
-/**
- * @typedef {{
- *   strokeStyle: (string|undefined),
- *   strokeWidth: number,
- *   size: number,
- *   lineCap: string,
- *   lineDash: Array.<number>,
- *   lineJoin: string,
- *   miterLimit: number
- * }}
- */
-ol.style.RegularShape.RenderOptions;
+    // 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 {ol.style.AtlasManager|undefined} atlasManager
+ * @param {number} index The index of the block to remove.
+ * @param {ol.AtlasBlock} newBlock1 The 1st block to add.
+ * @param {ol.AtlasBlock} newBlock2 The 2nd block to add.
  */
-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;
-    }
+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);
+};
 
-  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
-  };
+goog.provide('ol.style.AtlasManager');
 
-  if (atlasManager === undefined) {
-    // no atlas manager is used, create a new canvas
-    this.canvas_ = /** @type {HTMLCanvasElement} */
-        (goog.dom.createElement(goog.dom.TagName.CANVAS));
+goog.require('ol');
+goog.require('ol.style.Atlas');
 
-    this.canvas_.height = size;
-    this.canvas_.width = size;
 
-    // canvas.width and height are rounded to the closest integer
-    size = this.canvas_.width;
-    imageSize = size;
+/**
+ * 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 context = /** @type {CanvasRenderingContext2D} */
-        (this.canvas_.getContext('2d'));
-    this.draw_(renderOptions, context, 0, 0);
+  var options = opt_options || {};
 
-    this.createHitDetectionCanvas_(renderOptions);
-  } else {
-    // an atlas manager is used, add the symbol to an atlas
-    size = Math.round(size);
+  /**
+   * The size in pixels of the latest atlas image.
+   * @private
+   * @type {number}
+   */
+  this.currentSize_ = options.initialSize !== undefined ?
+      options.initialSize : ol.INITIAL_ATLAS_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);
-    }
+  /**
+   * 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;
 
-    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');
+  /**
+   * The size in pixels between images.
+   * @private
+   * @type {number}
+   */
+  this.space_ = options.space !== undefined ? options.space : 1;
 
-    this.canvas_ = info.image;
-    this.origin_ = [info.offsetX, info.offsetY];
-    imageSize = info.image.width;
+  /**
+   * @private
+   * @type {Array.<ol.style.Atlas>}
+   */
+  this.atlases_ = [new ol.style.Atlas(this.currentSize_, this.space_)];
 
-    if (hasCustomHitDetectionImage) {
-      this.hitDetectionCanvas_ = info.hitImage;
-      this.hitDetectionImageSize_ =
-          [info.hitImage.width, info.hitImage.height];
-    } else {
-      this.hitDetectionCanvas_ = this.canvas_;
-      this.hitDetectionImageSize_ = [imageSize, imageSize];
-    }
-  }
+  /**
+   * The size in pixels of the latest atlas image for hit-detection images.
+   * @private
+   * @type {number}
+   */
+  this.currentHitSize_ = this.currentSize_;
 
-  this.anchor_ = [size / 2, size / 2];
-  this.size_ = [size, size];
-  this.imageSize_ = [imageSize, imageSize];
+  /**
+   * @private
+   * @type {Array.<ol.style.Atlas>}
+   */
+  this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)];
 };
 
 
 /**
- * @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).
+ * @param {string} id The identifier of the entry to check.
+ * @return {?ol.AtlasManagerInfo} The position and atlas image for the
+ *    entry, or `null` if the entry is not part of the atlas manager.
  */
-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);
+ol.style.AtlasManager.prototype.getInfo = function(id) {
+  /** @type {?ol.AtlasInfo} */
+  var info = this.getInfo_(this.atlases_, id);
 
-  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 (!info) {
+    return null;
   }
+  var hitInfo = /** @type {ol.AtlasInfo} */ (this.getInfo_(this.hitAtlases_, id));
 
-  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();
+  return this.mergeInfos_(info, hitInfo);
 };
 
 
 /**
  * @private
- * @param {ol.style.RegularShape.RenderOptions} renderOptions
+ * @param {Array.<ol.style.Atlas>} atlases The atlases to search.
+ * @param {string} id The identifier of the entry to check.
+ * @return {?ol.AtlasInfo} The position and atlas image for the entry,
+ *    or `null` if the entry is not part of the atlases.
  */
-ol.style.RegularShape.prototype.createHitDetectionCanvas_ =
-    function(renderOptions) {
-  this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
-  if (this.fill_) {
-    this.hitDetectionCanvas_ = this.canvas_;
-    return;
+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;
+    }
   }
-
-  // if no fill style is set, create an extra hit-detection image with a
-  // default fill style
-  this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
-      (goog.dom.createElement(goog.dom.TagName.CANVAS));
-  var canvas = this.hitDetectionCanvas_;
-
-  canvas.height = renderOptions.size;
-  canvas.width = renderOptions.size;
-
-  var context = /** @type {CanvasRenderingContext2D} */
-      (canvas.getContext('2d'));
-  this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
+  return null;
 };
 
 
 /**
  * @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).
+ * @param {ol.AtlasInfo} info The info for the real image.
+ * @param {ol.AtlasInfo} hitInfo The info for the hit-detection
+ *    image.
+ * @return {?ol.AtlasManagerInfo} The position and atlas image for the
+ *    entry, or `null` if the entry is not part of the atlases.
  */
-ol.style.RegularShape.prototype.drawHitDetectionCanvas_ =
-    function(renderOptions, context, x, y) {
-  // reset transform
-  context.setTransform(1, 0, 0, 1, 0, 0);
+ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) {
+  return /** @type {ol.AtlasManagerInfo} */ ({
+    offsetX: info.offsetX,
+    offsetY: info.offsetY,
+    image: info.image,
+    hitImage: hitInfo.image
+  });
+};
 
-  // 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));
+/**
+ * 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.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;
   }
 
-  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();
+  /** @type {?ol.AtlasInfo} */
+  var info = this.add_(false,
+      id, width, height, renderCallback, opt_this);
+  if (!info) {
+    return null;
   }
-  context.closePath();
+
+  // 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 : ol.nullFunction;
+
+  var hitInfo = /** @type {ol.AtlasInfo} */ (this.add_(true,
+      id, width, height, renderHitCallback, opt_this));
+
+  return this.mergeInfos_(info, hitInfo);
 };
 
 
 /**
- * @inheritDoc
+ * @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.AtlasInfo}  The position and atlas image for the entry,
+ *    or `null` if the image is too big.
  */
-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_];
+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;
+    }
   }
-
-  return this.checksums_[0];
+  return null;
 };
 
 // Copyright 2009 The Closure Library Authors.
@@ -117012,10 +79136,34 @@ ol.style.RegularShape.prototype.getChecksum = function() {
 //
 // 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']);
+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.
@@ -117023,52 +79171,28 @@ goog.addDependency('demos/editor/helloworlddialogplugin.js', ['goog.demos.editor
  */
 
 goog.require('ol');
+goog.require('ol.AssertionError');
 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.colorlike');
 goog.require('ol.control');
 goog.require('ol.control.Attribution');
 goog.require('ol.control.Control');
@@ -117077,18 +79201,14 @@ 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.Event');
 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');
@@ -117099,8 +79219,8 @@ 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');
@@ -117109,11 +79229,30 @@ goog.require('ol.format.WKT');
 goog.require('ol.format.WMSCapabilities');
 goog.require('ol.format.WMSGetFeatureInfo');
 goog.require('ol.format.WMTSCapabilities');
+goog.require('ol.format.filter');
+goog.require('ol.format.filter.And');
+goog.require('ol.format.filter.Bbox');
+goog.require('ol.format.filter.Comparison');
+goog.require('ol.format.filter.ComparisonBinary');
+goog.require('ol.format.filter.During');
+goog.require('ol.format.filter.EqualTo');
+goog.require('ol.format.filter.Filter');
+goog.require('ol.format.filter.GreaterThan');
+goog.require('ol.format.filter.GreaterThanOrEqualTo');
+goog.require('ol.format.filter.Intersects');
+goog.require('ol.format.filter.IsBetween');
+goog.require('ol.format.filter.IsLike');
+goog.require('ol.format.filter.IsNull');
+goog.require('ol.format.filter.LessThan');
+goog.require('ol.format.filter.LessThanOrEqualTo');
+goog.require('ol.format.filter.Not');
+goog.require('ol.format.filter.NotEqualTo');
+goog.require('ol.format.filter.Or');
+goog.require('ol.format.filter.Spatial');
+goog.require('ol.format.filter.Within');
 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');
@@ -117126,133 +79265,92 @@ 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.Extent');
 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.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.CartoDB');
 goog.require('ol.source.Cluster');
 goog.require('ol.source.Image');
+goog.require('ol.source.ImageArcGISRest');
 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.TileVector');
 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');
 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.exportProperty(
+    ol.AssertionError.prototype,
+    'code',
+    ol.AssertionError.prototype.code);
 
 goog.exportSymbol(
     'ol.Attribution',
@@ -117264,11 +79362,6 @@ goog.exportProperty(
     'getHTML',
     ol.Attribution.prototype.getHTML);
 
-goog.exportProperty(
-    ol.CollectionEvent.prototype,
-    'element',
-    ol.CollectionEvent.prototype.element);
-
 goog.exportSymbol(
     'ol.Collection',
     ol.Collection,
@@ -117334,6 +79427,31 @@ goog.exportProperty(
     'setAt',
     ol.Collection.prototype.setAt);
 
+goog.exportProperty(
+    ol.Collection.Event.prototype,
+    'element',
+    ol.Collection.Event.prototype.element);
+
+goog.exportSymbol(
+    'ol.color.asArray',
+    ol.color.asArray,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.color.asString',
+    ol.color.asString,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.colorlike.asColorLike',
+    ol.colorlike.asColorLike,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.control.defaults',
+    ol.control.defaults,
+    OPENLAYERS);
+
 goog.exportSymbol(
     'ol.coordinate.add',
     ol.coordinate.add,
@@ -117464,6 +79582,11 @@ goog.exportSymbol(
     ol.extent.extend,
     OPENLAYERS);
 
+goog.exportSymbol(
+    'ol.extent.getArea',
+    ol.extent.getArea,
+    OPENLAYERS);
+
 goog.exportSymbol(
     'ol.extent.getBottomLeft',
     ol.extent.getBottomLeft,
@@ -117714,11 +79837,26 @@ goog.exportProperty(
     'getImage',
     ol.Image.prototype.getImage);
 
+goog.exportProperty(
+    ol.Image.prototype,
+    'load',
+    ol.Image.prototype.load);
+
 goog.exportProperty(
     ol.ImageTile.prototype,
     'getImage',
     ol.ImageTile.prototype.getImage);
 
+goog.exportSymbol(
+    'ol.inherits',
+    ol.inherits,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.interaction.defaults',
+    ol.interaction.defaults,
+    OPENLAYERS);
+
 goog.exportSymbol(
     'ol.Kinetic',
     ol.Kinetic,
@@ -117764,11 +79902,6 @@ goog.exportProperty(
     'addOverlay',
     ol.Map.prototype.addOverlay);
 
-goog.exportProperty(
-    ol.Map.prototype,
-    'beforeRender',
-    ol.Map.prototype.beforeRender);
-
 goog.exportProperty(
     ol.Map.prototype,
     'forEachFeatureAtPixel',
@@ -117819,6 +79952,11 @@ goog.exportProperty(
     'getOverlays',
     ol.Map.prototype.getOverlays);
 
+goog.exportProperty(
+    ol.Map.prototype,
+    'getOverlayById',
+    ol.Map.prototype.getOverlayById);
+
 goog.exportProperty(
     ol.Map.prototype,
     'getInteractions',
@@ -117929,16 +80067,6 @@ goog.exportProperty(
     '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',
@@ -117949,16 +80077,6 @@ goog.exportProperty(
     '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,
@@ -117994,6 +80112,16 @@ goog.exportProperty(
     'unset',
     ol.Object.prototype.unset);
 
+goog.exportProperty(
+    ol.Object.Event.prototype,
+    'key',
+    ol.Object.Event.prototype.key);
+
+goog.exportProperty(
+    ol.Object.Event.prototype,
+    'oldValue',
+    ol.Object.Event.prototype.oldValue);
+
 goog.exportSymbol(
     'ol.Observable',
     ol.Observable,
@@ -118009,6 +80137,11 @@ goog.exportProperty(
     'changed',
     ol.Observable.prototype.changed);
 
+goog.exportProperty(
+    ol.Observable.prototype,
+    'dispatchEvent',
+    ol.Observable.prototype.dispatchEvent);
+
 goog.exportProperty(
     ol.Observable.prototype,
     'getRevision',
@@ -118029,16 +80162,6 @@ goog.exportProperty(
     '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,
@@ -118049,6 +80172,11 @@ goog.exportProperty(
     'getElement',
     ol.Overlay.prototype.getElement);
 
+goog.exportProperty(
+    ol.Overlay.prototype,
+    'getId',
+    ol.Overlay.prototype.getId);
+
 goog.exportProperty(
     ol.Overlay.prototype,
     'getMap',
@@ -118094,21 +80222,166 @@ goog.exportProperty(
     'setPositioning',
     ol.Overlay.prototype.setPositioning);
 
+goog.exportSymbol(
+    'ol.proj.METERS_PER_UNIT',
+    ol.proj.METERS_PER_UNIT,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.proj.setProj4',
+    ol.proj.setProj4,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.proj.getPointResolution',
+    ol.proj.getPointResolution,
+    OPENLAYERS);
+
+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.equivalent',
+    ol.proj.equivalent,
+    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.render.toContext',
+    ol.render.toContext,
+    OPENLAYERS);
+
 goog.exportSymbol(
     'ol.size.toSize',
     ol.size.toSize,
     OPENLAYERS);
 
+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.exportProperty(
     ol.Tile.prototype,
     'getTileCoord',
     ol.Tile.prototype.getTileCoord);
 
+goog.exportProperty(
+    ol.Tile.prototype,
+    'load',
+    ol.Tile.prototype.load);
+
+goog.exportSymbol(
+    'ol.tilegrid.createXYZ',
+    ol.tilegrid.createXYZ,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.VectorTile.prototype,
+    'getFormat',
+    ol.VectorTile.prototype.getFormat);
+
+goog.exportProperty(
+    ol.VectorTile.prototype,
+    'getFeatures',
+    ol.VectorTile.prototype.getFeatures);
+
+goog.exportProperty(
+    ol.VectorTile.prototype,
+    'getProjection',
+    ol.VectorTile.prototype.getProjection);
+
+goog.exportProperty(
+    ol.VectorTile.prototype,
+    'setFeatures',
+    ol.VectorTile.prototype.setFeatures);
+
+goog.exportProperty(
+    ol.VectorTile.prototype,
+    'setProjection',
+    ol.VectorTile.prototype.setProjection);
+
+goog.exportProperty(
+    ol.VectorTile.prototype,
+    'setLoader',
+    ol.VectorTile.prototype.setLoader);
+
 goog.exportSymbol(
     'ol.View',
     ol.View,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.View.prototype,
+    'animate',
+    ol.View.prototype.animate);
+
+goog.exportProperty(
+    ol.View.prototype,
+    'getAnimating',
+    ol.View.prototype.getAnimating);
+
+goog.exportProperty(
+    ol.View.prototype,
+    'getInteracting',
+    ol.View.prototype.getInteracting);
+
+goog.exportProperty(
+    ol.View.prototype,
+    'cancelAnimations',
+    ol.View.prototype.cancelAnimations);
+
 goog.exportProperty(
     ol.View.prototype,
     'constrainCenter',
@@ -118134,6 +80407,36 @@ goog.exportProperty(
     'calculateExtent',
     ol.View.prototype.calculateExtent);
 
+goog.exportProperty(
+    ol.View.prototype,
+    'getMaxResolution',
+    ol.View.prototype.getMaxResolution);
+
+goog.exportProperty(
+    ol.View.prototype,
+    'getMinResolution',
+    ol.View.prototype.getMinResolution);
+
+goog.exportProperty(
+    ol.View.prototype,
+    'getMaxZoom',
+    ol.View.prototype.getMaxZoom);
+
+goog.exportProperty(
+    ol.View.prototype,
+    'setMaxZoom',
+    ol.View.prototype.setMaxZoom);
+
+goog.exportProperty(
+    ol.View.prototype,
+    'getMinZoom',
+    ol.View.prototype.getMinZoom);
+
+goog.exportProperty(
+    ol.View.prototype,
+    'setMinZoom',
+    ol.View.prototype.setMinZoom);
+
 goog.exportProperty(
     ol.View.prototype,
     'getProjection',
@@ -118144,6 +80447,16 @@ goog.exportProperty(
     'getResolution',
     ol.View.prototype.getResolution);
 
+goog.exportProperty(
+    ol.View.prototype,
+    'getResolutions',
+    ol.View.prototype.getResolutions);
+
+goog.exportProperty(
+    ol.View.prototype,
+    'getResolutionForExtent',
+    ol.View.prototype.getResolutionForExtent);
+
 goog.exportProperty(
     ol.View.prototype,
     'getRotation',
@@ -118154,6 +80467,11 @@ goog.exportProperty(
     'getZoom',
     ol.View.prototype.getZoom);
 
+goog.exportProperty(
+    ol.View.prototype,
+    'getZoomForResolution',
+    ol.View.prototype.getZoomForResolution);
+
 goog.exportProperty(
     ol.View.prototype,
     'fit',
@@ -118199,11 +80517,6 @@ goog.exportSymbol(
     ol.xml.parse,
     OPENLAYERS);
 
-goog.exportSymbol(
-    'ol.webgl.Context',
-    ol.webgl.Context,
-    OPENLAYERS);
-
 goog.exportProperty(
     ol.webgl.Context.prototype,
     'getGL',
@@ -118219,6 +80532,11 @@ goog.exportSymbol(
     ol.tilegrid.TileGrid,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.tilegrid.TileGrid.prototype,
+    'forEachTileCoord',
+    ol.tilegrid.TileGrid.prototype.forEachTileCoord);
+
 goog.exportProperty(
     ol.tilegrid.TileGrid.prototype,
     'getMaxZoom',
@@ -118244,6 +80562,11 @@ goog.exportProperty(
     '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',
@@ -118259,10 +80582,10 @@ goog.exportProperty(
     'getTileSize',
     ol.tilegrid.TileGrid.prototype.getTileSize);
 
-goog.exportSymbol(
-    'ol.tilegrid.createXYZ',
-    ol.tilegrid.createXYZ,
-    OPENLAYERS);
+goog.exportProperty(
+    ol.tilegrid.TileGrid.prototype,
+    'getZForResolution',
+    ol.tilegrid.TileGrid.prototype.getZForResolution);
 
 goog.exportSymbol(
     'ol.tilegrid.WMTS',
@@ -118291,29 +80614,19 @@ goog.exportSymbol(
 
 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);
+    'setRadius',
+    ol.style.Circle.prototype.setRadius);
 
 goog.exportSymbol(
     'ol.style.Fill',
     ol.style.Fill,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.style.Fill.prototype,
+    'clone',
+    ol.style.Fill.prototype.clone);
+
 goog.exportProperty(
     ol.style.Fill.prototype,
     'getColor',
@@ -118329,11 +80642,21 @@ goog.exportSymbol(
     ol.style.Icon,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.style.Icon.prototype,
+    'clone',
+    ol.style.Icon.prototype.clone);
+
 goog.exportProperty(
     ol.style.Icon.prototype,
     'getAnchor',
     ol.style.Icon.prototype.getAnchor);
 
+goog.exportProperty(
+    ol.style.Icon.prototype,
+    'getColor',
+    ol.style.Icon.prototype.getColor);
+
 goog.exportProperty(
     ol.style.Icon.prototype,
     'getImage',
@@ -118409,6 +80732,11 @@ goog.exportSymbol(
     ol.style.RegularShape,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.style.RegularShape.prototype,
+    'clone',
+    ol.style.RegularShape.prototype.clone);
+
 goog.exportProperty(
     ol.style.RegularShape.prototype,
     'getAnchor',
@@ -118464,6 +80792,11 @@ goog.exportSymbol(
     ol.style.Stroke,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.style.Stroke.prototype,
+    'clone',
+    ol.style.Stroke.prototype.clone);
+
 goog.exportProperty(
     ol.style.Stroke.prototype,
     'getColor',
@@ -118479,6 +80812,11 @@ goog.exportProperty(
     'getLineDash',
     ol.style.Stroke.prototype.getLineDash);
 
+goog.exportProperty(
+    ol.style.Stroke.prototype,
+    'getLineDashOffset',
+    ol.style.Stroke.prototype.getLineDashOffset);
+
 goog.exportProperty(
     ol.style.Stroke.prototype,
     'getLineJoin',
@@ -118509,6 +80847,11 @@ goog.exportProperty(
     'setLineDash',
     ol.style.Stroke.prototype.setLineDash);
 
+goog.exportProperty(
+    ol.style.Stroke.prototype,
+    'setLineDashOffset',
+    ol.style.Stroke.prototype.setLineDashOffset);
+
 goog.exportProperty(
     ol.style.Stroke.prototype,
     'setLineJoin',
@@ -118529,6 +80872,11 @@ goog.exportSymbol(
     ol.style.Style,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.style.Style.prototype,
+    'clone',
+    ol.style.Style.prototype.clone);
+
 goog.exportProperty(
     ol.style.Style.prototype,
     'getGeometry',
@@ -118544,21 +80892,41 @@ goog.exportProperty(
     'getFill',
     ol.style.Style.prototype.getFill);
 
+goog.exportProperty(
+    ol.style.Style.prototype,
+    'setFill',
+    ol.style.Style.prototype.setFill);
+
 goog.exportProperty(
     ol.style.Style.prototype,
     'getImage',
     ol.style.Style.prototype.getImage);
 
+goog.exportProperty(
+    ol.style.Style.prototype,
+    'setImage',
+    ol.style.Style.prototype.setImage);
+
 goog.exportProperty(
     ol.style.Style.prototype,
     'getStroke',
     ol.style.Style.prototype.getStroke);
 
+goog.exportProperty(
+    ol.style.Style.prototype,
+    'setStroke',
+    ol.style.Style.prototype.setStroke);
+
 goog.exportProperty(
     ol.style.Style.prototype,
     'getText',
     ol.style.Style.prototype.getText);
 
+goog.exportProperty(
+    ol.style.Style.prototype,
+    'setText',
+    ol.style.Style.prototype.setText);
+
 goog.exportProperty(
     ol.style.Style.prototype,
     'getZIndex',
@@ -118579,6 +80947,11 @@ goog.exportSymbol(
     ol.style.Text,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'clone',
+    ol.style.Text.prototype.clone);
+
 goog.exportProperty(
     ol.style.Text.prototype,
     'getFont',
@@ -118599,6 +80972,11 @@ goog.exportProperty(
     'getFill',
     ol.style.Text.prototype.getFill);
 
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'getRotateWithView',
+    ol.style.Text.prototype.getRotateWithView);
+
 goog.exportProperty(
     ol.style.Text.prototype,
     'getRotation',
@@ -118634,6 +81012,16 @@ goog.exportProperty(
     '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',
@@ -118670,40 +81058,110 @@ goog.exportProperty(
     ol.style.Text.prototype.setTextBaseline);
 
 goog.exportSymbol(
-    'ol.Sphere',
-    ol.Sphere,
+    'ol.source.BingMaps',
+    ol.source.BingMaps,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.source.BingMaps.TOS_ATTRIBUTION',
+    ol.source.BingMaps.TOS_ATTRIBUTION,
     OPENLAYERS);
 
 goog.exportProperty(
-    ol.Sphere.prototype,
-    'geodesicArea',
-    ol.Sphere.prototype.geodesicArea);
+    ol.source.BingMaps.prototype,
+    'getApiKey',
+    ol.source.BingMaps.prototype.getApiKey);
 
 goog.exportProperty(
-    ol.Sphere.prototype,
-    'haversineDistance',
-    ol.Sphere.prototype.haversineDistance);
+    ol.source.BingMaps.prototype,
+    'getImagerySet',
+    ol.source.BingMaps.prototype.getImagerySet);
 
 goog.exportSymbol(
-    'ol.source.BingMaps',
-    ol.source.BingMaps,
+    'ol.source.CartoDB',
+    ol.source.CartoDB,
     OPENLAYERS);
 
-goog.exportSymbol(
-    'ol.source.BingMaps.TOS_ATTRIBUTION',
-    ol.source.BingMaps.TOS_ATTRIBUTION,
-    OPENLAYERS);
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'getConfig',
+    ol.source.CartoDB.prototype.getConfig);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'updateConfig',
+    ol.source.CartoDB.prototype.updateConfig);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'setConfig',
+    ol.source.CartoDB.prototype.setConfig);
 
 goog.exportSymbol(
     'ol.source.Cluster',
     ol.source.Cluster,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.source.Cluster.prototype,
+    'getDistance',
+    ol.source.Cluster.prototype.getDistance);
+
 goog.exportProperty(
     ol.source.Cluster.prototype,
     'getSource',
     ol.source.Cluster.prototype.getSource);
 
+goog.exportProperty(
+    ol.source.Cluster.prototype,
+    'setDistance',
+    ol.source.Cluster.prototype.setDistance);
+
+goog.exportSymbol(
+    'ol.source.Image',
+    ol.source.Image,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.source.Image.Event.prototype,
+    'image',
+    ol.source.Image.Event.prototype.image);
+
+goog.exportSymbol(
+    'ol.source.ImageArcGISRest',
+    ol.source.ImageArcGISRest,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'getParams',
+    ol.source.ImageArcGISRest.prototype.getParams);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'getImageLoadFunction',
+    ol.source.ImageArcGISRest.prototype.getImageLoadFunction);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'getUrl',
+    ol.source.ImageArcGISRest.prototype.getUrl);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'setImageLoadFunction',
+    ol.source.ImageArcGISRest.prototype.setImageLoadFunction);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'setUrl',
+    ol.source.ImageArcGISRest.prototype.setUrl);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'updateParams',
+    ol.source.ImageArcGISRest.prototype.updateParams);
+
 goog.exportSymbol(
     'ol.source.ImageCanvas',
     ol.source.ImageCanvas,
@@ -118734,16 +81192,6 @@ goog.exportProperty(
     '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,
@@ -118814,16 +81262,6 @@ goog.exportProperty(
     '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,
@@ -118845,19 +81283,19 @@ goog.exportProperty(
     ol.source.Raster.prototype.setOperation);
 
 goog.exportProperty(
-    ol.source.RasterEvent.prototype,
+    ol.source.Raster.Event.prototype,
     'extent',
-    ol.source.RasterEvent.prototype.extent);
+    ol.source.Raster.Event.prototype.extent);
 
 goog.exportProperty(
-    ol.source.RasterEvent.prototype,
+    ol.source.Raster.Event.prototype,
     'resolution',
-    ol.source.RasterEvent.prototype.resolution);
+    ol.source.Raster.Event.prototype.resolution);
 
 goog.exportProperty(
-    ol.source.RasterEvent.prototype,
+    ol.source.Raster.Event.prototype,
     'data',
-    ol.source.RasterEvent.prototype.data);
+    ol.source.Raster.Event.prototype.data);
 
 goog.exportSymbol(
     'ol.source.Source',
@@ -118884,6 +81322,11 @@ goog.exportProperty(
     'getState',
     ol.source.Source.prototype.getState);
 
+goog.exportProperty(
+    ol.source.Source.prototype,
+    'refresh',
+    ol.source.Source.prototype.refresh);
+
 goog.exportProperty(
     ol.source.Source.prototype,
     'setAttributions',
@@ -118895,29 +81338,29 @@ goog.exportSymbol(
     OPENLAYERS);
 
 goog.exportSymbol(
-    'ol.source.TileArcGISRest',
-    ol.source.TileArcGISRest,
+    'ol.source.Tile',
+    ol.source.Tile,
     OPENLAYERS);
 
 goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getParams',
-    ol.source.TileArcGISRest.prototype.getParams);
+    ol.source.Tile.prototype,
+    'getTileGrid',
+    ol.source.Tile.prototype.getTileGrid);
 
 goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getUrls',
-    ol.source.TileArcGISRest.prototype.getUrls);
+    ol.source.Tile.Event.prototype,
+    'tile',
+    ol.source.Tile.Event.prototype.tile);
 
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'setUrl',
-    ol.source.TileArcGISRest.prototype.setUrl);
+goog.exportSymbol(
+    'ol.source.TileArcGISRest',
+    ol.source.TileArcGISRest,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.source.TileArcGISRest.prototype,
-    'setUrls',
-    ol.source.TileArcGISRest.prototype.setUrls);
+    'getParams',
+    ol.source.TileArcGISRest.prototype.getParams);
 
 goog.exportProperty(
     ol.source.TileArcGISRest.prototype,
@@ -118936,43 +81379,23 @@ goog.exportSymbol(
 
 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,
-    'setTileLoadFunction',
-    ol.source.TileImage.prototype.setTileLoadFunction);
+    'setRenderReprojectionEdges',
+    ol.source.TileImage.prototype.setRenderReprojectionEdges);
 
 goog.exportProperty(
     ol.source.TileImage.prototype,
-    'setTileUrlFunction',
-    ol.source.TileImage.prototype.setTileUrlFunction);
+    '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);
+    ol.source.TileJSON.prototype,
+    'getTileJSON',
+    ol.source.TileJSON.prototype.getTileJSON);
 
 goog.exportSymbol(
     'ol.source.TileUTFGrid',
@@ -118989,16 +81412,6 @@ goog.exportProperty(
     'forDataAtCoordinateAndResolution',
     ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution);
 
-goog.exportSymbol(
-    'ol.source.TileVector',
-    ol.source.TileVector,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getFeaturesAtCoordinateAndResolution',
-    ol.source.TileVector.prototype.getFeaturesAtCoordinateAndResolution);
-
 goog.exportSymbol(
     'ol.source.TileWMS',
     ol.source.TileWMS,
@@ -119016,23 +81429,43 @@ goog.exportProperty(
 
 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.TileWMS.prototype.getUrls);
+    ol.source.UrlTile.prototype.getUrls);
 
 goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'setUrl',
-    ol.source.TileWMS.prototype.setUrl);
+    ol.source.UrlTile.prototype,
+    'setTileLoadFunction',
+    ol.source.UrlTile.prototype.setTileLoadFunction);
 
 goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'setUrls',
-    ol.source.TileWMS.prototype.setUrls);
+    ol.source.UrlTile.prototype,
+    'setTileUrlFunction',
+    ol.source.UrlTile.prototype.setTileUrlFunction);
 
 goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'updateParams',
-    ol.source.TileWMS.prototype.updateParams);
+    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',
@@ -119104,15 +81537,30 @@ goog.exportProperty(
     'getFeatureById',
     ol.source.Vector.prototype.getFeatureById);
 
+goog.exportProperty(
+    ol.source.Vector.prototype,
+    'getFormat',
+    ol.source.Vector.prototype.getFormat);
+
+goog.exportProperty(
+    ol.source.Vector.prototype,
+    'getUrl',
+    ol.source.Vector.prototype.getUrl);
+
 goog.exportProperty(
     ol.source.Vector.prototype,
     'removeFeature',
     ol.source.Vector.prototype.removeFeature);
 
 goog.exportProperty(
-    ol.source.VectorEvent.prototype,
+    ol.source.Vector.Event.prototype,
     'feature',
-    ol.source.VectorEvent.prototype.feature);
+    ol.source.Vector.Event.prototype.feature);
+
+goog.exportSymbol(
+    'ol.source.VectorTile',
+    ol.source.VectorTile,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.WMTS',
@@ -119149,11 +81597,6 @@ goog.exportProperty(
     'getStyle',
     ol.source.WMTS.prototype.getStyle);
 
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getUrls',
-    ol.source.WMTS.prototype.getUrls);
-
 goog.exportProperty(
     ol.source.WMTS.prototype,
     'getVersion',
@@ -119174,16 +81617,6 @@ goog.exportSymbol(
     ol.source.XYZ,
     OPENLAYERS);
 
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getUrls',
-    ol.source.XYZ.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'setUrl',
-    ol.source.XYZ.prototype.setUrl);
-
 goog.exportSymbol(
     'ol.source.Zoomify',
     ol.source.Zoomify,
@@ -119209,151 +81642,81 @@ goog.exportProperty(
     'glContext',
     ol.render.Event.prototype.glContext);
 
-goog.exportSymbol(
-    'ol.render.VectorContext',
-    ol.render.VectorContext,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.render.webgl.Immediate.prototype,
-    'drawAsync',
-    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);
+    ol.render.Feature.prototype,
+    'get',
+    ol.render.Feature.prototype.get);
 
 goog.exportProperty(
-    ol.render.webgl.Immediate.prototype,
-    'drawLineStringGeometry',
-    ol.render.webgl.Immediate.prototype.drawLineStringGeometry);
+    ol.render.Feature.prototype,
+    'getExtent',
+    ol.render.Feature.prototype.getExtent);
 
 goog.exportProperty(
-    ol.render.webgl.Immediate.prototype,
-    'drawMultiLineStringGeometry',
-    ol.render.webgl.Immediate.prototype.drawMultiLineStringGeometry);
+    ol.render.Feature.prototype,
+    'getId',
+    ol.render.Feature.prototype.getId);
 
 goog.exportProperty(
-    ol.render.webgl.Immediate.prototype,
-    'drawMultiPointGeometry',
-    ol.render.webgl.Immediate.prototype.drawMultiPointGeometry);
+    ol.render.Feature.prototype,
+    'getGeometry',
+    ol.render.Feature.prototype.getGeometry);
 
 goog.exportProperty(
-    ol.render.webgl.Immediate.prototype,
-    'drawMultiPolygonGeometry',
-    ol.render.webgl.Immediate.prototype.drawMultiPolygonGeometry);
+    ol.render.Feature.prototype,
+    'getProperties',
+    ol.render.Feature.prototype.getProperties);
 
 goog.exportProperty(
-    ol.render.webgl.Immediate.prototype,
-    'drawPolygonGeometry',
-    ol.render.webgl.Immediate.prototype.drawPolygonGeometry);
+    ol.render.Feature.prototype,
+    'getType',
+    ol.render.Feature.prototype.getType);
 
-goog.exportProperty(
-    ol.render.webgl.Immediate.prototype,
-    'drawText',
-    ol.render.webgl.Immediate.prototype.drawText);
+goog.exportSymbol(
+    'ol.render.VectorContext',
+    ol.render.VectorContext,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.render.webgl.Immediate.prototype,
-    'setFillStrokeStyle',
-    ol.render.webgl.Immediate.prototype.setFillStrokeStyle);
+    'setStyle',
+    ol.render.webgl.Immediate.prototype.setStyle);
 
 goog.exportProperty(
     ol.render.webgl.Immediate.prototype,
-    'setImageStyle',
-    ol.render.webgl.Immediate.prototype.setImageStyle);
+    'drawGeometry',
+    ol.render.webgl.Immediate.prototype.drawGeometry);
 
 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);
+    ol.render.webgl.Immediate.prototype.drawFeature);
 
 goog.exportProperty(
     ol.render.canvas.Immediate.prototype,
-    'drawMultiPolygonGeometry',
-    ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry);
+    'drawCircle',
+    ol.render.canvas.Immediate.prototype.drawCircle);
 
 goog.exportProperty(
     ol.render.canvas.Immediate.prototype,
-    'setFillStrokeStyle',
-    ol.render.canvas.Immediate.prototype.setFillStrokeStyle);
+    'setStyle',
+    ol.render.canvas.Immediate.prototype.setStyle);
 
 goog.exportProperty(
     ol.render.canvas.Immediate.prototype,
-    'setImageStyle',
-    ol.render.canvas.Immediate.prototype.setImageStyle);
+    'drawGeometry',
+    ol.render.canvas.Immediate.prototype.drawGeometry);
 
 goog.exportProperty(
     ol.render.canvas.Immediate.prototype,
-    'setTextStyle',
-    ol.render.canvas.Immediate.prototype.setTextStyle);
+    'drawFeature',
+    ol.render.canvas.Immediate.prototype.drawFeature);
 
 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,
@@ -119409,121 +81772,11 @@ goog.exportProperty(
     '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,
+    'ol.proj.Units.METERS_PER_UNIT',
+    ol.proj.Units.METERS_PER_UNIT,
     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,
@@ -119604,6 +81857,71 @@ goog.exportProperty(
     'setLayers',
     ol.layer.Group.prototype.setLayers);
 
+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.Tile',
     ol.layer.Tile,
@@ -119659,6 +81977,31 @@ goog.exportProperty(
     '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,
+    '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,
@@ -119680,24 +82023,19 @@ goog.exportSymbol(
     OPENLAYERS);
 
 goog.exportProperty(
-    ol.interaction.DragAndDropEvent.prototype,
+    ol.interaction.DragAndDrop.Event.prototype,
     'features',
-    ol.interaction.DragAndDropEvent.prototype.features);
+    ol.interaction.DragAndDrop.Event.prototype.features);
 
 goog.exportProperty(
-    ol.interaction.DragAndDropEvent.prototype,
+    ol.interaction.DragAndDrop.Event.prototype,
     'file',
-    ol.interaction.DragAndDropEvent.prototype.file);
+    ol.interaction.DragAndDrop.Event.prototype.file);
 
 goog.exportProperty(
-    ol.interaction.DragAndDropEvent.prototype,
+    ol.interaction.DragAndDrop.Event.prototype,
     'projection',
-    ol.interaction.DragAndDropEvent.prototype.projection);
-
-goog.exportProperty(
-    ol.DragBoxEvent.prototype,
-    'coordinate',
-    ol.DragBoxEvent.prototype.coordinate);
+    ol.interaction.DragAndDrop.Event.prototype.projection);
 
 goog.exportSymbol(
     'ol.interaction.DragBox',
@@ -119709,19 +82047,29 @@ goog.exportProperty(
     'getGeometry',
     ol.interaction.DragBox.prototype.getGeometry);
 
+goog.exportProperty(
+    ol.interaction.DragBox.Event.prototype,
+    'coordinate',
+    ol.interaction.DragBox.Event.prototype.coordinate);
+
+goog.exportProperty(
+    ol.interaction.DragBox.Event.prototype,
+    'mapBrowserEvent',
+    ol.interaction.DragBox.Event.prototype.mapBrowserEvent);
+
 goog.exportSymbol(
     'ol.interaction.DragPan',
     ol.interaction.DragPan,
     OPENLAYERS);
 
 goog.exportSymbol(
-    'ol.interaction.DragRotateAndZoom',
-    ol.interaction.DragRotateAndZoom,
+    'ol.interaction.DragRotate',
+    ol.interaction.DragRotate,
     OPENLAYERS);
 
 goog.exportSymbol(
-    'ol.interaction.DragRotate',
-    ol.interaction.DragRotate,
+    'ol.interaction.DragRotateAndZoom',
+    ol.interaction.DragRotateAndZoom,
     OPENLAYERS);
 
 goog.exportSymbol(
@@ -119729,11 +82077,6 @@ goog.exportSymbol(
     ol.interaction.DragZoom,
     OPENLAYERS);
 
-goog.exportProperty(
-    ol.interaction.DrawEvent.prototype,
-    'feature',
-    ol.interaction.DrawEvent.prototype.feature);
-
 goog.exportSymbol(
     'ol.interaction.Draw',
     ol.interaction.Draw,
@@ -119764,6 +82107,36 @@ goog.exportSymbol(
     ol.interaction.Draw.createRegularPolygon,
     OPENLAYERS);
 
+goog.exportSymbol(
+    'ol.interaction.Draw.createBox',
+    ol.interaction.Draw.createBox,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.interaction.Draw.Event.prototype,
+    'feature',
+    ol.interaction.Draw.Event.prototype.feature);
+
+goog.exportSymbol(
+    'ol.interaction.Extent',
+    ol.interaction.Extent,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'getExtent',
+    ol.interaction.Extent.prototype.getExtent);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'setExtent',
+    ol.interaction.Extent.prototype.setExtent);
+
+goog.exportProperty(
+    ol.interaction.Extent.Event.prototype,
+    'extent_',
+    ol.interaction.Extent.Event.prototype.extent_);
+
 goog.exportSymbol(
     'ol.interaction.Interaction',
     ol.interaction.Interaction,
@@ -119774,16 +82147,16 @@ goog.exportProperty(
     '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,
@@ -119804,16 +82177,6 @@ goog.exportSymbol(
     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,
@@ -119824,6 +82187,21 @@ goog.exportSymbol(
     ol.interaction.Modify.handleEvent,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.interaction.Modify.prototype,
+    'removePoint',
+    ol.interaction.Modify.prototype.removePoint);
+
+goog.exportProperty(
+    ol.interaction.Modify.Event.prototype,
+    'features',
+    ol.interaction.Modify.Event.prototype.features);
+
+goog.exportProperty(
+    ol.interaction.Modify.Event.prototype,
+    'mapBrowserEvent',
+    ol.interaction.Modify.Event.prototype.mapBrowserEvent);
+
 goog.exportSymbol(
     'ol.interaction.MouseWheelZoom',
     ol.interaction.MouseWheelZoom,
@@ -119859,21 +82237,6 @@ goog.exportSymbol(
     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,
@@ -119884,6 +82247,11 @@ goog.exportProperty(
     'getFeatures',
     ol.interaction.Select.prototype.getFeatures);
 
+goog.exportProperty(
+    ol.interaction.Select.prototype,
+    'getHitTolerance',
+    ol.interaction.Select.prototype.getHitTolerance);
+
 goog.exportProperty(
     ol.interaction.Select.prototype,
     'getLayer',
@@ -119894,11 +82262,31 @@ goog.exportSymbol(
     ol.interaction.Select.handleEvent,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.interaction.Select.prototype,
+    'setHitTolerance',
+    ol.interaction.Select.prototype.setHitTolerance);
+
 goog.exportProperty(
     ol.interaction.Select.prototype,
     'setMap',
     ol.interaction.Select.prototype.setMap);
 
+goog.exportProperty(
+    ol.interaction.Select.Event.prototype,
+    'selected',
+    ol.interaction.Select.Event.prototype.selected);
+
+goog.exportProperty(
+    ol.interaction.Select.Event.prototype,
+    'deselected',
+    ol.interaction.Select.Event.prototype.deselected);
+
+goog.exportProperty(
+    ol.interaction.Select.Event.prototype,
+    'mapBrowserEvent',
+    ol.interaction.Select.Event.prototype.mapBrowserEvent);
+
 goog.exportSymbol(
     'ol.interaction.Snap',
     ol.interaction.Snap,
@@ -119919,6 +82307,26 @@ goog.exportSymbol(
     ol.interaction.Translate,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.interaction.Translate.prototype,
+    'getHitTolerance',
+    ol.interaction.Translate.prototype.getHitTolerance);
+
+goog.exportProperty(
+    ol.interaction.Translate.prototype,
+    'setHitTolerance',
+    ol.interaction.Translate.prototype.setHitTolerance);
+
+goog.exportProperty(
+    ol.interaction.Translate.Event.prototype,
+    'features',
+    ol.interaction.Translate.Event.prototype.features);
+
+goog.exportProperty(
+    ol.interaction.Translate.Event.prototype,
+    'coordinate',
+    ol.interaction.Translate.Event.prototype.coordinate);
+
 goog.exportSymbol(
     'ol.geom.Circle',
     ol.geom.Circle,
@@ -119979,11 +82387,26 @@ goog.exportProperty(
     'getClosestPoint',
     ol.geom.Geometry.prototype.getClosestPoint);
 
+goog.exportProperty(
+    ol.geom.Geometry.prototype,
+    'intersectsCoordinate',
+    ol.geom.Geometry.prototype.intersectsCoordinate);
+
 goog.exportProperty(
     ol.geom.Geometry.prototype,
     'getExtent',
     ol.geom.Geometry.prototype.getExtent);
 
+goog.exportProperty(
+    ol.geom.Geometry.prototype,
+    'rotate',
+    ol.geom.Geometry.prototype.rotate);
+
+goog.exportProperty(
+    ol.geom.Geometry.prototype,
+    'scale',
+    ol.geom.Geometry.prototype.scale);
+
 goog.exportProperty(
     ol.geom.Geometry.prototype,
     'simplify',
@@ -120094,6 +82517,11 @@ goog.exportProperty(
     'getCoordinates',
     ol.geom.LineString.prototype.getCoordinates);
 
+goog.exportProperty(
+    ol.geom.LineString.prototype,
+    'getCoordinateAt',
+    ol.geom.LineString.prototype.getCoordinateAt);
+
 goog.exportProperty(
     ol.geom.LineString.prototype,
     'getLength',
@@ -120459,6 +82887,86 @@ goog.exportSymbol(
     ol.format.Feature,
     OPENLAYERS);
 
+goog.exportSymbol(
+    'ol.format.filter.and',
+    ol.format.filter.and,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.or',
+    ol.format.filter.or,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.not',
+    ol.format.filter.not,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.bbox',
+    ol.format.filter.bbox,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.intersects',
+    ol.format.filter.intersects,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.within',
+    ol.format.filter.within,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.equalTo',
+    ol.format.filter.equalTo,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.notEqualTo',
+    ol.format.filter.notEqualTo,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.lessThan',
+    ol.format.filter.lessThan,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.lessThanOrEqualTo',
+    ol.format.filter.lessThanOrEqualTo,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.greaterThan',
+    ol.format.filter.greaterThan,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.greaterThanOrEqualTo',
+    ol.format.filter.greaterThanOrEqualTo,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.isNull',
+    ol.format.filter.isNull,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.between',
+    ol.format.filter.between,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.like',
+    ol.format.filter.like,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.during',
+    ol.format.filter.during,
+    OPENLAYERS);
+
 goog.exportSymbol(
     'ol.format.GeoJSON',
     ol.format.GeoJSON,
@@ -120514,6 +83022,51 @@ goog.exportProperty(
     'writeGeometryObject',
     ol.format.GeoJSON.prototype.writeGeometryObject);
 
+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.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.exportProperty(
+    ol.format.GMLBase.prototype,
+    'readFeatures',
+    ol.format.GMLBase.prototype.readFeatures);
+
 goog.exportSymbol(
     'ol.format.GPX',
     ol.format.GPX,
@@ -120589,6 +83142,16 @@ goog.exportProperty(
     'readNetworkLinks',
     ol.format.KML.prototype.readNetworkLinks);
 
+goog.exportProperty(
+    ol.format.KML.prototype,
+    'readRegion',
+    ol.format.KML.prototype.readRegion);
+
+goog.exportProperty(
+    ol.format.KML.prototype,
+    'readRegionFromNode',
+    ol.format.KML.prototype.readRegionFromNode);
+
 goog.exportProperty(
     ol.format.KML.prototype,
     'readProjection',
@@ -120604,6 +83167,26 @@ goog.exportProperty(
     'writeFeaturesNode',
     ol.format.KML.prototype.writeFeaturesNode);
 
+goog.exportSymbol(
+    'ol.format.MVT',
+    ol.format.MVT,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.format.MVT.prototype,
+    'readFeatures',
+    ol.format.MVT.prototype.readFeatures);
+
+goog.exportProperty(
+    ol.format.MVT.prototype,
+    'readProjection',
+    ol.format.MVT.prototype.readProjection);
+
+goog.exportProperty(
+    ol.format.MVT.prototype,
+    'setLayers',
+    ol.format.MVT.prototype.setLayers);
+
 goog.exportSymbol(
     'ol.format.OSMXML',
     ol.format.OSMXML,
@@ -120704,6 +83287,11 @@ goog.exportProperty(
     'readFeatureCollectionMetadata',
     ol.format.WFS.prototype.readFeatureCollectionMetadata);
 
+goog.exportSymbol(
+    'ol.format.WFS.writeFilter',
+    ol.format.WFS.writeFilter,
+    OPENLAYERS);
+
 goog.exportProperty(
     ol.format.WFS.prototype,
     'writeGetFeature',
@@ -120785,49 +83373,104 @@ goog.exportProperty(
     ol.format.WMTSCapabilities.prototype.read);
 
 goog.exportSymbol(
-    'ol.format.GML2',
-    ol.format.GML2,
+    'ol.format.filter.And',
+    ol.format.filter.And,
     OPENLAYERS);
 
 goog.exportSymbol(
-    'ol.format.GML3',
-    ol.format.GML3,
+    'ol.format.filter.Bbox',
+    ol.format.filter.Bbox,
     OPENLAYERS);
 
-goog.exportProperty(
-    ol.format.GML3.prototype,
-    'writeGeometryNode',
-    ol.format.GML3.prototype.writeGeometryNode);
+goog.exportSymbol(
+    'ol.format.filter.Comparison',
+    ol.format.filter.Comparison,
+    OPENLAYERS);
 
-goog.exportProperty(
-    ol.format.GML3.prototype,
-    'writeFeatures',
-    ol.format.GML3.prototype.writeFeatures);
+goog.exportSymbol(
+    'ol.format.filter.ComparisonBinary',
+    ol.format.filter.ComparisonBinary,
+    OPENLAYERS);
 
-goog.exportProperty(
-    ol.format.GML3.prototype,
-    'writeFeaturesNode',
-    ol.format.GML3.prototype.writeFeaturesNode);
+goog.exportSymbol(
+    'ol.format.filter.During',
+    ol.format.filter.During,
+    OPENLAYERS);
 
 goog.exportSymbol(
-    'ol.format.GML',
-    ol.format.GML,
+    'ol.format.filter.EqualTo',
+    ol.format.filter.EqualTo,
     OPENLAYERS);
 
-goog.exportProperty(
-    ol.format.GML.prototype,
-    'writeFeatures',
-    ol.format.GML.prototype.writeFeatures);
+goog.exportSymbol(
+    'ol.format.filter.Filter',
+    ol.format.filter.Filter,
+    OPENLAYERS);
 
-goog.exportProperty(
-    ol.format.GML.prototype,
-    'writeFeaturesNode',
-    ol.format.GML.prototype.writeFeaturesNode);
+goog.exportSymbol(
+    'ol.format.filter.GreaterThan',
+    ol.format.filter.GreaterThan,
+    OPENLAYERS);
 
-goog.exportProperty(
-    ol.format.GMLBase.prototype,
-    'readFeatures',
-    ol.format.GMLBase.prototype.readFeatures);
+goog.exportSymbol(
+    'ol.format.filter.GreaterThanOrEqualTo',
+    ol.format.filter.GreaterThanOrEqualTo,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.Intersects',
+    ol.format.filter.Intersects,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.IsBetween',
+    ol.format.filter.IsBetween,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.IsLike',
+    ol.format.filter.IsLike,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.IsNull',
+    ol.format.filter.IsNull,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.LessThan',
+    ol.format.filter.LessThan,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.LessThanOrEqualTo',
+    ol.format.filter.LessThanOrEqualTo,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.Not',
+    ol.format.filter.Not,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.NotEqualTo',
+    ol.format.filter.NotEqualTo,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.Or',
+    ol.format.filter.Or,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.Spatial',
+    ol.format.filter.Spatial,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.filter.Within',
+    ol.format.filter.Within,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.events.condition.altKeyOnly',
@@ -120894,6 +83537,31 @@ goog.exportSymbol(
     ol.events.condition.mouseOnly,
     OPENLAYERS);
 
+goog.exportSymbol(
+    'ol.events.condition.primaryAction',
+    ol.events.condition.primaryAction,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.events.Event.prototype,
+    'type',
+    ol.events.Event.prototype.type);
+
+goog.exportProperty(
+    ol.events.Event.prototype,
+    'target',
+    ol.events.Event.prototype.target);
+
+goog.exportProperty(
+    ol.events.Event.prototype,
+    'preventDefault',
+    ol.events.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.events.Event.prototype,
+    'stopPropagation',
+    ol.events.Event.prototype.stopPropagation);
+
 goog.exportSymbol(
     'ol.control.Attribution',
     ol.control.Attribution,
@@ -120944,11 +83612,6 @@ goog.exportProperty(
     'setTarget',
     ol.control.Control.prototype.setTarget);
 
-goog.exportSymbol(
-    'ol.control.defaults',
-    ol.control.defaults,
-    OPENLAYERS);
-
 goog.exportSymbol(
     'ol.control.FullScreen',
     ol.control.FullScreen,
@@ -121014,6 +83677,11 @@ goog.exportProperty(
     '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,
@@ -121064,21 +83732,16 @@ goog.exportSymbol(
     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',
@@ -121099,11 +83762,6 @@ goog.exportProperty(
     'un',
     ol.Object.prototype.un);
 
-goog.exportProperty(
-    ol.Object.prototype,
-    'unByKey',
-    ol.Object.prototype.unByKey);
-
 goog.exportProperty(
     ol.Collection.prototype,
     'get',
@@ -121139,6 +83797,11 @@ goog.exportProperty(
     'changed',
     ol.Collection.prototype.changed);
 
+goog.exportProperty(
+    ol.Collection.prototype,
+    'dispatchEvent',
+    ol.Collection.prototype.dispatchEvent);
+
 goog.exportProperty(
     ol.Collection.prototype,
     'getRevision',
@@ -121160,9 +83823,24 @@ goog.exportProperty(
     ol.Collection.prototype.un);
 
 goog.exportProperty(
-    ol.Collection.prototype,
-    'unByKey',
-    ol.Collection.prototype.unByKey);
+    ol.Collection.Event.prototype,
+    'type',
+    ol.Collection.Event.prototype.type);
+
+goog.exportProperty(
+    ol.Collection.Event.prototype,
+    'target',
+    ol.Collection.Event.prototype.target);
+
+goog.exportProperty(
+    ol.Collection.Event.prototype,
+    'preventDefault',
+    ol.Collection.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.Collection.Event.prototype,
+    'stopPropagation',
+    ol.Collection.Event.prototype.stopPropagation);
 
 goog.exportProperty(
     ol.DeviceOrientation.prototype,
@@ -121199,6 +83877,11 @@ goog.exportProperty(
     'changed',
     ol.DeviceOrientation.prototype.changed);
 
+goog.exportProperty(
+    ol.DeviceOrientation.prototype,
+    'dispatchEvent',
+    ol.DeviceOrientation.prototype.dispatchEvent);
+
 goog.exportProperty(
     ol.DeviceOrientation.prototype,
     'getRevision',
@@ -121219,11 +83902,6 @@ goog.exportProperty(
     'un',
     ol.DeviceOrientation.prototype.un);
 
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'unByKey',
-    ol.DeviceOrientation.prototype.unByKey);
-
 goog.exportProperty(
     ol.Feature.prototype,
     'get',
@@ -121259,6 +83937,11 @@ goog.exportProperty(
     'changed',
     ol.Feature.prototype.changed);
 
+goog.exportProperty(
+    ol.Feature.prototype,
+    'dispatchEvent',
+    ol.Feature.prototype.dispatchEvent);
+
 goog.exportProperty(
     ol.Feature.prototype,
     'getRevision',
@@ -121279,11 +83962,6 @@ goog.exportProperty(
     'un',
     ol.Feature.prototype.un);
 
-goog.exportProperty(
-    ol.Feature.prototype,
-    'unByKey',
-    ol.Feature.prototype.unByKey);
-
 goog.exportProperty(
     ol.Geolocation.prototype,
     'get',
@@ -121319,6 +83997,11 @@ goog.exportProperty(
     'changed',
     ol.Geolocation.prototype.changed);
 
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'dispatchEvent',
+    ol.Geolocation.prototype.dispatchEvent);
+
 goog.exportProperty(
     ol.Geolocation.prototype,
     'getRevision',
@@ -121339,16 +84022,16 @@ goog.exportProperty(
     '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.ImageTile.prototype,
+    'load',
+    ol.ImageTile.prototype.load);
+
 goog.exportProperty(
     ol.Map.prototype,
     'get',
@@ -121384,6 +84067,11 @@ goog.exportProperty(
     'changed',
     ol.Map.prototype.changed);
 
+goog.exportProperty(
+    ol.Map.prototype,
+    'dispatchEvent',
+    ol.Map.prototype.dispatchEvent);
+
 goog.exportProperty(
     ol.Map.prototype,
     'getRevision',
@@ -121405,9 +84093,24 @@ goog.exportProperty(
     ol.Map.prototype.un);
 
 goog.exportProperty(
-    ol.Map.prototype,
-    'unByKey',
-    ol.Map.prototype.unByKey);
+    ol.MapEvent.prototype,
+    'type',
+    ol.MapEvent.prototype.type);
+
+goog.exportProperty(
+    ol.MapEvent.prototype,
+    'target',
+    ol.MapEvent.prototype.target);
+
+goog.exportProperty(
+    ol.MapEvent.prototype,
+    'preventDefault',
+    ol.MapEvent.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.MapEvent.prototype,
+    'stopPropagation',
+    ol.MapEvent.prototype.stopPropagation);
 
 goog.exportProperty(
     ol.MapBrowserEvent.prototype,
@@ -121419,6 +84122,26 @@ goog.exportProperty(
     'frameState',
     ol.MapBrowserEvent.prototype.frameState);
 
+goog.exportProperty(
+    ol.MapBrowserEvent.prototype,
+    'type',
+    ol.MapBrowserEvent.prototype.type);
+
+goog.exportProperty(
+    ol.MapBrowserEvent.prototype,
+    'target',
+    ol.MapBrowserEvent.prototype.target);
+
+goog.exportProperty(
+    ol.MapBrowserEvent.prototype,
+    'preventDefault',
+    ol.MapBrowserEvent.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.MapBrowserEvent.prototype,
+    'stopPropagation',
+    ol.MapBrowserEvent.prototype.stopPropagation);
+
 goog.exportProperty(
     ol.MapBrowserPointerEvent.prototype,
     'originalEvent',
@@ -121459,6 +84182,36 @@ goog.exportProperty(
     'frameState',
     ol.MapBrowserPointerEvent.prototype.frameState);
 
+goog.exportProperty(
+    ol.MapBrowserPointerEvent.prototype,
+    'type',
+    ol.MapBrowserPointerEvent.prototype.type);
+
+goog.exportProperty(
+    ol.MapBrowserPointerEvent.prototype,
+    'target',
+    ol.MapBrowserPointerEvent.prototype.target);
+
+goog.exportProperty(
+    ol.Object.Event.prototype,
+    'type',
+    ol.Object.Event.prototype.type);
+
+goog.exportProperty(
+    ol.Object.Event.prototype,
+    'target',
+    ol.Object.Event.prototype.target);
+
+goog.exportProperty(
+    ol.Object.Event.prototype,
+    'preventDefault',
+    ol.Object.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.Object.Event.prototype,
+    'stopPropagation',
+    ol.Object.Event.prototype.stopPropagation);
+
 goog.exportProperty(
     ol.Overlay.prototype,
     'get',
@@ -121494,6 +84247,11 @@ goog.exportProperty(
     'changed',
     ol.Overlay.prototype.changed);
 
+goog.exportProperty(
+    ol.Overlay.prototype,
+    'dispatchEvent',
+    ol.Overlay.prototype.dispatchEvent);
+
 goog.exportProperty(
     ol.Overlay.prototype,
     'getRevision',
@@ -121515,9 +84273,24 @@ goog.exportProperty(
     ol.Overlay.prototype.un);
 
 goog.exportProperty(
-    ol.Overlay.prototype,
-    'unByKey',
-    ol.Overlay.prototype.unByKey);
+    ol.VectorImageTile.prototype,
+    'getTileCoord',
+    ol.VectorImageTile.prototype.getTileCoord);
+
+goog.exportProperty(
+    ol.VectorImageTile.prototype,
+    'load',
+    ol.VectorImageTile.prototype.load);
+
+goog.exportProperty(
+    ol.VectorTile.prototype,
+    'getTileCoord',
+    ol.VectorTile.prototype.getTileCoord);
+
+goog.exportProperty(
+    ol.VectorTile.prototype,
+    'load',
+    ol.VectorTile.prototype.load);
 
 goog.exportProperty(
     ol.View.prototype,
@@ -121554,6 +84327,11 @@ goog.exportProperty(
     'changed',
     ol.View.prototype.changed);
 
+goog.exportProperty(
+    ol.View.prototype,
+    'dispatchEvent',
+    ol.View.prototype.dispatchEvent);
+
 goog.exportProperty(
     ol.View.prototype,
     'getRevision',
@@ -121575,9 +84353,9 @@ goog.exportProperty(
     ol.View.prototype.un);
 
 goog.exportProperty(
-    ol.View.prototype,
-    'unByKey',
-    ol.View.prototype.unByKey);
+    ol.tilegrid.WMTS.prototype,
+    'forEachTileCoord',
+    ol.tilegrid.WMTS.prototype.forEachTileCoord);
 
 goog.exportProperty(
     ol.tilegrid.WMTS.prototype,
@@ -121604,6 +84382,11 @@ goog.exportProperty(
     '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',
@@ -121619,6 +84402,86 @@ goog.exportProperty(
     'getTileSize',
     ol.tilegrid.WMTS.prototype.getTileSize);
 
+goog.exportProperty(
+    ol.tilegrid.WMTS.prototype,
+    'getZForResolution',
+    ol.tilegrid.WMTS.prototype.getZForResolution);
+
+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.style.Circle.prototype,
+    'clone',
+    ol.style.Circle.prototype.clone);
+
+goog.exportProperty(
+    ol.style.Circle.prototype,
+    'getAngle',
+    ol.style.Circle.prototype.getAngle);
+
+goog.exportProperty(
+    ol.style.Circle.prototype,
+    'getFill',
+    ol.style.Circle.prototype.getFill);
+
+goog.exportProperty(
+    ol.style.Circle.prototype,
+    'getPoints',
+    ol.style.Circle.prototype.getPoints);
+
+goog.exportProperty(
+    ol.style.Circle.prototype,
+    'getRadius',
+    ol.style.Circle.prototype.getRadius);
+
+goog.exportProperty(
+    ol.style.Circle.prototype,
+    'getRadius2',
+    ol.style.Circle.prototype.getRadius2);
+
+goog.exportProperty(
+    ol.style.Circle.prototype,
+    'getStroke',
+    ol.style.Circle.prototype.getStroke);
+
 goog.exportProperty(
     ol.style.Circle.prototype,
     'getOpacity',
@@ -121699,46 +84562,6 @@ goog.exportProperty(
     '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',
@@ -121774,6 +84597,11 @@ goog.exportProperty(
     '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',
@@ -121794,11 +84622,6 @@ goog.exportProperty(
     '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',
@@ -121819,6 +84642,11 @@ goog.exportProperty(
     'getState',
     ol.source.Tile.prototype.getState);
 
+goog.exportProperty(
+    ol.source.Tile.prototype,
+    'refresh',
+    ol.source.Tile.prototype.refresh);
+
 goog.exportProperty(
     ol.source.Tile.prototype,
     'setAttributions',
@@ -121859,6 +84687,11 @@ goog.exportProperty(
     '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',
@@ -121880,15 +84713,145 @@ goog.exportProperty(
     ol.source.Tile.prototype.un);
 
 goog.exportProperty(
-    ol.source.Tile.prototype,
-    'unByKey',
-    ol.source.Tile.prototype.unByKey);
+    ol.source.UrlTile.prototype,
+    'getTileGrid',
+    ol.source.UrlTile.prototype.getTileGrid);
+
+goog.exportProperty(
+    ol.source.UrlTile.prototype,
+    'refresh',
+    ol.source.UrlTile.prototype.refresh);
+
+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.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,
+    'refresh',
+    ol.source.TileImage.prototype.refresh);
+
 goog.exportProperty(
     ol.source.TileImage.prototype,
     'getAttributions',
@@ -121949,6 +84912,11 @@ goog.exportProperty(
     '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',
@@ -121970,9 +84938,14 @@ goog.exportProperty(
     ol.source.TileImage.prototype.un);
 
 goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'unByKey',
-    ol.source.TileImage.prototype.unByKey);
+    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,
@@ -121984,6 +84957,11 @@ goog.exportProperty(
     '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',
@@ -121994,11 +84972,26 @@ goog.exportProperty(
     '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,
+    'refresh',
+    ol.source.BingMaps.prototype.refresh);
+
 goog.exportProperty(
     ol.source.BingMaps.prototype,
     'getAttributions',
@@ -122059,6 +85052,11 @@ goog.exportProperty(
     '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',
@@ -122080,9 +85078,284 @@ goog.exportProperty(
     ol.source.BingMaps.prototype.un);
 
 goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'unByKey',
-    ol.source.BingMaps.prototype.unByKey);
+    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,
+    'refresh',
+    ol.source.XYZ.prototype.refresh);
+
+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.CartoDB.prototype,
+    'setRenderReprojectionEdges',
+    ol.source.CartoDB.prototype.setRenderReprojectionEdges);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'setTileGridForProjection',
+    ol.source.CartoDB.prototype.setTileGridForProjection);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'getTileLoadFunction',
+    ol.source.CartoDB.prototype.getTileLoadFunction);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'getTileUrlFunction',
+    ol.source.CartoDB.prototype.getTileUrlFunction);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'getUrls',
+    ol.source.CartoDB.prototype.getUrls);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'setTileLoadFunction',
+    ol.source.CartoDB.prototype.setTileLoadFunction);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'setTileUrlFunction',
+    ol.source.CartoDB.prototype.setTileUrlFunction);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'setUrl',
+    ol.source.CartoDB.prototype.setUrl);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'setUrls',
+    ol.source.CartoDB.prototype.setUrls);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'getTileGrid',
+    ol.source.CartoDB.prototype.getTileGrid);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'refresh',
+    ol.source.CartoDB.prototype.refresh);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'getAttributions',
+    ol.source.CartoDB.prototype.getAttributions);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'getLogo',
+    ol.source.CartoDB.prototype.getLogo);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'getProjection',
+    ol.source.CartoDB.prototype.getProjection);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'getState',
+    ol.source.CartoDB.prototype.getState);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'setAttributions',
+    ol.source.CartoDB.prototype.setAttributions);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'get',
+    ol.source.CartoDB.prototype.get);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'getKeys',
+    ol.source.CartoDB.prototype.getKeys);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'getProperties',
+    ol.source.CartoDB.prototype.getProperties);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'set',
+    ol.source.CartoDB.prototype.set);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'setProperties',
+    ol.source.CartoDB.prototype.setProperties);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'unset',
+    ol.source.CartoDB.prototype.unset);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'changed',
+    ol.source.CartoDB.prototype.changed);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'dispatchEvent',
+    ol.source.CartoDB.prototype.dispatchEvent);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'getRevision',
+    ol.source.CartoDB.prototype.getRevision);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'on',
+    ol.source.CartoDB.prototype.on);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'once',
+    ol.source.CartoDB.prototype.once);
+
+goog.exportProperty(
+    ol.source.CartoDB.prototype,
+    'un',
+    ol.source.CartoDB.prototype.un);
 
 goog.exportProperty(
     ol.source.Vector.prototype,
@@ -122104,6 +85377,11 @@ goog.exportProperty(
     'getState',
     ol.source.Vector.prototype.getState);
 
+goog.exportProperty(
+    ol.source.Vector.prototype,
+    'refresh',
+    ol.source.Vector.prototype.refresh);
+
 goog.exportProperty(
     ol.source.Vector.prototype,
     'setAttributions',
@@ -122144,6 +85422,11 @@ goog.exportProperty(
     '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',
@@ -122164,11 +85447,6 @@ goog.exportProperty(
     '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',
@@ -122234,6 +85512,16 @@ goog.exportProperty(
     'getFeatureById',
     ol.source.Cluster.prototype.getFeatureById);
 
+goog.exportProperty(
+    ol.source.Cluster.prototype,
+    'getFormat',
+    ol.source.Cluster.prototype.getFormat);
+
+goog.exportProperty(
+    ol.source.Cluster.prototype,
+    'getUrl',
+    ol.source.Cluster.prototype.getUrl);
+
 goog.exportProperty(
     ol.source.Cluster.prototype,
     'removeFeature',
@@ -122259,6 +85547,11 @@ goog.exportProperty(
     'getState',
     ol.source.Cluster.prototype.getState);
 
+goog.exportProperty(
+    ol.source.Cluster.prototype,
+    'refresh',
+    ol.source.Cluster.prototype.refresh);
+
 goog.exportProperty(
     ol.source.Cluster.prototype,
     'setAttributions',
@@ -122299,6 +85592,11 @@ goog.exportProperty(
     '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',
@@ -122319,11 +85617,6 @@ goog.exportProperty(
     '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',
@@ -122344,6 +85637,11 @@ goog.exportProperty(
     'getState',
     ol.source.Image.prototype.getState);
 
+goog.exportProperty(
+    ol.source.Image.prototype,
+    'refresh',
+    ol.source.Image.prototype.refresh);
+
 goog.exportProperty(
     ol.source.Image.prototype,
     'setAttributions',
@@ -122384,6 +85682,11 @@ goog.exportProperty(
     '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',
@@ -122405,9 +85708,114 @@ goog.exportProperty(
     ol.source.Image.prototype.un);
 
 goog.exportProperty(
-    ol.source.Image.prototype,
-    'unByKey',
-    ol.source.Image.prototype.unByKey);
+    ol.source.Image.Event.prototype,
+    'type',
+    ol.source.Image.Event.prototype.type);
+
+goog.exportProperty(
+    ol.source.Image.Event.prototype,
+    'target',
+    ol.source.Image.Event.prototype.target);
+
+goog.exportProperty(
+    ol.source.Image.Event.prototype,
+    'preventDefault',
+    ol.source.Image.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.source.Image.Event.prototype,
+    'stopPropagation',
+    ol.source.Image.Event.prototype.stopPropagation);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'getAttributions',
+    ol.source.ImageArcGISRest.prototype.getAttributions);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'getLogo',
+    ol.source.ImageArcGISRest.prototype.getLogo);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'getProjection',
+    ol.source.ImageArcGISRest.prototype.getProjection);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'getState',
+    ol.source.ImageArcGISRest.prototype.getState);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'refresh',
+    ol.source.ImageArcGISRest.prototype.refresh);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'setAttributions',
+    ol.source.ImageArcGISRest.prototype.setAttributions);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'get',
+    ol.source.ImageArcGISRest.prototype.get);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'getKeys',
+    ol.source.ImageArcGISRest.prototype.getKeys);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'getProperties',
+    ol.source.ImageArcGISRest.prototype.getProperties);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'set',
+    ol.source.ImageArcGISRest.prototype.set);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'setProperties',
+    ol.source.ImageArcGISRest.prototype.setProperties);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'unset',
+    ol.source.ImageArcGISRest.prototype.unset);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'changed',
+    ol.source.ImageArcGISRest.prototype.changed);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'dispatchEvent',
+    ol.source.ImageArcGISRest.prototype.dispatchEvent);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'getRevision',
+    ol.source.ImageArcGISRest.prototype.getRevision);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'on',
+    ol.source.ImageArcGISRest.prototype.on);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'once',
+    ol.source.ImageArcGISRest.prototype.once);
+
+goog.exportProperty(
+    ol.source.ImageArcGISRest.prototype,
+    'un',
+    ol.source.ImageArcGISRest.prototype.un);
 
 goog.exportProperty(
     ol.source.ImageCanvas.prototype,
@@ -122429,6 +85837,11 @@ goog.exportProperty(
     'getState',
     ol.source.ImageCanvas.prototype.getState);
 
+goog.exportProperty(
+    ol.source.ImageCanvas.prototype,
+    'refresh',
+    ol.source.ImageCanvas.prototype.refresh);
+
 goog.exportProperty(
     ol.source.ImageCanvas.prototype,
     'setAttributions',
@@ -122469,6 +85882,11 @@ goog.exportProperty(
     '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',
@@ -122489,11 +85907,6 @@ goog.exportProperty(
     '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',
@@ -122514,6 +85927,11 @@ goog.exportProperty(
     'getState',
     ol.source.ImageMapGuide.prototype.getState);
 
+goog.exportProperty(
+    ol.source.ImageMapGuide.prototype,
+    'refresh',
+    ol.source.ImageMapGuide.prototype.refresh);
+
 goog.exportProperty(
     ol.source.ImageMapGuide.prototype,
     'setAttributions',
@@ -122554,6 +85972,11 @@ goog.exportProperty(
     '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',
@@ -122574,11 +85997,6 @@ goog.exportProperty(
     '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',
@@ -122599,6 +86017,11 @@ goog.exportProperty(
     'getState',
     ol.source.ImageStatic.prototype.getState);
 
+goog.exportProperty(
+    ol.source.ImageStatic.prototype,
+    'refresh',
+    ol.source.ImageStatic.prototype.refresh);
+
 goog.exportProperty(
     ol.source.ImageStatic.prototype,
     'setAttributions',
@@ -122639,6 +86062,11 @@ goog.exportProperty(
     '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',
@@ -122659,11 +86087,6 @@ goog.exportProperty(
     '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',
@@ -122684,6 +86107,11 @@ goog.exportProperty(
     'getState',
     ol.source.ImageVector.prototype.getState);
 
+goog.exportProperty(
+    ol.source.ImageVector.prototype,
+    'refresh',
+    ol.source.ImageVector.prototype.refresh);
+
 goog.exportProperty(
     ol.source.ImageVector.prototype,
     'setAttributions',
@@ -122724,6 +86152,11 @@ goog.exportProperty(
     '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',
@@ -122744,11 +86177,6 @@ goog.exportProperty(
     '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',
@@ -122769,6 +86197,11 @@ goog.exportProperty(
     'getState',
     ol.source.ImageWMS.prototype.getState);
 
+goog.exportProperty(
+    ol.source.ImageWMS.prototype,
+    'refresh',
+    ol.source.ImageWMS.prototype.refresh);
+
 goog.exportProperty(
     ol.source.ImageWMS.prototype,
     'setAttributions',
@@ -122809,6 +86242,11 @@ goog.exportProperty(
     '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',
@@ -122830,239 +86268,24 @@ goog.exportProperty(
     ol.source.ImageWMS.prototype.un);
 
 goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'unByKey',
-    ol.source.ImageWMS.prototype.unByKey);
-
-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,
-    'setTileLoadFunction',
-    ol.source.XYZ.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'setTileUrlFunction',
-    ol.source.XYZ.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getTileGrid',
-    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,
-    '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,
-    'getUrls',
-    ol.source.MapQuest.prototype.getUrls);
+    ol.source.OSM.prototype,
+    'setRenderReprojectionEdges',
+    ol.source.OSM.prototype.setRenderReprojectionEdges);
 
 goog.exportProperty(
-    ol.source.MapQuest.prototype,
-    'setUrl',
-    ol.source.MapQuest.prototype.setUrl);
+    ol.source.OSM.prototype,
+    'setTileGridForProjection',
+    ol.source.OSM.prototype.setTileGridForProjection);
 
 goog.exportProperty(
-    ol.source.MapQuest.prototype,
+    ol.source.OSM.prototype,
     'getTileLoadFunction',
-    ol.source.MapQuest.prototype.getTileLoadFunction);
+    ol.source.OSM.prototype.getTileLoadFunction);
 
 goog.exportProperty(
-    ol.source.MapQuest.prototype,
+    ol.source.OSM.prototype,
     'getTileUrlFunction',
-    ol.source.MapQuest.prototype.getTileUrlFunction);
-
-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,
-    '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,
-    '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);
+    ol.source.OSM.prototype.getTileUrlFunction);
 
 goog.exportProperty(
     ol.source.OSM.prototype,
@@ -123071,33 +86294,33 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.OSM.prototype,
-    'setUrl',
-    ol.source.OSM.prototype.setUrl);
+    'setTileLoadFunction',
+    ol.source.OSM.prototype.setTileLoadFunction);
 
 goog.exportProperty(
     ol.source.OSM.prototype,
-    'getTileLoadFunction',
-    ol.source.OSM.prototype.getTileLoadFunction);
+    'setTileUrlFunction',
+    ol.source.OSM.prototype.setTileUrlFunction);
 
 goog.exportProperty(
     ol.source.OSM.prototype,
-    'getTileUrlFunction',
-    ol.source.OSM.prototype.getTileUrlFunction);
+    'setUrl',
+    ol.source.OSM.prototype.setUrl);
 
 goog.exportProperty(
     ol.source.OSM.prototype,
-    'setTileLoadFunction',
-    ol.source.OSM.prototype.setTileLoadFunction);
+    'setUrls',
+    ol.source.OSM.prototype.setUrls);
 
 goog.exportProperty(
     ol.source.OSM.prototype,
-    'setTileUrlFunction',
-    ol.source.OSM.prototype.setTileUrlFunction);
+    'getTileGrid',
+    ol.source.OSM.prototype.getTileGrid);
 
 goog.exportProperty(
     ol.source.OSM.prototype,
-    'getTileGrid',
-    ol.source.OSM.prototype.getTileGrid);
+    'refresh',
+    ol.source.OSM.prototype.refresh);
 
 goog.exportProperty(
     ol.source.OSM.prototype,
@@ -123159,6 +86382,11 @@ goog.exportProperty(
     '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',
@@ -123179,11 +86407,6 @@ goog.exportProperty(
     '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',
@@ -123204,6 +86427,11 @@ goog.exportProperty(
     'getState',
     ol.source.Raster.prototype.getState);
 
+goog.exportProperty(
+    ol.source.Raster.prototype,
+    'refresh',
+    ol.source.Raster.prototype.refresh);
+
 goog.exportProperty(
     ol.source.Raster.prototype,
     'setAttributions',
@@ -123244,6 +86472,11 @@ goog.exportProperty(
     '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',
@@ -123265,19 +86498,34 @@ goog.exportProperty(
     ol.source.Raster.prototype.un);
 
 goog.exportProperty(
-    ol.source.Raster.prototype,
-    'unByKey',
-    ol.source.Raster.prototype.unByKey);
+    ol.source.Raster.Event.prototype,
+    'type',
+    ol.source.Raster.Event.prototype.type);
+
+goog.exportProperty(
+    ol.source.Raster.Event.prototype,
+    'target',
+    ol.source.Raster.Event.prototype.target);
+
+goog.exportProperty(
+    ol.source.Raster.Event.prototype,
+    'preventDefault',
+    ol.source.Raster.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.source.Raster.Event.prototype,
+    'stopPropagation',
+    ol.source.Raster.Event.prototype.stopPropagation);
 
 goog.exportProperty(
     ol.source.Stamen.prototype,
-    'getUrls',
-    ol.source.Stamen.prototype.getUrls);
+    'setRenderReprojectionEdges',
+    ol.source.Stamen.prototype.setRenderReprojectionEdges);
 
 goog.exportProperty(
     ol.source.Stamen.prototype,
-    'setUrl',
-    ol.source.Stamen.prototype.setUrl);
+    'setTileGridForProjection',
+    ol.source.Stamen.prototype.setTileGridForProjection);
 
 goog.exportProperty(
     ol.source.Stamen.prototype,
@@ -123289,6 +86537,11 @@ goog.exportProperty(
     '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',
@@ -123299,11 +86552,26 @@ goog.exportProperty(
     '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,
+    'refresh',
+    ol.source.Stamen.prototype.refresh);
+
 goog.exportProperty(
     ol.source.Stamen.prototype,
     'getAttributions',
@@ -123364,6 +86632,11 @@ goog.exportProperty(
     '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',
@@ -123385,9 +86658,34 @@ goog.exportProperty(
     ol.source.Stamen.prototype.un);
 
 goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'unByKey',
-    ol.source.Stamen.prototype.unByKey);
+    ol.source.Tile.Event.prototype,
+    'type',
+    ol.source.Tile.Event.prototype.type);
+
+goog.exportProperty(
+    ol.source.Tile.Event.prototype,
+    'target',
+    ol.source.Tile.Event.prototype.target);
+
+goog.exportProperty(
+    ol.source.Tile.Event.prototype,
+    'preventDefault',
+    ol.source.Tile.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.source.Tile.Event.prototype,
+    'stopPropagation',
+    ol.source.Tile.Event.prototype.stopPropagation);
+
+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,
@@ -123399,6 +86697,11 @@ goog.exportProperty(
     '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',
@@ -123409,11 +86712,26 @@ goog.exportProperty(
     '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,
+    'refresh',
+    ol.source.TileArcGISRest.prototype.refresh);
+
 goog.exportProperty(
     ol.source.TileArcGISRest.prototype,
     'getAttributions',
@@ -123474,6 +86792,11 @@ goog.exportProperty(
     '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',
@@ -123494,16 +86817,16 @@ goog.exportProperty(
     '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,
+    'refresh',
+    ol.source.TileDebug.prototype.refresh);
+
 goog.exportProperty(
     ol.source.TileDebug.prototype,
     'getAttributions',
@@ -123564,6 +86887,11 @@ goog.exportProperty(
     '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',
@@ -123585,9 +86913,14 @@ goog.exportProperty(
     ol.source.TileDebug.prototype.un);
 
 goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'unByKey',
-    ol.source.TileDebug.prototype.unByKey);
+    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,
@@ -123599,6 +86932,11 @@ goog.exportProperty(
     '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',
@@ -123609,11 +86947,26 @@ goog.exportProperty(
     '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,
+    'refresh',
+    ol.source.TileJSON.prototype.refresh);
+
 goog.exportProperty(
     ol.source.TileJSON.prototype,
     'getAttributions',
@@ -123674,6 +87027,11 @@ goog.exportProperty(
     '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',
@@ -123694,16 +87052,16 @@ goog.exportProperty(
     '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,
+    'refresh',
+    ol.source.TileUTFGrid.prototype.refresh);
+
 goog.exportProperty(
     ol.source.TileUTFGrid.prototype,
     'getAttributions',
@@ -123764,6 +87122,11 @@ goog.exportProperty(
     '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',
@@ -123785,164 +87148,14 @@ goog.exportProperty(
     ol.source.TileUTFGrid.prototype.un);
 
 goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'unByKey',
-    ol.source.TileUTFGrid.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'addFeature',
-    ol.source.TileVector.prototype.addFeature);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'addFeatures',
-    ol.source.TileVector.prototype.addFeatures);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'clear',
-    ol.source.TileVector.prototype.clear);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'forEachFeature',
-    ol.source.TileVector.prototype.forEachFeature);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'forEachFeatureInExtent',
-    ol.source.TileVector.prototype.forEachFeatureInExtent);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'forEachFeatureIntersectingExtent',
-    ol.source.TileVector.prototype.forEachFeatureIntersectingExtent);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getFeaturesCollection',
-    ol.source.TileVector.prototype.getFeaturesCollection);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getFeatures',
-    ol.source.TileVector.prototype.getFeatures);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getFeaturesAtCoordinate',
-    ol.source.TileVector.prototype.getFeaturesAtCoordinate);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getFeaturesInExtent',
-    ol.source.TileVector.prototype.getFeaturesInExtent);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getClosestFeatureToCoordinate',
-    ol.source.TileVector.prototype.getClosestFeatureToCoordinate);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getExtent',
-    ol.source.TileVector.prototype.getExtent);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getFeatureById',
-    ol.source.TileVector.prototype.getFeatureById);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'removeFeature',
-    ol.source.TileVector.prototype.removeFeature);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getAttributions',
-    ol.source.TileVector.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getLogo',
-    ol.source.TileVector.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getProjection',
-    ol.source.TileVector.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getState',
-    ol.source.TileVector.prototype.getState);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'setAttributions',
-    ol.source.TileVector.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'get',
-    ol.source.TileVector.prototype.get);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getKeys',
-    ol.source.TileVector.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getProperties',
-    ol.source.TileVector.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'set',
-    ol.source.TileVector.prototype.set);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'setProperties',
-    ol.source.TileVector.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'unset',
-    ol.source.TileVector.prototype.unset);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'changed',
-    ol.source.TileVector.prototype.changed);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'getRevision',
-    ol.source.TileVector.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'on',
-    ol.source.TileVector.prototype.on);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'once',
-    ol.source.TileVector.prototype.once);
-
-goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'un',
-    ol.source.TileVector.prototype.un);
+    ol.source.TileWMS.prototype,
+    'setRenderReprojectionEdges',
+    ol.source.TileWMS.prototype.setRenderReprojectionEdges);
 
 goog.exportProperty(
-    ol.source.TileVector.prototype,
-    'unByKey',
-    ol.source.TileVector.prototype.unByKey);
+    ol.source.TileWMS.prototype,
+    'setTileGridForProjection',
+    ol.source.TileWMS.prototype.setTileGridForProjection);
 
 goog.exportProperty(
     ol.source.TileWMS.prototype,
@@ -123954,6 +87167,11 @@ goog.exportProperty(
     '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',
@@ -123964,11 +87182,26 @@ goog.exportProperty(
     '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,
+    'refresh',
+    ol.source.TileWMS.prototype.refresh);
+
 goog.exportProperty(
     ol.source.TileWMS.prototype,
     'getAttributions',
@@ -124029,6 +87262,11 @@ goog.exportProperty(
     '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',
@@ -124050,9 +87288,164 @@ goog.exportProperty(
     ol.source.TileWMS.prototype.un);
 
 goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'unByKey',
-    ol.source.TileWMS.prototype.unByKey);
+    ol.source.Vector.Event.prototype,
+    'type',
+    ol.source.Vector.Event.prototype.type);
+
+goog.exportProperty(
+    ol.source.Vector.Event.prototype,
+    'target',
+    ol.source.Vector.Event.prototype.target);
+
+goog.exportProperty(
+    ol.source.Vector.Event.prototype,
+    'preventDefault',
+    ol.source.Vector.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.source.Vector.Event.prototype,
+    'stopPropagation',
+    ol.source.Vector.Event.prototype.stopPropagation);
+
+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,
+    'refresh',
+    ol.source.VectorTile.prototype.refresh);
+
+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.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,
@@ -124064,6 +87457,11 @@ goog.exportProperty(
     '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',
@@ -124074,11 +87472,26 @@ goog.exportProperty(
     '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,
+    'refresh',
+    ol.source.WMTS.prototype.refresh);
+
 goog.exportProperty(
     ol.source.WMTS.prototype,
     'getAttributions',
@@ -124139,6 +87552,11 @@ goog.exportProperty(
     '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',
@@ -124160,9 +87578,14 @@ goog.exportProperty(
     ol.source.WMTS.prototype.un);
 
 goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'unByKey',
-    ol.source.WMTS.prototype.unByKey);
+    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,
@@ -124174,6 +87597,11 @@ goog.exportProperty(
     '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',
@@ -124184,11 +87612,26 @@ goog.exportProperty(
     '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,
+    'refresh',
+    ol.source.Zoomify.prototype.refresh);
+
 goog.exportProperty(
     ol.source.Zoomify.prototype,
     'getAttributions',
@@ -124249,6 +87692,11 @@ goog.exportProperty(
     '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',
@@ -124270,15 +87718,25 @@ goog.exportProperty(
     ol.source.Zoomify.prototype.un);
 
 goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'unByKey',
-    ol.source.Zoomify.prototype.unByKey);
+    ol.reproj.Tile.prototype,
+    'getTileCoord',
+    ol.reproj.Tile.prototype.getTileCoord);
+
+goog.exportProperty(
+    ol.reproj.Tile.prototype,
+    'load',
+    ol.reproj.Tile.prototype.load);
 
 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',
@@ -124299,16 +87757,16 @@ goog.exportProperty(
     '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',
@@ -124329,16 +87787,16 @@ goog.exportProperty(
     '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',
@@ -124359,16 +87817,16 @@ goog.exportProperty(
     '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',
@@ -124389,16 +87847,16 @@ goog.exportProperty(
     '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',
@@ -124420,164 +87878,74 @@ goog.exportProperty(
     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,
+    ol.renderer.canvas.Layer.prototype,
     'changed',
-    ol.renderer.dom.Layer.prototype.changed);
-
-goog.exportProperty(
-    ol.renderer.dom.Layer.prototype,
-    'getRevision',
-    ol.renderer.dom.Layer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.renderer.dom.Layer.prototype,
-    'on',
-    ol.renderer.dom.Layer.prototype.on);
-
-goog.exportProperty(
-    ol.renderer.dom.Layer.prototype,
-    'once',
-    ol.renderer.dom.Layer.prototype.once);
-
-goog.exportProperty(
-    ol.renderer.dom.Layer.prototype,
-    'un',
-    ol.renderer.dom.Layer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.dom.Layer.prototype,
-    'unByKey',
-    ol.renderer.dom.Layer.prototype.unByKey);
+    ol.renderer.canvas.Layer.prototype.changed);
 
 goog.exportProperty(
-    ol.renderer.dom.ImageLayer.prototype,
-    'changed',
-    ol.renderer.dom.ImageLayer.prototype.changed);
+    ol.renderer.canvas.Layer.prototype,
+    'dispatchEvent',
+    ol.renderer.canvas.Layer.prototype.dispatchEvent);
 
 goog.exportProperty(
-    ol.renderer.dom.ImageLayer.prototype,
+    ol.renderer.canvas.Layer.prototype,
     'getRevision',
-    ol.renderer.dom.ImageLayer.prototype.getRevision);
+    ol.renderer.canvas.Layer.prototype.getRevision);
 
 goog.exportProperty(
-    ol.renderer.dom.ImageLayer.prototype,
+    ol.renderer.canvas.Layer.prototype,
     'on',
-    ol.renderer.dom.ImageLayer.prototype.on);
+    ol.renderer.canvas.Layer.prototype.on);
 
 goog.exportProperty(
-    ol.renderer.dom.ImageLayer.prototype,
+    ol.renderer.canvas.Layer.prototype,
     'once',
-    ol.renderer.dom.ImageLayer.prototype.once);
+    ol.renderer.canvas.Layer.prototype.once);
 
 goog.exportProperty(
-    ol.renderer.dom.ImageLayer.prototype,
+    ol.renderer.canvas.Layer.prototype,
     'un',
-    ol.renderer.dom.ImageLayer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.dom.ImageLayer.prototype,
-    'unByKey',
-    ol.renderer.dom.ImageLayer.prototype.unByKey);
+    ol.renderer.canvas.Layer.prototype.un);
 
 goog.exportProperty(
-    ol.renderer.dom.TileLayer.prototype,
+    ol.renderer.canvas.IntermediateCanvas.prototype,
     'changed',
-    ol.renderer.dom.TileLayer.prototype.changed);
+    ol.renderer.canvas.IntermediateCanvas.prototype.changed);
 
 goog.exportProperty(
-    ol.renderer.dom.TileLayer.prototype,
-    'getRevision',
-    ol.renderer.dom.TileLayer.prototype.getRevision);
+    ol.renderer.canvas.IntermediateCanvas.prototype,
+    'dispatchEvent',
+    ol.renderer.canvas.IntermediateCanvas.prototype.dispatchEvent);
 
 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,
+    ol.renderer.canvas.IntermediateCanvas.prototype,
     'getRevision',
-    ol.renderer.dom.VectorLayer.prototype.getRevision);
+    ol.renderer.canvas.IntermediateCanvas.prototype.getRevision);
 
 goog.exportProperty(
-    ol.renderer.dom.VectorLayer.prototype,
+    ol.renderer.canvas.IntermediateCanvas.prototype,
     'on',
-    ol.renderer.dom.VectorLayer.prototype.on);
+    ol.renderer.canvas.IntermediateCanvas.prototype.on);
 
 goog.exportProperty(
-    ol.renderer.dom.VectorLayer.prototype,
+    ol.renderer.canvas.IntermediateCanvas.prototype,
     'once',
-    ol.renderer.dom.VectorLayer.prototype.once);
+    ol.renderer.canvas.IntermediateCanvas.prototype.once);
 
 goog.exportProperty(
-    ol.renderer.dom.VectorLayer.prototype,
+    ol.renderer.canvas.IntermediateCanvas.prototype,
     'un',
-    ol.renderer.dom.VectorLayer.prototype.un);
+    ol.renderer.canvas.IntermediateCanvas.prototype.un);
 
 goog.exportProperty(
-    ol.renderer.dom.VectorLayer.prototype,
-    'unByKey',
-    ol.renderer.dom.VectorLayer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
+    ol.renderer.canvas.ImageLayer.prototype,
     'changed',
-    ol.renderer.canvas.Layer.prototype.changed);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
-    'getRevision',
-    ol.renderer.canvas.Layer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
-    'on',
-    ol.renderer.canvas.Layer.prototype.on);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
-    'once',
-    ol.renderer.canvas.Layer.prototype.once);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
-    'un',
-    ol.renderer.canvas.Layer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
-    'unByKey',
-    ol.renderer.canvas.Layer.prototype.unByKey);
+    ol.renderer.canvas.ImageLayer.prototype.changed);
 
 goog.exportProperty(
     ol.renderer.canvas.ImageLayer.prototype,
-    'changed',
-    ol.renderer.canvas.ImageLayer.prototype.changed);
+    'dispatchEvent',
+    ol.renderer.canvas.ImageLayer.prototype.dispatchEvent);
 
 goog.exportProperty(
     ol.renderer.canvas.ImageLayer.prototype,
@@ -124599,16 +87967,16 @@ goog.exportProperty(
     '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',
@@ -124629,16 +87997,16 @@ goog.exportProperty(
     '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',
@@ -124660,9 +88028,74 @@ goog.exportProperty(
     ol.renderer.canvas.VectorLayer.prototype.un);
 
 goog.exportProperty(
-    ol.renderer.canvas.VectorLayer.prototype,
-    'unByKey',
-    ol.renderer.canvas.VectorLayer.prototype.unByKey);
+    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.render.Event.prototype,
+    'type',
+    ol.render.Event.prototype.type);
+
+goog.exportProperty(
+    ol.render.Event.prototype,
+    'target',
+    ol.render.Event.prototype.target);
+
+goog.exportProperty(
+    ol.render.Event.prototype,
+    'preventDefault',
+    ol.render.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.render.Event.prototype,
+    'stopPropagation',
+    ol.render.Event.prototype.stopPropagation);
+
+goog.exportProperty(
+    ol.pointer.PointerEvent.prototype,
+    'type',
+    ol.pointer.PointerEvent.prototype.type);
+
+goog.exportProperty(
+    ol.pointer.PointerEvent.prototype,
+    'target',
+    ol.pointer.PointerEvent.prototype.target);
+
+goog.exportProperty(
+    ol.pointer.PointerEvent.prototype,
+    'preventDefault',
+    ol.pointer.PointerEvent.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.pointer.PointerEvent.prototype,
+    'stopPropagation',
+    ol.pointer.PointerEvent.prototype.stopPropagation);
 
 goog.exportProperty(
     ol.layer.Base.prototype,
@@ -124699,6 +88132,11 @@ goog.exportProperty(
     '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',
@@ -124720,9 +88158,124 @@ goog.exportProperty(
     ol.layer.Base.prototype.un);
 
 goog.exportProperty(
-    ol.layer.Base.prototype,
-    'unByKey',
-    ol.layer.Base.prototype.unByKey);
+    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.Layer.prototype,
@@ -124819,6 +88372,11 @@ goog.exportProperty(
     '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',
@@ -124839,11 +88397,6 @@ goog.exportProperty(
     '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',
@@ -124949,6 +88502,11 @@ goog.exportProperty(
     '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',
@@ -124969,11 +88527,6 @@ goog.exportProperty(
     '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',
@@ -125099,6 +88652,11 @@ goog.exportProperty(
     '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',
@@ -125119,11 +88677,6 @@ goog.exportProperty(
     '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',
@@ -125229,6 +88782,11 @@ goog.exportProperty(
     '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',
@@ -125250,259 +88808,284 @@ goog.exportProperty(
     ol.layer.Image.prototype.un);
 
 goog.exportProperty(
-    ol.layer.Image.prototype,
-    'unByKey',
-    ol.layer.Image.prototype.unByKey);
+    ol.layer.Tile.prototype,
+    'setMap',
+    ol.layer.Tile.prototype.setMap);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
+    'setSource',
+    ol.layer.Tile.prototype.setSource);
+
+goog.exportProperty(
+    ol.layer.Tile.prototype,
     'getExtent',
-    ol.layer.Group.prototype.getExtent);
+    ol.layer.Tile.prototype.getExtent);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'getMaxResolution',
-    ol.layer.Group.prototype.getMaxResolution);
+    ol.layer.Tile.prototype.getMaxResolution);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'getMinResolution',
-    ol.layer.Group.prototype.getMinResolution);
+    ol.layer.Tile.prototype.getMinResolution);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'getOpacity',
-    ol.layer.Group.prototype.getOpacity);
+    ol.layer.Tile.prototype.getOpacity);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'getVisible',
-    ol.layer.Group.prototype.getVisible);
+    ol.layer.Tile.prototype.getVisible);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'getZIndex',
-    ol.layer.Group.prototype.getZIndex);
+    ol.layer.Tile.prototype.getZIndex);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'setExtent',
-    ol.layer.Group.prototype.setExtent);
+    ol.layer.Tile.prototype.setExtent);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'setMaxResolution',
-    ol.layer.Group.prototype.setMaxResolution);
+    ol.layer.Tile.prototype.setMaxResolution);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'setMinResolution',
-    ol.layer.Group.prototype.setMinResolution);
+    ol.layer.Tile.prototype.setMinResolution);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'setOpacity',
-    ol.layer.Group.prototype.setOpacity);
+    ol.layer.Tile.prototype.setOpacity);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'setVisible',
-    ol.layer.Group.prototype.setVisible);
+    ol.layer.Tile.prototype.setVisible);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'setZIndex',
-    ol.layer.Group.prototype.setZIndex);
+    ol.layer.Tile.prototype.setZIndex);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'get',
-    ol.layer.Group.prototype.get);
+    ol.layer.Tile.prototype.get);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'getKeys',
-    ol.layer.Group.prototype.getKeys);
+    ol.layer.Tile.prototype.getKeys);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'getProperties',
-    ol.layer.Group.prototype.getProperties);
+    ol.layer.Tile.prototype.getProperties);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'set',
-    ol.layer.Group.prototype.set);
+    ol.layer.Tile.prototype.set);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'setProperties',
-    ol.layer.Group.prototype.setProperties);
+    ol.layer.Tile.prototype.setProperties);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'unset',
-    ol.layer.Group.prototype.unset);
+    ol.layer.Tile.prototype.unset);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'changed',
-    ol.layer.Group.prototype.changed);
+    ol.layer.Tile.prototype.changed);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
+    'dispatchEvent',
+    ol.layer.Tile.prototype.dispatchEvent);
+
+goog.exportProperty(
+    ol.layer.Tile.prototype,
     'getRevision',
-    ol.layer.Group.prototype.getRevision);
+    ol.layer.Tile.prototype.getRevision);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'on',
-    ol.layer.Group.prototype.on);
+    ol.layer.Tile.prototype.on);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'once',
-    ol.layer.Group.prototype.once);
+    ol.layer.Tile.prototype.once);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
+    ol.layer.Tile.prototype,
     'un',
-    ol.layer.Group.prototype.un);
+    ol.layer.Tile.prototype.un);
 
 goog.exportProperty(
-    ol.layer.Group.prototype,
-    'unByKey',
-    ol.layer.Group.prototype.unByKey);
+    ol.layer.VectorTile.prototype,
+    'getSource',
+    ol.layer.VectorTile.prototype.getSource);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    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.Tile.prototype.setMap);
+    ol.layer.VectorTile.prototype.setMap);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'setSource',
-    ol.layer.Tile.prototype.setSource);
+    ol.layer.VectorTile.prototype.setSource);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'getExtent',
-    ol.layer.Tile.prototype.getExtent);
+    ol.layer.VectorTile.prototype.getExtent);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'getMaxResolution',
-    ol.layer.Tile.prototype.getMaxResolution);
+    ol.layer.VectorTile.prototype.getMaxResolution);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'getMinResolution',
-    ol.layer.Tile.prototype.getMinResolution);
+    ol.layer.VectorTile.prototype.getMinResolution);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'getOpacity',
-    ol.layer.Tile.prototype.getOpacity);
+    ol.layer.VectorTile.prototype.getOpacity);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'getVisible',
-    ol.layer.Tile.prototype.getVisible);
+    ol.layer.VectorTile.prototype.getVisible);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'getZIndex',
-    ol.layer.Tile.prototype.getZIndex);
+    ol.layer.VectorTile.prototype.getZIndex);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'setExtent',
-    ol.layer.Tile.prototype.setExtent);
+    ol.layer.VectorTile.prototype.setExtent);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'setMaxResolution',
-    ol.layer.Tile.prototype.setMaxResolution);
+    ol.layer.VectorTile.prototype.setMaxResolution);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'setMinResolution',
-    ol.layer.Tile.prototype.setMinResolution);
+    ol.layer.VectorTile.prototype.setMinResolution);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'setOpacity',
-    ol.layer.Tile.prototype.setOpacity);
+    ol.layer.VectorTile.prototype.setOpacity);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'setVisible',
-    ol.layer.Tile.prototype.setVisible);
+    ol.layer.VectorTile.prototype.setVisible);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'setZIndex',
-    ol.layer.Tile.prototype.setZIndex);
+    ol.layer.VectorTile.prototype.setZIndex);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'get',
-    ol.layer.Tile.prototype.get);
+    ol.layer.VectorTile.prototype.get);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'getKeys',
-    ol.layer.Tile.prototype.getKeys);
+    ol.layer.VectorTile.prototype.getKeys);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'getProperties',
-    ol.layer.Tile.prototype.getProperties);
+    ol.layer.VectorTile.prototype.getProperties);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'set',
-    ol.layer.Tile.prototype.set);
+    ol.layer.VectorTile.prototype.set);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'setProperties',
-    ol.layer.Tile.prototype.setProperties);
+    ol.layer.VectorTile.prototype.setProperties);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'unset',
-    ol.layer.Tile.prototype.unset);
+    ol.layer.VectorTile.prototype.unset);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'changed',
-    ol.layer.Tile.prototype.changed);
+    ol.layer.VectorTile.prototype.changed);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
+    'dispatchEvent',
+    ol.layer.VectorTile.prototype.dispatchEvent);
+
+goog.exportProperty(
+    ol.layer.VectorTile.prototype,
     'getRevision',
-    ol.layer.Tile.prototype.getRevision);
+    ol.layer.VectorTile.prototype.getRevision);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'on',
-    ol.layer.Tile.prototype.on);
+    ol.layer.VectorTile.prototype.on);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'once',
-    ol.layer.Tile.prototype.once);
+    ol.layer.VectorTile.prototype.once);
 
 goog.exportProperty(
-    ol.layer.Tile.prototype,
+    ol.layer.VectorTile.prototype,
     'un',
-    ol.layer.Tile.prototype.un);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'unByKey',
-    ol.layer.Tile.prototype.unByKey);
+    ol.layer.VectorTile.prototype.un);
 
 goog.exportProperty(
     ol.interaction.Interaction.prototype,
@@ -125539,6 +89122,11 @@ goog.exportProperty(
     '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',
@@ -125559,16 +89147,16 @@ goog.exportProperty(
     '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',
@@ -125609,6 +89197,11 @@ goog.exportProperty(
     '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',
@@ -125629,16 +89222,16 @@ goog.exportProperty(
     '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',
@@ -125679,6 +89272,11 @@ goog.exportProperty(
     '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',
@@ -125700,15 +89298,35 @@ goog.exportProperty(
     ol.interaction.DragAndDrop.prototype.un);
 
 goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'unByKey',
-    ol.interaction.DragAndDrop.prototype.unByKey);
+    ol.interaction.DragAndDrop.Event.prototype,
+    'type',
+    ol.interaction.DragAndDrop.Event.prototype.type);
+
+goog.exportProperty(
+    ol.interaction.DragAndDrop.Event.prototype,
+    'target',
+    ol.interaction.DragAndDrop.Event.prototype.target);
+
+goog.exportProperty(
+    ol.interaction.DragAndDrop.Event.prototype,
+    'preventDefault',
+    ol.interaction.DragAndDrop.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.interaction.DragAndDrop.Event.prototype,
+    'stopPropagation',
+    ol.interaction.DragAndDrop.Event.prototype.stopPropagation);
 
 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',
@@ -125749,6 +89367,11 @@ goog.exportProperty(
     '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',
@@ -125769,16 +89392,16 @@ goog.exportProperty(
     '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',
@@ -125819,6 +89442,11 @@ goog.exportProperty(
     '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',
@@ -125840,15 +89468,35 @@ goog.exportProperty(
     ol.interaction.DragBox.prototype.un);
 
 goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'unByKey',
-    ol.interaction.DragBox.prototype.unByKey);
+    ol.interaction.DragBox.Event.prototype,
+    'type',
+    ol.interaction.DragBox.Event.prototype.type);
+
+goog.exportProperty(
+    ol.interaction.DragBox.Event.prototype,
+    'target',
+    ol.interaction.DragBox.Event.prototype.target);
+
+goog.exportProperty(
+    ol.interaction.DragBox.Event.prototype,
+    'preventDefault',
+    ol.interaction.DragBox.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.interaction.DragBox.Event.prototype,
+    'stopPropagation',
+    ol.interaction.DragBox.Event.prototype.stopPropagation);
 
 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',
@@ -125889,6 +89537,11 @@ goog.exportProperty(
     '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',
@@ -125910,149 +89563,154 @@ goog.exportProperty(
     ol.interaction.DragPan.prototype.un);
 
 goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'unByKey',
-    ol.interaction.DragPan.prototype.unByKey);
+    ol.interaction.DragRotate.prototype,
+    'getActive',
+    ol.interaction.DragRotate.prototype.getActive);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'getActive',
-    ol.interaction.DragRotateAndZoom.prototype.getActive);
+    ol.interaction.DragRotate.prototype,
+    'getMap',
+    ol.interaction.DragRotate.prototype.getMap);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
+    ol.interaction.DragRotate.prototype,
     'setActive',
-    ol.interaction.DragRotateAndZoom.prototype.setActive);
+    ol.interaction.DragRotate.prototype.setActive);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
+    ol.interaction.DragRotate.prototype,
     'get',
-    ol.interaction.DragRotateAndZoom.prototype.get);
+    ol.interaction.DragRotate.prototype.get);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
+    ol.interaction.DragRotate.prototype,
     'getKeys',
-    ol.interaction.DragRotateAndZoom.prototype.getKeys);
+    ol.interaction.DragRotate.prototype.getKeys);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
+    ol.interaction.DragRotate.prototype,
     'getProperties',
-    ol.interaction.DragRotateAndZoom.prototype.getProperties);
+    ol.interaction.DragRotate.prototype.getProperties);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
+    ol.interaction.DragRotate.prototype,
     'set',
-    ol.interaction.DragRotateAndZoom.prototype.set);
+    ol.interaction.DragRotate.prototype.set);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
+    ol.interaction.DragRotate.prototype,
     'setProperties',
-    ol.interaction.DragRotateAndZoom.prototype.setProperties);
+    ol.interaction.DragRotate.prototype.setProperties);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
+    ol.interaction.DragRotate.prototype,
     'unset',
-    ol.interaction.DragRotateAndZoom.prototype.unset);
+    ol.interaction.DragRotate.prototype.unset);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
+    ol.interaction.DragRotate.prototype,
     'changed',
-    ol.interaction.DragRotateAndZoom.prototype.changed);
+    ol.interaction.DragRotate.prototype.changed);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
+    ol.interaction.DragRotate.prototype,
+    'dispatchEvent',
+    ol.interaction.DragRotate.prototype.dispatchEvent);
+
+goog.exportProperty(
+    ol.interaction.DragRotate.prototype,
     'getRevision',
-    ol.interaction.DragRotateAndZoom.prototype.getRevision);
+    ol.interaction.DragRotate.prototype.getRevision);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
+    ol.interaction.DragRotate.prototype,
     'on',
-    ol.interaction.DragRotateAndZoom.prototype.on);
+    ol.interaction.DragRotate.prototype.on);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
+    ol.interaction.DragRotate.prototype,
     'once',
-    ol.interaction.DragRotateAndZoom.prototype.once);
+    ol.interaction.DragRotate.prototype.once);
 
 goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
+    ol.interaction.DragRotate.prototype,
     'un',
-    ol.interaction.DragRotateAndZoom.prototype.un);
+    ol.interaction.DragRotate.prototype.un);
 
 goog.exportProperty(
     ol.interaction.DragRotateAndZoom.prototype,
-    'unByKey',
-    ol.interaction.DragRotateAndZoom.prototype.unByKey);
+    'getActive',
+    ol.interaction.DragRotateAndZoom.prototype.getActive);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'getActive',
-    ol.interaction.DragRotate.prototype.getActive);
+    ol.interaction.DragRotateAndZoom.prototype,
+    'getMap',
+    ol.interaction.DragRotateAndZoom.prototype.getMap);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
+    ol.interaction.DragRotateAndZoom.prototype,
     'setActive',
-    ol.interaction.DragRotate.prototype.setActive);
+    ol.interaction.DragRotateAndZoom.prototype.setActive);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
+    ol.interaction.DragRotateAndZoom.prototype,
     'get',
-    ol.interaction.DragRotate.prototype.get);
+    ol.interaction.DragRotateAndZoom.prototype.get);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
+    ol.interaction.DragRotateAndZoom.prototype,
     'getKeys',
-    ol.interaction.DragRotate.prototype.getKeys);
+    ol.interaction.DragRotateAndZoom.prototype.getKeys);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
+    ol.interaction.DragRotateAndZoom.prototype,
     'getProperties',
-    ol.interaction.DragRotate.prototype.getProperties);
+    ol.interaction.DragRotateAndZoom.prototype.getProperties);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
+    ol.interaction.DragRotateAndZoom.prototype,
     'set',
-    ol.interaction.DragRotate.prototype.set);
+    ol.interaction.DragRotateAndZoom.prototype.set);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
+    ol.interaction.DragRotateAndZoom.prototype,
     'setProperties',
-    ol.interaction.DragRotate.prototype.setProperties);
+    ol.interaction.DragRotateAndZoom.prototype.setProperties);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
+    ol.interaction.DragRotateAndZoom.prototype,
     'unset',
-    ol.interaction.DragRotate.prototype.unset);
+    ol.interaction.DragRotateAndZoom.prototype.unset);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
+    ol.interaction.DragRotateAndZoom.prototype,
     'changed',
-    ol.interaction.DragRotate.prototype.changed);
+    ol.interaction.DragRotateAndZoom.prototype.changed);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
+    ol.interaction.DragRotateAndZoom.prototype,
+    'dispatchEvent',
+    ol.interaction.DragRotateAndZoom.prototype.dispatchEvent);
+
+goog.exportProperty(
+    ol.interaction.DragRotateAndZoom.prototype,
     'getRevision',
-    ol.interaction.DragRotate.prototype.getRevision);
+    ol.interaction.DragRotateAndZoom.prototype.getRevision);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
+    ol.interaction.DragRotateAndZoom.prototype,
     'on',
-    ol.interaction.DragRotate.prototype.on);
+    ol.interaction.DragRotateAndZoom.prototype.on);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
+    ol.interaction.DragRotateAndZoom.prototype,
     'once',
-    ol.interaction.DragRotate.prototype.once);
+    ol.interaction.DragRotateAndZoom.prototype.once);
 
 goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
+    ol.interaction.DragRotateAndZoom.prototype,
     'un',
-    ol.interaction.DragRotate.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'unByKey',
-    ol.interaction.DragRotate.prototype.unByKey);
+    ol.interaction.DragRotateAndZoom.prototype.un);
 
 goog.exportProperty(
     ol.interaction.DragZoom.prototype,
@@ -126064,6 +89722,11 @@ goog.exportProperty(
     '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',
@@ -126104,6 +89767,11 @@ goog.exportProperty(
     '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',
@@ -126124,16 +89792,16 @@ goog.exportProperty(
     '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',
@@ -126174,6 +89842,11 @@ goog.exportProperty(
     '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',
@@ -126195,15 +89868,130 @@ goog.exportProperty(
     ol.interaction.Draw.prototype.un);
 
 goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'unByKey',
-    ol.interaction.Draw.prototype.unByKey);
+    ol.interaction.Draw.Event.prototype,
+    'type',
+    ol.interaction.Draw.Event.prototype.type);
+
+goog.exportProperty(
+    ol.interaction.Draw.Event.prototype,
+    'target',
+    ol.interaction.Draw.Event.prototype.target);
+
+goog.exportProperty(
+    ol.interaction.Draw.Event.prototype,
+    'preventDefault',
+    ol.interaction.Draw.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.interaction.Draw.Event.prototype,
+    'stopPropagation',
+    ol.interaction.Draw.Event.prototype.stopPropagation);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'getActive',
+    ol.interaction.Extent.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'getMap',
+    ol.interaction.Extent.prototype.getMap);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'setActive',
+    ol.interaction.Extent.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'get',
+    ol.interaction.Extent.prototype.get);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'getKeys',
+    ol.interaction.Extent.prototype.getKeys);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'getProperties',
+    ol.interaction.Extent.prototype.getProperties);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'set',
+    ol.interaction.Extent.prototype.set);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'setProperties',
+    ol.interaction.Extent.prototype.setProperties);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'unset',
+    ol.interaction.Extent.prototype.unset);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'changed',
+    ol.interaction.Extent.prototype.changed);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'dispatchEvent',
+    ol.interaction.Extent.prototype.dispatchEvent);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'getRevision',
+    ol.interaction.Extent.prototype.getRevision);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'on',
+    ol.interaction.Extent.prototype.on);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'once',
+    ol.interaction.Extent.prototype.once);
+
+goog.exportProperty(
+    ol.interaction.Extent.prototype,
+    'un',
+    ol.interaction.Extent.prototype.un);
+
+goog.exportProperty(
+    ol.interaction.Extent.Event.prototype,
+    'type',
+    ol.interaction.Extent.Event.prototype.type);
+
+goog.exportProperty(
+    ol.interaction.Extent.Event.prototype,
+    'target',
+    ol.interaction.Extent.Event.prototype.target);
+
+goog.exportProperty(
+    ol.interaction.Extent.Event.prototype,
+    'preventDefault',
+    ol.interaction.Extent.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.interaction.Extent.Event.prototype,
+    'stopPropagation',
+    ol.interaction.Extent.Event.prototype.stopPropagation);
 
 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',
@@ -126244,6 +90032,11 @@ goog.exportProperty(
     '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',
@@ -126264,16 +90057,16 @@ goog.exportProperty(
     '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',
@@ -126314,6 +90107,11 @@ goog.exportProperty(
     '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',
@@ -126334,16 +90132,16 @@ goog.exportProperty(
     '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',
@@ -126384,6 +90182,11 @@ goog.exportProperty(
     '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',
@@ -126405,15 +90208,35 @@ goog.exportProperty(
     ol.interaction.Modify.prototype.un);
 
 goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'unByKey',
-    ol.interaction.Modify.prototype.unByKey);
+    ol.interaction.Modify.Event.prototype,
+    'type',
+    ol.interaction.Modify.Event.prototype.type);
+
+goog.exportProperty(
+    ol.interaction.Modify.Event.prototype,
+    'target',
+    ol.interaction.Modify.Event.prototype.target);
+
+goog.exportProperty(
+    ol.interaction.Modify.Event.prototype,
+    'preventDefault',
+    ol.interaction.Modify.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.interaction.Modify.Event.prototype,
+    'stopPropagation',
+    ol.interaction.Modify.Event.prototype.stopPropagation);
 
 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',
@@ -126454,6 +90277,11 @@ goog.exportProperty(
     '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',
@@ -126474,16 +90302,16 @@ goog.exportProperty(
     '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',
@@ -126524,6 +90352,11 @@ goog.exportProperty(
     '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',
@@ -126544,16 +90377,16 @@ goog.exportProperty(
     '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',
@@ -126594,6 +90427,11 @@ goog.exportProperty(
     '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',
@@ -126614,16 +90452,16 @@ goog.exportProperty(
     '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',
@@ -126664,6 +90502,11 @@ goog.exportProperty(
     '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',
@@ -126685,15 +90528,35 @@ goog.exportProperty(
     ol.interaction.Select.prototype.un);
 
 goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'unByKey',
-    ol.interaction.Select.prototype.unByKey);
+    ol.interaction.Select.Event.prototype,
+    'type',
+    ol.interaction.Select.Event.prototype.type);
+
+goog.exportProperty(
+    ol.interaction.Select.Event.prototype,
+    'target',
+    ol.interaction.Select.Event.prototype.target);
+
+goog.exportProperty(
+    ol.interaction.Select.Event.prototype,
+    'preventDefault',
+    ol.interaction.Select.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.interaction.Select.Event.prototype,
+    'stopPropagation',
+    ol.interaction.Select.Event.prototype.stopPropagation);
 
 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',
@@ -126734,6 +90597,11 @@ goog.exportProperty(
     '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',
@@ -126754,16 +90622,16 @@ goog.exportProperty(
     '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',
@@ -126804,6 +90672,11 @@ goog.exportProperty(
     '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',
@@ -126825,9 +90698,24 @@ goog.exportProperty(
     ol.interaction.Translate.prototype.un);
 
 goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'unByKey',
-    ol.interaction.Translate.prototype.unByKey);
+    ol.interaction.Translate.Event.prototype,
+    'type',
+    ol.interaction.Translate.Event.prototype.type);
+
+goog.exportProperty(
+    ol.interaction.Translate.Event.prototype,
+    'target',
+    ol.interaction.Translate.Event.prototype.target);
+
+goog.exportProperty(
+    ol.interaction.Translate.Event.prototype,
+    'preventDefault',
+    ol.interaction.Translate.Event.prototype.preventDefault);
+
+goog.exportProperty(
+    ol.interaction.Translate.Event.prototype,
+    'stopPropagation',
+    ol.interaction.Translate.Event.prototype.stopPropagation);
 
 goog.exportProperty(
     ol.geom.Geometry.prototype,
@@ -126864,6 +90752,11 @@ goog.exportProperty(
     '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',
@@ -126884,21 +90777,31 @@ goog.exportProperty(
     '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,
+    'intersectsCoordinate',
+    ol.geom.SimpleGeometry.prototype.intersectsCoordinate);
+
 goog.exportProperty(
     ol.geom.SimpleGeometry.prototype,
     'getExtent',
     ol.geom.SimpleGeometry.prototype.getExtent);
 
+goog.exportProperty(
+    ol.geom.SimpleGeometry.prototype,
+    'rotate',
+    ol.geom.SimpleGeometry.prototype.rotate);
+
+goog.exportProperty(
+    ol.geom.SimpleGeometry.prototype,
+    'scale',
+    ol.geom.SimpleGeometry.prototype.scale);
+
 goog.exportProperty(
     ol.geom.SimpleGeometry.prototype,
     'simplify',
@@ -126944,6 +90847,11 @@ goog.exportProperty(
     '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',
@@ -126964,11 +90872,6 @@ goog.exportProperty(
     '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',
@@ -126984,11 +90887,26 @@ goog.exportProperty(
     'getLayout',
     ol.geom.Circle.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.Circle.prototype,
+    'rotate',
+    ol.geom.Circle.prototype.rotate);
+
+goog.exportProperty(
+    ol.geom.Circle.prototype,
+    'scale',
+    ol.geom.Circle.prototype.scale);
+
 goog.exportProperty(
     ol.geom.Circle.prototype,
     'getClosestPoint',
     ol.geom.Circle.prototype.getClosestPoint);
 
+goog.exportProperty(
+    ol.geom.Circle.prototype,
+    'intersectsCoordinate',
+    ol.geom.Circle.prototype.intersectsCoordinate);
+
 goog.exportProperty(
     ol.geom.Circle.prototype,
     'getExtent',
@@ -127034,6 +90952,11 @@ goog.exportProperty(
     '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',
@@ -127054,21 +90977,31 @@ goog.exportProperty(
     '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,
+    'intersectsCoordinate',
+    ol.geom.GeometryCollection.prototype.intersectsCoordinate);
+
 goog.exportProperty(
     ol.geom.GeometryCollection.prototype,
     'getExtent',
     ol.geom.GeometryCollection.prototype.getExtent);
 
+goog.exportProperty(
+    ol.geom.GeometryCollection.prototype,
+    'rotate',
+    ol.geom.GeometryCollection.prototype.rotate);
+
+goog.exportProperty(
+    ol.geom.GeometryCollection.prototype,
+    'scale',
+    ol.geom.GeometryCollection.prototype.scale);
+
 goog.exportProperty(
     ol.geom.GeometryCollection.prototype,
     'simplify',
@@ -127114,6 +91047,11 @@ goog.exportProperty(
     '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',
@@ -127134,11 +91072,6 @@ goog.exportProperty(
     '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',
@@ -127154,11 +91087,26 @@ goog.exportProperty(
     'getLayout',
     ol.geom.LinearRing.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.LinearRing.prototype,
+    'rotate',
+    ol.geom.LinearRing.prototype.rotate);
+
+goog.exportProperty(
+    ol.geom.LinearRing.prototype,
+    'scale',
+    ol.geom.LinearRing.prototype.scale);
+
 goog.exportProperty(
     ol.geom.LinearRing.prototype,
     'getClosestPoint',
     ol.geom.LinearRing.prototype.getClosestPoint);
 
+goog.exportProperty(
+    ol.geom.LinearRing.prototype,
+    'intersectsCoordinate',
+    ol.geom.LinearRing.prototype.intersectsCoordinate);
+
 goog.exportProperty(
     ol.geom.LinearRing.prototype,
     'getExtent',
@@ -127209,6 +91157,11 @@ goog.exportProperty(
     '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',
@@ -127229,11 +91182,6 @@ goog.exportProperty(
     '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',
@@ -127249,11 +91197,26 @@ goog.exportProperty(
     'getLayout',
     ol.geom.LineString.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.LineString.prototype,
+    'rotate',
+    ol.geom.LineString.prototype.rotate);
+
+goog.exportProperty(
+    ol.geom.LineString.prototype,
+    'scale',
+    ol.geom.LineString.prototype.scale);
+
 goog.exportProperty(
     ol.geom.LineString.prototype,
     'getClosestPoint',
     ol.geom.LineString.prototype.getClosestPoint);
 
+goog.exportProperty(
+    ol.geom.LineString.prototype,
+    'intersectsCoordinate',
+    ol.geom.LineString.prototype.intersectsCoordinate);
+
 goog.exportProperty(
     ol.geom.LineString.prototype,
     'getExtent',
@@ -127304,6 +91267,11 @@ goog.exportProperty(
     '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',
@@ -127324,11 +91292,6 @@ goog.exportProperty(
     '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',
@@ -127344,11 +91307,26 @@ goog.exportProperty(
     'getLayout',
     ol.geom.MultiLineString.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.MultiLineString.prototype,
+    'rotate',
+    ol.geom.MultiLineString.prototype.rotate);
+
+goog.exportProperty(
+    ol.geom.MultiLineString.prototype,
+    'scale',
+    ol.geom.MultiLineString.prototype.scale);
+
 goog.exportProperty(
     ol.geom.MultiLineString.prototype,
     'getClosestPoint',
     ol.geom.MultiLineString.prototype.getClosestPoint);
 
+goog.exportProperty(
+    ol.geom.MultiLineString.prototype,
+    'intersectsCoordinate',
+    ol.geom.MultiLineString.prototype.intersectsCoordinate);
+
 goog.exportProperty(
     ol.geom.MultiLineString.prototype,
     'getExtent',
@@ -127399,6 +91377,11 @@ goog.exportProperty(
     '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',
@@ -127419,11 +91402,6 @@ goog.exportProperty(
     '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',
@@ -127439,11 +91417,26 @@ goog.exportProperty(
     'getLayout',
     ol.geom.MultiPoint.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.MultiPoint.prototype,
+    'rotate',
+    ol.geom.MultiPoint.prototype.rotate);
+
+goog.exportProperty(
+    ol.geom.MultiPoint.prototype,
+    'scale',
+    ol.geom.MultiPoint.prototype.scale);
+
 goog.exportProperty(
     ol.geom.MultiPoint.prototype,
     'getClosestPoint',
     ol.geom.MultiPoint.prototype.getClosestPoint);
 
+goog.exportProperty(
+    ol.geom.MultiPoint.prototype,
+    'intersectsCoordinate',
+    ol.geom.MultiPoint.prototype.intersectsCoordinate);
+
 goog.exportProperty(
     ol.geom.MultiPoint.prototype,
     'getExtent',
@@ -127494,6 +91487,11 @@ goog.exportProperty(
     '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',
@@ -127514,11 +91512,6 @@ goog.exportProperty(
     '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',
@@ -127534,11 +91527,26 @@ goog.exportProperty(
     'getLayout',
     ol.geom.MultiPolygon.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.MultiPolygon.prototype,
+    'rotate',
+    ol.geom.MultiPolygon.prototype.rotate);
+
+goog.exportProperty(
+    ol.geom.MultiPolygon.prototype,
+    'scale',
+    ol.geom.MultiPolygon.prototype.scale);
+
 goog.exportProperty(
     ol.geom.MultiPolygon.prototype,
     'getClosestPoint',
     ol.geom.MultiPolygon.prototype.getClosestPoint);
 
+goog.exportProperty(
+    ol.geom.MultiPolygon.prototype,
+    'intersectsCoordinate',
+    ol.geom.MultiPolygon.prototype.intersectsCoordinate);
+
 goog.exportProperty(
     ol.geom.MultiPolygon.prototype,
     'getExtent',
@@ -127589,6 +91597,11 @@ goog.exportProperty(
     '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',
@@ -127609,11 +91622,6 @@ goog.exportProperty(
     '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',
@@ -127629,11 +91637,26 @@ goog.exportProperty(
     'getLayout',
     ol.geom.Point.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.Point.prototype,
+    'rotate',
+    ol.geom.Point.prototype.rotate);
+
+goog.exportProperty(
+    ol.geom.Point.prototype,
+    'scale',
+    ol.geom.Point.prototype.scale);
+
 goog.exportProperty(
     ol.geom.Point.prototype,
     'getClosestPoint',
     ol.geom.Point.prototype.getClosestPoint);
 
+goog.exportProperty(
+    ol.geom.Point.prototype,
+    'intersectsCoordinate',
+    ol.geom.Point.prototype.intersectsCoordinate);
+
 goog.exportProperty(
     ol.geom.Point.prototype,
     'getExtent',
@@ -127684,6 +91707,11 @@ goog.exportProperty(
     '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',
@@ -127704,11 +91732,6 @@ goog.exportProperty(
     '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',
@@ -127724,11 +91747,26 @@ goog.exportProperty(
     'getLayout',
     ol.geom.Polygon.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.Polygon.prototype,
+    'rotate',
+    ol.geom.Polygon.prototype.rotate);
+
+goog.exportProperty(
+    ol.geom.Polygon.prototype,
+    'scale',
+    ol.geom.Polygon.prototype.scale);
+
 goog.exportProperty(
     ol.geom.Polygon.prototype,
     'getClosestPoint',
     ol.geom.Polygon.prototype.getClosestPoint);
 
+goog.exportProperty(
+    ol.geom.Polygon.prototype,
+    'intersectsCoordinate',
+    ol.geom.Polygon.prototype.intersectsCoordinate);
+
 goog.exportProperty(
     ol.geom.Polygon.prototype,
     'getExtent',
@@ -127779,6 +91817,11 @@ goog.exportProperty(
     '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',
@@ -127800,9 +91843,9 @@ goog.exportProperty(
     ol.geom.Polygon.prototype.un);
 
 goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'unByKey',
-    ol.geom.Polygon.prototype.unByKey);
+    ol.format.GML.prototype,
+    'readFeatures',
+    ol.format.GML.prototype.readFeatures);
 
 goog.exportProperty(
     ol.format.GML2.prototype,
@@ -127814,11 +91857,6 @@ goog.exportProperty(
     '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',
@@ -127854,6 +91892,11 @@ goog.exportProperty(
     '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',
@@ -127874,11 +91917,6 @@ goog.exportProperty(
     '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',
@@ -127929,6 +91967,11 @@ goog.exportProperty(
     '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',
@@ -127949,11 +91992,6 @@ goog.exportProperty(
     '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',
@@ -128004,6 +92042,11 @@ goog.exportProperty(
     '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',
@@ -128024,11 +92067,6 @@ goog.exportProperty(
     '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',
@@ -128079,6 +92117,11 @@ goog.exportProperty(
     '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',
@@ -128099,11 +92142,6 @@ goog.exportProperty(
     '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',
@@ -128154,6 +92192,11 @@ goog.exportProperty(
     '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',
@@ -128174,11 +92217,6 @@ goog.exportProperty(
     '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',
@@ -128229,6 +92267,11 @@ goog.exportProperty(
     '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',
@@ -128249,11 +92292,6 @@ goog.exportProperty(
     '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',
@@ -128304,6 +92342,11 @@ goog.exportProperty(
     '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',
@@ -128324,11 +92367,6 @@ goog.exportProperty(
     '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',
@@ -128379,6 +92417,11 @@ goog.exportProperty(
     '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',
@@ -128399,11 +92442,6 @@ goog.exportProperty(
     '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',
@@ -128454,6 +92492,11 @@ goog.exportProperty(
     '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',
@@ -128474,11 +92517,6 @@ goog.exportProperty(
     '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',
@@ -128529,6 +92567,11 @@ goog.exportProperty(
     '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',
@@ -128548,11 +92591,7 @@ goog.exportProperty(
     ol.control.ZoomToExtent.prototype,
     'un',
     ol.control.ZoomToExtent.prototype.un);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'unByKey',
-    ol.control.ZoomToExtent.prototype.unByKey);
+ol.VERSION = 'v4.2.0';
 OPENLAYERS.ol = ol;
 
   return OPENLAYERS.ol;
diff --git a/VIPSWeb/static/js/3rdparty/ol.js b/VIPSWeb/static/js/3rdparty/ol.js
old mode 100755
new mode 100644
index 22e2cfb7..cca34863
--- a/VIPSWeb/static/js/3rdparty/ol.js
+++ b/VIPSWeb/static/js/3rdparty/ol.js
@@ -1,8 +1,7 @@
-// OpenLayers 3. See http://openlayers.org/
-// License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md
-// Version: v3.10.1
-
-(function (root, factory) {
+// OpenLayers. See https://openlayers.org/
+// License: https://raw.githubusercontent.com/openlayers/openlayers/master/LICENSE.md
+// Version: v4.2.0
+;(function (root, factory) {
   if (typeof exports === "object") {
     module.exports = factory();
   } else if (typeof define === "function" && define.amd) {
@@ -12,937 +11,1029 @@
   }
 }(this, function () {
   var OPENLAYERS = {};
-  var l,aa=aa||{},ba=this;function ca(a){return void 0!==a}function t(a,c,d){a=a.split(".");d=d||ba;a[0]in d||!d.execScript||d.execScript("var "+a[0]);for(var e;a.length&&(e=a.shift());)!a.length&&ca(c)?d[e]=c:d[e]?d=d[e]:d=d[e]={}}function da(){}function ea(a){a.Bb=function(){return a.eg?a.eg:a.eg=new a}}
-function fa(a){var c=typeof a;if("object"==c)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return c;var d=Object.prototype.toString.call(a);if("[object Window]"==d)return"object";if("[object Array]"==d||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==d||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
-else if("function"==c&&"undefined"==typeof a.call)return"object";return c}function ga(a){return"array"==fa(a)}function ha(a){var c=fa(a);return"array"==c||"object"==c&&"number"==typeof a.length}function ia(a){return"string"==typeof a}function ja(a){return"number"==typeof a}function ka(a){return"function"==fa(a)}function la(a){var c=typeof a;return"object"==c&&null!=a||"function"==c}function v(a){return a[ma]||(a[ma]=++na)}var ma="closure_uid_"+(1E9*Math.random()>>>0),na=0;
-function oa(a,c,d){return a.call.apply(a.bind,arguments)}function pa(a,c,d){if(!a)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 a.apply(c,d)}}return function(){return a.apply(c,arguments)}}function qa(a,c,d){qa=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?oa:pa;return qa.apply(null,arguments)}
-function ra(a,c){var d=Array.prototype.slice.call(arguments,1);return function(){var c=d.slice();c.push.apply(c,arguments);return a.apply(this,c)}}var sa=Date.now||function(){return+new Date};function w(a,c){function d(){}d.prototype=c.prototype;a.ba=c.prototype;a.prototype=new d;a.prototype.constructor=a;a.Ao=function(a,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(a,h)}};var ta,ua;function wa(){};function xa(a){if(Error.captureStackTrace)Error.captureStackTrace(this,xa);else{var c=Error().stack;c&&(this.stack=c)}a&&(this.message=String(a))}w(xa,Error);xa.prototype.name="CustomError";var ya;function za(a,c){var d=a.length-c.length;return 0<=d&&a.indexOf(c,d)==d}function Aa(a,c){for(var d=a.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 Ba=String.prototype.trim?function(a){return a.trim()}:function(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")};
-function Ca(a){if(!Da.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(Ea,"&amp;"));-1!=a.indexOf("<")&&(a=a.replace(Fa,"&lt;"));-1!=a.indexOf(">")&&(a=a.replace(Ga,"&gt;"));-1!=a.indexOf('"')&&(a=a.replace(Ha,"&quot;"));-1!=a.indexOf("'")&&(a=a.replace(Ja,"&#39;"));-1!=a.indexOf("\x00")&&(a=a.replace(Ka,"&#0;"));return a}var Ea=/&/g,Fa=/</g,Ga=/>/g,Ha=/"/g,Ja=/'/g,Ka=/\x00/g,Da=/[\x00&<>"']/,La=String.prototype.repeat?function(a,c){return a.repeat(c)}:function(a,c){return Array(c+1).join(a)};
-function Na(a){a=ca(void 0)?a.toFixed(void 0):String(a);var c=a.indexOf(".");-1==c&&(c=a.length);return La("0",Math.max(0,2-c))+a}
-function Oa(a,c){for(var d=0,e=Ba(String(a)).split("."),f=Ba(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=Pa(0==q[1].length?0:parseInt(q[1],10),0==r[1].length?0:parseInt(r[1],10))||Pa(0==q[2].length,0==r[2].length)||Pa(q[2],r[2])}while(0==d)}return d}function Pa(a,c){return a<c?-1:a>c?1:0};function Qa(a,c,d){return Math.min(Math.max(a,c),d)}function Sa(a,c,d,e,f,g){var h=f-d,k=g-e;if(0!==h||0!==k){var m=((a-d)*h+(c-e)*k)/(h*h+k*k);1<m?(d=f,e=g):0<m&&(d+=h*m,e+=k*m)}return Ta(a,c,d,e)}function Ta(a,c,d,e){a=d-a;c=e-c;return a*a+c*c};function Ua(a){return function(c){if(c)return[Qa(c[0],a[0],a[2]),Qa(c[1],a[1],a[3])]}}function Va(a){return a};var Wa=Array.prototype;function Xa(a,c){return Wa.indexOf.call(a,c,void 0)}function Ya(a,c){Wa.forEach.call(a,c,void 0)}function Za(a,c){return Wa.filter.call(a,c,void 0)}function ab(a,c){return Wa.map.call(a,c,void 0)}function bb(a,c){return Wa.some.call(a,c,void 0)}function cb(a,c){var d=db(a,c,void 0);return 0>d?null:ia(a)?a.charAt(d):a[d]}function db(a,c,d){for(var e=a.length,f=ia(a)?a.split(""):a,g=0;g<e;g++)if(g in f&&c.call(d,f[g],g,a))return g;return-1}
-function eb(a,c){var d=Xa(a,c),e;(e=0<=d)&&Wa.splice.call(a,d,1);return e}function fb(a){return Wa.concat.apply(Wa,arguments)}function gb(a){var c=a.length;if(0<c){for(var d=Array(c),e=0;e<c;e++)d[e]=a[e];return d}return[]}function hb(a,c){for(var d=1;d<arguments.length;d++){var e=arguments[d];if(ha(e)){var f=a.length||0,g=e.length||0;a.length=f+g;for(var h=0;h<g;h++)a[f+h]=e[h]}else a.push(e)}}function ib(a,c,d,e){Wa.splice.apply(a,jb(arguments,1))}
-function jb(a,c,d){return 2>=arguments.length?Wa.slice.call(a,c):Wa.slice.call(a,c,d)}function kb(a,c){a.sort(c||lb)}function mb(a){for(var c=nb,d=0;d<a.length;d++)a[d]={index:d,value:a[d]};var e=c||lb;kb(a,function(a,c){return e(a.value,c.value)||a.index-c.index});for(d=0;d<a.length;d++)a[d]=a[d].value}function ob(a,c){if(!ha(a)||!ha(c)||a.length!=c.length)return!1;for(var d=a.length,e=qb,f=0;f<d;f++)if(!e(a[f],c[f]))return!1;return!0}function lb(a,c){return a>c?1:a<c?-1:0}
-function qb(a,c){return a===c}function rb(a){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=rb.apply(null,jb(e,f,f+8192)),h=0;h<g.length;h++)c.push(g[h]);else c.push(e)}return c};function sb(a,c){return 0<=a.indexOf(c)}function tb(a,c,d){var e=a.length;if(a[0]<=c)return 0;if(!(c<=a[e-1]))if(0<d)for(d=1;d<e;++d){if(a[d]<c)return d-1}else if(0>d)for(d=1;d<e;++d){if(a[d]<=c)return d}else for(d=1;d<e;++d){if(a[d]==c)return d;if(a[d]<c)return a[d-1]-c<c-a[d]?d-1:d}return e-1};function ub(a){return function(c,d,e){if(void 0!==c)return c=tb(a,c,e),c=Qa(c+d,0,a.length-1),a[c]}}function vb(a,c,d){return function(e,f,g){if(void 0!==e)return e=Math.max(Math.floor(Math.log(c/e)/Math.log(a)+(0<g?0:0>g?1:.5))+f,0),void 0!==d&&(e=Math.min(e,d)),c/Math.pow(a,e)}};function wb(a,c){var d=a%c;return 0>d*c?d+c:d}function xb(a,c,d){return a+d*(c-a)}function yb(a){return a*Math.PI/180};function zb(a){if(void 0!==a)return 0}function Ab(a,c){if(void 0!==a)return a+c}function Bb(a){var c=2*Math.PI/a;return function(a,e){if(void 0!==a)return a=Math.floor((a+e)/c+.5)*c}}function Cb(){var a=yb(5);return function(c,d){if(void 0!==c)return Math.abs(c+d)<=a?0:c+d}};function Db(a,c,d){this.center=a;this.resolution=c;this.rotation=d};var Fb;a:{var Gb=ba.navigator;if(Gb){var Hb=Gb.userAgent;if(Hb){Fb=Hb;break a}}Fb=""}function Ib(a){return-1!=Fb.indexOf(a)};function Jb(a,c,d){for(var e in a)c.call(d,a[e],e,a)}function Kb(a,c){for(var d in a)if(c.call(void 0,a[d],d,a))return!0;return!1}function Lb(a){var c=0,d;for(d in a)c++;return c}function Mb(a){var c=[],d=0,e;for(e in a)c[d++]=a[e];return c}function Nb(a){var c=[],d=0,e;for(e in a)c[d++]=e;return c}function Ob(a,c){return c in a}function Pb(a,c){for(var d in a)if(a[d]==c)return!0;return!1}function Qb(a,c){for(var d in a)if(c.call(void 0,a[d],d,a))return d}
-function Rb(a){for(var c in a)return!1;return!0}function Sb(a){for(var c in a)delete a[c]}function Tb(a,c,d){return c in a?a[c]:d}function Ub(a,c){var d=[];return c in a?a[c]:a[c]=d}function Vb(a){var c={},d;for(d in a)c[d]=a[d];return c}function Wb(a){var c=fa(a);if("object"==c||"array"==c){if(ka(a.clone))return a.clone();var c="array"==c?[]:{},d;for(d in a)c[d]=Wb(a[d]);return c}return a}var Xb="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" ");
-function Yb(a,c){for(var d,e,f=1;f<arguments.length;f++){e=arguments[f];for(d in e)a[d]=e[d];for(var g=0;g<Xb.length;g++)d=Xb[g],Object.prototype.hasOwnProperty.call(e,d)&&(a[d]=e[d])}};var Zb=Ib("Opera")||Ib("OPR"),$b=Ib("Trident")||Ib("MSIE"),ac=Ib("Edge"),bc=Ib("Gecko")&&!(-1!=Fb.toLowerCase().indexOf("webkit")&&!Ib("Edge"))&&!(Ib("Trident")||Ib("MSIE"))&&!Ib("Edge"),cc=-1!=Fb.toLowerCase().indexOf("webkit")&&!Ib("Edge"),dc=Ib("Macintosh"),ec=Ib("Windows"),fc=Ib("Linux")||Ib("CrOS");function gc(){var a=Fb;if(bc)return/rv\:([^\);]+)(\)|;)/.exec(a);if(ac)return/Edge\/([\d\.]+)/.exec(a);if($b)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(cc)return/WebKit\/(\S+)/.exec(a)}
-function hc(){var a=ba.document;return a?a.documentMode:void 0}var ic=function(){if(Zb&&ba.opera){var a;var c=ba.opera.version;try{a=c()}catch(d){a=c}return a}a="";(c=gc())&&(a=c?c[1]:"");return $b&&(c=hc(),c>parseFloat(a))?String(c):a}(),kc={};function lc(a){return kc[a]||(kc[a]=0<=Oa(ic,a))}var mc=ba.document,nc=mc&&$b?hc()||("CSS1Compat"==mc.compatMode?parseInt(ic,10):5):void 0;var oc=!$b||9<=nc,pc=!$b||9<=nc,qc=$b&&!lc("9");!cc||lc("528");bc&&lc("1.9b")||$b&&lc("8")||Zb&&lc("9.5")||cc&&lc("528");bc&&!lc("8")||$b&&lc("9");function rc(){0!=sc&&(tc[v(this)]=this);this.ca=this.ca;this.ka=this.ka}var sc=0,tc={};rc.prototype.ca=!1;rc.prototype.Tc=function(){if(!this.ca&&(this.ca=!0,this.Y(),0!=sc)){var a=v(this);delete tc[a]}};function uc(a,c){var d=ra(vc,c);a.ca?d.call(void 0):(a.ka||(a.ka=[]),a.ka.push(ca(void 0)?qa(d,void 0):d))}rc.prototype.Y=function(){if(this.ka)for(;this.ka.length;)this.ka.shift()()};function vc(a){a&&"function"==typeof a.Tc&&a.Tc()};function wc(a,c){this.type=a;this.g=this.target=c;this.i=!1;this.ah=!0}wc.prototype.c=function(){this.i=!0};wc.prototype.preventDefault=function(){this.ah=!1};function xc(a){a.c()}function yc(a){a.preventDefault()};var zc=$b?"focusout":"DOMFocusOut";function Ac(a){Ac[" "](a);return a}Ac[" "]=da;function Bc(a,c){wc.call(this,a?a.type:"");this.relatedTarget=this.g=this.target=null;this.A=this.j=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.v=this.f=this.a=this.C=!1;this.state=null;this.l=!1;this.b=null;if(a){var d=this.type=a.type,e=a.changedTouches?a.changedTouches[0]:null;this.target=a.target||a.srcElement;this.g=c;var f=a.relatedTarget;if(f){if(bc){var g;a:{try{Ac(f.nodeName);g=!0;break a}catch(h){}g=!1}g||(f=null)}}else"mouseover"==d?
-f=a.fromElement:"mouseout"==d&&(f=a.toElement);this.relatedTarget=f;null===e?(this.offsetX=cc||void 0!==a.offsetX?a.offsetX:a.layerX,this.offsetY=cc||void 0!==a.offsetY?a.offsetY:a.layerY,this.clientX=void 0!==a.clientX?a.clientX:a.pageX,this.clientY=void 0!==a.clientY?a.clientY:a.pageY,this.screenX=a.screenX||0,this.screenY=a.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=
-a.button;this.j=a.keyCode||0;this.A=a.charCode||("keypress"==d?a.keyCode:0);this.C=a.ctrlKey;this.a=a.altKey;this.f=a.shiftKey;this.v=a.metaKey;this.l=dc?a.metaKey:a.ctrlKey;this.state=a.state;this.b=a;a.defaultPrevented&&this.preventDefault()}}w(Bc,wc);var Cc=[1,4,2];function Dc(a){return(oc?0==a.b.button:"click"==a.type?!0:!!(a.b.button&Cc[0]))&&!(cc&&dc&&a.C)}Bc.prototype.c=function(){Bc.ba.c.call(this);this.b.stopPropagation?this.b.stopPropagation():this.b.cancelBubble=!0};
-Bc.prototype.preventDefault=function(){Bc.ba.preventDefault.call(this);var a=this.b;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,qc)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(c){}};var Fc="closure_listenable_"+(1E6*Math.random()|0);function Gc(a){return!(!a||!a[Fc])}var Hc=0;function Ic(a,c,d,e,f){this.listener=a;this.b=null;this.src=c;this.type=d;this.Mc=!!e;this.Nd=f;this.key=++Hc;this.Cc=this.td=!1}function Jc(a){a.Cc=!0;a.listener=null;a.b=null;a.src=null;a.Nd=null};function Kc(a){this.src=a;this.b={};this.a=0}Kc.prototype.add=function(a,c,d,e,f){var g=a.toString();a=this.b[g];a||(a=this.b[g]=[],this.a++);var h=Lc(a,c,e,f);-1<h?(c=a[h],d||(c.td=!1)):(c=new Ic(c,this.src,g,!!e,f),c.td=d,a.push(c));return c};Kc.prototype.remove=function(a,c,d,e){a=a.toString();if(!(a in this.b))return!1;var f=this.b[a];c=Lc(f,c,d,e);return-1<c?(Jc(f[c]),Wa.splice.call(f,c,1),0==f.length&&(delete this.b[a],this.a--),!0):!1};
-function Mc(a,c){var d=c.type;if(!(d in a.b))return!1;var e=eb(a.b[d],c);e&&(Jc(c),0==a.b[d].length&&(delete a.b[d],a.a--));return e}function Nc(a,c,d,e,f){a=a.b[c.toString()];c=-1;a&&(c=Lc(a,d,e,f));return-1<c?a[c]:null}function Oc(a,c,d){var e=ca(c),f=e?c.toString():"",g=ca(d);return Kb(a.b,function(a){for(var c=0;c<a.length;++c)if(!(e&&a[c].type!=f||g&&a[c].Mc!=d))return!0;return!1})}
-function Lc(a,c,d,e){for(var f=0;f<a.length;++f){var g=a[f];if(!g.Cc&&g.listener==c&&g.Mc==!!d&&g.Nd==e)return f}return-1};var Pc="closure_lm_"+(1E6*Math.random()|0),Qc={},Rc=0;function B(a,c,d,e,f){if(ga(c)){for(var g=0;g<c.length;g++)B(a,c[g],d,e,f);return null}d=Sc(d);return Gc(a)?a.Pa(c,d,e,f):Tc(a,c,d,!1,e,f)}
-function Tc(a,c,d,e,f,g){if(!c)throw Error("Invalid event type");var h=!!f,k=Uc(a);k||(a[Pc]=k=new Kc(a));d=k.add(c,d,e,f,g);if(d.b)return d;e=Vc();d.b=e;e.src=a;e.listener=d;if(a.addEventListener)a.addEventListener(c.toString(),e,h);else if(a.attachEvent)a.attachEvent(Wc(c.toString()),e);else throw Error("addEventListener and attachEvent are unavailable.");Rc++;return d}
-function Vc(){var a=Xc,c=pc?function(d){return a.call(c.src,c.listener,d)}:function(d){d=a.call(c.src,c.listener,d);if(!d)return d};return c}function Yc(a,c,d,e,f){if(ga(c)){for(var g=0;g<c.length;g++)Yc(a,c[g],d,e,f);return null}d=Sc(d);return Gc(a)?a.fb.add(String(c),d,!0,e,f):Tc(a,c,d,!0,e,f)}function Zc(a,c,d,e,f){if(ga(c))for(var g=0;g<c.length;g++)Zc(a,c[g],d,e,f);else d=Sc(d),Gc(a)?a.wf(c,d,e,f):a&&(a=Uc(a))&&(c=Nc(a,c,d,!!e,f))&&$c(c)}
-function $c(a){if(ja(a)||!a||a.Cc)return!1;var c=a.src;if(Gc(c))return Mc(c.fb,a);var d=a.type,e=a.b;c.removeEventListener?c.removeEventListener(d,e,a.Mc):c.detachEvent&&c.detachEvent(Wc(d),e);Rc--;(d=Uc(c))?(Mc(d,a),0==d.a&&(d.src=null,c[Pc]=null)):Jc(a);return!0}function Wc(a){return a in Qc?Qc[a]:Qc[a]="on"+a}function ad(a,c,d,e){var f=!0;if(a=Uc(a))if(c=a.b[c.toString()])for(c=c.concat(),a=0;a<c.length;a++){var g=c[a];g&&g.Mc==d&&!g.Cc&&(g=bd(g,e),f=f&&!1!==g)}return f}
-function bd(a,c){var d=a.listener,e=a.Nd||a.src;a.td&&$c(a);return d.call(e,c)}
-function Xc(a,c){if(a.Cc)return!0;if(!pc){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 Bc(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=a.type,h=f.length-1;!d.i&&0<=h;h--){d.g=f[h];var k=ad(f[h],g,!0,d),e=e&&k}for(h=0;!d.i&&h<f.length;h++)d.g=f[h],k=
-ad(f[h],g,!1,d),e=e&&k}return e}return bd(a,new Bc(c,this))}function Uc(a){a=a[Pc];return a instanceof Kc?a:null}var cd="__closure_events_fn_"+(1E9*Math.random()>>>0);function Sc(a){if(ka(a))return a;a[cd]||(a[cd]=function(c){return a.handleEvent(c)});return a[cd]};function dd(){rc.call(this);this.fb=new Kc(this);this.pd=this;this.Ma=null}w(dd,rc);dd.prototype[Fc]=!0;l=dd.prototype;l.addEventListener=function(a,c,d,e){B(this,a,c,d,e)};l.removeEventListener=function(a,c,d,e){Zc(this,a,c,d,e)};
-function C(a,c){var d,e=a.Ma;if(e)for(d=[];e;e=e.Ma)d.push(e);var e=a.pd,f=c,g=f.type||f;if(ia(f))f=new wc(f,e);else if(f instanceof wc)f.target=f.target||e;else{var h=f,f=new wc(g,e);Yb(f,h)}var h=!0,k;if(d)for(var m=d.length-1;!f.i&&0<=m;m--)k=f.g=d[m],h=ed(k,g,!0,f)&&h;f.i||(k=f.g=e,h=ed(k,g,!0,f)&&h,f.i||(h=ed(k,g,!1,f)&&h));if(d)for(m=0;!f.i&&m<d.length;m++)k=f.g=d[m],h=ed(k,g,!1,f)&&h;return h}
-l.Y=function(){dd.ba.Y.call(this);if(this.fb){var a=this.fb,c=0,d;for(d in a.b){for(var e=a.b[d],f=0;f<e.length;f++)++c,Jc(e[f]);delete a.b[d];a.a--}}this.Ma=null};l.Pa=function(a,c,d,e){return this.fb.add(String(a),c,!1,d,e)};l.wf=function(a,c,d,e){return this.fb.remove(String(a),c,d,e)};
-function ed(a,c,d,e){c=a.fb.b[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.Cc&&h.Mc==d){var k=h.listener,m=h.Nd||h.src;h.td&&Mc(a.fb,h);f=!1!==k.call(m,e)&&f}}return f&&0!=e.ah}function fd(a,c,d){return Oc(a.fb,ca(c)?String(c):void 0,d)};function gd(){dd.call(this);this.b=0}w(gd,dd);function hd(a){$c(a)}l=gd.prototype;l.s=function(){++this.b;C(this,"change")};l.K=function(){return this.b};l.D=function(a,c,d){return B(this,a,c,!1,d)};l.L=function(a,c,d){return Yc(this,a,c,!1,d)};l.J=function(a,c,d){Zc(this,a,c,!1,d)};l.M=hd;function id(a,c,d){wc.call(this,a);this.key=c;this.oldValue=d}w(id,wc);function jd(a){gd.call(this);v(this);this.C={};void 0!==a&&this.H(a)}w(jd,gd);var kd={};function ld(a){return kd.hasOwnProperty(a)?kd[a]:kd[a]="change:"+a}l=jd.prototype;l.get=function(a){var c;this.C.hasOwnProperty(a)&&(c=this.C[a]);return c};l.O=function(){return Object.keys(this.C)};l.P=function(){var a={},c;for(c in this.C)a[c]=this.C[c];return a};
-function md(a,c,d){var e;e=ld(c);C(a,new id(e,c,d));C(a,new id("propertychange",c,d))}l.set=function(a,c){var d=this.C[a];this.C[a]=c;md(this,a,d)};l.H=function(a){for(var c in a)this.set(c,a[c])};l.S=function(a){if(a in this.C){var c=this.C[a];delete this.C[a];md(this,a,c)}};function nd(a,c,d){void 0===d&&(d=[0,0]);d[0]=a[0]+2*c;d[1]=a[1]+2*c;return d}function od(a,c,d){void 0===d&&(d=[0,0]);d[0]=a[0]*c+.5|0;d[1]=a[1]*c+.5|0;return d}function pd(a,c){if(ga(a))return a;void 0===c?c=[a,a]:(c[0]=a,c[1]=a);return c};function qd(a,c){a[0]+=c[0];a[1]+=c[1];return a}function rd(a,c){var d=a[0],e=a[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 sd(a,c){var d=wb(a+180,360)-180,e=Math.abs(Math.round(3600*d));return Math.floor(e/3600)+"\u00b0 "+Na(Math.floor(e/60%60))+"\u2032 "+Na(Math.floor(e%60))+"\u2033 "+c.charAt(0>d?1:0)}
-function td(a,c,d){return a?c.replace("{x}",a[0].toFixed(d)).replace("{y}",a[1].toFixed(d)):""}function ud(a,c){for(var d=!0,e=a.length-1;0<=e;--e)if(a[e]!=c[e]){d=!1;break}return d}function vd(a,c){var d=Math.cos(c),e=Math.sin(c),f=a[1]*d+a[0]*e;a[0]=a[0]*d-a[1]*e;a[1]=f;return a}function wd(a,c){var d=a[0]-c[0],e=a[1]-c[1];return d*d+e*e}function xd(a,c){return wd(a,rd(a,c))}function yd(a,c){return td(a,"{x}, {y}",c)};function zd(a){this.length=a.length||a;for(var c=0;c<this.length;c++)this[c]=a[c]||0}zd.prototype.b=4;zd.prototype.set=function(a,c){c=c||0;for(var d=0;d<a.length&&c+d<this.length;d++)this[c+d]=a[d]};zd.prototype.toString=Array.prototype.join;"undefined"==typeof Float32Array&&(zd.BYTES_PER_ELEMENT=4,zd.prototype.BYTES_PER_ELEMENT=zd.prototype.b,zd.prototype.set=zd.prototype.set,zd.prototype.toString=zd.prototype.toString,t("Float32Array",zd,void 0));function Ad(a){this.length=a.length||a;for(var c=0;c<this.length;c++)this[c]=a[c]||0}Ad.prototype.b=8;Ad.prototype.set=function(a,c){c=c||0;for(var d=0;d<a.length&&c+d<this.length;d++)this[c+d]=a[d]};Ad.prototype.toString=Array.prototype.join;if("undefined"==typeof Float64Array){try{Ad.BYTES_PER_ELEMENT=8}catch(a){}Ad.prototype.BYTES_PER_ELEMENT=Ad.prototype.b;Ad.prototype.set=Ad.prototype.set;Ad.prototype.toString=Ad.prototype.toString;t("Float64Array",Ad,void 0)};function Bd(a,c,d,e,f){a[0]=c;a[1]=d;a[2]=e;a[3]=f};function Cd(){var a=Array(16);Dd(a,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);return a}function Ed(){var a=Array(16);Dd(a,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);return a}function Dd(a,c,d,e,f,g,h,k,m,n,p,q,r,u,y,A,F){a[0]=c;a[1]=d;a[2]=e;a[3]=f;a[4]=g;a[5]=h;a[6]=k;a[7]=m;a[8]=n;a[9]=p;a[10]=q;a[11]=r;a[12]=u;a[13]=y;a[14]=A;a[15]=F}
-function Fd(a,c){a[0]=c[0];a[1]=c[1];a[2]=c[2];a[3]=c[3];a[4]=c[4];a[5]=c[5];a[6]=c[6];a[7]=c[7];a[8]=c[8];a[9]=c[9];a[10]=c[10];a[11]=c[11];a[12]=c[12];a[13]=c[13];a[14]=c[14];a[15]=c[15]}function Gd(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=0;a[5]=1;a[6]=0;a[7]=0;a[8]=0;a[9]=0;a[10]=1;a[11]=0;a[12]=0;a[13]=0;a[14]=0;a[15]=1}
-function Hd(a,c,d){var e=a[0],f=a[1],g=a[2],h=a[3],k=a[4],m=a[5],n=a[6],p=a[7],q=a[8],r=a[9],u=a[10],y=a[11],A=a[12],F=a[13],z=a[14];a=a[15];var x=c[0],K=c[1],J=c[2],I=c[3],N=c[4],va=c[5],Ra=c[6],M=c[7],Ia=c[8],pb=c[9],Ma=c[10],Eb=c[11],$a=c[12],Ec=c[13],jc=c[14];c=c[15];d[0]=e*x+k*K+q*J+A*I;d[1]=f*x+m*K+r*J+F*I;d[2]=g*x+n*K+u*J+z*I;d[3]=h*x+p*K+y*J+a*I;d[4]=e*N+k*va+q*Ra+A*M;d[5]=f*N+m*va+r*Ra+F*M;d[6]=g*N+n*va+u*Ra+z*M;d[7]=h*N+p*va+y*Ra+a*M;d[8]=e*Ia+k*pb+q*Ma+A*Eb;d[9]=f*Ia+m*pb+r*Ma+F*Eb;d[10]=
-g*Ia+n*pb+u*Ma+z*Eb;d[11]=h*Ia+p*pb+y*Ma+a*Eb;d[12]=e*$a+k*Ec+q*jc+A*c;d[13]=f*$a+m*Ec+r*jc+F*c;d[14]=g*$a+n*Ec+u*jc+z*c;d[15]=h*$a+p*Ec+y*jc+a*c}
-function Id(a,c){var d=a[0],e=a[1],f=a[2],g=a[3],h=a[4],k=a[5],m=a[6],n=a[7],p=a[8],q=a[9],r=a[10],u=a[11],y=a[12],A=a[13],F=a[14],z=a[15],x=d*k-e*h,K=d*m-f*h,J=d*n-g*h,I=e*m-f*k,N=e*n-g*k,va=f*n-g*m,Ra=p*A-q*y,M=p*F-r*y,Ia=p*z-u*y,pb=q*F-r*A,Ma=q*z-u*A,Eb=r*z-u*F,$a=x*Eb-K*Ma+J*pb+I*Ia-N*M+va*Ra;0!=$a&&($a=1/$a,c[0]=(k*Eb-m*Ma+n*pb)*$a,c[1]=(-e*Eb+f*Ma-g*pb)*$a,c[2]=(A*va-F*N+z*I)*$a,c[3]=(-q*va+r*N-u*I)*$a,c[4]=(-h*Eb+m*Ia-n*M)*$a,c[5]=(d*Eb-f*Ia+g*M)*$a,c[6]=(-y*va+F*J-z*K)*$a,c[7]=(p*va-r*J+u*
-K)*$a,c[8]=(h*Ma-k*Ia+n*Ra)*$a,c[9]=(-d*Ma+e*Ia-g*Ra)*$a,c[10]=(y*N-A*J+z*x)*$a,c[11]=(-p*N+q*J-u*x)*$a,c[12]=(-h*pb+k*M-m*Ra)*$a,c[13]=(d*pb-e*M+f*Ra)*$a,c[14]=(-y*I+A*K-F*x)*$a,c[15]=(p*I-q*K+r*x)*$a)}function Jd(a,c,d){var e=a[1]*c+a[5]*d+0*a[9]+a[13],f=a[2]*c+a[6]*d+0*a[10]+a[14],g=a[3]*c+a[7]*d+0*a[11]+a[15];a[12]=a[0]*c+a[4]*d+0*a[8]+a[12];a[13]=e;a[14]=f;a[15]=g}
-function Kd(a,c,d){Dd(a,a[0]*c,a[1]*c,a[2]*c,a[3]*c,a[4]*d,a[5]*d,a[6]*d,a[7]*d,1*a[8],1*a[9],1*a[10],1*a[11],a[12],a[13],a[14],a[15])}function Ld(a,c){var d=a[0],e=a[1],f=a[2],g=a[3],h=a[4],k=a[5],m=a[6],n=a[7],p=Math.cos(c),q=Math.sin(c);a[0]=d*p+h*q;a[1]=e*p+k*q;a[2]=f*p+m*q;a[3]=g*p+n*q;a[4]=d*-q+h*p;a[5]=e*-q+k*p;a[6]=f*-q+m*p;a[7]=g*-q+n*p}new Float64Array(3);new Float64Array(3);new Float64Array(4);new Float64Array(4);new Float64Array(4);new Float64Array(16);function Md(a){for(var c=Nd(),d=0,e=a.length;d<e;++d)Od(c,a[d]);return c}function Pd(a,c,d){var e=Math.min.apply(null,a),f=Math.min.apply(null,c);a=Math.max.apply(null,a);c=Math.max.apply(null,c);return Qd(e,f,a,c,d)}function Rd(a,c,d){return d?(d[0]=a[0]-c,d[1]=a[1]-c,d[2]=a[2]+c,d[3]=a[3]+c,d):[a[0]-c,a[1]-c,a[2]+c,a[3]+c]}function Sd(a,c){return c?(c[0]=a[0],c[1]=a[1],c[2]=a[2],c[3]=a[3],c):a.slice()}
-function Td(a,c,d){c=c<a[0]?a[0]-c:a[2]<c?c-a[2]:0;a=d<a[1]?a[1]-d:a[3]<d?d-a[3]:0;return c*c+a*a}function Ud(a,c){return Vd(a,c[0],c[1])}function Wd(a,c){return a[0]<=c[0]&&c[2]<=a[2]&&a[1]<=c[1]&&c[3]<=a[3]}function Vd(a,c,d){return a[0]<=c&&c<=a[2]&&a[1]<=d&&d<=a[3]}function Xd(a,c){var d=a[1],e=a[2],f=a[3],g=c[0],h=c[1],k=0;g<a[0]?k=k|16:g>e&&(k=k|4);h<d?k|=8:h>f&&(k|=2);0===k&&(k=1);return k}function Nd(){return[Infinity,Infinity,-Infinity,-Infinity]}
-function Qd(a,c,d,e,f){return f?(f[0]=a,f[1]=c,f[2]=d,f[3]=e,f):[a,c,d,e]}function Yd(a,c){var d=a[0],e=a[1];return Qd(d,e,d,e,c)}function Zd(a,c){return a[0]==c[0]&&a[2]==c[2]&&a[1]==c[1]&&a[3]==c[3]}function $d(a,c){c[0]<a[0]&&(a[0]=c[0]);c[2]>a[2]&&(a[2]=c[2]);c[1]<a[1]&&(a[1]=c[1]);c[3]>a[3]&&(a[3]=c[3]);return a}function Od(a,c){c[0]<a[0]&&(a[0]=c[0]);c[0]>a[2]&&(a[2]=c[0]);c[1]<a[1]&&(a[1]=c[1]);c[1]>a[3]&&(a[3]=c[1])}
-function ae(a,c,d,e,f){for(;d<e;d+=f){var g=a,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 a}function be(a,c,d){var e;return(e=c.call(d,ce(a)))||(e=c.call(d,de(a)))||(e=c.call(d,ee(a)))?e:(e=c.call(d,fe(a)))?e:!1}function ce(a){return[a[0],a[1]]}function de(a){return[a[2],a[1]]}function ge(a){return[(a[0]+a[2])/2,(a[1]+a[3])/2]}
-function he(a,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]=a[0]+h*c-k*d,e[g]=a[1]+h*d+k*c;return Pd(f,e,void 0)}function ie(a){return a[3]-a[1]}function je(a,c,d){d=d?d:Nd();ke(a,c)&&(d[0]=a[0]>c[0]?a[0]:c[0],d[1]=a[1]>c[1]?a[1]:c[1],d[2]=a[2]<c[2]?a[2]:c[2],d[3]=a[3]<c[3]?a[3]:c[3]);return d}function fe(a){return[a[0],a[3]]}function ee(a){return[a[2],a[3]]}function le(a){return a[2]-a[0]}
-function ke(a,c){return a[0]<=c[2]&&a[2]>=c[0]&&a[1]<=c[3]&&a[3]>=c[1]}function me(a){return a[2]<a[0]||a[3]<a[1]}function ne(a,c){var d=(a[2]-a[0])/2*(c-1),e=(a[3]-a[1])/2*(c-1);a[0]-=d;a[2]+=d;a[1]-=e;a[3]+=e}function oe(a,c,d){a=[a[0],a[1],a[0],a[3],a[2],a[1],a[2],a[3]];c(a,a,2);return Pd([a[0],a[2],a[4],a[6]],[a[1],a[3],a[5],a[7]],d)};function pe(a){return function(){return a}}var qe=pe(!1),re=pe(!0),se=pe(null);function te(a){return a}function ue(a){var c;c=c||0;return function(){return a.apply(this,Array.prototype.slice.call(arguments,0,c))}}function ve(a){var c=arguments,d=c.length;return function(){for(var a,f=0;f<d;f++)a=c[f].apply(this,arguments);return a}}function we(a){var c=arguments,d=c.length;return function(){for(var a=0;a<d;a++)if(!c[a].apply(this,arguments))return!1;return!0}};/*
+  var k,aa=this;function t(a,b){var c=OPENLAYERS;a=a.split(".");c=c||aa;a[0]in c||!c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)a.length||void 0===b?c[d]&&c[d]!==Object.prototype[d]?c=c[d]:c=c[d]={}:c[d]=b};var ea,fa;function ia(a,b){return a>b?1:a<b?-1:0}function ja(a,b){return 0<=a.indexOf(b)}function ka(a,b,c){var d=a.length;if(a[0]<=b)return 0;if(!(b<=a[d-1]))if(0<c)for(c=1;c<d;++c){if(a[c]<b)return c-1}else if(0>c)for(c=1;c<d;++c){if(a[c]<=b)return c}else for(c=1;c<d;++c){if(a[c]==b)return c;if(a[c]<b)return a[c-1]-b<b-a[c]?c-1:c}return d-1}function la(a,b){var c=Array.isArray(b)?b:[b],d=c.length;for(b=0;b<d;b++)a[a.length]=c[b]}function ma(a,b){b=a.indexOf(b);-1<b&&a.splice(b,1)}
+function na(a,b){for(var c=a.length>>>0,d,e=0;e<c;e++)if(d=a[e],b(d,e,a))return d;return null}function pa(a,b){var c=a.length;if(c!==b.length)return!1;for(var d=0;d<c;d++)if(a[d]!==b[d])return!1;return!0}function qa(a){var b=ra,c=a.length,d=Array(a.length),e;for(e=0;e<c;e++)d[e]={index:e,value:a[e]};d.sort(function(a,c){return b(a.value,c.value)||a.index-c.index});for(e=0;e<a.length;e++)a[e]=d[e].value}function sa(a,b){var c;return a.every(function(d,e){c=e;return!b(d,e,a)})?-1:c}
+function ta(a,b){var c=b||ia;return a.every(function(b,e){if(!e)return!0;b=c(a[e-1],b);return!(0<b||0===b)})};function v(a,b){a.prototype=Object.create(b.prototype);a.prototype.constructor=a}function ua(){}function w(a){return a.Vo||(a.Vo=++va)}var va=0;function wa(a){this.message="Assertion failed. See https://openlayers.org/en/v4.2.0/doc/errors/#"+a+" for details.";this.code=a;this.name="AssertionError"}v(wa,Error);function xa(a,b){if(!a)throw new wa(b);};function ya(a,b,c,d){this.ca=a;this.$=b;this.da=c;this.ia=d}function za(a,b,c){return a.ca<=b&&b<=a.$&&a.da<=c&&c<=a.ia}function Aa(a,b){return a.ca==b.ca&&a.da==b.da&&a.$==b.$&&a.ia==b.ia}function Ba(a,b){return a.ca<=b.$&&a.$>=b.ca&&a.da<=b.ia&&a.ia>=b.da};function Ca(a,b,c){return Math.min(Math.max(a,b),c)}var Da=function(){var a;"cosh"in Math?a=Math.cosh:a=function(a){a=Math.exp(a);return(a+1/a)/2};return a}();function Ea(a){xa(0<a,29);return Math.pow(2,Math.ceil(Math.log(a)/Math.LN2))}function Fa(a,b,c,d,e,f){var g=e-c,h=f-d;if(g||h){var l=((a-c)*g+(b-d)*h)/(g*g+h*h);1<l?(c=e,d=f):0<l&&(c+=g*l,d+=h*l)}return Ga(a,b,c,d)}function Ga(a,b,c,d){a=c-a;b=d-b;return a*a+b*b}function Ha(a){return a*Math.PI/180}function Ia(a,b){a%=b;return 0>a*b?a+b:a}
+function Ja(a,b,c){return a+c*(b-a)};function Ka(a,b,c){void 0===c&&(c=[0,0]);c[0]=a[0]+2*b;c[1]=a[1]+2*b;return c}function La(a,b,c){void 0===c&&(c=[0,0]);c[0]=a[0]*b+.5|0;c[1]=a[1]*b+.5|0;return c}function Ma(a,b){if(Array.isArray(a))return a;void 0===b?b=[a,a]:b[0]=b[1]=a;return b};function Na(a){for(var b=Oa(),c=0,d=a.length;c<d;++c)Pa(b,a[c]);return b}function Qa(a,b,c){return c?(c[0]=a[0]-b,c[1]=a[1]-b,c[2]=a[2]+b,c[3]=a[3]+b,c):[a[0]-b,a[1]-b,a[2]+b,a[3]+b]}function Ra(a,b){return b?(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b):a.slice()}function Sa(a,b,c){b=b<a[0]?a[0]-b:a[2]<b?b-a[2]:0;a=c<a[1]?a[1]-c:a[3]<c?c-a[3]:0;return b*b+a*a}function Ta(a,b){return Ua(a,b[0],b[1])}function Va(a,b){return a[0]<=b[0]&&b[2]<=a[2]&&a[1]<=b[1]&&b[3]<=a[3]}
+function Ua(a,b,c){return a[0]<=b&&b<=a[2]&&a[1]<=c&&c<=a[3]}function Wa(a,b){var c=a[1],d=a[2],e=a[3],f=b[0];b=b[1];var g=0;f<a[0]?g|=16:f>d&&(g|=4);b<c?g|=8:b>e&&(g|=2);g||(g=1);return g}function Oa(){return[Infinity,Infinity,-Infinity,-Infinity]}function Xa(a,b,c,d,e){return e?(e[0]=a,e[1]=b,e[2]=c,e[3]=d,e):[a,b,c,d]}function Ya(a){return Xa(Infinity,Infinity,-Infinity,-Infinity,a)}function Za(a,b){var c=a[0];a=a[1];return Xa(c,a,c,a,b)}function $a(a,b,c,d,e){e=Ya(e);return ab(e,a,b,c,d)}
+function bb(a,b){return a[0]==b[0]&&a[2]==b[2]&&a[1]==b[1]&&a[3]==b[3]}function cb(a,b){b[0]<a[0]&&(a[0]=b[0]);b[2]>a[2]&&(a[2]=b[2]);b[1]<a[1]&&(a[1]=b[1]);b[3]>a[3]&&(a[3]=b[3]);return a}function Pa(a,b){b[0]<a[0]&&(a[0]=b[0]);b[0]>a[2]&&(a[2]=b[0]);b[1]<a[1]&&(a[1]=b[1]);b[1]>a[3]&&(a[3]=b[1])}function ab(a,b,c,d,e){for(;c<d;c+=e){var f=a,g=b[c],h=b[c+1];f[0]=Math.min(f[0],g);f[1]=Math.min(f[1],h);f[2]=Math.max(f[2],g);f[3]=Math.max(f[3],h)}return a}
+function db(a,b,c){var d;return(d=b.call(c,eb(a)))||(d=b.call(c,gb(a)))||(d=b.call(c,hb(a)))?d:(d=b.call(c,ib(a)))?d:!1}function jb(a){var b=0;kb(a)||(b=lb(a)*mb(a));return b}function eb(a){return[a[0],a[1]]}function gb(a){return[a[2],a[1]]}function nb(a){return[(a[0]+a[2])/2,(a[1]+a[3])/2]}
+function ob(a,b,c,d,e){var f=b*d[0]/2;d=b*d[1]/2;b=Math.cos(c);var g=Math.sin(c);c=f*b;f*=g;b*=d;var h=d*g,l=a[0],m=a[1];a=l-c+h;d=l-c-h;g=l+c-h;c=l+c+h;var h=m-f-b,l=m-f+b,n=m+f+b,f=m+f-b;return Xa(Math.min(a,d,g,c),Math.min(h,l,n,f),Math.max(a,d,g,c),Math.max(h,l,n,f),e)}function mb(a){return a[3]-a[1]}function pb(a,b,c){c=c?c:Oa();qb(a,b)&&(c[0]=a[0]>b[0]?a[0]:b[0],c[1]=a[1]>b[1]?a[1]:b[1],c[2]=a[2]<b[2]?a[2]:b[2],c[3]=a[3]<b[3]?a[3]:b[3]);return c}function ib(a){return[a[0],a[3]]}
+function hb(a){return[a[2],a[3]]}function lb(a){return a[2]-a[0]}function qb(a,b){return a[0]<=b[2]&&a[2]>=b[0]&&a[1]<=b[3]&&a[3]>=b[1]}function kb(a){return a[2]<a[0]||a[3]<a[1]}function rb(a,b){var c=(a[2]-a[0])/2*(b-1);b=(a[3]-a[1])/2*(b-1);a[0]-=c;a[2]+=c;a[1]-=b;a[3]+=b}
+function sb(a,b,c){a=[a[0],a[1],a[0],a[3],a[2],a[1],a[2],a[3]];b(a,a,2);var d=[a[0],a[2],a[4],a[6]],e=[a[1],a[3],a[5],a[7]];b=Math.min.apply(null,d);a=Math.min.apply(null,e);d=Math.max.apply(null,d);e=Math.max.apply(null,e);return Xa(b,a,d,e,c)};var tb="function"===typeof Object.assign?Object.assign:function(a,b){if(!a||null===a)throw new TypeError("Cannot convert undefined or null to object");for(var c=Object(a),d=1,e=arguments.length;d<e;++d){var f=arguments[d];if(void 0!==f&&null!==f)for(var g in f)f.hasOwnProperty(g)&&(c[g]=f[g])}return c};function ub(a){for(var b in a)delete a[b]}function vb(a){var b=[],c;for(c in a)b.push(a[c]);return b}function wb(a){for(var b in a)return!1;return!b};/*
 
  Latitude/longitude spherical geodesy formulae taken from
  http://www.movable-type.co.uk/scripts/latlong.html
  Licensed under CC-BY-3.0.
 */
-function xe(a){this.radius=a}xe.prototype.a=function(a){for(var c=0,d=a.length,e=a[d-1][0],f=a[d-1][1],g=0;g<d;g++)var h=a[g][0],k=a[g][1],c=c+yb(h-e)*(2+Math.sin(yb(f))+Math.sin(yb(k))),e=h,f=k;return c*this.radius*this.radius/2};xe.prototype.b=function(a,c){var d=yb(a[1]),e=yb(c[1]),f=(e-d)/2,g=yb(c[0]-a[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))};
-xe.prototype.offset=function(a,c,d){var e=yb(a[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*(yb(a[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 ye=new xe(6370997);var ze={};ze.degrees=2*Math.PI*ye.radius/360;ze.ft=.3048;ze.m=1;ze["us-ft"]=1200/3937;
-function Ae(a){this.b=a.code;this.a=a.units;this.j=void 0!==a.extent?a.extent:null;this.i=void 0!==a.worldExtent?a.worldExtent:null;this.g=void 0!==a.axisOrientation?a.axisOrientation:"enu";this.f=void 0!==a.global?a.global:!1;this.c=!(!this.f||!this.j);this.C=void 0!==a.getPointResolution?a.getPointResolution:this.fj;this.l=null;var c=Be,d=a.code;if("function"==typeof proj4&&void 0===c[d]){var e=proj4.defs(d);if(void 0!==e){void 0!==e.axis&&void 0===a.axisOrientation&&(this.g=e.axis);void 0===a.units&&
-(a=e.units,void 0===e.to_meter||void 0!==a&&void 0!==ze[a]||(a=e.to_meter.toString(),ze[a]=e.to_meter),this.a=a);for(var f in c)a=proj4.defs(f),void 0!==a&&(c=Ce(f),a===e?De([c,this]):(a=proj4(f,d),Ee(c,this,a.forward,a.inverse)))}}}l=Ae.prototype;l.Gi=function(){return this.b};l.R=function(){return this.j};l.yl=function(){return this.a};l.Ed=function(){return ze[this.a]};l.qj=function(){return this.i};function Fe(a){return a.g}l.ck=function(){return this.f};
-l.Qn=function(a){this.f=a;this.c=!(!a||!this.j)};l.zl=function(a){this.j=a;this.c=!(!this.f||!a)};l.$n=function(a){this.i=a};l.Pn=function(a){this.C=a};l.fj=function(a,c){if("degrees"==this.a)return a;var d=Ge(this,Ce("EPSG:4326")),e=[c[0]-a/2,c[1],c[0]+a/2,c[1],c[0],c[1]-a/2,c[0],c[1]+a/2],e=d(e,e,2),d=(ye.b(e.slice(0,2),e.slice(2,4))+ye.b(e.slice(4,6),e.slice(6,8)))/2,e=this.Ed();void 0!==e&&(d/=e);return d};l.getPointResolution=function(a,c){return this.C(a,c)};var Be={},He={};
-function De(a){Ie(a);a.forEach(function(c){a.forEach(function(a){c!==a&&Je(c,a,Le)})})}function Me(){var a=Ne,c=Oe,d=Pe;Qe.forEach(function(e){a.forEach(function(a){Je(e,a,c);Je(a,e,d)})})}function Re(a){Be[a.b]=a;Je(a,a,Le)}function Ie(a){var c=[];a.forEach(function(a){c.push(Re(a))})}function Se(a){return a?ia(a)?Ce(a):a:Ce("EPSG:3857")}function Je(a,c,d){a=a.b;c=c.b;a in He||(He[a]={});He[a][c]=d}function Ee(a,c,d,e){a=Ce(a);c=Ce(c);Je(a,c,Te(d));Je(c,a,Te(e))}
-function Te(a){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=a([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 Ce(a){var c;a instanceof Ae?c=a:ia(a)?(c=Be[a],void 0===c&&"function"==typeof proj4&&void 0!==proj4.defs(a)&&(c=new Ae({code:a}),Re(c))):c=null;return c}function Ue(a,c){return a===c?!0:a.b===c.b?!0:a.a!=c.a?!1:Ge(a,c)===Le}function Ve(a,c){var d=Ce(a),e=Ce(c);return Ge(d,e)}
-function Ge(a,c){var d=a.b,e=c.b,f;d in He&&e in He[d]&&(f=He[d][e]);void 0===f&&(f=We);return f}function We(a,c){if(void 0!==c&&a!==c){for(var d=0,e=a.length;d<e;++d)c[d]=a[d];a=c}return a}function Le(a,c){var d;if(void 0!==c){d=0;for(var e=a.length;d<e;++d)c[d]=a[d];d=c}else d=a.slice();return d}function Xe(a,c,d){return Ve(c,d)(a,void 0,a.length)}function Ye(a,c,d){c=Ve(c,d);return oe(a,c)};function Ze(){jd.call(this);this.A=Nd();this.v=-1;this.g={};this.l=this.j=0}w(Ze,jd);l=Ze.prototype;l.Xa=function(a,c){var d=c?c:[NaN,NaN];this.Va(a[0],a[1],d,Infinity);return d};l.He=function(a){return this.Xb(a[0],a[1])};l.Xb=qe;l.R=function(a){this.v!=this.b&&(this.A=this.ud(this.A),this.v=this.b);var c=this.A;a?(a[0]=c[0],a[1]=c[1],a[2]=c[2],a[3]=c[3]):a=c;return a};l.eb=function(a){return this.Hd(a*a)};l.Sa=function(a,c){this.Ob(Ve(a,c));return this};function $e(a,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 u=a[c],y=a[c+1];q[r++]=h*u+m*y+p;q[r++]=k*u+n*y+f}g&&q.length!=r&&(q.length=r);return q};function af(){Ze.call(this);this.a="XY";this.G=2;this.o=null}w(af,Ze);function bf(a){if("XY"==a)return 2;if("XYZ"==a||"XYM"==a)return 3;if("XYZM"==a)return 4}l=af.prototype;l.Xb=qe;l.ud=function(a){var c=this.o,d=this.o.length,e=this.G;a=Qd(Infinity,Infinity,-Infinity,-Infinity,a);return ae(a,c,0,d,e)};l.sb=function(){return this.o.slice(0,this.G)};l.tb=function(){return this.o.slice(this.o.length-this.G)};l.ub=function(){return this.a};
-l.Hd=function(a){this.l!=this.b&&(Sb(this.g),this.j=0,this.l=this.b);if(0>a||0!==this.j&&a<=this.j)return this;var c=a.toString();if(this.g.hasOwnProperty(c))return this.g[c];var d=this.tc(a);if(d.o.length<this.o.length)return this.g[c]=d;this.j=a;return this};l.tc=function(){return this};function cf(a,c,d){a.G=bf(c);a.a=c;a.o=d}function df(a,c,d,e){if(c)d=bf(c);else{for(c=0;c<e;++c){if(0===d.length){a.a="XY";a.G=2;return}d=d[0]}d=d.length;c=2==d?"XY":3==d?"XYZ":4==d?"XYZM":void 0}a.a=c;a.G=d}
-l.Ob=function(a){this.o&&(a(this.o,this.o,this.G),this.s())};l.wc=function(a,c){var d=this.o;if(d){var e=d.length,f=this.G,g=d?d:[],h=0,k,m;for(k=0;k<e;k+=f)for(g[h++]=d[k]+a,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.s()}};function ef(a,c,d,e){for(var f=0,g=a[d-e],h=a[d-e+1];c<d;c+=e)var k=a[c],m=a[c+1],f=f+(h*k-g*m),g=k,h=m;return f/2}function ff(a,c,d,e){var f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g],f=f+ef(a,c,k,e);c=k}return f};function gf(a,c,d,e,f,g,h){var k=a[c],m=a[c+1],n=a[d]-k,p=a[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]=xb(a[c+f],a[d+f],g);h.length=e;return}for(f=0;f<e;++f)h[f]=a[c+f];h.length=e}function hf(a,c,d,e,f){var g=a[c],h=a[c+1];for(c+=e;c<d;c+=e){var k=a[c],m=a[c+1],g=Ta(g,h,k,m);g>f&&(f=g);g=k;h=m}return f}function jf(a,c,d,e,f){var g,h;g=0;for(h=d.length;g<h;++g){var k=d[g];f=hf(a,c,k,e,f);c=k}return f}
-function kf(a,c,d,e,f,g,h,k,m,n,p){if(c==d)return n;var q;if(0===f){q=Ta(h,k,a[c],a[c+1]);if(q<n){for(p=0;p<e;++p)m[p]=a[c+p];m.length=e;return q}return n}for(var r=p?p:[NaN,NaN],u=c+e;u<d;)if(gf(a,u-e,u,e,h,k,r),q=Ta(h,k,r[0],r[1]),q<n){n=q;for(p=0;p<e;++p)m[p]=r[p];m.length=e;u+=e}else u+=e*Math.max((Math.sqrt(q)-Math.sqrt(n))/f|0,1);if(g&&(gf(a,d-e,c,e,h,k,r),q=Ta(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 lf(a,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 u=d[q];n=kf(a,c,u,e,f,g,h,k,m,n,p);c=u}return n};function mf(a,c){var d=0,e,f;e=0;for(f=c.length;e<f;++e)a[d++]=c[e];return d}function nf(a,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)a[c++]=h[k]}return c}function of(a,c,d,e,f){f=f?f:[];var g=0,h,k;h=0;for(k=d.length;h<k;++h)c=nf(a,c,d[h],e),f[g++]=c;f.length=g;return f};function pf(a,c,d,e,f){f=void 0!==f?f:[];for(var g=0;c<d;c+=e)f[g++]=a.slice(c,c+e);f.length=g;return f}function qf(a,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++]=pf(a,c,m,e,f[g]);c=m}f.length=g;return f};function rf(a,c,d,e,f,g,h){var k=(d-c)/e;if(3>k){for(;c<d;c+=e)g[h++]=a[c],g[h++]=a[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(),u=0,y=a[r],A=a[r+1],F=a[q],z=a[q+1];for(p=r+e;p<q;p+=e){var x=Sa(a[p],a[p+1],y,A,F,z);x>u&&(n=p,u=x)}u>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++]=a[c+p*e],g[h++]=a[c+p*e+1]);return h}
-function sf(a,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=a,r=p,u=e,y=f,A=g;if(c!=r){var F=y*Math.round(q[c]/y),z=y*Math.round(q[c+1]/y);c+=u;A[h++]=F;A[h++]=z;var x=void 0,K=void 0;do if(x=y*Math.round(q[c]/y),K=y*Math.round(q[c+1]/y),c+=u,c==r){A[h++]=x;A[h++]=K;break a}while(x==F&&K==z);for(;c<r;){var J,I;J=y*Math.round(q[c]/y);I=y*Math.round(q[c+1]/y);c+=u;if(J!=x||I!=K){var N=x-F,va=K-z,Ra=J-F,M=I-z;N*M==va*Ra&&(0>N&&Ra<N||N==Ra||0<N&&Ra>N)&&(0>va&&M<va||va==M||0<va&&
-M>va)||(A[h++]=x,A[h++]=K,F=x,z=K);x=J;K=I}}A[h++]=x;A[h++]=K}}k.push(h);c=p}return h};function tf(a,c){af.call(this);this.c=this.i=-1;this.ja(a,c)}w(tf,af);l=tf.prototype;l.clone=function(){var a=new tf(null);uf(a,this.a,this.o.slice());return a};l.Va=function(a,c,d,e){if(e<Td(this.R(),a,c))return e;this.c!=this.b&&(this.i=Math.sqrt(hf(this.o,0,this.o.length,this.G,0)),this.c=this.b);return kf(this.o,0,this.o.length,this.G,this.i,!0,a,c,d,e)};l.$k=function(){return ef(this.o,0,this.o.length,this.G)};l.U=function(){return pf(this.o,0,this.o.length,this.G)};
-l.tc=function(a){var c=[];c.length=rf(this.o,0,this.o.length,this.G,a,c,0);a=new tf(null);uf(a,"XY",c);return a};l.W=function(){return"LinearRing"};l.ja=function(a,c){a?(df(this,c,a,1),this.o||(this.o=[]),this.o.length=nf(this.o,0,a,this.G),this.s()):uf(this,"XY",null)};function uf(a,c,d){cf(a,c,d);a.s()};function D(a,c){af.call(this);this.ja(a,c)}w(D,af);l=D.prototype;l.clone=function(){var a=new D(null);vf(a,this.a,this.o.slice());return a};l.Va=function(a,c,d,e){var f=this.o;a=Ta(a,c,f[0],f[1]);if(a<e){e=this.G;for(c=0;c<e;++c)d[c]=f[c];d.length=e;return a}return e};l.U=function(){return this.o?this.o.slice():[]};l.ud=function(a){return Yd(this.o,a)};l.W=function(){return"Point"};l.ua=function(a){return Vd(a,this.o[0],this.o[1])};
-l.ja=function(a,c){a?(df(this,c,a,0),this.o||(this.o=[]),this.o.length=mf(this.o,a),this.s()):vf(this,"XY",null)};function vf(a,c,d){cf(a,c,d);a.s()};function wf(a,c,d,e,f){return!be(f,function(f){return!xf(a,c,d,e,f[0],f[1])})}function xf(a,c,d,e,f,g){for(var h=!1,k=a[d-e],m=a[d-e+1];c<d;c+=e){var n=a[c],p=a[c+1];m>g!=p>g&&f<(n-k)*(g-m)/(p-m)+k&&(h=!h);k=n;m=p}return h}function yf(a,c,d,e,f,g){if(0===d.length||!xf(a,c,d[0],e,f,g))return!1;var h;c=1;for(h=d.length;c<h;++c)if(xf(a,d[c-1],d[c],e,f,g))return!1;return!0};function zf(a,c,d,e,f,g,h){var k,m,n,p,q,r=f[g+1],u=[],y=d[0];n=a[y-e];q=a[y-e+1];for(k=c;k<y;k+=e){p=a[k];m=a[k+1];if(r<=q&&m<=r||q<=r&&r<=m)n=(r-q)/(m-q)*(p-n)+n,u.push(n);n=p;q=m}y=NaN;q=-Infinity;u.sort();n=u[0];k=1;for(m=u.length;k<m;++k){p=u[k];var A=Math.abs(p-n);A>q&&(n=(n+p)/2,yf(a,c,d,e,n,r)&&(y=n,q=A));n=p}isNaN(y)&&(y=f[g]);return h?(h.push(y,r),h):[y,r]};function Af(a,c,d,e,f,g){for(var h=[a[c],a[c+1]],k=[],m;c+e<d;c+=e){k[0]=a[c+e];k[1]=a[c+e+1];if(m=f.call(g,h,k))return m;h[0]=k[0];h[1]=k[1]}return!1};function Bf(a,c,d,e,f){var g=ae(Nd(),a,c,d,e);return ke(f,g)?Wd(f,g)||g[0]>=f[0]&&g[2]<=f[2]||g[1]>=f[1]&&g[3]<=f[3]?!0:Af(a,c,d,e,function(a,c){var d=!1,e=Xd(f,a),g=Xd(f,c);if(1===e||1===g)d=!0;else{var q=f[0],r=f[1],u=f[2],y=f[3],A=c[0],F=c[1],z=(F-a[1])/(A-a[0]);g&2&&!(e&2)&&(d=A-(F-y)/z,d=d>=q&&d<=u);d||!(g&4)||e&4||(d=F-(A-u)*z,d=d>=r&&d<=y);d||!(g&8)||e&8||(d=A-(F-r)/z,d=d>=q&&d<=u);d||!(g&16)||e&16||(d=F-(A-q)*z,d=d>=r&&d<=y)}return d}):!1}
-function Cf(a,c,d,e,f){var g=d[0];if(!(Bf(a,c,g,e,f)||xf(a,c,g,e,f[0],f[1])||xf(a,c,g,e,f[0],f[3])||xf(a,c,g,e,f[2],f[1])||xf(a,c,g,e,f[2],f[3])))return!1;if(1===d.length)return!0;c=1;for(g=d.length;c<g;++c)if(wf(a,d[c-1],d[c],e,f))return!1;return!0};function Df(a,c,d,e){for(var f=0,g=a[d-e],h=a[d-e+1];c<d;c+=e)var k=a[c],m=a[c+1],f=f+(k-g)*(m+h),g=k,h=m;return 0<f}function Ef(a,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=Df(a,f,k,d);if(0===g){if(e&&f||!e&&!f)return!1}else if(e&&!f||!e&&f)return!1;f=k}return!0}
-function Ff(a,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=Df(a,c,k,e);if(0===g?f&&m||!f&&!m:f&&!m||!f&&m)for(var m=a,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 Gf(a,c,d,e){var f=0,g,h;g=0;for(h=c.length;g<h;++g)f=Ff(a,f,c[g],d,e);return f};function E(a,c){af.call(this);this.c=[];this.u=-1;this.B=null;this.T=this.N=this.I=-1;this.i=null;this.ja(a,c)}w(E,af);l=E.prototype;l.li=function(a){this.o?hb(this.o,a.o):this.o=a.o.slice();this.c.push(this.o.length);this.s()};l.clone=function(){var a=new E(null);Hf(a,this.a,this.o.slice(),this.c.slice());return a};l.Va=function(a,c,d,e){if(e<Td(this.R(),a,c))return e;this.N!=this.b&&(this.I=Math.sqrt(jf(this.o,0,this.c,this.G,0)),this.N=this.b);return lf(this.o,0,this.c,this.G,this.I,!0,a,c,d,e)};
-l.Xb=function(a,c){return yf(If(this),0,this.c,this.G,a,c)};l.cl=function(){return ff(If(this),0,this.c,this.G)};l.U=function(a){var c;void 0!==a?(c=If(this).slice(),Ff(c,0,this.c,this.G,a)):c=this.o;return qf(c,0,this.c,this.G)};function Jf(a){if(a.u!=a.b){var c=ge(a.R());a.B=zf(If(a),0,a.c,a.G,c,0);a.u=a.b}return a.B}l.Si=function(){return new D(Jf(this))};l.Xi=function(){return this.c.length};
-l.Tf=function(a){if(0>a||this.c.length<=a)return null;var c=new tf(null);uf(c,this.a,this.o.slice(0===a?0:this.c[a-1],this.c[a]));return c};l.Dd=function(){var a=this.a,c=this.o,d=this.c,e=[],f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g],m=new tf(null);uf(m,a,c.slice(f,k));e.push(m);f=k}return e};function If(a){if(a.T!=a.b){var c=a.o;Ef(c,a.c,a.G)?a.i=c:(a.i=c.slice(),a.i.length=Ff(a.i,0,a.c,a.G));a.T=a.b}return a.i}
-l.tc=function(a){var c=[],d=[];c.length=sf(this.o,0,this.c,this.G,Math.sqrt(a),c,0,d);a=new E(null);Hf(a,"XY",c,d);return a};l.W=function(){return"Polygon"};l.ua=function(a){return Cf(If(this),0,this.c,this.G,a)};l.ja=function(a,c){if(a){df(this,c,a,2);this.o||(this.o=[]);var d=of(this.o,0,a,this.G,this.c);this.o.length=0===d.length?0:d[d.length-1];this.s()}else Hf(this,"XY",null,this.c)};function Hf(a,c,d,e){cf(a,c,d);a.c=e;a.s()}
-function Kf(a,c,d,e){var f=e?e:32;e=[];var g;for(g=0;g<f;++g)hb(e,a.offset(c,d,2*Math.PI*g/f));e.push(e[0],e[1]);a=new E(null);Hf(a,"XY",e,[e.length]);return a}function Lf(a){var c=a[0],d=a[1],e=a[2];a=a[3];c=[c,d,c,a,e,a,e,d,c,d];d=new E(null);Hf(d,"XY",c,[c.length]);return d}function Mf(a,c,d){var e=c?c:32,f=a.G;c=a.a;for(var g=new E(null,c),e=f*(e+1),f=[],h=0;h<e;h++)f[h]=0;Hf(g,c,f,[f.length]);Nf(g,a.$c(),a.df(),d);return g}
-function Nf(a,c,d,e){var f=a.o,g=a.a,h=a.G,k=a.c,m=f.length/h-1;e=e?e:0;for(var n,p,q=0;q<=m;++q)p=q*h,n=e+2*wb(q,m)*Math.PI/m,f[p]=c[0]+d*Math.cos(n),f[p+1]=c[1]+d*Math.sin(n);Hf(a,g,f,k)};function Of(a){jd.call(this);a=a||{};this.c=[0,0];var c={};c.center=void 0!==a.center?a.center:null;this.g=Se(a.projection);var d,e,f,g=void 0!==a.minZoom?a.minZoom:0;d=void 0!==a.maxZoom?a.maxZoom:28;var h=void 0!==a.zoomFactor?a.zoomFactor:2;if(void 0!==a.resolutions)d=a.resolutions,e=d[0],f=d[d.length-1],d=ub(d);else{e=Se(a.projection);f=e.R();var k=(f?Math.max(le(f),ie(f)):360*ze.degrees/ze[e.a])/256/Math.pow(2,0),m=k/Math.pow(2,28);e=a.maxResolution;void 0!==e?g=0:e=k/Math.pow(h,g);f=a.minResolution;
-void 0===f&&(f=void 0!==a.maxZoom?void 0!==a.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=vb(h,e,d-g)}this.a=e;this.i=f;this.f=g;g=void 0!==a.extent?Ua(a.extent):Va;(void 0!==a.enableRotation?a.enableRotation:1)?(e=a.constrainRotation,e=void 0===e||!0===e?Cb():!1===e?Ab:ja(e)?Bb(e):Ab):e=zb;this.j=new Db(g,d,e);void 0!==a.resolution?c.resolution=a.resolution:void 0!==a.zoom&&(c.resolution=this.constrainResolution(this.a,a.zoom-this.f));
-c.rotation=void 0!==a.rotation?a.rotation:0;this.H(c)}w(Of,jd);l=Of.prototype;l.vd=function(a){return this.j.center(a)};l.constrainResolution=function(a,c,d){return this.j.resolution(a,c||0,d||0)};l.constrainRotation=function(a,c){return this.j.rotation(a,c||0)};l.Fa=function(){return this.get("center")};l.Kc=function(a){var c=this.Fa(),d=this.aa(),e=this.va();return he(c,d,e,a)};l.Kk=function(){return this.g};l.aa=function(){return this.get("resolution")};
-function Qf(a){var c=a.a,d=Math.log(c/a.i)/Math.log(2);return function(a){return c/Math.pow(2,a*d)}}l.va=function(){return this.get("rotation")};function Rf(a){var c=a.a,d=Math.log(c/a.i)/Math.log(2);return function(a){return Math.log(c/a)/Math.log(2)/d}}function Sf(a){var c=a.Fa(),d=a.g,e=a.aa();a=a.va();return{center:[Math.round(c[0]/e)*e,Math.round(c[1]/e)*e],projection:void 0!==d?d:null,resolution:e,rotation:a}}
-l.rj=function(){var a,c=this.aa();if(void 0!==c){var d,e=0;do{d=this.constrainResolution(this.a,e);if(d==c){a=e;break}++e}while(d>this.i)}return void 0!==a?this.f+a:a};
-l.Ke=function(a,c,d){a instanceof af||(a=Lf(a));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.f,0):h=0;var k=a.o,m=this.va(),e=Math.cos(-m),m=Math.sin(-m),n=Infinity,p=Infinity,q=-Infinity,r=-Infinity;a=a.G;for(var u=0,y=k.length;u<y;u+=a)var A=k[u]*e-k[u+1]*m,F=k[u]*m+k[u+1]*e,n=Math.min(n,
-A),p=Math.min(p,F),q=Math.max(q,A),r=Math.max(r,F);k=[n,p,q,r];c=[c[0]-d[1]-d[3],c[1]-d[0]-d[2]];c=Math.max(le(k)/c[0],ie(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.wb(c);m=-m;g=(n+q)/2+(d[1]-d[3])/2*c;d=(p+r)/2+(d[0]-d[2])/2*c;this.Ra([g*e-d*m,d*e+g*m])};
-l.ri=function(a,c,d){var e=this.va(),f=Math.cos(-e),e=Math.sin(-e),g=a[0]*f-a[1]*e;a=a[1]*f+a[0]*e;var h=this.aa(),g=g+(c[0]/2-d[0])*h;a+=(d[1]-c[1]/2)*h;e=-e;this.Ra([g*f-a*e,a*f+g*e])};function Tf(a){return!!a.Fa()&&void 0!==a.aa()}l.rotate=function(a,c){if(void 0!==c){var d,e=this.Fa();void 0!==e&&(d=[e[0]-c[0],e[1]-c[1]],vd(d,a-this.va()),qd(d,c));this.Ra(d)}this.Xd(a)};l.Ra=function(a){this.set("center",a)};function Uf(a,c){a.c[1]+=c}l.wb=function(a){this.set("resolution",a)};
-l.Xd=function(a){this.set("rotation",a)};l.ao=function(a){a=this.constrainResolution(this.a,a-this.f,0);this.wb(a)};function Vf(a){return Math.pow(a,3)}function Wf(a){return 1-Vf(1-a)}function Xf(a){return 3*a*a-2*a*a*a}function Yf(a){return a}function Zf(a){return.5>a?Xf(2*a):1-Xf(2*(a-.5))};function $f(a){var c=a.source,d=a.start?a.start:Date.now(),e=c[0],f=c[1],g=void 0!==a.duration?a.duration:1E3,h=a.easing?a.easing:Xf;return function(a,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 ag(a){var c=a.rotation?a.rotation:0,d=a.start?a.start:Date.now(),e=void 0!==a.duration?a.duration:1E3,f=a.easing?a.easing:Xf,g=a.anchor?a.anchor:null;return function(a,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];vd(n,m);qd(n,g)}k.viewHints[0]+=1;return!0}return!1}}
-function bg(a){var c=a.resolution,d=a.start?a.start:Date.now(),e=void 0!==a.duration?a.duration:1E3,f=a.easing?a.easing:Xf;return function(a,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 cg(a,c,d,e){return void 0!==e?(e[0]=a,e[1]=c,e[2]=d,e):[a,c,d]}function dg(a,c,d){return a+"/"+c+"/"+d}function eg(a){var c=a[0],d=Array(c),e=1<<c-1,f,g;for(f=0;f<c;++f)g=48,a[1]&e&&(g+=1),a[2]&e&&(g+=2),d[f]=String.fromCharCode(g),e>>=1;return d.join("")}function fg(a){return dg(a[0],a[1],a[2])}function gg(a,c,d){var e=a[0],f=hg(c,a);d=ig(d);if(Ud(d,f))return a;a=le(d);f[0]+=a*Math.ceil((d[0]-f[0])/a);return c.Jd(f,e)}
-function jg(a,c){var d=a[0],e=a[1],f=a[2];if(c.minZoom>d||d>c.maxZoom)return!1;var g=c.R();return(d=g?kg(c,g,d):c.a?c.a[d]:null)?lg(d,e,f):!0};function mg(a,c,d,e){this.b=a;this.f=c;this.a=d;this.c=e}mg.prototype.contains=function(a){return lg(this,a[1],a[2])};function lg(a,c,d){return a.b<=c&&c<=a.f&&a.a<=d&&d<=a.c}function ng(a,c){return a.b==c.b&&a.a==c.a&&a.f==c.f&&a.c==c.c}function og(a){return a.c-a.a+1}function pg(a){return a.f-a.b+1}function qg(a,c){return a.b<=c.f&&a.f>=c.b&&a.a<=c.c&&a.c>=c.a};function rg(a){this.a=a.html;this.b=a.tileRanges?a.tileRanges:null}rg.prototype.c=function(){return this.a};function sg(a,c,d){wc.call(this,a,d);this.element=c}w(sg,wc);function tg(a){jd.call(this);this.a=a?a:[];ug(this)}w(tg,jd);l=tg.prototype;l.clear=function(){for(;0<this.Gb();)this.pop()};l.Ze=function(a){var c,d;c=0;for(d=a.length;c<d;++c)this.push(a[c]);return this};l.forEach=function(a,c){this.a.forEach(a,c)};l.tk=function(){return this.a};l.item=function(a){return this.a[a]};l.Gb=function(){return this.get("length")};l.Od=function(a,c){ib(this.a,a,0,c);ug(this);C(this,new sg("add",c,this))};
-l.pop=function(){return this.tf(this.Gb()-1)};l.push=function(a){var c=this.a.length;this.Od(c,a);return c};l.remove=function(a){var c=this.a,d,e;d=0;for(e=c.length;d<e;++d)if(c[d]===a)return this.tf(d)};l.tf=function(a){var c=this.a[a];Wa.splice.call(this.a,a,1);ug(this);C(this,new sg("remove",c,this));return c};l.Mn=function(a,c){var d=this.Gb();if(a<d)d=this.a[a],this.a[a]=c,C(this,new sg("remove",d,this)),C(this,new sg("add",c,this));else{for(;d<a;++d)this.Od(d,void 0);this.Od(a,c)}};
-function ug(a){a.set("length",a.a.length)};var vg=/^#(?:[0-9a-f]{3}){1,2}$/i,wg=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i,xg=/^(?: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 yg(a){return ga(a)?a:zg(a)}function Ag(a){if(!ia(a)){var c=a[0];c!=(c|0)&&(c=c+.5|0);var d=a[1];d!=(d|0)&&(d=d+.5|0);var e=a[2];e!=(e|0)&&(e=e+.5|0);a="rgba("+c+","+d+","+e+","+a[3]+")"}return a}
-var zg=function(){var a={},c=0;return function(d){var e;if(a.hasOwnProperty(d))e=a[d];else{if(1024<=c){e=0;for(var f in a)0===(e++&3)&&(delete a[f],--c)}var g,h;vg.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=xg.exec(d))?(e=Number(h[1]),f=Number(h[2]),g=Number(h[3]),h=Number(h[4]),e=[e,f,g,h],e=Bg(e,e)):(h=wg.exec(d))?(e=Number(h[1]),f=Number(h[2]),g=Number(h[3]),
-e=[e,f,g,1],e=Bg(e,e)):e=void 0;a[d]=e;++c}return e}}();function Bg(a,c){var d=c||[];d[0]=Qa(a[0]+.5|0,0,255);d[1]=Qa(a[1]+.5|0,0,255);d[2]=Qa(a[2]+.5|0,0,255);d[3]=Qa(a[3],0,1);return d};var Cg=!$b||9<=nc;!bc&&!$b||$b&&9<=nc||bc&&lc("1.9.1");$b&&lc("9");function Dg(a,c){this.x=ca(a)?a:0;this.y=ca(c)?c:0}l=Dg.prototype;l.clone=function(){return new Dg(this.x,this.y)};l.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};l.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};l.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};l.scale=function(a,c){var d=ja(c)?c:a;this.x*=a;this.y*=d;return this};function Eg(a,c){this.width=a;this.height=c}l=Eg.prototype;l.clone=function(){return new Eg(this.width,this.height)};l.oi=function(){return this.width*this.height};l.ya=function(){return!this.oi()};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(a,c){var d=ja(c)?c:a;this.width*=a;this.height*=d;return this};function Fg(a){return a?new Gg(Hg(a)):ya||(ya=new Gg)}function Ig(a){var c=document;return ia(a)?c.getElementById(a):a}function Jg(a,c){Jb(c,function(c,e){"style"==e?a.style.cssText=c:"class"==e?a.className=c:"for"==e?a.htmlFor=c:Kg.hasOwnProperty(e)?a.setAttribute(Kg[e],c):0==e.lastIndexOf("aria-",0)||0==e.lastIndexOf("data-",0)?a.setAttribute(e,c):a[e]=c})}
-var Kg={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"};function Lg(a){a=a.document.documentElement;return new Eg(a.clientWidth,a.clientHeight)}
-function Mg(a,c,d){var e=arguments,f=document,g=e[0],h=e[1];if(!Cg&&h&&(h.name||h.type)){g=["<",g];h.name&&g.push(' name="',Ca(h.name),'"');if(h.type){g.push(' type="',Ca(h.type),'"');var k={};Yb(k,h);delete k.type;h=k}g.push(">");g=g.join("")}g=f.createElement(g);h&&(ia(h)?g.className=h:ga(h)?g.className=h.join(" "):Jg(g,h));2<e.length&&Ng(f,g,e,2);return g}
-function Ng(a,c,d,e){function f(d){d&&c.appendChild(ia(d)?a.createTextNode(d):d)}for(;e<d.length;e++){var g=d[e];!ha(g)||la(g)&&0<g.nodeType?f(g):Ya(Og(g)?gb(g):g,f)}}function Pg(a){return document.createElement(a)}function Qg(a,c){Ng(Hg(a),a,arguments,1)}function Rg(a){for(var c;c=a.firstChild;)a.removeChild(c)}function Sg(a,c,d){a.insertBefore(c,a.childNodes[d]||null)}function Tg(a){a&&a.parentNode&&a.parentNode.removeChild(a)}function Ug(a,c){var d=c.parentNode;d&&d.replaceChild(a,c)}
-function Vg(a){if(ca(a.firstElementChild))a=a.firstElementChild;else for(a=a.firstChild;a&&1!=a.nodeType;)a=a.nextSibling;return a}function Wg(a,c){if(a.contains&&1==c.nodeType)return a==c||a.contains(c);if("undefined"!=typeof a.compareDocumentPosition)return a==c||Boolean(a.compareDocumentPosition(c)&16);for(;c&&a!=c;)c=c.parentNode;return c==a}function Hg(a){return 9==a.nodeType?a:a.ownerDocument||a.document}
-function Og(a){if(a&&"number"==typeof a.length){if(la(a))return"function"==typeof a.item||"string"==typeof a.item;if(ka(a))return"function"==typeof a.item}return!1}function Gg(a){this.b=a||ba.document||document}function Xg(){return!0}
-function Yg(a){var c=a.b;a=c.scrollingElement?c.scrollingElement:cc?c.body||c.documentElement:c.documentElement;c=c.parentWindow||c.defaultView;return $b&&lc("10")&&c.pageYOffset!=a.scrollTop?new Dg(a.scrollLeft,a.scrollTop):new Dg(c.pageXOffset||a.scrollLeft,c.pageYOffset||a.scrollTop)}Gg.prototype.appendChild=function(a,c){a.appendChild(c)};Gg.prototype.contains=Wg;function Zg(a){if(a.classList)return a.classList;a=a.className;return ia(a)&&a.match(/\S+/g)||[]}function $g(a,c){var d;a.classList?d=a.classList.contains(c):(d=Zg(a),d=0<=Xa(d,c));return d}function ah(a,c){a.classList?a.classList.add(c):$g(a,c)||(a.className+=0<a.className.length?" "+c:c)}function bh(a,c){a.classList?a.classList.remove(c):$g(a,c)&&(a.className=Za(Zg(a),function(a){return a!=c}).join(" "))}function ch(a,c){$g(a,c)?bh(a,c):ah(a,c)};function dh(a,c,d,e){this.top=a;this.right=c;this.bottom=d;this.left=e}l=dh.prototype;l.clone=function(){return new dh(this.top,this.right,this.bottom,this.left)};l.contains=function(a){return this&&a?a instanceof dh?a.left>=this.left&&a.right<=this.right&&a.top>=this.top&&a.bottom<=this.bottom:a.x>=this.left&&a.x<=this.right&&a.y>=this.top&&a.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(a,c){var d=ja(c)?c:a;this.left*=a;this.right*=a;this.top*=d;this.bottom*=d;return this};function eh(a,c,d,e){this.left=a;this.top=c;this.width=d;this.height=e}l=eh.prototype;l.clone=function(){return new eh(this.left,this.top,this.width,this.height)};l.contains=function(a){return a instanceof eh?this.left<=a.left&&this.left+this.width>=a.left+a.width&&this.top<=a.top&&this.top+this.height>=a.top+a.height:a.x>=this.left&&a.x<=this.left+this.width&&a.y>=this.top&&a.y<=this.top+this.height};
-l.distance=function(a){var c=a.x<this.left?this.left-a.x:Math.max(a.x-(this.left+this.width),0);a=a.y<this.top?this.top-a.y:Math.max(a.y-(this.top+this.height),0);return Math.sqrt(c*c+a*a)};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(a,c){var d=ja(c)?c:a;this.left*=a;this.width*=a;this.top*=d;this.height*=d;return this};function fh(a,c){var d=Hg(a);return d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(a,null))?d[c]||d.getPropertyValue(c)||"":""}function gh(a,c){return fh(a,c)||(a.currentStyle?a.currentStyle[c]:null)||a.style&&a.style[c]}function hh(a,c,d){var e;c instanceof Dg?(e=c.x,c=c.y):(e=c,c=d);a.style.left=ih(e);a.style.top=ih(c)}
-function jh(a){var c;try{c=a.getBoundingClientRect()}catch(d){return{left:0,top:0,right:0,bottom:0}}$b&&a.ownerDocument.body&&(a=a.ownerDocument,c.left-=a.documentElement.clientLeft+a.body.clientLeft,c.top-=a.documentElement.clientTop+a.body.clientTop);return c}function kh(a){if(1==a.nodeType)return a=jh(a),new Dg(a.left,a.top);a=a.changedTouches?a.changedTouches[0]:a;return new Dg(a.clientX,a.clientY)}function ih(a){"number"==typeof a&&(a=a+"px");return a}
-function lh(a){var c=mh;if("none"!=gh(a,"display"))return c(a);var d=a.style,e=d.display,f=d.visibility,g=d.position;d.visibility="hidden";d.position="absolute";d.display="inline";a=c(a);d.display=e;d.position=g;d.visibility=f;return a}function mh(a){var c=a.offsetWidth,d=a.offsetHeight,e=cc&&!c&&!d;return ca(c)&&!e||!a.getBoundingClientRect?new Eg(c,d):(a=jh(a),new Eg(a.right-a.left,a.bottom-a.top))}function nh(a,c){a.style.display=c?"":"none"}
-function oh(a,c,d,e){if(/^\d+px?$/.test(c))return parseInt(c,10);var f=a.style[d],g=a.runtimeStyle[d];a.runtimeStyle[d]=a.currentStyle[d];a.style[d]=c;c=a.style[e];a.style[d]=f;a.runtimeStyle[d]=g;return c}function ph(a,c){var d=a.currentStyle?a.currentStyle[c]:null;return d?oh(a,d,"left","pixelLeft"):0}
-function qh(a,c){if($b){var d=ph(a,c+"Left"),e=ph(a,c+"Right"),f=ph(a,c+"Top"),g=ph(a,c+"Bottom");return new dh(f,e,g,d)}d=fh(a,c+"Left");e=fh(a,c+"Right");f=fh(a,c+"Top");g=fh(a,c+"Bottom");return new dh(parseFloat(f),parseFloat(e),parseFloat(g),parseFloat(d))}var rh={thin:2,medium:4,thick:6};function sh(a,c){if("none"==(a.currentStyle?a.currentStyle[c+"Style"]:null))return 0;var d=a.currentStyle?a.currentStyle[c+"Width"]:null;return d in rh?rh[d]:oh(a,d,"left","pixelLeft")}
-function th(a){if($b&&!(9<=nc)){var c=sh(a,"borderLeft"),d=sh(a,"borderRight"),e=sh(a,"borderTop");a=sh(a,"borderBottom");return new dh(e,d,a,c)}c=fh(a,"borderLeftWidth");d=fh(a,"borderRightWidth");e=fh(a,"borderTopWidth");a=fh(a,"borderBottomWidth");return new dh(parseFloat(e),parseFloat(d),parseFloat(a),parseFloat(c))};function uh(a,c,d){wc.call(this,a);this.map=c;this.frameState=void 0!==d?d:null}w(uh,wc);function vh(a){jd.call(this);this.element=a.element?a.element:null;this.a=this.T=null;this.A=[];this.render=a.render?a.render:wa;a.target&&this.c(a.target)}w(vh,jd);vh.prototype.Y=function(){Tg(this.element);vh.ba.Y.call(this)};vh.prototype.g=function(){return this.a};
-vh.prototype.setMap=function(a){this.a&&Tg(this.element);0<this.A.length&&(this.A.forEach($c),this.A.length=0);if(this.a=a)(this.T?this.T:a.B).appendChild(this.element),this.render!==wa&&this.A.push(B(a,"postrender",this.render,!1,this)),a.render()};vh.prototype.c=function(a){this.T=Ig(a)};function wh(){this.c=0;this.f={};this.a=this.b=null}l=wh.prototype;l.clear=function(){this.c=0;this.f={};this.a=this.b=null};function xh(a,c){return a.f.hasOwnProperty(c)}l.forEach=function(a,c){for(var d=this.b;d;)a.call(c,d.kc,d.Rd,this),d=d.$a};l.get=function(a){a=this.f[a];if(a===this.a)return a.kc;a===this.b?(this.b=this.b.$a,this.b.Kb=null):(a.$a.Kb=a.Kb,a.Kb.$a=a.$a);a.$a=null;a.Kb=this.a;this.a=this.a.$a=a;return a.kc};l.Qb=function(){return this.c};
-l.O=function(){var a=Array(this.c),c=0,d;for(d=this.a;d;d=d.Kb)a[c++]=d.Rd;return a};l.Ub=function(){var a=Array(this.c),c=0,d;for(d=this.a;d;d=d.Kb)a[c++]=d.kc;return a};l.pop=function(){var a=this.b;delete this.f[a.Rd];a.$a&&(a.$a.Kb=null);this.b=a.$a;this.b||(this.a=null);--this.c;return a.kc};l.set=function(a,c){var d={Rd:a,$a:null,Kb:this.a,kc:c};this.a?this.a.$a=d:this.b=d;this.a=d;this.f[a]=d;++this.c};function yh(a){wh.call(this);this.g=void 0!==a?a:2048}w(yh,wh);function zh(a){return a.Qb()>a.g};function Ah(a,c){dd.call(this);this.b=a;this.state=c}w(Ah,dd);function Bh(a){C(a,"change")}Ah.prototype.jb=function(){return v(this).toString()};Ah.prototype.j=function(){return this.b};function Ch(a){jd.call(this);this.i=Ce(a.projection);this.j=void 0!==a.attributions?a.attributions:null;this.T=a.logo;this.v=void 0!==a.state?a.state:"ready";this.N=void 0!==a.wrapX?a.wrapX:!1}w(Ch,jd);l=Ch.prototype;l.be=wa;l.na=function(){return this.j};l.ma=function(){return this.T};l.oa=function(){return this.i};l.pa=function(){return this.v};function Dh(a){return a.N}l.la=function(a){this.j=a;this.s()};function Eh(a,c){a.v=c;a.s()};function Fh(a){this.minZoom=void 0!==a.minZoom?a.minZoom:0;this.b=a.resolutions;this.maxZoom=this.b.length-1;this.c=void 0!==a.origin?a.origin:null;this.g=null;void 0!==a.origins&&(this.g=a.origins);var c=a.extent;void 0===c||this.c||this.g||(this.c=fe(c));this.j=null;void 0!==a.tileSizes&&(this.j=a.tileSizes);this.l=void 0!==a.tileSize?a.tileSize:this.j?null:256;this.A=void 0!==c?c:null;this.a=null;void 0!==a.sizes?this.a=a.sizes.map(function(a){return new mg(Math.min(0,a[0]),Math.max(a[0]-1,-1),
-Math.min(0,a[1]),Math.max(a[1]-1,-1))},this):c&&Gh(this,c);this.f=[0,0]}var Hh=[0,0,0];function Ih(a,c,d,e,f){f=Jh(a,c,f);for(c=c[0]-1;c>=a.minZoom;){if(d.call(null,c,kg(a,f,c,e)))return!0;--c}return!1}l=Fh.prototype;l.R=function(){return this.A};l.Uf=function(){return this.maxZoom};l.Vf=function(){return this.minZoom};l.ta=function(a){return this.c?this.c:this.g[a]};l.aa=function(a){return this.b[a]};l.Kg=function(){return this.b};
-function Kh(a,c,d,e){return c[0]<a.maxZoom?(e=Jh(a,c,e),kg(a,e,c[0]+1,d)):null}function Lh(a,c,d,e){Mh(a,c[0],c[1],d,!1,Hh);var f=Hh[1],g=Hh[2];Mh(a,c[2],c[3],d,!0,Hh);a=Hh[1];c=Hh[2];void 0!==e?(e.b=f,e.f=a,e.a=g,e.c=c):e=new mg(f,a,g,c);return e}function kg(a,c,d,e){d=a.aa(d);return Lh(a,c,d,e)}function hg(a,c){var d=a.ta(c[0]),e=a.aa(c[0]),f=pd(a.Ka(c[0]),a.f);return[d[0]+(c[1]+.5)*f[0]*e,d[1]+(c[2]+.5)*f[1]*e]}
-function Jh(a,c,d){var e=a.ta(c[0]),f=a.aa(c[0]);a=pd(a.Ka(c[0]),a.f);var g=e[0]+c[1]*a[0]*f;c=e[1]+c[2]*a[1]*f;return Qd(g,c,g+a[0]*f,c+a[1]*f,d)}l.Yc=function(a,c,d){return Mh(this,a[0],a[1],c,!1,d)};function Mh(a,c,d,e,f,g){var h=Nh(a,e),k=e/a.aa(h),m=a.ta(h);a=pd(a.Ka(h),a.f);c=k*Math.floor((c-m[0])/e+(f?.5:0))/a[0];d=k*Math.floor((d-m[1])/e+(f?0:.5))/a[1];f?(c=Math.ceil(c)-1,d=Math.ceil(d)-1):(c=Math.floor(c),d=Math.floor(d));return cg(h,c,d,g)}
-l.Jd=function(a,c,d){c=this.aa(c);return Mh(this,a[0],a[1],c,!1,d)};l.Ka=function(a){return this.l?this.l:this.j[a]};function Nh(a,c){var d=tb(a.b,c,0);return Qa(d,a.minZoom,a.maxZoom)}function Gh(a,c){for(var d=a.b.length,e=Array(d),f=a.minZoom;f<d;++f)e[f]=kg(a,c,f);a.a=e}function Oh(a){var c={};Yb(c,void 0!==a?a:{});void 0===c.extent&&(c.extent=Ce("EPSG:3857").R());c.resolutions=Ph(c.extent,c.maxZoom,c.tileSize);delete c.maxZoom;return new Fh(c)}
-function Ph(a,c,d){c=void 0!==c?c:42;var e=ie(a);a=le(a);d=pd(void 0!==d?d:256);d=Math.max(a/d[0],e/d[1]);c+=1;e=Array(c);for(a=0;a<c;++a)e[a]=d/Math.pow(2,a);return e}function ig(a){a=Ce(a);var c=a.R();c||(a=180*ze.degrees/a.Ed(),c=Qd(-a,-a,a,a));return c};function Rh(a){Ch.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,projection:a.projection,state:a.state,wrapX:a.wrapX});this.da=void 0!==a.opaque?a.opaque:!1;this.fa=void 0!==a.tilePixelRatio?a.tilePixelRatio:1;this.tileGrid=void 0!==a.tileGrid?a.tileGrid:null;this.a=new yh;this.c=[0,0]}w(Rh,Ch);function Sh(a,c,d,e){for(var f=!0,g,h,k=d.b;k<=d.f;++k)for(var m=d.a;m<=d.c;++m)g=a.hb(c,k,m),h=!1,xh(a.a,g)&&(g=a.a.get(g),(h=2===g.state)&&(h=!1!==e(g))),h||(f=!1);return f}l=Rh.prototype;
-l.Bd=function(){return 0};l.hb=dg;l.Ca=function(){return this.tileGrid};function Th(a,c){var d;if(a.tileGrid)d=a.tileGrid;else if(d=c.l,!d){d=ig(c);var e=Ph(d,void 0,void 0);d=new Fh({extent:d,origin:fe(d),resolutions:e,tileSize:void 0});c.l=d}return d}l.Tb=function(a,c,d){c=Th(this,d);return od(pd(c.Ka(a),this.c),this.fa,this.c)};function Uh(a,c,d){d=void 0!==d?d:a.i;var e=Th(a,d);a.N&&d.f&&(c=gg(c,e,d));return jg(c,e)?c:null}l.yf=wa;function Vh(a,c){wc.call(this,a);this.tile=c}w(Vh,wc);function Wh(a){a=a?a:{};this.B=Pg("UL");this.v=Pg("LI");this.B.appendChild(this.v);nh(this.v,!1);this.f=void 0!==a.collapsed?a.collapsed:!0;this.i=void 0!==a.collapsible?a.collapsible:!0;this.i||(this.f=!1);var c=a.className?a.className:"ol-attribution",d=a.tipLabel?a.tipLabel:"Attributions",e=a.collapseLabel?a.collapseLabel:"\u00bb";this.N=ia(e)?Mg("SPAN",{},e):e;e=a.label?a.label:"i";this.I=ia(e)?Mg("SPAN",{},e):e;d=Mg("BUTTON",{type:"button",title:d},this.i&&!this.f?this.N:this.I);B(d,"click",
-this.Nk,!1,this);B(d,["mouseout",zc],function(){this.blur()},!1);c=Mg("DIV",c+" ol-unselectable ol-control"+(this.f&&this.i?" ol-collapsed":"")+(this.i?"":" ol-uncollapsible"),this.B,d);vh.call(this,{element:c,render:a.render?a.render:Xh,target:a.target});this.u=!0;this.l={};this.j={};this.X={}}w(Wh,vh);
-function Xh(a){if(a=a.frameState){var c,d,e,f,g,h,k,m,n,p,q,r=a.layerStatesArray,u=Vb(a.attributions),y={},A=a.viewState.projection;d=0;for(c=r.length;d<c;d++)if(h=r[d].layer.ea())if(p=v(h).toString(),n=h.j)for(e=0,f=n.length;e<f;e++)if(k=n[e],m=v(k).toString(),!(m in u)){if(g=a.usedTiles[p]){var F=Th(h,A);a:{q=k;var z=A;if(q.b){var x=void 0,K=void 0,J=void 0,I=void 0;for(I in g)if(I in q.b)for(var J=g[I],N,x=0,K=q.b[I].length;x<K;++x){N=q.b[I][x];if(qg(N,J)){q=!0;break a}var va=kg(F,z.R(),parseInt(I,
-10)),Ra=pg(va);if(J.b<va.b||J.f>va.f)if(qg(N,new mg(wb(J.b,Ra),wb(J.f,Ra),J.a,J.c))||pg(J)>Ra&&qg(N,va)){q=!0;break a}}q=!1}else q=!0}}else q=!1;q?(m in y&&delete y[m],u[m]=k):y[m]=k}c=[u,y];d=c[0];c=c[1];for(var M in this.l)M in d?(this.j[M]||(nh(this.l[M],!0),this.j[M]=!0),delete d[M]):M in c?(this.j[M]&&(nh(this.l[M],!1),delete this.j[M]),delete c[M]):(Tg(this.l[M]),delete this.l[M],delete this.j[M]);for(M in d)e=Pg("LI"),e.innerHTML=d[M].a,this.B.appendChild(e),this.l[M]=e,this.j[M]=!0;for(M in c)e=
-Pg("LI"),e.innerHTML=c[M].a,nh(e,!1),this.B.appendChild(e),this.l[M]=e;M=!Rb(this.j)||!Rb(a.logos);this.u!=M&&(nh(this.element,M),this.u=M);M&&Rb(this.j)?ah(this.element,"ol-logo-only"):bh(this.element,"ol-logo-only");var Ia;a=a.logos;M=this.X;for(Ia in M)Ia in a||(Tg(M[Ia]),delete M[Ia]);for(var pb in a)pb in M||(Ia=new Image,Ia.src=pb,d=a[pb],""===d?d=Ia:(d=Mg("A",{href:d}),d.appendChild(Ia)),this.v.appendChild(d),M[pb]=d);nh(this.v,!Rb(a))}else this.u&&(nh(this.element,!1),this.u=!1)}l=Wh.prototype;
-l.Nk=function(a){a.preventDefault();Yh(this)};function Yh(a){ch(a.element,"ol-collapsed");a.f?Ug(a.N,a.I):Ug(a.I,a.N);a.f=!a.f}l.Mk=function(){return this.i};l.Pk=function(a){this.i!==a&&(this.i=a,ch(this.element,"ol-uncollapsible"),!a&&this.f&&Yh(this))};l.Ok=function(a){this.i&&this.f!==a&&Yh(this)};l.Lk=function(){return this.f};function Zh(a){a=a?a:{};var c=a.className?a.className:"ol-rotate",d=a.label?a.label:"\u21e7";this.f=null;ia(d)?this.f=Mg("SPAN","ol-compass",d):(this.f=d,ah(this.f,"ol-compass"));d=Mg("BUTTON",{"class":c+"-reset",type:"button",title:a.tipLabel?a.tipLabel:"Reset rotation"},this.f);B(d,"click",Zh.prototype.v,!1,this);c=Mg("DIV",c+" ol-unselectable ol-control",d);vh.call(this,{element:c,render:a.render?a.render:$h,target:a.target});this.i=a.duration?a.duration:250;this.j=void 0!==a.autoHide?a.autoHide:
-!0;this.l=void 0;this.j&&ah(this.element,"ol-hidden")}w(Zh,vh);Zh.prototype.v=function(a){a.preventDefault();a=this.a;var c=a.Z();if(c){var d=c.va();void 0!==d&&(0<this.i&&(d%=2*Math.PI,d<-Math.PI&&(d+=2*Math.PI),d>Math.PI&&(d-=2*Math.PI),a.Aa(ag({rotation:d,duration:this.i,easing:Wf}))),c.Xd(0))}};
-function $h(a){if(a=a.frameState){a=a.viewState.rotation;if(a!=this.l){var c="rotate("+a+"rad)";if(this.j){var d=this.element;0===a?ah(d,"ol-hidden"):bh(d,"ol-hidden")}this.f.style.msTransform=c;this.f.style.webkitTransform=c;this.f.style.transform=c}this.l=a}};function ai(a){a=a?a:{};var c=a.className?a.className:"ol-zoom",d=a.delta?a.delta:1,e=a.zoomOutLabel?a.zoomOutLabel:"\u2212",f=a.zoomOutTipLabel?a.zoomOutTipLabel:"Zoom out",g=Mg("BUTTON",{"class":c+"-in",type:"button",title:a.zoomInTipLabel?a.zoomInTipLabel:"Zoom in"},a.zoomInLabel?a.zoomInLabel:"+");B(g,"click",ra(ai.prototype.j,d),!1,this);e=Mg("BUTTON",{"class":c+"-out",type:"button",title:f},e);B(e,"click",ra(ai.prototype.j,-d),!1,this);c=Mg("DIV",c+" ol-unselectable ol-control",g,e);vh.call(this,
-{element:c,target:a.target});this.f=a.duration?a.duration:250}w(ai,vh);ai.prototype.j=function(a,c){c.preventDefault();var d=this.a,e=d.Z();if(e){var f=e.aa();f&&(0<this.f&&d.Aa(bg({resolution:f,duration:this.f,easing:Wf})),d=e.constrainResolution(f,a),e.wb(d))}};function bi(a){a=a?a:{};var c=new tg;(void 0!==a.zoom?a.zoom:1)&&c.push(new ai(a.zoomOptions));(void 0!==a.rotate?a.rotate:1)&&c.push(new Zh(a.rotateOptions));(void 0!==a.attribution?a.attribution:1)&&c.push(new Wh(a.attributionOptions));return c};var ci=cc?"webkitfullscreenchange":bc?"mozfullscreenchange":$b?"MSFullscreenChange":"fullscreenchange";function di(){var a=Fg().b,c=a.body;return!!(c.webkitRequestFullscreen||c.mozRequestFullScreen&&a.mozFullScreenEnabled||c.msRequestFullscreen&&a.msFullscreenEnabled||c.requestFullscreen&&a.fullscreenEnabled)}
-function ei(a){a.webkitRequestFullscreen?a.webkitRequestFullscreen():a.mozRequestFullScreen?a.mozRequestFullScreen():a.msRequestFullscreen?a.msRequestFullscreen():a.requestFullscreen&&a.requestFullscreen()}function fi(){var a=Fg().b;return!!(a.webkitIsFullScreen||a.mozFullScreen||a.msFullscreenElement||a.fullscreenElement)};function gi(a){a=a?a:{};this.f=a.className?a.className:"ol-full-screen";var c=a.label?a.label:"\u2194";this.j=ia(c)?document.createTextNode(String(c)):c;c=a.labelActive?a.labelActive:"\u00d7";this.i=ia(c)?document.createTextNode(String(c)):c;c=a.tipLabel?a.tipLabel:"Toggle full-screen";c=Mg("BUTTON",{"class":this.f+"-"+fi(),type:"button",title:c},this.j);B(c,"click",this.u,!1,this);B(ba.document,ci,this.l,!1,this);var d=this.f+" ol-unselectable ol-control "+(di()?"":"ol-unsupported"),c=Mg("DIV",d,
-c);vh.call(this,{element:c,target:a.target});this.v=void 0!==a.keys?a.keys:!1}w(gi,vh);gi.prototype.u=function(a){a.preventDefault();di()&&(a=this.a)&&(fi()?(a=Fg().b,a.webkitCancelFullScreen?a.webkitCancelFullScreen():a.mozCancelFullScreen?a.mozCancelFullScreen():a.msExitFullscreen?a.msExitFullscreen():a.exitFullscreen&&a.exitFullscreen()):(a=a.bf(),a=Ig(a),this.v?a.mozRequestFullScreenWithKeys?a.mozRequestFullScreenWithKeys():a.webkitRequestFullscreen?a.webkitRequestFullscreen():ei(a):ei(a)))};
-gi.prototype.l=function(){var a=this.f+"-true",c=this.f+"-false",d=Vg(this.element),e=this.a;fi()?($g(d,c)&&(bh(d,c),ah(d,a)),Ug(this.i,this.j)):($g(d,a)&&(bh(d,a),ah(d,c)),Ug(this.j,this.i));e&&e.Fc()};function hi(a){a=a?a:{};var c=Mg("DIV",a.className?a.className:"ol-mouse-position");vh.call(this,{element:c,render:a.render?a.render:ii,target:a.target});B(this,ld("projection"),this.Qk,!1,this);a.coordinateFormat&&this.eh(a.coordinateFormat);a.projection&&this.qg(Ce(a.projection));this.v=a.undefinedHTML?a.undefinedHTML:"";this.l=c.innerHTML;this.i=this.j=this.f=null}w(hi,vh);
-function ii(a){a=a.frameState;a?this.f!=a.viewState.projection&&(this.f=a.viewState.projection,this.j=null):this.f=null;ji(this,this.i)}l=hi.prototype;l.Qk=function(){this.j=null};l.Qf=function(){return this.get("coordinateFormat")};l.pg=function(){return this.get("projection")};l.Kj=function(a){this.i=this.a.zd(a.b);ji(this,this.i)};l.Lj=function(){ji(this,null);this.i=null};
-l.setMap=function(a){hi.ba.setMap.call(this,a);a&&(a=a.a,this.A.push(B(a,"mousemove",this.Kj,!1,this),B(a,"mouseout",this.Lj,!1,this)))};l.eh=function(a){this.set("coordinateFormat",a)};l.qg=function(a){this.set("projection",a)};function ji(a,c){var d=a.v;if(c&&a.f){if(!a.j){var e=a.pg();a.j=e?Ge(a.f,e):We}if(e=a.a.xa(c))a.j(e,e),d=(d=a.Qf())?d(e):e.toString()}a.l&&d==a.l||(a.element.innerHTML=d,a.l=d)};function ki(a,c,d){rc.call(this);this.ha=null;this.c=!1;this.j=a;this.g=d;this.b=c||window;this.a=qa(this.f,this)}w(ki,rc);ki.prototype.start=function(){li(this);this.c=!1;var a=mi(this),c=ni(this);a&&!c&&this.b.mozRequestAnimationFrame?(this.ha=B(this.b,"MozBeforePaint",this.a),this.b.mozRequestAnimationFrame(null),this.c=!0):this.ha=a&&c?a.call(this.b,this.a):this.b.setTimeout(ue(this.a),20)};
-function li(a){if(null!=a.ha){var c=mi(a),d=ni(a);c&&!d&&a.b.mozRequestAnimationFrame?$c(a.ha):c&&d?d.call(a.b,a.ha):a.b.clearTimeout(a.ha)}a.ha=null}ki.prototype.f=function(){this.c&&this.ha&&$c(this.ha);this.ha=null;this.j.call(this.g,sa())};ki.prototype.Y=function(){li(this);ki.ba.Y.call(this)};function mi(a){a=a.b;return a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame||a.msRequestAnimationFrame||null}
-function ni(a){a=a.b;return a.cancelAnimationFrame||a.cancelRequestAnimationFrame||a.webkitCancelRequestAnimationFrame||a.mozCancelRequestAnimationFrame||a.oCancelRequestAnimationFrame||a.msCancelRequestAnimationFrame||null};function oi(a){ba.setTimeout(function(){throw a;},0)}function pi(a,c){var d=a;c&&(d=qa(a,c));d=qi(d);!ka(ba.setImmediate)||ba.Window&&ba.Window.prototype&&ba.Window.prototype.setImmediate==ba.setImmediate?(ri||(ri=si()),ri(d)):ba.setImmediate(d)}var ri;
-function si(){var a=ba.MessageChannel;"undefined"===typeof a&&"undefined"!==typeof window&&window.postMessage&&window.addEventListener&&!Ib("Presto")&&(a=function(){var a=document.createElement("IFRAME");a.style.display="none";a.src="";document.documentElement.appendChild(a);var c=a.contentWindow,a=c.document;a.open();a.write("");a.close();var d="callImmediate"+Math.random(),e="file:"==c.location.protocol?"*":c.location.protocol+"//"+c.location.host,a=qa(function(a){if(("*"==e||a.origin==e)&&a.data==
-d)this.port1.onmessage()},this);c.addEventListener("message",a,!1);this.port1={};this.port2={postMessage:function(){c.postMessage(d,e)}}});if("undefined"!==typeof a&&!Ib("Trident")&&!Ib("MSIE")){var c=new a,d={},e=d;c.port1.onmessage=function(){if(ca(d.next)){d=d.next;var a=d.Mf;d.Mf=null;a()}};return function(a){e.next={Mf:a};e=e.next;c.port2.postMessage(0)}}return"undefined"!==typeof document&&"onreadystatechange"in document.createElement("SCRIPT")?function(a){var c=document.createElement("SCRIPT");
-c.onreadystatechange=function(){c.onreadystatechange=null;c.parentNode.removeChild(c);c=null;a();a=null};document.documentElement.appendChild(c)}:function(a){ba.setTimeout(a,0)}}var qi=te;function ti(a,c){this.a={};this.b=[];this.c=0;var d=arguments.length;if(1<d){if(d%2)throw Error("Uneven number of arguments");for(var e=0;e<d;e+=2)this.set(arguments[e],arguments[e+1])}else if(a){a instanceof ti?(d=a.O(),e=a.Ub()):(d=Nb(a),e=Mb(a));for(var f=0;f<d.length;f++)this.set(d[f],e[f])}}l=ti.prototype;l.Qb=function(){return this.c};l.Ub=function(){ui(this);for(var a=[],c=0;c<this.b.length;c++)a.push(this.a[this.b[c]]);return a};l.O=function(){ui(this);return this.b.concat()};
-l.ya=function(){return 0==this.c};l.clear=function(){this.a={};this.c=this.b.length=0};l.remove=function(a){return vi(this.a,a)?(delete this.a[a],this.c--,this.b.length>2*this.c&&ui(this),!0):!1};function ui(a){if(a.c!=a.b.length){for(var c=0,d=0;c<a.b.length;){var e=a.b[c];vi(a.a,e)&&(a.b[d++]=e);c++}a.b.length=d}if(a.c!=a.b.length){for(var f={},d=c=0;c<a.b.length;)e=a.b[c],vi(f,e)||(a.b[d++]=e,f[e]=1),c++;a.b.length=d}}l.get=function(a,c){return vi(this.a,a)?this.a[a]:c};
-l.set=function(a,c){vi(this.a,a)||(this.c++,this.b.push(a));this.a[a]=c};l.forEach=function(a,c){for(var d=this.O(),e=0;e<d.length;e++){var f=d[e],g=this.get(f);a.call(c,g,f,this)}};l.clone=function(){return new ti(this)};function vi(a,c){return Object.prototype.hasOwnProperty.call(a,c)};function wi(){this.b=sa()}new wi;wi.prototype.set=function(a){this.b=a};wi.prototype.reset=function(){this.set(sa())};wi.prototype.get=function(){return this.b};function xi(a){dd.call(this);this.b=a||window;this.a=B(this.b,"resize",this.f,!1,this);this.c=Lg(this.b||window)}w(xi,dd);xi.prototype.Y=function(){xi.ba.Y.call(this);this.a&&($c(this.a),this.a=null);this.c=this.b=null};xi.prototype.f=function(){var a=Lg(this.b||window),c=this.c;a==c||a&&c&&a.width==c.width&&a.height==c.height||(this.c=a,C(this,"resize"))};function yi(a,c,d,e,f){if(!($b||ac||cc&&lc("525")))return!0;if(dc&&f)return zi(a);if(f&&!e)return!1;ja(c)&&(c=Ai(c));if(!d&&(17==c||18==c||dc&&91==c))return!1;if((cc||ac)&&e&&d)switch(a){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($b&&e&&c==a)return!1;switch(a){case 13:return!0;case 27:return!(cc||ac)}return zi(a)}
-function zi(a){if(48<=a&&57>=a||96<=a&&106>=a||65<=a&&90>=a||(cc||ac)&&0==a)return!0;switch(a){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 Ai(a){if(bc)a=Bi(a);else if(dc&&cc)a:switch(a){case 93:a=91;break a}return a}
-function Bi(a){switch(a){case 61:return 187;case 59:return 186;case 173:return 189;case 224:return 91;case 0:return 224;default:return a}};function Ci(a,c){dd.call(this);a&&Di(this,a,c)}w(Ci,dd);l=Ci.prototype;l.Zc=null;l.Pd=null;l.We=null;l.Qd=null;l.Oa=-1;l.Eb=-1;l.De=!1;
-var Ei={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},Fi={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},Gi=$b||ac||cc&&lc("525"),Hi=dc&&bc;
-Ci.prototype.b=function(a){if(cc||ac)if(17==this.Oa&&!a.C||18==this.Oa&&!a.a||dc&&91==this.Oa&&!a.v)this.Eb=this.Oa=-1;-1==this.Oa&&(a.C&&17!=a.j?this.Oa=17:a.a&&18!=a.j?this.Oa=18:a.v&&91!=a.j&&(this.Oa=91));Gi&&!yi(a.j,this.Oa,a.f,a.C,a.a)?this.handleEvent(a):(this.Eb=Ai(a.j),Hi&&(this.De=a.a))};Ci.prototype.a=function(a){this.Eb=this.Oa=-1;this.De=a.a};
-Ci.prototype.handleEvent=function(a){var c=a.b,d,e,f=c.altKey;$b&&"keypress"==a.type?(d=this.Eb,e=13!=d&&27!=d?c.keyCode:0):(cc||ac)&&"keypress"==a.type?(d=this.Eb,e=0<=c.charCode&&63232>c.charCode&&zi(d)?c.charCode:0):Zb&&!cc?(d=this.Eb,e=zi(d)?c.keyCode:0):(d=c.keyCode||this.Eb,e=c.charCode||0,Hi&&(f=this.De),dc&&63==e&&224==d&&(d=191));var g=d=Ai(d),h=c.keyIdentifier;d?63232<=d&&d in Ei?g=Ei[d]:25==d&&a.f&&(g=9):h&&h in Fi&&(g=Fi[h]);this.Oa=g;a=new Ii(g,e,0,c);a.a=f;C(this,a)};
-function Di(a,c,d){a.Qd&&Ji(a);a.Zc=c;a.Pd=B(a.Zc,"keypress",a,d);a.We=B(a.Zc,"keydown",a.b,d,a);a.Qd=B(a.Zc,"keyup",a.a,d,a)}function Ji(a){a.Pd&&($c(a.Pd),$c(a.We),$c(a.Qd),a.Pd=null,a.We=null,a.Qd=null);a.Zc=null;a.Oa=-1;a.Eb=-1}Ci.prototype.Y=function(){Ci.ba.Y.call(this);Ji(this)};function Ii(a,c,d,e){Bc.call(this,e);this.type="key";this.j=a;this.A=c}w(Ii,Bc);function Ki(a,c){dd.call(this);var d=this.b=a;(d=la(d)&&1==d.nodeType?this.b:this.b?this.b.body:null)&&gh(d,"direction");this.a=B(this.b,bc?"DOMMouseScroll":"mousewheel",this,c)}w(Ki,dd);
-Ki.prototype.handleEvent=function(a){var c=0,d=0;a=a.b;if("mousewheel"==a.type){c=1;if($b||cc&&(ec||lc("532.0")))c=40;d=Li(-a.wheelDelta,c);c=ca(a.wheelDeltaX)?Li(-a.wheelDeltaY,c):d}else d=a.detail,100<d?d=3:-100>d&&(d=-3),ca(a.axis)&&a.axis===a.HORIZONTAL_AXIS||(c=d);ja(this.c)&&(c=Math.min(Math.max(c,-this.c),this.c));d=new Mi(d,a,0,c);C(this,d)};function Li(a,c){return cc&&(dc||fc)&&0!=a%c?a:a/c}Ki.prototype.Y=function(){Ki.ba.Y.call(this);$c(this.a);this.a=null};
-function Mi(a,c,d,e){Bc.call(this,c);this.type="mousewheel";this.detail=a;this.u=e}w(Mi,Bc);function Ni(a,c,d){wc.call(this,a);this.b=c;a=d?d:{};this.buttons=Oi(a);this.pressure=Pi(a,this.buttons);this.bubbles="bubbles"in a?a.bubbles:!1;this.cancelable="cancelable"in a?a.cancelable:!1;this.view="view"in a?a.view:null;this.detail="detail"in a?a.detail:null;this.screenX="screenX"in a?a.screenX:0;this.screenY="screenY"in a?a.screenY:0;this.clientX="clientX"in a?a.clientX:0;this.clientY="clientY"in a?a.clientY:0;this.button="button"in a?a.button:0;this.relatedTarget="relatedTarget"in a?a.relatedTarget:
-null;this.pointerId="pointerId"in a?a.pointerId:0;this.width="width"in a?a.width:0;this.height="height"in a?a.height:0;this.pointerType="pointerType"in a?a.pointerType:"";this.isPrimary="isPrimary"in a?a.isPrimary:!1;c.preventDefault&&(this.preventDefault=function(){c.preventDefault()})}w(Ni,wc);function Oi(a){if(a.buttons||Qi)a=a.buttons;else switch(a.which){case 1:a=1;break;case 2:a=4;break;case 3:a=2;break;default:a=0}return a}
-function Pi(a,c){var d=0;a.pressure?d=a.pressure:d=c?.5:0;return d}var Qi=!1;try{Qi=1===(new MouseEvent("click",{buttons:1})).buttons}catch(a){};function Ri(a,c){var d=Pg("CANVAS");a&&(d.width=a);c&&(d.height=c);return d.getContext("2d")}
-var Si=function(){var a;return function(){if(void 0===a)if(ba.getComputedStyle){var c=Pg("P"),d,e={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.appendChild(c);for(var f in e)f in c.style&&(c.style[f]="translate(1px,1px)",d=ba.getComputedStyle(c).getPropertyValue(e[f]));Tg(c);a=d&&"none"!==d}else a=!1;return a}}(),Ti=function(){var a;return function(){if(void 0===a)if(ba.getComputedStyle){var c=
-Pg("P"),d,e={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.appendChild(c);for(var f in e)f in c.style&&(c.style[f]="translate3d(1px,1px,1px)",d=ba.getComputedStyle(c).getPropertyValue(e[f]));Tg(c);a=d&&"none"!==d}else a=!1;return a}}();function Ui(a,c){var d=a.style;d.WebkitTransform=c;d.MozTransform=c;d.b=c;d.msTransform=c;d.transform=c;$b&&lc("9.0")&&(a.style.transformOrigin="0 0")}
-function Vi(a,c){var d;if(Ti()){var e=Array(16);for(d=0;16>d;++d)e[d]=c[d].toFixed(6);Ui(a,"matrix3d("+e.join(",")+")")}else if(Si()){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);Ui(a,"matrix("+f.join(",")+")")}else a.style.left=Math.round(c[12])+"px",a.style.top=Math.round(c[13])+"px"};var Wi=["experimental-webgl","webgl","webkit-3d","moz-webgl"];function Xi(a,c){var d,e,f=Wi.length;for(e=0;e<f;++e)try{if(d=a.getContext(Wi[e],c))return d}catch(g){}return null};var Yi,Zi=ba.devicePixelRatio||1,$i=!1,aj=function(){if(!("HTMLCanvasElement"in ba))return!1;try{var a=Ri();return a?(void 0!==a.setLineDash&&($i=!0),!0):!1}catch(c){return!1}}(),bj="DeviceOrientationEvent"in ba,cj="geolocation"in ba.navigator,dj="ontouchstart"in ba,ej="PointerEvent"in ba,fj=!!ba.navigator.msPointerEnabled,gj=!1,hj,ij=[];if("WebGLRenderingContext"in ba)try{var jj=Xi(Pg("CANVAS"),{failIfMajorPerformanceCaveat:!0});jj&&(gj=!0,hj=jj.getParameter(jj.MAX_TEXTURE_SIZE),ij=jj.getSupportedExtensions())}catch(a){}
-Yi=gj;ua=ij;ta=hj;function kj(a,c){this.b=a;this.g=c};function lj(a){kj.call(this,a,{mousedown:this.ek,mousemove:this.fk,mouseup:this.ik,mouseover:this.hk,mouseout:this.gk});this.a=a.a;this.c=[]}w(lj,kj);function mj(a,c){for(var d=a.c,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 nj(a){var c=oj(a,a.b),d=c.preventDefault;c.preventDefault=function(){a.preventDefault();d()};c.pointerId=1;c.isPrimary=!0;c.pointerType="mouse";return c}l=lj.prototype;
-l.ek=function(a){if(!mj(this,a)){(1).toString()in this.a&&this.cancel(a);var c=nj(a);this.a[(1).toString()]=a;pj(this.b,rj,c,a)}};l.fk=function(a){if(!mj(this,a)){var c=nj(a);pj(this.b,sj,c,a)}};l.ik=function(a){if(!mj(this,a)){var c=this.a[(1).toString()];c&&c.button===a.button&&(c=nj(a),pj(this.b,tj,c,a),delete this.a[(1).toString()])}};l.hk=function(a){if(!mj(this,a)){var c=nj(a);uj(this.b,c,a)}};l.gk=function(a){if(!mj(this,a)){var c=nj(a);vj(this.b,c,a)}};
-l.cancel=function(a){var c=nj(a);this.b.cancel(c,a);delete this.a[(1).toString()]};function wj(a){kj.call(this,a,{MSPointerDown:this.nk,MSPointerMove:this.pk,MSPointerUp:this.sk,MSPointerOut:this.qk,MSPointerOver:this.rk,MSPointerCancel:this.mk,MSGotPointerCapture:this.kk,MSLostPointerCapture:this.lk});this.a=a.a;this.c=["","unavailable","touch","pen","mouse"]}w(wj,kj);function xj(a,c){var d=c;ja(c.b.pointerType)&&(d=oj(c,c.b),d.pointerType=a.c[c.b.pointerType]);return d}l=wj.prototype;l.nk=function(a){this.a[a.b.pointerId.toString()]=a;var c=xj(this,a);pj(this.b,rj,c,a)};
-l.pk=function(a){var c=xj(this,a);pj(this.b,sj,c,a)};l.sk=function(a){var c=xj(this,a);pj(this.b,tj,c,a);delete this.a[a.b.pointerId.toString()]};l.qk=function(a){var c=xj(this,a);vj(this.b,c,a)};l.rk=function(a){var c=xj(this,a);uj(this.b,c,a)};l.mk=function(a){var c=xj(this,a);this.b.cancel(c,a);delete this.a[a.b.pointerId.toString()]};l.lk=function(a){C(this.b,new Ni("lostpointercapture",a,a.b))};l.kk=function(a){C(this.b,new Ni("gotpointercapture",a,a.b))};function yj(a){kj.call(this,a,{pointerdown:this.an,pointermove:this.bn,pointerup:this.en,pointerout:this.cn,pointerover:this.dn,pointercancel:this.$m,gotpointercapture:this.sj,lostpointercapture:this.dk})}w(yj,kj);l=yj.prototype;l.an=function(a){zj(this.b,a)};l.bn=function(a){zj(this.b,a)};l.en=function(a){zj(this.b,a)};l.cn=function(a){zj(this.b,a)};l.dn=function(a){zj(this.b,a)};l.$m=function(a){zj(this.b,a)};l.dk=function(a){zj(this.b,a)};l.sj=function(a){zj(this.b,a)};function Aj(a,c){kj.call(this,a,{touchstart:this.fo,touchmove:this.eo,touchend:this.co,touchcancel:this.bo});this.a=a.a;this.i=c;this.c=void 0;this.j=0;this.f=void 0}w(Aj,kj);l=Aj.prototype;l.$g=function(){this.j=0;this.f=void 0};
-function Bj(a,c,d){c=oj(c,d);c.pointerId=d.identifier+2;c.bubbles=!0;c.cancelable=!0;c.detail=a.j;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=a.c===d.identifier;c.pointerType="touch";c.clientX=d.clientX;c.clientY=d.clientY;c.screenX=d.screenX;c.screenY=d.screenY;return c}
-function Cj(a,c,d){function e(){c.preventDefault()}var f=Array.prototype.slice.call(c.b.changedTouches),g=f.length,h,k;for(h=0;h<g;++h)k=Bj(a,c,f[h]),k.preventDefault=e,d.call(a,c,k)}
-l.fo=function(a){var c=a.b.touches,d=Nb(this.a),e=d.length;if(e>=c.length){var f=[],g,h,k;for(g=0;g<e;++g){h=d[g];k=this.a[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.ec)}for(g=0;g<f.length;++g)this.Ee(a,f[g])}c=Lb(this.a);if(0===c||1===c&&(1).toString()in this.a)this.c=a.b.changedTouches[0].identifier,void 0!==this.f&&ba.clearTimeout(this.f);Dj(this,a);this.j++;Cj(this,a,this.Wm)};
-l.Wm=function(a,c){this.a[c.pointerId]={target:c.target,ec:c,Lg:c.target};var d=this.b;c.bubbles=!0;pj(d,Ej,c,a);d=this.b;c.bubbles=!1;pj(d,Fj,c,a);pj(this.b,rj,c,a)};l.eo=function(a){a.preventDefault();Cj(this,a,this.jk)};l.jk=function(a,c){var d=this.a[c.pointerId];if(d){var e=d.ec,f=d.Lg;pj(this.b,sj,c,a);e&&f!==c.target&&(e.relatedTarget=c.target,c.relatedTarget=f,e.target=f,c.target?(vj(this.b,e,a),uj(this.b,c,a)):(c.target=f,c.relatedTarget=null,this.Ee(a,c)));d.ec=c;d.Lg=c.target}};
-l.co=function(a){Dj(this,a);Cj(this,a,this.ho)};l.ho=function(a,c){pj(this.b,tj,c,a);this.b.ec(c,a);var d=this.b;c.bubbles=!1;pj(d,Gj,c,a);delete this.a[c.pointerId];c.isPrimary&&(this.c=void 0,this.f=ba.setTimeout(qa(this.$g,this),200))};l.bo=function(a){Cj(this,a,this.Ee)};l.Ee=function(a,c){this.b.cancel(c,a);this.b.ec(c,a);var d=this.b;c.bubbles=!1;pj(d,Gj,c,a);delete this.a[c.pointerId];c.isPrimary&&(this.c=void 0,this.f=ba.setTimeout(qa(this.$g,this),200))};
-function Dj(a,c){var d=a.i.c,e=c.b.changedTouches[0];if(a.c===e.identifier){var f=[e.clientX,e.clientY];d.push(f);ba.setTimeout(function(){eb(d,f)},2500)}};function Hj(a){dd.call(this);this.f=a;this.a={};this.c={};this.b=[];ej?Ij(this,new yj(this)):fj?Ij(this,new wj(this)):(a=new lj(this),Ij(this,a),dj&&Ij(this,new Aj(this,a)));a=this.b.length;for(var c,d=0;d<a;d++)c=this.b[d],Jj(this,Object.keys(c.g))}w(Hj,dd);function Ij(a,c){var d=Object.keys(c.g);d&&(d.forEach(function(a){var d=c.g[a];d&&(this.c[a]=qa(d,c))},a),a.b.push(c))}Hj.prototype.g=function(a){var c=this.c[a.type];c&&c(a)};
-function Jj(a,c){c.forEach(function(a){B(this.f,a,this.g,!1,this)},a)}function Kj(a,c){c.forEach(function(a){Zc(this.f,a,this.g,!1,this)},a)}function oj(a,c){for(var d={},e,f=0,g=Lj.length;f<g;f++)e=Lj[f][0],d[e]=a[e]||c[e]||Lj[f][1];return d}Hj.prototype.ec=function(a,c){a.bubbles=!0;pj(this,Mj,a,c)};Hj.prototype.cancel=function(a,c){pj(this,Nj,a,c)};function vj(a,c,d){a.ec(c,d);var e=c.relatedTarget;e&&Wg(c.target,e)||(c.bubbles=!1,pj(a,Gj,c,d))}
-function uj(a,c,d){c.bubbles=!0;pj(a,Ej,c,d);var e=c.relatedTarget;e&&Wg(c.target,e)||(c.bubbles=!1,pj(a,Fj,c,d))}function pj(a,c,d,e){C(a,new Ni(c,e,d))}function zj(a,c){C(a,new Ni(c.type,c,c.b))}Hj.prototype.Y=function(){for(var a=this.b.length,c,d=0;d<a;d++)c=this.b[d],Kj(this,Object.keys(c.g));Hj.ba.Y.call(this)};
-var sj="pointermove",rj="pointerdown",tj="pointerup",Ej="pointerover",Mj="pointerout",Fj="pointerenter",Gj="pointerleave",Nj="pointercancel",Lj=[["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 Oj(a,c,d,e,f){uh.call(this,a,c,f);this.b=d;this.originalEvent=d.b;this.pixel=c.zd(this.originalEvent);this.coordinate=c.xa(this.pixel);this.dragging=void 0!==e?e:!1}w(Oj,uh);Oj.prototype.preventDefault=function(){Oj.ba.preventDefault.call(this);this.b.preventDefault()};Oj.prototype.c=function(){Oj.ba.c.call(this);this.b.c()};function Pj(a,c,d,e,f){Oj.call(this,a,c,d.b,e,f);this.a=d}w(Pj,Oj);
-function Qj(a){dd.call(this);this.c=a;this.j=0;this.i=!1;this.a=this.l=this.f=null;a=this.c.a;this.v=0;this.A={};this.g=new Hj(a);this.b=null;this.l=B(this.g,rj,this.Oj,!1,this);this.C=B(this.g,sj,this.An,!1,this)}w(Qj,dd);function Rj(a,c){var d;d=new Pj(Sj,a.c,c);C(a,d);0!==a.j?(ba.clearTimeout(a.j),a.j=0,d=new Pj(Tj,a.c,c),C(a,d)):a.j=ba.setTimeout(qa(function(){this.j=0;var a=new Pj(Uj,this.c,c);C(this,a)},a),250)}
-function Vj(a,c){c.type==Wj||c.type==Xj?delete a.A[c.pointerId]:c.type==Yj&&(a.A[c.pointerId]=!0);a.v=Lb(a.A)}l=Qj.prototype;l.ag=function(a){Vj(this,a);var c=new Pj(Wj,this.c,a);C(this,c);!this.i&&0===a.button&&Rj(this,this.a);0===this.v&&(this.f.forEach($c),this.f=null,this.i=!1,this.a=null,vc(this.b),this.b=null)};
-l.Oj=function(a){Vj(this,a);var c=new Pj(Yj,this.c,a);C(this,c);this.a=a;this.f||(this.b=new Hj(document),this.f=[B(this.b,Zj,this.Gk,!1,this),B(this.b,Wj,this.ag,!1,this),B(this.g,Xj,this.ag,!1,this)])};l.Gk=function(a){if(a.clientX!=this.a.clientX||a.clientY!=this.a.clientY){this.i=!0;var c=new Pj(ak,this.c,a,this.i);C(this,c)}a.preventDefault()};l.An=function(a){C(this,new Pj(a.type,this.c,a,!(!this.a||a.clientX==this.a.clientX&&a.clientY==this.a.clientY)))};
-l.Y=function(){this.C&&($c(this.C),this.C=null);this.l&&($c(this.l),this.l=null);this.f&&(this.f.forEach($c),this.f=null);this.b&&(vc(this.b),this.b=null);this.g&&(vc(this.g),this.g=null);Qj.ba.Y.call(this)};var Uj="singleclick",Sj="click",Tj="dblclick",ak="pointerdrag",Zj="pointermove",Yj="pointerdown",Wj="pointerup",Xj="pointercancel",bk={yo:Uj,no:Sj,oo:Tj,ro:ak,uo:Zj,qo:Yj,xo:Wj,wo:"pointerover",vo:"pointerout",so:"pointerenter",to:"pointerleave",po:Xj};function ck(a){jd.call(this);var c=Vb(a);c.opacity=void 0!==a.opacity?a.opacity:1;c.visible=void 0!==a.visible?a.visible:!0;c.zIndex=void 0!==a.zIndex?a.zIndex:0;c.maxResolution=void 0!==a.maxResolution?a.maxResolution:Infinity;c.minResolution=void 0!==a.minResolution?a.minResolution:0;this.H(c)}w(ck,jd);
-function dk(a){var c=a.Hb(),d=a.Ue(),e=a.ib(),f=a.R(),g=a.Ib(),h=a.Cb(),k=a.Db();return{layer:a,opacity:Qa(c,0,1),v:d,visible:e,Fb:!0,extent:f,zIndex:g,maxResolution:h,minResolution:Math.max(k,0)}}l=ck.prototype;l.R=function(){return this.get("extent")};l.Cb=function(){return this.get("maxResolution")};l.Db=function(){return this.get("minResolution")};l.Hb=function(){return this.get("opacity")};l.ib=function(){return this.get("visible")};l.Ib=function(){return this.get("zIndex")};
-l.Yb=function(a){this.set("extent",a)};l.gc=function(a){this.set("maxResolution",a)};l.hc=function(a){this.set("minResolution",a)};l.Zb=function(a){this.set("opacity",a)};l.$b=function(a){this.set("visible",a)};l.ac=function(a){this.set("zIndex",a)};function ek(){};function fk(a,c,d,e,f,g){wc.call(this,a,c);this.vectorContext=d;this.frameState=e;this.context=f;this.glContext=g}w(fk,wc);function gk(a){var c=Vb(a);delete c.source;ck.call(this,c);this.j=this.l=this.i=null;a.map&&this.setMap(a.map);B(this,ld("source"),this.Uj,!1,this);this.Ec(a.source?a.source:null)}w(gk,ck);function hk(a,c){return a.visible&&c>=a.minResolution&&c<a.maxResolution}l=gk.prototype;l.Te=function(a){a=a?a:[];a.push(dk(this));return a};l.ea=function(){return this.get("source")||null};l.Ue=function(){var a=this.ea();return a?a.v:"undefined"};l.xl=function(){this.s()};
-l.Uj=function(){this.j&&($c(this.j),this.j=null);var a=this.ea();a&&(this.j=B(a,"change",this.xl,!1,this));this.s()};l.setMap=function(a){$c(this.i);this.i=null;a||this.s();$c(this.l);this.l=null;a&&(this.i=B(a,"precompose",function(a){var d=dk(this);d.Fb=!1;d.zIndex=Infinity;a.frameState.layerStatesArray.push(d);a.frameState.layerStates[v(this)]=d},!1,this),this.l=B(this,"change",a.render,!1,a),this.s())};l.Ec=function(a){this.set("source",a)};function ik(a,c,d,e,f){dd.call(this);this.j=f;this.extent=a;this.g=d;this.resolution=c;this.state=e}w(ik,dd);function jk(a){C(a,"change")}ik.prototype.R=function(){return this.extent};ik.prototype.aa=function(){return this.resolution};function kk(a,c,d,e,f,g,h,k){Gd(a);0===c&&0===d||Jd(a,c,d);1==e&&1==f||Kd(a,e,f);0!==g&&Ld(a,g);0===h&&0===k||Jd(a,h,k);return a}function lk(a,c){return a[0]==c[0]&&a[1]==c[1]&&a[4]==c[4]&&a[5]==c[5]&&a[12]==c[12]&&a[13]==c[13]}function mk(a,c,d){var e=a[1],f=a[5],g=a[13],h=c[0];c=c[1];d[0]=a[0]*h+a[4]*c+a[12];d[1]=e*h+f*c+g;return d};function nk(a){gd.call(this);this.a=a}w(nk,gd);l=nk.prototype;l.Ta=wa;l.bc=function(a,c,d,e){a=a.slice();mk(c.pixelToCoordinateMatrix,a,a);if(this.Ta(a,c,re,this))return d.call(e,this.a)};l.$d=qe;l.wd=function(a,c){return function(d,e){return Sh(a,d,e,function(a){c[d]||(c[d]={});c[d][a.b.toString()]=a})}};l.Al=function(a){2===a.target.state&&ok(this)};function pk(a,c){var d=c.state;2!=d&&3!=d&&B(c,"change",a.Al,!1,a);0==d&&(c.load(),d=c.state);return 2==d}
-function ok(a){var c=a.a;c.ib()&&"ready"==c.Ue()&&a.s()}function qk(a,c){zh(c.a)&&a.postRenderFunctions.push(ra(function(a,c,f){c=v(a).toString();a=a.a;f=f.usedTiles[c];for(var g;zh(a)&&!(c=a.b.kc,g=c.b[0].toString(),g in f&&f[g].contains(c.b));)a.pop().Tc()},c))}function rk(a,c){if(c){var d,e,f;e=0;for(f=c.length;e<f;++e)d=c[e],a[v(d).toString()]=d}}function sk(a,c){var d=c.T;void 0!==d&&(ia(d)?a.logos[d]="":la(d)&&(a.logos[d.src]=d.href))}
-function tk(a,c,d,e){c=v(c).toString();d=d.toString();c in a?d in a[c]?(a=a[c][d],e.b<a.b&&(a.b=e.b),e.f>a.f&&(a.f=e.f),e.a<a.a&&(a.a=e.a),e.c>a.c&&(a.c=e.c)):a[c][d]=e:(a[c]={},a[c][d]=e)}function uk(a,c,d){return[c*(Math.round(a[0]/c)+d[0]%2/2),c*(Math.round(a[1]/c)+d[1]%2/2)]}
-function vk(a,c,d,e,f,g,h,k,m,n){var p=v(c).toString();p in a.wantedTiles||(a.wantedTiles[p]={});var q=a.wantedTiles[p];a=a.tileQueue;var r=d.minZoom,u,y,A,F,z,x;for(x=h;x>=r;--x)for(y=kg(d,g,x,y),A=d.aa(x),F=y.b;F<=y.f;++F)for(z=y.a;z<=y.c;++z)h-x<=k?(u=c.Sb(x,F,z,e,f),0==u.state&&(q[fg(u.b)]=!0,u.jb()in a.c||wk(a,[u,p,hg(d,u.b),A])),void 0!==m&&m.call(n,u)):c.yf(x,F,z)};function xk(a){this.v=a.opacity;this.B=a.rotateWithView;this.C=a.rotation;this.A=a.scale;this.N=a.snapToPixel}l=xk.prototype;l.de=function(){return this.v};l.Gd=function(){return this.B};l.ee=function(){return this.C};l.fe=function(){return this.A};l.Id=function(){return this.N};l.ge=function(a){this.v=a};l.he=function(a){this.C=a};l.ie=function(a){this.A=a};function yk(a){a=a||{};this.g=void 0!==a.anchor?a.anchor:[.5,.5];this.f=null;this.a=void 0!==a.anchorOrigin?a.anchorOrigin:"top-left";this.i=void 0!==a.anchorXUnits?a.anchorXUnits:"fraction";this.l=void 0!==a.anchorYUnits?a.anchorYUnits:"fraction";var c=void 0!==a.crossOrigin?a.crossOrigin:null,d=void 0!==a.img?a.img:null,e=void 0!==a.imgSize?a.imgSize:null,f=a.src;void 0!==f&&0!==f.length||!d||(f=d.src);var g=void 0!==a.src?0:2,h=zk.Bb(),k=h.get(f,c);k||(k=new Ak(d,f,e,c,g),h.set(f,c,k));this.b=
-k;this.ca=void 0!==a.offset?a.offset:[0,0];this.c=void 0!==a.offsetOrigin?a.offsetOrigin:"top-left";this.j=null;this.u=void 0!==a.size?a.size:null;xk.call(this,{opacity:void 0!==a.opacity?a.opacity:1,rotation:void 0!==a.rotation?a.rotation:0,scale:void 0!==a.scale?a.scale:1,snapToPixel:void 0!==a.snapToPixel?a.snapToPixel:!0,rotateWithView:void 0!==a.rotateWithView?a.rotateWithView:!1})}w(yk,xk);l=yk.prototype;
-l.Ab=function(){if(this.f)return this.f;var a=this.g,c=this.kb();if("fraction"==this.i||"fraction"==this.l){if(!c)return null;a=this.g.slice();"fraction"==this.i&&(a[0]*=c[0]);"fraction"==this.l&&(a[1]*=c[1])}if("top-left"!=this.a){if(!c)return null;a===this.g&&(a=this.g.slice());if("top-right"==this.a||"bottom-right"==this.a)a[0]=-a[0]+c[0];if("bottom-left"==this.a||"bottom-right"==this.a)a[1]=-a[1]+c[1]}return this.f=a};l.Jb=function(){return this.b.b};l.Cd=function(){return this.b.c};l.dd=function(){return this.b.a};
-l.ce=function(){var a=this.b;if(!a.g)if(a.l){var c=a.c[0],d=a.c[1],e=Ri(c,d);e.fillRect(0,0,c,d);a.g=e.canvas}else a.g=a.b;return a.g};l.ta=function(){if(this.j)return this.j;var a=this.ca;if("top-left"!=this.c){var c=this.kb(),d=this.b.c;if(!c||!d)return null;a=a.slice();if("top-right"==this.c||"bottom-right"==this.c)a[0]=d[0]-c[0]-a[0];if("bottom-left"==this.c||"bottom-right"==this.c)a[1]=d[1]-c[1]-a[1]}return this.j=a};l.om=function(){return this.b.j};l.kb=function(){return this.u?this.u:this.b.c};
-l.Ye=function(a,c){return B(this.b,"change",a,!1,c)};l.load=function(){this.b.load()};l.xf=function(a,c){Zc(this.b,"change",a,!1,c)};function Ak(a,c,d,e,f){dd.call(this);this.g=null;this.b=a?a:new Image;e&&(this.b.crossOrigin=e);this.f=null;this.a=f;this.c=d;this.j=c;this.l=!1;2==this.a&&Bk(this)}w(Ak,dd);function Bk(a){var c=Ri(1,1);try{c.drawImage(a.b,0,0),c.getImageData(0,0,1,1)}catch(d){a.l=!0}}Ak.prototype.i=function(){this.a=3;this.f.forEach($c);this.f=null;C(this,"change")};
-Ak.prototype.C=function(){this.a=2;this.c=[this.b.width,this.b.height];this.f.forEach($c);this.f=null;Bk(this);C(this,"change")};Ak.prototype.load=function(){if(0==this.a){this.a=1;this.f=[Yc(this.b,"error",this.i,!1,this),Yc(this.b,"load",this.C,!1,this)];try{this.b.src=this.j}catch(a){this.i()}}};function zk(){this.b={};this.a=0}ea(zk);zk.prototype.clear=function(){this.b={};this.a=0};zk.prototype.get=function(a,c){var d=c+":"+a;return d in this.b?this.b[d]:null};
-zk.prototype.set=function(a,c,d){this.b[c+":"+a]=d;++this.a};function Ck(a,c){rc.call(this);this.j=c;this.f={};this.A={}}w(Ck,rc);function Dk(a){var c=a.viewState,d=a.coordinateToPixelMatrix;kk(d,a.size[0]/2,a.size[1]/2,1/c.resolution,-1/c.resolution,-c.rotation,-c.center[0],-c.center[1]);Id(d,a.pixelToCoordinateMatrix)}l=Ck.prototype;l.Y=function(){Jb(this.f,vc);Ck.ba.Y.call(this)};
-function Ek(){var a=zk.Bb();if(32<a.a){var c=0,d,e;for(d in a.b){e=a.b[d];var f;if(f=0===(c++&3))Gc(e)?e=fd(e,void 0,void 0):(e=Uc(e),e=!!e&&Oc(e,void 0,void 0)),f=!e;f&&(delete a.b[d],--a.a)}}}
-l.ff=function(a,c,d,e,f,g){var h,k=c.viewState,m=k.resolution,n=k.projection,k=a;if(n.c){var n=n.R(),p=le(n),q=a[0];if(q<n[0]||q>n[2])k=[q+p*Math.ceil((n[0]-q)/p),a[1]]}n=c.layerStatesArray;for(p=n.length-1;0<=p;--p){var r=n[p],q=r.layer;if(!r.Fb||hk(r,m)&&f.call(g,q))if(r=Fk(this,q),q.ea()&&(h=r.Ta(Dh(q.ea())?k:a,c,d,e)),h)return h}};
-l.yg=function(a,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(hk(h,k)&&f.call(g,p)&&(h=Fk(this,p).bc(a,c,d,e)))return h}};l.zg=function(a,c,d,e){return void 0!==this.ff(a,c,re,this,d,e)};function Fk(a,c){var d=v(c).toString();if(d in a.f)return a.f[d];var e=a.Ie(c);a.f[d]=e;a.A[d]=B(e,"change",a.Ej,!1,a);return e}l.Ej=function(){this.j.render()};l.pe=wa;
-l.Gn=function(a,c){for(var d in this.f)if(!(c&&d in c.layerStates)){var e=d,f=this.f[e];delete this.f[e];$c(this.A[e]);delete this.A[e];vc(f)}};function Gk(a,c){for(var d in a.f)if(!(d in c.layerStates)){c.postRenderFunctions.push(qa(a.Gn,a));break}}function nb(a,c){return a.zIndex-c.zIndex};function Hk(a,c){this.i=a;this.g=c;this.b=[];this.a=[];this.c={}}Hk.prototype.clear=function(){this.b.length=0;this.a.length=0;Sb(this.c)};function Ik(a){var c=a.b,d=a.a,e=c[0];1==c.length?(c.length=0,d.length=0):(c[0]=c.pop(),d[0]=d.pop(),Jk(a,0));c=a.g(e);delete a.c[c];return e}function wk(a,c){var d=a.i(c);Infinity!=d&&(a.b.push(c),a.a.push(d),a.c[a.g(c)]=!0,Kk(a,0,a.b.length-1))}Hk.prototype.Qb=function(){return this.b.length};Hk.prototype.ya=function(){return 0===this.b.length};
-function Jk(a,c){for(var d=a.b,e=a.a,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;Kk(a,k,c)}function Kk(a,c,d){var e=a.b;a=a.a;for(var f=e[d],g=a[d];d>c;){var h=d-1>>1;if(a[h]>g)e[d]=e[h],a[d]=a[h],d=h;else break}e[d]=f;a[d]=g}function Lk(a){var c=a.i,d=a.b,e=a.a,f=0,g=d.length,h,k,m;for(k=0;k<g;++k)h=d[k],m=c(h),Infinity==m?delete a.c[a.g(h)]:(e[f]=m,d[f++]=h);d.length=f;e.length=f;for(c=(a.b.length>>1)-1;0<=c;c--)Jk(a,c)};function Mk(a,c){Hk.call(this,function(c){return a.apply(null,c)},function(a){return a[0].jb()});this.l=c;this.f=0}w(Mk,Hk);Mk.prototype.j=function(a){a=a.target;var c=a.state;if(2===c||3===c||4===c)Zc(a,"change",this.j,!1,this),--this.f,this.l()};function Nk(a,c,d){for(var e=0,f;a.f<c&&e<d&&0<a.Qb();)f=Ik(a)[0],0===f.state&&(B(f,"change",a.j,!1,a),f.load(),++a.f,++e)};function Ok(a,c,d){this.f=a;this.c=c;this.j=d;this.b=[];this.a=this.g=0}function Pk(a,c){var d=a.f,e=a.a,f=a.c-e,g=Math.log(a.c/a.a)/a.f;return $f({source:c,duration:g,easing:function(a){return e*(Math.exp(d*a*g)-1)/f}})};function Qk(a){jd.call(this);this.A=null;this.g(!0);this.handleEvent=a.handleEvent}w(Qk,jd);Qk.prototype.c=function(){return this.get("active")};Qk.prototype.g=function(a){this.set("active",a)};Qk.prototype.setMap=function(a){this.A=a};function Rk(a,c,d,e,f){if(void 0!==d){var g=c.va(),h=c.Fa();void 0!==g&&h&&f&&0<f&&(a.Aa(ag({rotation:g,duration:f,easing:Wf})),e&&a.Aa($f({source:h,duration:f,easing:Wf})));c.rotate(d,e)}}
-function Sk(a,c,d,e,f){var g=c.aa();d=c.constrainResolution(g,d,0);Tk(a,c,d,e,f)}function Tk(a,c,d,e,f){if(d){var g=c.aa(),h=c.Fa();void 0!==g&&h&&d!==g&&f&&0<f&&(a.Aa(bg({resolution:g,duration:f,easing:Wf})),e&&a.Aa($f({source:h,duration:f,easing:Wf})));if(e){var k;a=c.Fa();f=c.aa();void 0!==a&&void 0!==f&&(k=[e[0]-d*(e[0]-a[0])/f,e[1]-d*(e[1]-a[1])/f]);c.Ra(k)}c.wb(d)}};function Uk(a){a=a?a:{};this.a=a.delta?a.delta:1;Qk.call(this,{handleEvent:Vk});this.f=a.duration?a.duration:250}w(Uk,Qk);function Vk(a){var c=!1,d=a.b;if(a.type==Tj){var c=a.map,e=a.coordinate,d=d.f?-this.a:this.a,f=c.Z();Sk(c,f,d,e,this.f);a.preventDefault();c=!0}return!c};function Wk(a){a=a.b;return a.a&&!a.l&&a.f}function Xk(a){return"pointermove"==a.type}function Yk(a){return a.type==Uj}function Zk(a){a=a.b;return!a.a&&!a.l&&!a.f}function $k(a){a=a.b;return!a.a&&!a.l&&a.f}function al(a){a=a.b.target.tagName;return"INPUT"!==a&&"SELECT"!==a&&"TEXTAREA"!==a}function bl(a){return 1==a.a.pointerId};function cl(a){a=a?a:{};Qk.call(this,{handleEvent:a.handleEvent?a.handleEvent:dl});this.lc=a.handleDownEvent?a.handleDownEvent:qe;this.mc=a.handleDragEvent?a.handleDragEvent:wa;this.Ic=a.handleMoveEvent?a.handleMoveEvent:wa;this.ze=a.handleUpEvent?a.handleUpEvent:qe;this.u=!1;this.X={};this.j=[]}w(cl,Qk);function el(a){for(var c=a.length,d=0,e=0,f=0;f<c;f++)d+=a[f].clientX,e+=a[f].clientY;return[d/c,e/c]}
-function dl(a){if(!(a instanceof Pj))return!0;var c=!1,d=a.type;if(d===Yj||d===ak||d===Wj)d=a.a,a.type==Wj?delete this.X[d.pointerId]:a.type==Yj?this.X[d.pointerId]=d:d.pointerId in this.X&&(this.X[d.pointerId]=d),this.j=Mb(this.X);this.u&&(a.type==ak?this.mc(a):a.type==Wj&&(this.u=this.ze(a)));a.type==Yj?(this.u=a=this.lc(a),c=this.ic(a)):a.type==Zj&&this.Ic(a);return!c}cl.prototype.ic=te;function fl(a){cl.call(this,{handleDownEvent:gl,handleDragEvent:hl,handleUpEvent:il});a=a?a:{};this.a=a.kinetic;this.f=this.i=null;this.v=a.condition?a.condition:Zk;this.l=!1}w(fl,cl);function hl(a){var c=el(this.j);this.a&&this.a.b.push(c[0],c[1],Date.now());if(this.f){var d=this.f[0]-c[0],e=c[1]-this.f[1];a=a.map;var f=a.Z(),g=Sf(f),e=d=[d,e],h=g.resolution;e[0]*=h;e[1]*=h;vd(d,g.rotation);qd(d,g.center);d=f.vd(d);a.render();f.Ra(d)}this.f=c}
-function il(a){a=a.map;var c=a.Z();if(0===this.j.length){var d;if(d=!this.l&&this.a)if(d=this.a,6>d.b.length)d=!1;else{var e=Date.now()-d.j,f=d.b.length-3;if(d.b[f+2]<e)d=!1;else{for(var g=f-3;0<g&&d.b[g+2]>e;)g-=3;var e=d.b[f+2]-d.b[g+2],h=d.b[f]-d.b[g],f=d.b[f+1]-d.b[g+1];d.g=Math.atan2(f,h);d.a=Math.sqrt(h*h+f*f)/e;d=d.a>d.c}}d&&(d=this.a,d=(d.c-d.a)/d.f,f=this.a.g,g=c.Fa(),this.i=Pk(this.a,g),a.Aa(this.i),g=a.Ba(g),d=a.xa([g[0]-d*Math.cos(f),g[1]-d*Math.sin(f)]),d=c.vd(d),c.Ra(d));Uf(c,-1);a.render();
-return!1}this.f=null;return!0}function gl(a){if(0<this.j.length&&this.v(a)){var c=a.map,d=c.Z();this.f=null;this.u||Uf(d,1);c.render();this.i&&eb(c.N,this.i)&&(d.Ra(a.frameState.viewState.center),this.i=null);this.a&&(a=this.a,a.b.length=0,a.g=0,a.a=0);this.l=1<this.j.length;return!0}return!1}fl.prototype.ic=qe;function jl(a){a=a?a:{};cl.call(this,{handleDownEvent:kl,handleDragEvent:ll,handleUpEvent:ml});this.f=a.condition?a.condition:Wk;this.a=void 0;this.i=a.duration?a.duration:250}w(jl,cl);function ll(a){if(bl(a)){var c=a.map,d=c.Ea();a=a.pixel;d=Math.atan2(d[1]/2-a[1],a[0]-d[0]/2);if(void 0!==this.a){a=d-this.a;var e=c.Z(),f=e.va();c.render();Rk(c,e,f-a)}this.a=d}}
-function ml(a){if(!bl(a))return!0;a=a.map;var c=a.Z();Uf(c,-1);var d=c.va(),e=this.i,d=c.constrainRotation(d,0);Rk(a,c,d,void 0,e);return!1}function kl(a){return bl(a)&&Dc(a.b)&&this.f(a)?(a=a.map,Uf(a.Z(),1),a.render(),this.a=void 0,!0):!1}jl.prototype.ic=qe;function nl(a){this.c=this.a=this.f=this.g=this.b=null;this.i=a}w(nl,rc);function pl(a){var c=a.f,d=a.a;a=[c,[c[0],d[1]],d,[d[0],c[1]]].map(a.b.xa,a.b);a[4]=a[0].slice();return new E([a])}nl.prototype.Y=function(){this.setMap(null)};nl.prototype.j=function(a){var c=this.c,d=this.i;a.vectorContext.oc(Infinity,function(a){a.Ia(d.f,d.c);a.Ja(d.a);a.Pb(c,null)})};nl.prototype.V=function(){return this.c};function ql(a){a.b&&a.f&&a.a&&a.b.render()}
-nl.prototype.setMap=function(a){this.g&&($c(this.g),this.g=null,this.b.render(),this.b=null);if(this.b=a)this.g=B(a,"postcompose",this.j,!1,this),ql(this)};function rl(a,c){wc.call(this,a);this.coordinate=c}w(rl,wc);function sl(a){cl.call(this,{handleDownEvent:tl,handleDragEvent:ul,handleUpEvent:vl});a=a?a:{};this.f=new nl(a.style?a.style:null);this.a=null;this.v=a.condition?a.condition:re}w(sl,cl);function ul(a){if(bl(a)){var c=this.f;a=a.pixel;c.f=this.a;c.a=a;c.c=pl(c);ql(c)}}sl.prototype.V=function(){return this.f.V()};sl.prototype.l=wa;
-function vl(a){if(!bl(a))return!0;this.f.setMap(null);var c=a.pixel[0]-this.a[0],d=a.pixel[1]-this.a[1];64<=c*c+d*d&&(this.l(a),C(this,new rl("boxend",a.coordinate)));return!1}function tl(a){if(bl(a)&&Dc(a.b)&&this.v(a)){this.a=a.pixel;this.f.setMap(a.map);var c=this.f,d=this.a;c.f=this.a;c.a=d;c.c=pl(c);ql(c);C(this,new rl("boxstart",a.coordinate));return!0}return!1};function wl(){this.a=-1};function xl(){this.a=-1;this.a=64;this.b=Array(4);this.g=Array(this.a);this.f=this.c=0;this.reset()}w(xl,wl);xl.prototype.reset=function(){this.b[0]=1732584193;this.b[1]=4023233417;this.b[2]=2562383102;this.b[3]=271733878;this.f=this.c=0};
-function yl(a,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=a.b[0];d=a.b[1];var f=a.b[2],g=a.b[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;a.b[0]=a.b[0]+c&4294967295;a.b[1]=a.b[1]+(f+(h<<21&4294967295|h>>>11))&4294967295;a.b[2]=a.b[2]+f&4294967295;a.b[3]=a.b[3]+g&4294967295}
-function zl(a,c){var d;ca(d)||(d=c.length);for(var e=d-a.a,f=a.g,g=a.c,h=0;h<d;){if(0==g)for(;h<=e;)yl(a,c,h),h+=a.a;if(ia(c))for(;h<d;){if(f[g++]=c.charCodeAt(h++),g==a.a){yl(a,f);g=0;break}}else for(;h<d;)if(f[g++]=c[h++],g==a.a){yl(a,f);g=0;break}}a.c=g;a.f+=d};function Al(a){a=a||{};this.b=void 0!==a.color?a.color:null;this.f=a.lineCap;this.c=void 0!==a.lineDash?a.lineDash:null;this.g=a.lineJoin;this.j=a.miterLimit;this.a=a.width;this.i=void 0}l=Al.prototype;l.um=function(){return this.b};l.Ui=function(){return this.f};l.vm=function(){return this.c};l.Vi=function(){return this.g};l.$i=function(){return this.j};l.wm=function(){return this.a};l.xm=function(a){this.b=a;this.i=void 0};l.Rn=function(a){this.f=a;this.i=void 0};
-l.ym=function(a){this.c=a;this.i=void 0};l.Sn=function(a){this.g=a;this.i=void 0};l.Tn=function(a){this.j=a;this.i=void 0};l.Zn=function(a){this.a=a;this.i=void 0};
-l.rb=function(){if(void 0===this.i){var a="s"+(this.b?Ag(this.b):"-")+","+(void 0!==this.f?this.f.toString():"-")+","+(this.c?this.c.toString():"-")+","+(void 0!==this.g?this.g:"-")+","+(void 0!==this.j?this.j.toString():"-")+","+(void 0!==this.a?this.a.toString():"-"),c=new xl;zl(c,a);var d=Array((56>c.c?c.a:2*c.a)-c.c);d[0]=128;for(a=1;a<d.length-8;++a)d[a]=0;for(var e=8*c.f,a=d.length-8;a<d.length;++a)d[a]=e&255,e/=256;zl(c,d);d=Array(16);for(a=e=0;4>a;++a)for(var f=0;32>f;f+=8)d[e++]=c.b[a]>>>
-f&255;if(8192>=d.length)c=String.fromCharCode.apply(null,d);else for(c="",a=0;a<d.length;a+=8192)c+=String.fromCharCode.apply(null,jb(d,a,a+8192));this.i=c}return this.i};var Bl=[0,0,0,1],Cl=[],Dl=[0,0,0,1];function El(a){a=a||{};this.b=void 0!==a.color?a.color:null;this.a=void 0}El.prototype.c=function(){return this.b};El.prototype.f=function(a){this.b=a;this.a=void 0};El.prototype.rb=function(){void 0===this.a&&(this.a="f"+(this.b?Ag(this.b):"-"));return this.a};function Fl(a){a=a||{};this.j=this.b=this.g=null;this.f=void 0!==a.fill?a.fill:null;this.a=void 0!==a.stroke?a.stroke:null;this.c=a.radius;this.u=[0,0];this.l=this.ca=this.i=null;var c=a.atlasManager,d,e=null,f,g=0;this.a&&(f=Ag(this.a.b),g=this.a.a,void 0===g&&(g=1),e=this.a.c,$i||(e=null));var h=2*(this.c+g)+1;f={strokeStyle:f,hd:g,size:h,lineDash:e};void 0===c?(this.b=Pg("CANVAS"),this.b.height=h,this.b.width=h,d=h=this.b.width,c=this.b.getContext("2d"),this.Gg(f,c,0,0),this.f?this.j=this.b:(c=
-this.j=Pg("CANVAS"),c.height=f.size,c.width=f.size,c=c.getContext("2d"),this.Fg(f,c,0,0))):(h=Math.round(h),(e=!this.f)&&(d=qa(this.Fg,this,f)),g=this.rb(),f=c.add(g,h,h,qa(this.Gg,this,f),d),this.b=f.image,this.u=[f.offsetX,f.offsetY],d=f.image.width,this.j=e?f.dg:this.b);this.i=[h/2,h/2];this.ca=[h,h];this.l=[d,d];xk.call(this,{opacity:1,rotateWithView:!1,rotation:0,scale:1,snapToPixel:void 0!==a.snapToPixel?a.snapToPixel:!0})}w(Fl,xk);l=Fl.prototype;l.Ab=function(){return this.i};l.lm=function(){return this.f};
-l.ce=function(){return this.j};l.Jb=function(){return this.b};l.dd=function(){return 2};l.Cd=function(){return this.l};l.ta=function(){return this.u};l.mm=function(){return this.c};l.kb=function(){return this.ca};l.nm=function(){return this.a};l.Ye=wa;l.load=wa;l.xf=wa;
-l.Gg=function(a,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();c.arc(a.size/2,a.size/2,this.c,0,2*Math.PI,!0);this.f&&(c.fillStyle=Ag(this.f.b),c.fill());this.a&&(c.strokeStyle=a.strokeStyle,c.lineWidth=a.hd,a.lineDash&&c.setLineDash(a.lineDash),c.stroke());c.closePath()};
-l.Fg=function(a,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();c.arc(a.size/2,a.size/2,this.c,0,2*Math.PI,!0);c.fillStyle=Ag(Bl);c.fill();this.a&&(c.strokeStyle=a.strokeStyle,c.lineWidth=a.hd,a.lineDash&&c.setLineDash(a.lineDash),c.stroke());c.closePath()};l.rb=function(){var a=this.a?this.a.rb():"-",c=this.f?this.f.rb():"-";this.g&&a==this.g[1]&&c==this.g[2]&&this.c==this.g[3]||(this.g=["c"+a+c+(void 0!==this.c?this.c.toString():"-"),a,c,this.c]);return this.g[0]};function Gl(a){a=a||{};this.i=null;this.g=Hl;void 0!==a.geometry&&this.Jg(a.geometry);this.f=void 0!==a.fill?a.fill:null;this.j=void 0!==a.image?a.image:null;this.c=void 0!==a.stroke?a.stroke:null;this.a=void 0!==a.text?a.text:null;this.b=a.zIndex}l=Gl.prototype;l.V=function(){return this.i};l.Oi=function(){return this.g};l.zm=function(){return this.f};l.Am=function(){return this.j};l.Bm=function(){return this.c};l.Cm=function(){return this.a};l.Dm=function(){return this.b};
-l.Jg=function(a){ka(a)?this.g=a:ia(a)?this.g=function(c){return c.get(a)}:a?void 0!==a&&(this.g=function(){return a}):this.g=Hl;this.i=a};l.Em=function(a){this.b=a};function Il(a){if(!ka(a)){var c;c=ga(a)?a:[a];a=function(){return c}}return a}var Jl=null;function Kl(){if(!Jl){var a=new El({color:"rgba(255,255,255,0.4)"}),c=new Al({color:"#3399CC",width:1.25});Jl=[new Gl({image:new Fl({fill:a,stroke:c,radius:5}),fill:a,stroke:c})]}return Jl}
-function Ll(){var a={},c=[255,255,255,1],d=[0,153,255,1];a.Polygon=[new Gl({fill:new El({color:[255,255,255,.5]})})];a.MultiPolygon=a.Polygon;a.LineString=[new Gl({stroke:new Al({color:c,width:5})}),new Gl({stroke:new Al({color:d,width:3})})];a.MultiLineString=a.LineString;a.Circle=a.Polygon.concat(a.LineString);a.Point=[new Gl({image:new Fl({radius:6,fill:new El({color:d}),stroke:new Al({color:c,width:1.5})}),zIndex:Infinity})];a.MultiPoint=a.Point;a.GeometryCollection=a.Polygon.concat(a.LineString,
-a.Point);return a}function Hl(a){return a.V()};function Ml(a){var c=a?a:{};a=c.condition?c.condition:$k;this.i=c.duration?c.duration:200;c=c.style?c.style:new Gl({stroke:new Al({color:[0,0,255,1]})});sl.call(this,{condition:a,style:c})}w(Ml,sl);Ml.prototype.l=function(){var a=this.A,c=a.Z(),d=a.Ea(),e=this.V().R(),d=c.constrainResolution(Math.max(le(e)/d[0],ie(e)/d[1])),f=c.aa(),g=c.Fa();a.Aa(bg({resolution:f,duration:this.i,easing:Wf}));a.Aa($f({source:g,duration:this.i,easing:Wf}));c.Ra(ge(e));c.wb(d)};function Nl(a){Qk.call(this,{handleEvent:Ol});a=a||{};this.a=void 0!==a.condition?a.condition:we(Zk,al);this.f=void 0!==a.duration?a.duration:100;this.j=void 0!==a.pixelDelta?a.pixelDelta:128}w(Nl,Qk);
-function Ol(a){var c=!1;if("key"==a.type){var d=a.b.j;if(this.a(a)&&(40==d||37==d||39==d||38==d)){var e=a.map,c=e.Z(),f=c.aa()*this.j,g=0,h=0;40==d?h=-f:37==d?g=-f:39==d?g=f:h=f;d=[g,h];vd(d,c.va());f=this.f;if(g=c.Fa())f&&0<f&&e.Aa($f({source:g,duration:f,easing:Yf})),e=c.vd([g[0]+d[0],g[1]+d[1]]),c.Ra(e);a.preventDefault();c=!0}}return!c};function Pl(a){Qk.call(this,{handleEvent:Ql});a=a?a:{};this.f=a.condition?a.condition:al;this.a=a.delta?a.delta:1;this.j=a.duration?a.duration:100}w(Pl,Qk);function Ql(a){var c=!1;if("key"==a.type){var d=a.b.A;if(this.f(a)&&(43==d||45==d)){c=a.map;d=43==d?this.a:-this.a;c.render();var e=c.Z();Sk(c,e,d,void 0,this.j);a.preventDefault();c=!0}}return!c};function Rl(a){Qk.call(this,{handleEvent:Sl});a=a||{};this.f=0;this.u=void 0!==a.duration?a.duration:250;this.l=void 0!==a.useAnchor?a.useAnchor:!0;this.a=null;this.i=this.j=void 0}w(Rl,Qk);function Sl(a){var c=!1;if("mousewheel"==a.type){var c=a.map,d=a.b;this.l&&(this.a=a.coordinate);this.f+=d.u;void 0===this.j&&(this.j=Date.now());d=Math.max(80-(Date.now()-this.j),0);ba.clearTimeout(this.i);this.i=ba.setTimeout(qa(this.v,this,c),d);a.preventDefault();c=!0}return!c}
-Rl.prototype.v=function(a){var c=Qa(this.f,-1,1),d=a.Z();a.render();Sk(a,d,-c,this.a,this.u);this.f=0;this.a=null;this.i=this.j=void 0};Rl.prototype.B=function(a){this.l=a;a||(this.a=null)};function Tl(a){cl.call(this,{handleDownEvent:Ul,handleDragEvent:Vl,handleUpEvent:Wl});a=a||{};this.f=null;this.i=void 0;this.a=!1;this.l=0;this.B=void 0!==a.threshold?a.threshold:.3;this.v=void 0!==a.duration?a.duration:250}w(Tl,cl);
-function Vl(a){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.i&&(c=d-this.i,this.l+=c,!this.a&&Math.abs(this.l)>this.B&&(this.a=!0));this.i=d;a=a.map;d=kh(a.a);e=el(this.j);e[0]-=d.x;e[1]-=d.y;this.f=a.xa(e);this.a&&(d=a.Z(),e=d.va(),a.render(),Rk(a,d,e+c,this.f))}function Wl(a){if(2>this.j.length){a=a.map;var c=a.Z();Uf(c,-1);if(this.a){var d=c.va(),e=this.f,f=this.v,d=c.constrainRotation(d,0);Rk(a,c,d,e,f)}return!1}return!0}
-function Ul(a){return 2<=this.j.length?(a=a.map,this.f=null,this.i=void 0,this.a=!1,this.l=0,this.u||Uf(a.Z(),1),a.render(),!0):!1}Tl.prototype.ic=qe;function Xl(a){cl.call(this,{handleDownEvent:Yl,handleDragEvent:Zl,handleUpEvent:$l});a=a?a:{};this.f=null;this.l=void 0!==a.duration?a.duration:400;this.a=void 0;this.i=1}w(Xl,cl);function Zl(a){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.i=c);a=a.map;var f=a.Z(),d=f.aa(),e=kh(a.a),g=el(this.j);g[0]-=e.x;g[1]-=e.y;this.f=a.xa(g);a.render();Tk(a,f,d*c,this.f)}
-function $l(a){if(2>this.j.length){a=a.map;var c=a.Z();Uf(c,-1);var d=c.aa(),e=this.f,f=this.l,d=c.constrainResolution(d,0,this.i-1);Tk(a,c,d,e,f);return!1}return!0}function Yl(a){return 2<=this.j.length?(a=a.map,this.f=null,this.a=void 0,this.i=1,this.u||Uf(a.Z(),1),a.render(),!0):!1}Xl.prototype.ic=qe;function am(a){a=a?a:{};var c=new tg,d=new Ok(-.005,.05,100);(void 0!==a.altShiftDragRotate?a.altShiftDragRotate:1)&&c.push(new jl);(void 0!==a.doubleClickZoom?a.doubleClickZoom:1)&&c.push(new Uk({delta:a.zoomDelta,duration:a.zoomDuration}));(void 0!==a.dragPan?a.dragPan:1)&&c.push(new fl({kinetic:d}));(void 0!==a.pinchRotate?a.pinchRotate:1)&&c.push(new Tl);(void 0!==a.pinchZoom?a.pinchZoom:1)&&c.push(new Xl({duration:a.zoomDuration}));if(void 0!==a.keyboard?a.keyboard:1)c.push(new Nl),c.push(new Pl({delta:a.zoomDelta,
-duration:a.zoomDuration}));(void 0!==a.mouseWheelZoom?a.mouseWheelZoom:1)&&c.push(new Rl({duration:a.zoomDuration}));(void 0!==a.shiftDragZoom?a.shiftDragZoom:1)&&c.push(new Ml);return c};function bm(a){var c=a||{};a=Vb(c);delete a.layers;c=c.layers;ck.call(this,a);this.c=[];this.a={};B(this,ld("layers"),this.Gj,!1,this);c?ga(c)&&(c=new tg(c.slice())):c=new tg;this.jh(c)}w(bm,ck);l=bm.prototype;l.Ld=function(){this.ib()&&this.s()};
-l.Gj=function(){this.c.forEach($c);this.c.length=0;var a=this.xc();this.c.push(B(a,"add",this.Fj,!1,this),B(a,"remove",this.Hj,!1,this));Jb(this.a,function(a){a.forEach($c)});Sb(this.a);var a=a.a,c,d,e;c=0;for(d=a.length;c<d;c++)e=a[c],this.a[v(e).toString()]=[B(e,"propertychange",this.Ld,!1,this),B(e,"change",this.Ld,!1,this)];this.s()};l.Fj=function(a){a=a.element;var c=v(a).toString();this.a[c]=[B(a,"propertychange",this.Ld,!1,this),B(a,"change",this.Ld,!1,this)];this.s()};
-l.Hj=function(a){a=v(a.element).toString();this.a[a].forEach($c);delete this.a[a];this.s()};l.xc=function(){return this.get("layers")};l.jh=function(a){this.set("layers",a)};
-l.Te=function(a){var c=void 0!==a?a:[],d=c.length;this.xc().forEach(function(a){a.Te(c)});a=dk(this);var e,f;for(e=c.length;d<e;d++)f=c[d],f.opacity*=a.opacity,f.visible=f.visible&&a.visible,f.maxResolution=Math.min(f.maxResolution,a.maxResolution),f.minResolution=Math.max(f.minResolution,a.minResolution),void 0!==a.extent&&(f.extent=void 0!==f.extent?je(f.extent,a.extent):a.extent);return c};l.Ue=function(){return"ready"};function cm(a){Ae.call(this,{code:a,units:"m",extent:dm,global:!0,worldExtent:em})}w(cm,Ae);cm.prototype.getPointResolution=function(a,c){var d=c[1]/6378137;return a/((Math.exp(d)+Math.exp(-d))/2)};var fm=6378137*Math.PI,dm=[-fm,-fm,fm,fm],em=[-180,-85,180,85],Ne="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(a){return new cm(a)});
-function Oe(a,c,d){var e=a.length;d=1<d?d:2;void 0===c&&(2<d?c=a.slice():c=Array(e));for(var f=0;f<e;f+=d)c[f]=6378137*Math.PI*a[f]/180,c[f+1]=6378137*Math.log(Math.tan(Math.PI*(a[f+1]+90)/360));return c}function Pe(a,c,d){var e=a.length;d=1<d?d:2;void 0===c&&(2<d?c=a.slice():c=Array(e));for(var f=0;f<e;f+=d)c[f]=180*a[f]/(6378137*Math.PI),c[f+1]=360*Math.atan(Math.exp(a[f+1]/6378137))/Math.PI-90;return c};function gm(a,c){Ae.call(this,{code:a,units:"degrees",extent:hm,axisOrientation:c,global:!0,worldExtent:hm})}w(gm,Ae);gm.prototype.getPointResolution=function(a){return a};
-var hm=[-180,-90,180,90],Qe=[new gm("CRS:84"),new gm("EPSG:4326","neu"),new gm("urn:ogc:def:crs:EPSG::4326","neu"),new gm("urn:ogc:def:crs:EPSG:6.6:4326","neu"),new gm("urn:ogc:def:crs:OGC:1.3:CRS84"),new gm("urn:ogc:def:crs:OGC:2:84"),new gm("http://www.opengis.net/gml/srs/epsg.xml#4326","neu"),new gm("urn:x-ogc:def:crs:EPSG:4326","neu")];function im(){De(Ne);De(Qe);Me()};function jm(a){gk.call(this,a?a:{})}w(jm,gk);function G(a){a=a?a:{};var c=Vb(a);delete c.preload;delete c.useInterimTilesOnError;gk.call(this,c);this.f(void 0!==a.preload?a.preload:0);this.g(void 0!==a.useInterimTilesOnError?a.useInterimTilesOnError:!0)}w(G,gk);G.prototype.a=function(){return this.get("preload")};G.prototype.f=function(a){this.set("preload",a)};G.prototype.c=function(){return this.get("useInterimTilesOnError")};G.prototype.g=function(a){this.set("useInterimTilesOnError",a)};function H(a){a=a?a:{};var c=Vb(a);delete c.style;delete c.renderBuffer;delete c.updateWhileAnimating;delete c.updateWhileInteracting;gk.call(this,c);this.a=void 0!==a.renderBuffer?a.renderBuffer:100;this.A=null;this.c=void 0;this.g(a.style);this.B=void 0!==a.updateWhileAnimating?a.updateWhileAnimating:!1;this.N=void 0!==a.updateWhileInteracting?a.updateWhileInteracting:!1}w(H,gk);H.prototype.T=function(){return this.A};H.prototype.X=function(){return this.c};
-H.prototype.g=function(a){this.A=void 0!==a?a:Kl;this.c=null===a?void 0:Il(this.A);this.s()};function km(a,c,d,e,f){this.u={};this.c=a;this.N=c;this.g=d;this.ka=e;this.Ic=f;this.j=this.b=this.a=this.ra=this.Ma=this.fa=null;this.Na=this.wa=this.v=this.X=this.T=this.I=0;this.lb=!1;this.i=this.mb=0;this.nb=!1;this.$=0;this.f="";this.C=this.ca=this.lc=this.pd=0;this.da=this.A=this.l=null;this.B=[];this.mc=Cd()}
-function lm(a,c,d){if(a.j){c=$e(c,0,d,2,a.ka,a.B);d=a.c;var e=a.mc,f=d.globalAlpha;1!=a.v&&(d.globalAlpha=f*a.v);var g=a.mb;a.lb&&(g+=a.Ic);var h,k;h=0;for(k=c.length;h<k;h+=2){var m=c[h]-a.I,n=c[h+1]-a.T;a.nb&&(m=m+.5|0,n=n+.5|0);if(0!==g||1!=a.i){var p=m+a.I,q=n+a.T;kk(e,p,q,a.i,a.i,g,-p,-q);d.setTransform(e[0],e[1],e[4],e[5],e[12],e[13])}d.drawImage(a.j,a.wa,a.Na,a.$,a.X,m,n,a.$,a.X)}0===g&&1==a.i||d.setTransform(1,0,0,1,0,0);1!=a.v&&(d.globalAlpha=f)}}
-function nm(a,c,d,e){var f=0;if(a.da&&""!==a.f){a.l&&om(a,a.l);a.A&&pm(a,a.A);var g=a.da,h=a.c,k=a.ra;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,a.ra={font:g.font,textAlign:g.textAlign,textBaseline:g.textBaseline});c=$e(c,f,d,e,a.ka,a.B);for(g=a.c;f<d;f+=e){h=c[f]+a.pd;k=c[f+1]+a.lc;if(0!==
-a.ca||1!=a.C){var m=kk(a.mc,h,k,a.C,a.C,a.ca,-h,-k);g.setTransform(m[0],m[1],m[4],m[5],m[12],m[13])}a.A&&g.strokeText(a.f,h,k);a.l&&g.fillText(a.f,h,k)}0===a.ca&&1==a.C||g.setTransform(1,0,0,1,0,0)}}function qm(a,c,d,e,f,g){var h=a.c;a=$e(c,d,e,f,a.ka,a.B);h.moveTo(a[0],a[1]);for(c=2;c<a.length;c+=2)h.lineTo(a[c],a[c+1]);g&&h.lineTo(a[0],a[1]);return e}function rm(a,c,d,e,f){var g=a.c,h,k;h=0;for(k=e.length;h<k;++h)d=qm(a,c,d,e[h],f,!0),g.closePath();return d}l=km.prototype;
-l.oc=function(a,c){var d=a.toString(),e=this.u[d];void 0!==e?e.push(c):this.u[d]=[c]};l.pc=function(a){if(ke(this.g,a.R())){if(this.a||this.b){this.a&&om(this,this.a);this.b&&pm(this,this.b);var c;c=(c=a.o)?$e(c,0,c.length,a.G,this.ka,this.B):null;var d=c[2]-c[0],e=c[3]-c[1],d=Math.sqrt(d*d+e*e),e=this.c;e.beginPath();e.arc(c[0],c[1],d,0,2*Math.PI);this.a&&e.fill();this.b&&e.stroke()}""!==this.f&&nm(this,a.$c(),2,2)}};
-l.Je=function(a,c){var d=(0,c.g)(a);if(d&&ke(this.g,d.R())){var e=c.b;void 0===e&&(e=0);this.oc(e,function(a){a.Ia(c.f,c.c);a.bb(c.j);a.Ja(c.a);sm[d.W()].call(a,d,null)})}};l.xd=function(a,c){var d=a.f,e,f;e=0;for(f=d.length;e<f;++e){var g=d[e];sm[g.W()].call(this,g,c)}};l.pb=function(a){var c=a.o;a=a.G;this.j&&lm(this,c,c.length);""!==this.f&&nm(this,c,c.length,a)};l.ob=function(a){var c=a.o;a=a.G;this.j&&lm(this,c,c.length);""!==this.f&&nm(this,c,c.length,a)};
-l.yb=function(a){if(ke(this.g,a.R())){if(this.b){pm(this,this.b);var c=this.c,d=a.o;c.beginPath();qm(this,d,0,d.length,a.G,!1);c.stroke()}""!==this.f&&(a=tm(a),nm(this,a,2,2))}};l.qc=function(a){var c=a.R();if(ke(this.g,c)){if(this.b){pm(this,this.b);var c=this.c,d=a.o,e=0,f=a.c,g=a.G;c.beginPath();var h,k;h=0;for(k=f.length;h<k;++h)e=qm(this,d,e,f[h],g,!1);c.stroke()}""!==this.f&&(a=um(a),nm(this,a,a.length,2))}};
-l.Pb=function(a){if(ke(this.g,a.R())){if(this.b||this.a){this.a&&om(this,this.a);this.b&&pm(this,this.b);var c=this.c;c.beginPath();rm(this,If(a),0,a.c,a.G);this.a&&c.fill();this.b&&c.stroke()}""!==this.f&&(a=Jf(a),nm(this,a,2,2))}};
-l.rc=function(a){if(ke(this.g,a.R())){if(this.b||this.a){this.a&&om(this,this.a);this.b&&pm(this,this.b);var c=this.c,d=vm(a),e=0,f=a.c,g=a.G,h,k;h=0;for(k=f.length;h<k;++h){var m=f[h];c.beginPath();e=rm(this,d,e,m,g);this.a&&c.fill();this.b&&c.stroke()}}""!==this.f&&(a=wm(a),nm(this,a,a.length,2))}};function xm(a){var c=Object.keys(a.u).map(Number);kb(c);var d,e,f,g,h;d=0;for(e=c.length;d<e;++d)for(f=a.u[c[d].toString()],g=0,h=f.length;g<h;++g)f[g](a)}
-function om(a,c){var d=a.c,e=a.fa;e?e.fillStyle!=c.fillStyle&&(e.fillStyle=d.fillStyle=c.fillStyle):(d.fillStyle=c.fillStyle,a.fa={fillStyle:c.fillStyle})}
-function pm(a,c){var d=a.c,e=a.Ma;e?(e.lineCap!=c.lineCap&&(e.lineCap=d.lineCap=c.lineCap),$i&&!ob(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,$i&&d.setLineDash(c.lineDash),d.lineJoin=c.lineJoin,d.lineWidth=
-c.lineWidth,d.miterLimit=c.miterLimit,d.strokeStyle=c.strokeStyle,a.Ma={lineCap:c.lineCap,lineDash:c.lineDash,lineJoin:c.lineJoin,lineWidth:c.lineWidth,miterLimit:c.miterLimit,strokeStyle:c.strokeStyle})}
-l.Ia=function(a,c){if(a){var d=a.b;this.a={fillStyle:Ag(d?d:Bl)}}else this.a=null;if(c){var d=c.b,e=c.f,f=c.c,g=c.g,h=c.a,k=c.j;this.b={lineCap:void 0!==e?e:"round",lineDash:f?f:Cl,lineJoin:void 0!==g?g:"round",lineWidth:this.N*(void 0!==h?h:1),miterLimit:void 0!==k?k:10,strokeStyle:Ag(d?d:Dl)}}else this.b=null};
-l.bb=function(a){if(a){var c=a.Ab(),d=a.Jb(1),e=a.ta(),f=a.kb();this.I=c[0];this.T=c[1];this.X=f[1];this.j=d;this.v=a.v;this.wa=e[0];this.Na=e[1];this.lb=a.B;this.mb=a.C;this.i=a.A;this.nb=a.N;this.$=f[0]}else this.j=null};
-l.Ja=function(a){if(a){var c=a.b;c?(c=c.b,this.l={fillStyle:Ag(c?c:Bl)}):this.l=null;var d=a.j;if(d){var c=d.b,e=d.f,f=d.c,g=d.g,h=d.a,d=d.j;this.A={lineCap:void 0!==e?e:"round",lineDash:f?f:Cl,lineJoin:void 0!==g?g:"round",lineWidth:void 0!==h?h:1,miterLimit:void 0!==d?d:10,strokeStyle:Ag(c?c:Dl)}}else this.A=null;var c=a.f,e=a.C,f=a.A,g=a.g,h=a.a,d=a.c,k=a.i;a=a.l;this.da={font:void 0!==c?c:"10px sans-serif",textAlign:void 0!==k?k:"center",textBaseline:void 0!==a?a:"middle"};this.f=void 0!==d?d:
-"";this.pd=void 0!==e?this.N*e:0;this.lc=void 0!==f?this.N*f:0;this.ca=void 0!==g?g:0;this.C=this.N*(void 0!==h?h:1)}else this.f=""};var sm={Point:km.prototype.pb,LineString:km.prototype.yb,Polygon:km.prototype.Pb,MultiPoint:km.prototype.ob,MultiLineString:km.prototype.qc,MultiPolygon:km.prototype.rc,GeometryCollection:km.prototype.xd,Circle:km.prototype.pc};function ym(a){nk.call(this,a);this.I=Cd()}w(ym,nk);
-ym.prototype.v=function(a,c,d){zm(this,"precompose",d,a,void 0);var e=this.cd();if(e){var f=c.extent,g=void 0!==f;if(g){var h=a.pixelRatio,k=fe(f),m=ee(f),n=de(f),f=ce(f);mk(a.coordinateToPixelMatrix,k,k);mk(a.coordinateToPixelMatrix,m,m);mk(a.coordinateToPixelMatrix,n,n);mk(a.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.Se();k=d.globalAlpha;d.globalAlpha=c.opacity;0===a.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()}zm(this,"postcompose",d,a,void 0)};function zm(a,c,d,e,f){var g=a.a;fd(g,c)&&(a=void 0!==f?f:Am(a,e,0),a=new km(d,e.pixelRatio,e.extent,a,e.viewState.rotation),C(g,new fk(c,g,a,e,d,null)),xm(a))}
-function Am(a,c,d){var e=c.viewState,f=c.pixelRatio;return kk(a.I,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 Bm(a,c){var d=[0,0];mk(c,a,d);return d}
-var Cm=function(){var a=null,c=null;return function(d){if(!a){a=Ri(1,1);c=a.createImageData(1,1);var e=c.data;e[0]=42;e[1]=84;e[2]=126;e[3]=255}var e=a.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,a.putImageData(c,e,d),d=a.getImageData(e,d,1,1),f=ob(c.data,d.data));return f}}();var Dm=["Polygon","LineString","Image","Text"];function Em(a,c,d){this.ra=a;this.$=c;this.f=null;this.g=0;this.resolution=d;this.T=this.I=null;this.a=[];this.coordinates=[];this.fa=Cd();this.b=[];this.da=[];this.Ma=Cd()}w(Em,ek);
-function Fm(a,c,d,e,f,g){var h=a.coordinates.length,k=a.Ne(),m=[c[d],c[d+1]],n=[NaN,NaN],p=!0,q,r,u;for(q=d+f;q<e;q+=f)n[0]=c[q],n[1]=c[q+1],u=Xd(k,n),u!==r?(p&&(a.coordinates[h++]=m[0],a.coordinates[h++]=m[1]),a.coordinates[h++]=n[0],a.coordinates[h++]=n[1],p=!1):1===u?(a.coordinates[h++]=n[0],a.coordinates[h++]=n[1],p=!1):p=!0,m[0]=n[0],m[1]=n[1],r=u;q===d+f&&(a.coordinates[h++]=m[0],a.coordinates[h++]=m[1]);g&&(a.coordinates[h++]=c[d],a.coordinates[h++]=c[d+1]);return h}
-function Gm(a,c){a.I=[0,c,0];a.a.push(a.I);a.T=[0,c,0];a.b.push(a.T)}
-function Hm(a,c,d,e,f,g,h,k,m){var n;lk(e,a.fa)?n=a.da:(n=$e(a.coordinates,0,a.coordinates.length,2,e,a.da),Fd(a.fa,e));e=0;var p=h.length,q=0,r;for(a=a.Ma;e<p;){var u=h[e],y,A,F,z;switch(u[0]){case 0:q=u[1];r=v(q).toString();void 0===g[r]&&q.V()?void 0===m||ke(m,q.V().R())?++e:e=u[2]:e=u[2];break;case 1:c.beginPath();++e;break;case 2:q=u[1];r=n[q];var x=n[q+1],K=n[q+2]-r,q=n[q+3]-x;c.arc(r,x,Math.sqrt(K*K+q*q),0,2*Math.PI,!0);++e;break;case 3:c.closePath();++e;break;case 4:q=u[1];r=u[2];y=u[3];F=
-u[4]*d;var J=u[5]*d,I=u[6];A=u[7];var N=u[8],va=u[9],x=u[11],K=u[12],Ra=u[13],M=u[14];for(u[10]&&(x+=f);q<r;q+=2){u=n[q]-F;z=n[q+1]-J;Ra&&(u=u+.5|0,z=z+.5|0);if(1!=K||0!==x){var Ia=u+F,pb=z+J;kk(a,Ia,pb,K,K,x,-Ia,-pb);c.setTransform(a[0],a[1],a[4],a[5],a[12],a[13])}Ia=c.globalAlpha;1!=A&&(c.globalAlpha=Ia*A);c.drawImage(y,N,va,M,I,u,z,M*d,I*d);1!=A&&(c.globalAlpha=Ia);1==K&&0===x||c.setTransform(1,0,0,1,0,0)}++e;break;case 5:q=u[1];r=u[2];F=u[3];J=u[4]*d;I=u[5]*d;x=u[6];K=u[7]*d;y=u[8];for(A=u[9];q<
-r;q+=2){u=n[q]+J;z=n[q+1]+I;if(1!=K||0!==x)kk(a,u,z,K,K,x,-u,-z),c.setTransform(a[0],a[1],a[4],a[5],a[12],a[13]);A&&c.strokeText(F,u,z);y&&c.fillText(F,u,z);1==K&&0===x||c.setTransform(1,0,0,1,0,0)}++e;break;case 6:if(void 0!==k&&(q=u[1],q=k(q)))return q;++e;break;case 7:c.fill();++e;break;case 8:q=u[1];r=u[2];c.moveTo(n[q],n[q+1]);for(q+=2;q<r;q+=2)c.lineTo(n[q],n[q+1]);++e;break;case 9:c.fillStyle=u[1];++e;break;case 10:q=void 0!==u[7]?u[7]:!0;r=u[2];c.strokeStyle=u[1];c.lineWidth=q?r*d:r;c.lineCap=
-u[3];c.lineJoin=u[4];c.miterLimit=u[5];$i&&c.setLineDash(u[6]);++e;break;case 11:c.font=u[1];c.textAlign=u[2];c.textBaseline=u[3];++e;break;case 12:c.stroke();++e;break;default:++e}}}function Im(a){var c=a.b;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=a.b;for(g=d;h<g;){var k=f[h];f[h]=f[g];f[g]=k;++h;--g}h=-1}}function Jm(a,c){a.I[2]=a.a.length;a.I=null;a.T[2]=a.b.length;a.T=null;var d=[6,c];a.a.push(d);a.b.push(d)}Em.prototype.Zd=wa;
-Em.prototype.Ne=function(){return this.$};function Km(a,c,d){Em.call(this,a,c,d);this.l=this.X=null;this.ka=this.ca=this.N=this.B=this.u=this.v=this.A=this.C=this.i=this.j=this.c=void 0}w(Km,Em);
-Km.prototype.pb=function(a,c){if(this.l){Gm(this,c);var d=a.o,e=this.coordinates.length,d=Fm(this,d,0,d.length,a.G,!1);this.a.push([4,e,d,this.l,this.c,this.j,this.i,this.C,this.A,this.v,this.u,this.B,this.N,this.ca,this.ka]);this.b.push([4,e,d,this.X,this.c,this.j,this.i,this.C,this.A,this.v,this.u,this.B,this.N,this.ca,this.ka]);Jm(this,c)}};
-Km.prototype.ob=function(a,c){if(this.l){Gm(this,c);var d=a.o,e=this.coordinates.length,d=Fm(this,d,0,d.length,a.G,!1);this.a.push([4,e,d,this.l,this.c,this.j,this.i,this.C,this.A,this.v,this.u,this.B,this.N,this.ca,this.ka]);this.b.push([4,e,d,this.X,this.c,this.j,this.i,this.C,this.A,this.v,this.u,this.B,this.N,this.ca,this.ka]);Jm(this,c)}};Km.prototype.Zd=function(){Im(this);this.j=this.c=void 0;this.l=this.X=null;this.ka=this.ca=this.B=this.u=this.v=this.A=this.C=this.N=this.i=void 0};
-Km.prototype.bb=function(a){var c=a.Ab(),d=a.kb(),e=a.ce(1),f=a.Jb(1),g=a.ta();this.c=c[0];this.j=c[1];this.X=e;this.l=f;this.i=d[1];this.C=a.v;this.A=g[0];this.v=g[1];this.u=a.B;this.B=a.C;this.N=a.A;this.ca=a.N;this.ka=d[0]};function Lm(a,c,d){Em.call(this,a,c,d);this.c={Sc:void 0,Nc:void 0,Oc:null,Pc:void 0,Qc:void 0,Rc:void 0,Xe:0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}w(Lm,Em);
-function Mm(a,c,d,e,f){var g=a.coordinates.length;c=Fm(a,c,d,e,f,!1);g=[8,g,c];a.a.push(g);a.b.push(g);return e}l=Lm.prototype;l.Ne=function(){this.f||(this.f=Sd(this.$),0<this.g&&Rd(this.f,this.resolution*(this.g+1)/2,this.f));return this.f};
-function Nm(a){var c=a.c,d=c.strokeStyle,e=c.lineCap,f=c.lineDash,g=c.lineJoin,h=c.lineWidth,k=c.miterLimit;c.Sc==d&&c.Nc==e&&ob(c.Oc,f)&&c.Pc==g&&c.Qc==h&&c.Rc==k||(c.Xe!=a.coordinates.length&&(a.a.push([12]),c.Xe=a.coordinates.length),a.a.push([10,d,h,e,g,k,f],[1]),c.Sc=d,c.Nc=e,c.Oc=f,c.Pc=g,c.Qc=h,c.Rc=k)}
-l.yb=function(a,c){var d=this.c,e=d.lineWidth;void 0!==d.strokeStyle&&void 0!==e&&(Nm(this),Gm(this,c),this.b.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash],[1]),d=a.o,Mm(this,d,0,d.length,a.G),this.b.push([12]),Jm(this,c))};
-l.qc=function(a,c){var d=this.c,e=d.lineWidth;if(void 0!==d.strokeStyle&&void 0!==e){Nm(this);Gm(this,c);this.b.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash],[1]);var d=a.c,e=a.o,f=a.G,g=0,h,k;h=0;for(k=d.length;h<k;++h)g=Mm(this,e,g,d[h],f);this.b.push([12]);Jm(this,c)}};l.Zd=function(){this.c.Xe!=this.coordinates.length&&this.a.push([12]);Im(this);this.c=null};
-l.Ia=function(a,c){var d=c.b;this.c.strokeStyle=Ag(d?d:Dl);d=c.f;this.c.lineCap=void 0!==d?d:"round";d=c.c;this.c.lineDash=d?d:Cl;d=c.g;this.c.lineJoin=void 0!==d?d:"round";d=c.a;this.c.lineWidth=void 0!==d?d:1;d=c.j;this.c.miterLimit=void 0!==d?d:10;this.c.lineWidth>this.g&&(this.g=this.c.lineWidth,this.f=null)};
-function Om(a,c,d){Em.call(this,a,c,d);this.c={Nf:void 0,Sc:void 0,Nc:void 0,Oc:null,Pc:void 0,Qc:void 0,Rc:void 0,fillStyle:void 0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}w(Om,Em);
-function Pm(a,c,d,e,f){var g=a.c,h=[1];a.a.push(h);a.b.push(h);var k,h=0;for(k=e.length;h<k;++h){var m=e[h],n=a.coordinates.length;d=Fm(a,c,d,m,f,!0);d=[8,n,d];n=[3];a.a.push(d,n);a.b.push(d,n);d=m}c=[7];a.b.push(c);void 0!==g.fillStyle&&a.a.push(c);void 0!==g.strokeStyle&&(g=[12],a.a.push(g),a.b.push(g));return d}l=Om.prototype;
-l.pc=function(a,c){var d=this.c,e=d.strokeStyle;if(void 0!==d.fillStyle||void 0!==e){Qm(this);Gm(this,c);this.b.push([9,Ag(Bl)]);void 0!==d.strokeStyle&&this.b.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]);var f=a.o,e=this.coordinates.length;Fm(this,f,0,f.length,a.G,!1);f=[1];e=[2,e];this.a.push(f,e);this.b.push(f,e);e=[7];this.b.push(e);void 0!==d.fillStyle&&this.a.push(e);void 0!==d.strokeStyle&&(d=[12],this.a.push(d),this.b.push(d));Jm(this,c)}};
-l.Pb=function(a,c){var d=this.c,e=d.strokeStyle;if(void 0!==d.fillStyle||void 0!==e)Qm(this),Gm(this,c),this.b.push([9,Ag(Bl)]),void 0!==d.strokeStyle&&this.b.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]),d=a.c,e=If(a),Pm(this,e,0,d,a.G),Jm(this,c)};
-l.rc=function(a,c){var d=this.c,e=d.strokeStyle;if(void 0!==d.fillStyle||void 0!==e){Qm(this);Gm(this,c);this.b.push([9,Ag(Bl)]);void 0!==d.strokeStyle&&this.b.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]);var d=a.c,e=vm(a),f=a.G,g=0,h,k;h=0;for(k=d.length;h<k;++h)g=Pm(this,e,g,d[h],f);Jm(this,c)}};l.Zd=function(){Im(this);this.c=null;var a=this.ra;if(0!==a){var c=this.coordinates,d,e;d=0;for(e=c.length;d<e;++d)c[d]=a*Math.round(c[d]/a)}};
-l.Ne=function(){this.f||(this.f=Sd(this.$),0<this.g&&Rd(this.f,this.resolution*(this.g+1)/2,this.f));return this.f};
-l.Ia=function(a,c){var d=this.c;if(a){var e=a.b;d.fillStyle=Ag(e?e:Bl)}else d.fillStyle=void 0;c?(e=c.b,d.strokeStyle=Ag(e?e:Dl),e=c.f,d.lineCap=void 0!==e?e:"round",e=c.c,d.lineDash=e?e.slice():Cl,e=c.g,d.lineJoin=void 0!==e?e:"round",e=c.a,d.lineWidth=void 0!==e?e:1,e=c.j,d.miterLimit=void 0!==e?e:10,d.lineWidth>this.g&&(this.g=d.lineWidth,this.f=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 Qm(a){var c=a.c,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.Nf!=d&&(a.a.push([9,d]),c.Nf=c.fillStyle);void 0===e||c.Sc==e&&c.Nc==f&&c.Oc==g&&c.Pc==h&&c.Qc==k&&c.Rc==m||(a.a.push([10,e,k,f,h,m,g]),c.Sc=e,c.Nc=f,c.Oc=g,c.Pc=h,c.Qc=k,c.Rc=m)}function Rm(a,c,d){Em.call(this,a,c,d);this.ca=this.N=this.B=null;this.l="";this.u=this.v=this.A=this.C=0;this.i=this.j=this.c=null}w(Rm,Em);
-Rm.prototype.qb=function(a,c,d,e,f,g){if(""!==this.l&&this.i&&(this.c||this.j)){if(this.c){f=this.c;var h=this.B;if(!h||h.fillStyle!=f.fillStyle){var k=[9,f.fillStyle];this.a.push(k);this.b.push(k);h?h.fillStyle=f.fillStyle:this.B={fillStyle:f.fillStyle}}}this.j&&(f=this.j,h=this.N,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.a.push(k),this.b.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.N={lineCap:f.lineCap,lineDash:f.lineDash,lineJoin:f.lineJoin,lineWidth:f.lineWidth,miterLimit:f.miterLimit,strokeStyle:f.strokeStyle}));f=this.i;h=this.ca;h&&h.font==f.font&&h.textAlign==f.textAlign&&h.textBaseline==f.textBaseline||(k=[11,f.font,f.textAlign,f.textBaseline],this.a.push(k),this.b.push(k),h?
-(h.font=f.font,h.textAlign=f.textAlign,h.textBaseline=f.textBaseline):this.ca={font:f.font,textAlign:f.textAlign,textBaseline:f.textBaseline});Gm(this,g);f=this.coordinates.length;a=Fm(this,a,c,d,e,!1);a=[5,f,a,this.l,this.C,this.A,this.v,this.u,!!this.c,!!this.j];this.a.push(a);this.b.push(a);Jm(this,g)}};
-Rm.prototype.Ja=function(a){if(a){var c=a.b;c?(c=c.b,c=Ag(c?c:Bl),this.c?this.c.fillStyle=c:this.c={fillStyle:c}):this.c=null;var d=a.j;if(d){var c=d.b,e=d.f,f=d.c,g=d.g,h=d.a,d=d.j,e=void 0!==e?e:"round",f=f?f.slice():Cl,g=void 0!==g?g:"round",h=void 0!==h?h:1,d=void 0!==d?d:10,c=Ag(c?c:Dl);if(this.j){var k=this.j;k.lineCap=e;k.lineDash=f;k.lineJoin=g;k.lineWidth=h;k.miterLimit=d;k.strokeStyle=c}else this.j={lineCap:e,lineDash:f,lineJoin:g,lineWidth:h,miterLimit:d,strokeStyle:c}}else this.j=null;
-var m=a.f,c=a.C,e=a.A,f=a.g,h=a.a,d=a.c,g=a.i,k=a.l;a=void 0!==m?m:"10px sans-serif";g=void 0!==g?g:"center";k=void 0!==k?k:"middle";this.i?(m=this.i,m.font=a,m.textAlign=g,m.textBaseline=k):this.i={font:a,textAlign:g,textBaseline:k};this.l=void 0!==d?d:"";this.C=void 0!==c?c:0;this.A=void 0!==e?e:0;this.v=void 0!==f?f:0;this.u=void 0!==h?h:1}else this.l=""};function Sm(a,c,d,e){this.l=a;this.c=c;this.i=d;this.f=e;this.a={};this.g=Ri(1,1);this.j=Cd()}
-function Tm(a){for(var c in a.a){var d=a.a[c],e;for(e in d)d[e].Zd()}}function Um(a,c,d,e,f,g){var h=a.j;kk(h,.5,.5,1/d,-1/d,-e,-c[0],-c[1]);var k=a.g;k.clearRect(0,0,1,1);var m;void 0!==a.f&&(m=Nd(),Od(m,c),Rd(m,d*a.f,m));return Vm(a,k,h,e,f,function(a){if(0<k.getImageData(0,0,1,1).data[3]){if(a=g(a))return a;k.clearRect(0,0,1,1)}},m)}
-Sm.prototype.b=function(a,c){var d=void 0!==a?a.toString():"0",e=this.a[d];void 0===e&&(e={},this.a[d]=e);d=e[c];void 0===d&&(d=new Wm[c](this.l,this.c,this.i),e[c]=d);return d};Sm.prototype.ya=function(){return Rb(this.a)};
-function Xm(a,c,d,e,f,g){var h=Object.keys(a.a).map(Number);kb(h);var k=a.c,m=k[0],n=k[1],p=k[2],k=k[3],m=[m,n,m,k,p,k,p,n];$e(m,0,8,2,e,m);c.save();c.beginPath();c.moveTo(m[0],m[1]);c.lineTo(m[2],m[3]);c.lineTo(m[4],m[5]);c.lineTo(m[6],m[7]);c.closePath();c.clip();for(var q,r,m=0,n=h.length;m<n;++m)for(q=a.a[h[m].toString()],p=0,k=Dm.length;p<k;++p)r=q[Dm[p]],void 0!==r&&Hm(r,c,d,e,f,g,r.a,void 0);c.restore()}
-function Vm(a,c,d,e,f,g,h){var k=Object.keys(a.a).map(Number);kb(k,function(a,c){return c-a});var m,n,p,q,r;m=0;for(n=k.length;m<n;++m)for(q=a.a[k[m].toString()],p=Dm.length-1;0<=p;--p)if(r=q[Dm[p]],void 0!==r&&(r=Hm(r,c,1,d,e,f,r.b,g,h)))return r}var Wm={Image:Km,LineString:Lm,Polygon:Om,Text:Rm};function Ym(a,c,d){af.call(this);this.uf(a,c?c:0,d)}w(Ym,af);l=Ym.prototype;l.clone=function(){var a=new Ym(null);cf(a,this.a,this.o.slice());a.s();return a};l.Va=function(a,c,d,e){var f=this.o;a-=f[0];var g=c-f[1];c=a*a+g*g;if(c<e){if(0===c)for(e=0;e<this.G;++e)d[e]=f[e];else for(e=this.df()/Math.sqrt(c),d[0]=f[0]+e*a,d[1]=f[1]+e*g,e=2;e<this.G;++e)d[e]=f[e];d.length=this.G;return c}return e};l.Xb=function(a,c){var d=this.o,e=a-d[0],d=c-d[1];return e*e+d*d<=Zm(this)};
-l.$c=function(){return this.o.slice(0,this.G)};l.ud=function(a){var c=this.o,d=c[this.G]-c[0];return Qd(c[0]-d,c[1]-d,c[0]+d,c[1]+d,a)};l.df=function(){return Math.sqrt(Zm(this))};function Zm(a){var c=a.o[a.G]-a.o[0];a=a.o[a.G+1]-a.o[1];return c*c+a*a}l.W=function(){return"Circle"};l.ua=function(a){var c=this.R();return ke(a,c)?(c=this.$c(),a[0]<=c[0]&&a[2]>=c[0]||a[1]<=c[1]&&a[3]>=c[1]?!0:be(a,this.He,this)):!1};
-l.Wk=function(a){var c=this.G,d=a.slice();d[c]=d[0]+(this.o[c]-this.o[0]);var e;for(e=1;e<c;++e)d[c+e]=a[e];cf(this,this.a,d);this.s()};l.uf=function(a,c,d){if(a){df(this,d,a,0);this.o||(this.o=[]);d=this.o;a=mf(d,a);d[a++]=d[0]+c;var e;c=1;for(e=this.G;c<e;++c)d[a++]=d[c];d.length=a}else cf(this,"XY",null);this.s()};l.Xk=function(a){this.o[this.G]=this.o[0]+a;this.s()};function $m(a){Ze.call(this);this.f=a?a:null;an(this)}w($m,Ze);function bn(a){var c=[],d,e;d=0;for(e=a.length;d<e;++d)c.push(a[d].clone());return c}function cn(a){var c,d;if(a.f)for(c=0,d=a.f.length;c<d;++c)Zc(a.f[c],"change",a.s,!1,a)}function an(a){var c,d;if(a.f)for(c=0,d=a.f.length;c<d;++c)B(a.f[c],"change",a.s,!1,a)}l=$m.prototype;l.clone=function(){var a=new $m(null);a.gh(this.f);return a};
-l.Va=function(a,c,d,e){if(e<Td(this.R(),a,c))return e;var f=this.f,g,h;g=0;for(h=f.length;g<h;++g)e=f[g].Va(a,c,d,e);return e};l.Xb=function(a,c){var d=this.f,e,f;e=0;for(f=d.length;e<f;++e)if(d[e].Xb(a,c))return!0;return!1};l.ud=function(a){Qd(Infinity,Infinity,-Infinity,-Infinity,a);for(var c=this.f,d=0,e=c.length;d<e;++d)$d(a,c[d].R());return a};l.Rf=function(){return bn(this.f)};
-l.Hd=function(a){this.l!=this.b&&(Sb(this.g),this.j=0,this.l=this.b);if(0>a||0!==this.j&&a<this.j)return this;var c=a.toString();if(this.g.hasOwnProperty(c))return this.g[c];var d=[],e=this.f,f=!1,g,h;g=0;for(h=e.length;g<h;++g){var k=e[g],m=k.Hd(a);d.push(m);m!==k&&(f=!0)}if(f)return a=new $m(null),cn(a),a.f=d,an(a),a.s(),this.g[c]=a;this.j=a;return this};l.W=function(){return"GeometryCollection"};l.ua=function(a){var c=this.f,d,e;d=0;for(e=c.length;d<e;++d)if(c[d].ua(a))return!0;return!1};
-l.ya=function(){return 0===this.f.length};l.gh=function(a){a=bn(a);cn(this);this.f=a;an(this);this.s()};l.Ob=function(a){var c=this.f,d,e;d=0;for(e=c.length;d<e;++d)c[d].Ob(a);this.s()};l.wc=function(a,c){var d=this.f,e,f;e=0;for(f=d.length;e<f;++e)d[e].wc(a,c);this.s()};l.Y=function(){cn(this);$m.ba.Y.call(this)};function dn(a,c,d,e,f){var g=NaN,h=NaN,k=(d-c)/e;if(0!==k)if(1==k)g=a[c],h=a[c+1];else if(2==k)g=.5*a[c]+.5*a[c+e],h=.5*a[c+1]+.5*a[c+e+1];else{var h=a[c],k=a[c+1],m=0,g=[0],n;for(n=c+e;n<d;n+=e){var p=a[n],q=a[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=lb,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=xb(a[c],a[c+e],d),h=xb(a[c+1],a[c+e+1],d)):(g=a[c+r*e],h=a[c+r*e+1])}return f?(f[0]=
-g,f[1]=h,f):[g,h]}function en(a,c,d,e,f,g){if(d==c)return null;if(f<a[c+e-1])return g?(d=a.slice(c,c+e),d[e-1]=f,d):null;if(a[d-1]<f)return g?(d=a.slice(d-e,d),d[e-1]=f,d):null;if(f==a[c+e-1])return a.slice(c,c+e);c/=e;for(d/=e;c<d;)g=c+d>>1,f<a[(g+1)*e-1]?d=g:c=g+1;d=a[c*e-1];if(f==d)return a.slice((c-1)*e,(c-1)*e+e);g=(f-d)/(a[(c+1)*e-1]-d);d=[];var h;for(h=0;h<e-1;++h)d.push(xb(a[(c-1)*e+h],a[c*e+h],g));d.push(f);return d}
-function fn(a,c,d,e,f,g){var h=0;if(g)return en(a,h,c[c.length-1],d,e,f);if(e<a[d-1])return f?(a=a.slice(0,d),a[d-1]=e,a):null;if(a[a.length-1]<e)return f?(a=a.slice(a.length-d),a[d-1]=e,a):null;f=0;for(g=c.length;f<g;++f){var k=c[f];if(h!=k){if(e<a[h+d-1])break;if(e<=a[k-1])return en(a,h,k,d,e,!1);h=k}}return null};function L(a,c){af.call(this);this.c=null;this.u=this.B=this.i=-1;this.ja(a,c)}w(L,af);l=L.prototype;l.ji=function(a){this.o?hb(this.o,a):this.o=a.slice();this.s()};l.clone=function(){var a=new L(null);gn(a,this.a,this.o.slice());return a};l.Va=function(a,c,d,e){if(e<Td(this.R(),a,c))return e;this.u!=this.b&&(this.B=Math.sqrt(hf(this.o,0,this.o.length,this.G,0)),this.u=this.b);return kf(this.o,0,this.o.length,this.G,this.B,!1,a,c,d,e)};
-l.zi=function(a,c){return Af(this.o,0,this.o.length,this.G,a,c)};l.Yk=function(a,c){return"XYM"!=this.a&&"XYZM"!=this.a?null:en(this.o,0,this.o.length,this.G,a,void 0!==c?c:!1)};l.U=function(){return pf(this.o,0,this.o.length,this.G)};l.Zk=function(){var a=this.o,c=this.G,d=a[0],e=a[1],f=0,g;for(g=0+c;g<this.o.length;g+=c)var h=a[g],k=a[g+1],f=f+Math.sqrt((h-d)*(h-d)+(k-e)*(k-e)),d=h,e=k;return f};function tm(a){a.i!=a.b&&(a.c=dn(a.o,0,a.o.length,a.G,a.c),a.i=a.b);return a.c}
-l.tc=function(a){var c=[];c.length=rf(this.o,0,this.o.length,this.G,a,c,0);a=new L(null);gn(a,"XY",c);return a};l.W=function(){return"LineString"};l.ua=function(a){return Bf(this.o,0,this.o.length,this.G,a)};l.ja=function(a,c){a?(df(this,c,a,1),this.o||(this.o=[]),this.o.length=nf(this.o,0,a,this.G),this.s()):gn(this,"XY",null)};function gn(a,c,d){cf(a,c,d);a.s()};function O(a,c){af.call(this);this.c=[];this.i=this.u=-1;this.ja(a,c)}w(O,af);l=O.prototype;l.ki=function(a){this.o?hb(this.o,a.o.slice()):this.o=a.o.slice();this.c.push(this.o.length);this.s()};l.clone=function(){var a=new O(null);hn(a,this.a,this.o.slice(),this.c.slice());return a};l.Va=function(a,c,d,e){if(e<Td(this.R(),a,c))return e;this.i!=this.b&&(this.u=Math.sqrt(jf(this.o,0,this.c,this.G,0)),this.i=this.b);return lf(this.o,0,this.c,this.G,this.u,!1,a,c,d,e)};
-l.al=function(a,c,d){return"XYM"!=this.a&&"XYZM"!=this.a||0===this.o.length?null:fn(this.o,this.c,this.G,a,void 0!==c?c:!1,void 0!==d?d:!1)};l.U=function(){return qf(this.o,0,this.c,this.G)};l.Wi=function(a){if(0>a||this.c.length<=a)return null;var c=new L(null);gn(c,this.a,this.o.slice(0===a?0:this.c[a-1],this.c[a]));return c};l.Xc=function(){var a=this.o,c=this.c,d=this.a,e=[],f=0,g,h;g=0;for(h=c.length;g<h;++g){var k=c[g],m=new L(null);gn(m,d,a.slice(f,k));e.push(m);f=k}return e};
-function um(a){var c=[],d=a.o,e=0,f=a.c;a=a.G;var g,h;g=0;for(h=f.length;g<h;++g){var k=f[g],e=dn(d,e,k,a);hb(c,e);e=k}return c}l.tc=function(a){var c=[],d=[],e=this.o,f=this.c,g=this.G,h=0,k=0,m,n;m=0;for(n=f.length;m<n;++m){var p=f[m],k=rf(e,h,p,g,a,c,k);d.push(k);h=p}c.length=k;a=new O(null);hn(a,"XY",c,d);return a};l.W=function(){return"MultiLineString"};l.ua=function(a){a:{var c=this.o,d=this.c,e=this.G,f=0,g,h;g=0;for(h=d.length;g<h;++g){if(Bf(c,f,d[g],e,a)){a=!0;break a}f=d[g]}a=!1}return a};
-l.ja=function(a,c){if(a){df(this,c,a,2);this.o||(this.o=[]);var d=of(this.o,0,a,this.G,this.c);this.o.length=0===d.length?0:d[d.length-1];this.s()}else hn(this,"XY",null,this.c)};function hn(a,c,d,e){cf(a,c,d);a.c=e;a.s()}function jn(a,c){var d=a.a,e=[],f=[],g,h;g=0;for(h=c.length;g<h;++g){var k=c[g];0===g&&(d=k.a);hb(e,k.o);f.push(e.length)}hn(a,d,e,f)};function kn(a,c){af.call(this);this.ja(a,c)}w(kn,af);l=kn.prototype;l.mi=function(a){this.o?hb(this.o,a.o):this.o=a.o.slice();this.s()};l.clone=function(){var a=new kn(null);cf(a,this.a,this.o.slice());a.s();return a};l.Va=function(a,c,d,e){if(e<Td(this.R(),a,c))return e;var f=this.o,g=this.G,h,k,m;h=0;for(k=f.length;h<k;h+=g)if(m=Ta(a,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.U=function(){return pf(this.o,0,this.o.length,this.G)};
-l.ej=function(a){var c=this.o?this.o.length/this.G:0;if(0>a||c<=a)return null;c=new D(null);vf(c,this.a,this.o.slice(a*this.G,(a+1)*this.G));return c};l.Yd=function(){var a=this.o,c=this.a,d=this.G,e=[],f,g;f=0;for(g=a.length;f<g;f+=d){var h=new D(null);vf(h,c,a.slice(f,f+d));e.push(h)}return e};l.W=function(){return"MultiPoint"};l.ua=function(a){var c=this.o,d=this.G,e,f,g,h;e=0;for(f=c.length;e<f;e+=d)if(g=c[e],h=c[e+1],Vd(a,g,h))return!0;return!1};
-l.ja=function(a,c){a?(df(this,c,a,1),this.o||(this.o=[]),this.o.length=nf(this.o,0,a,this.G)):cf(this,"XY",null);this.s()};function P(a,c){af.call(this);this.c=[];this.u=-1;this.B=null;this.T=this.N=this.I=-1;this.i=null;this.ja(a,c)}w(P,af);l=P.prototype;l.ni=function(a){if(this.o){var c=this.o.length;hb(this.o,a.o);a=a.c.slice();var d,e;d=0;for(e=a.length;d<e;++d)a[d]+=c}else this.o=a.o.slice(),a=a.c.slice(),this.c.push();this.c.push(a);this.s()};l.clone=function(){var a=new P(null),c=Wb(this.c);ln(a,this.a,this.o.slice(),c);return a};
-l.Va=function(a,c,d,e){if(e<Td(this.R(),a,c))return e;if(this.N!=this.b){var f=this.c,g=0,h=0,k,m;k=0;for(m=f.length;k<m;++k)var n=f[k],h=jf(this.o,g,n,this.G,h),g=n[n.length-1];this.I=Math.sqrt(h);this.N=this.b}f=vm(this);g=this.c;h=this.G;k=this.I;m=0;var n=[NaN,NaN],p,q;p=0;for(q=g.length;p<q;++p){var r=g[p];e=lf(f,m,r,h,k,!0,a,c,d,e,n);m=r[r.length-1]}return e};
-l.Xb=function(a,c){var d;a:{d=vm(this);var e=this.c,f=0;if(0!==e.length){var g,h;g=0;for(h=e.length;g<h;++g){var k=e[g];if(yf(d,f,k,this.G,a,c)){d=!0;break a}f=k[k.length-1]}}d=!1}return d};l.bl=function(){var a=vm(this),c=this.c,d=0,e=0,f,g;f=0;for(g=c.length;f<g;++f)var h=c[f],e=e+ff(a,d,h,this.G),d=h[h.length-1];return e};
-l.U=function(a){var c;void 0!==a?(c=vm(this).slice(),Gf(c,this.c,this.G,a)):c=this.o;a=c;c=this.c;var d=this.G,e=0,f=[],g=0,h,k;h=0;for(k=c.length;h<k;++h){var m=c[h];f[g++]=qf(a,e,m,d,f[g]);e=m[m.length-1]}f.length=g;return f};
-function wm(a){if(a.u!=a.b){var c=a.o,d=a.c,e=a.G,f=0,g=[],h,k,m=Nd();h=0;for(k=d.length;h<k;++h){var n=d[h],m=ae(Qd(Infinity,Infinity,-Infinity,-Infinity,void 0),c,f,n[0],e);g.push((m[0]+m[2])/2,(m[1]+m[3])/2);f=n[n.length-1]}c=vm(a);d=a.c;e=a.G;f=0;h=[];k=0;for(m=d.length;k<m;++k)n=d[k],h=zf(c,f,n,e,g,2*k,h),f=n[n.length-1];a.B=h;a.u=a.b}return a.B}l.Ti=function(){var a=new kn(null),c=wm(this).slice();cf(a,"XY",c);a.s();return a};
-function vm(a){if(a.T!=a.b){var c=a.o,d;a:{d=a.c;var e,f;e=0;for(f=d.length;e<f;++e)if(!Ef(c,d[e],a.G,void 0)){d=!1;break a}d=!0}d?a.i=c:(a.i=c.slice(),a.i.length=Gf(a.i,a.c,a.G));a.T=a.b}return a.i}l.tc=function(a){var c=[],d=[],e=this.o,f=this.c,g=this.G;a=Math.sqrt(a);var h=0,k=0,m,n;m=0;for(n=f.length;m<n;++m){var p=f[m],q=[],k=sf(e,h,p,g,a,c,k,q);d.push(q);h=p[p.length-1]}c.length=k;e=new P(null);ln(e,"XY",c,d);return e};
-l.gj=function(a){if(0>a||this.c.length<=a)return null;var c;0===a?c=0:(c=this.c[a-1],c=c[c.length-1]);a=this.c[a].slice();var d=a[a.length-1];if(0!==c){var e,f;e=0;for(f=a.length;e<f;++e)a[e]-=c}e=new E(null);Hf(e,this.a,this.o.slice(c,d),a);return e};l.Fd=function(){var a=this.a,c=this.o,d=this.c,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 E(null);Hf(k,a,c.slice(f,p),n);e.push(k);f=p}return e};l.W=function(){return"MultiPolygon"};
-l.ua=function(a){a:{var c=vm(this),d=this.c,e=this.G,f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g];if(Cf(c,f,k,e,a)){a=!0;break a}f=k[k.length-1]}a=!1}return a};l.ja=function(a,c){if(a){df(this,c,a,3);this.o||(this.o=[]);var d=this.o,e=this.G,f=this.c,g=0,f=f?f:[],h=0,k,m;k=0;for(m=a.length;k<m;++k)g=of(d,g,a[k],e,f[h]),f[h++]=g,g=g[g.length-1];f.length=h;0===f.length?this.o.length=0:(d=f[f.length-1],this.o.length=0===d.length?0:d[d.length-1]);this.s()}else ln(this,"XY",null,this.c)};
-function ln(a,c,d,e){cf(a,c,d);a.c=e;a.s()}function mn(a,c){var d=a.a,e=[],f=[],g,h,k;g=0;for(h=c.length;g<h;++g){var m=c[g];0===g&&(d=m.a);var n=e.length;k=m.c;var p,q;p=0;for(q=k.length;p<q;++p)k[p]+=n;hb(e,m.o);f.push(k)}ln(a,d,e,f)};function nn(a,c){return v(a)-v(c)}function on(a,c){var d=.5*a/c;return d*d}function pn(a,c,d,e,f,g){var h=!1,k,m;if(k=d.j)m=k.dd(),2==m||3==m?k.xf(f,g):(0==m&&k.load(),k.Ye(f,g),h=!0);if(f=(0,d.g)(c))e=f.Hd(e),(0,qn[e.W()])(a,e,d,c);return h}
-var qn={Point:function(a,c,d,e){var f=d.j;if(f){if(2!=f.dd())return;var g=a.b(d.b,"Image");g.bb(f);g.pb(c,e)}if(f=d.a)a=a.b(d.b,"Text"),a.Ja(f),a.qb(c.U(),0,2,2,c,e)},LineString:function(a,c,d,e){var f=d.c;if(f){var g=a.b(d.b,"LineString");g.Ia(null,f);g.yb(c,e)}if(f=d.a)a=a.b(d.b,"Text"),a.Ja(f),a.qb(tm(c),0,2,2,c,e)},Polygon:function(a,c,d,e){var f=d.f,g=d.c;if(f||g){var h=a.b(d.b,"Polygon");h.Ia(f,g);h.Pb(c,e)}if(f=d.a)a=a.b(d.b,"Text"),a.Ja(f),a.qb(Jf(c),0,2,2,c,e)},MultiPoint:function(a,c,d,
-e){var f=d.j;if(f){if(2!=f.dd())return;var g=a.b(d.b,"Image");g.bb(f);g.ob(c,e)}if(f=d.a)a=a.b(d.b,"Text"),a.Ja(f),d=c.o,a.qb(d,0,d.length,c.G,c,e)},MultiLineString:function(a,c,d,e){var f=d.c;if(f){var g=a.b(d.b,"LineString");g.Ia(null,f);g.qc(c,e)}if(f=d.a)a=a.b(d.b,"Text"),a.Ja(f),d=um(c),a.qb(d,0,d.length,2,c,e)},MultiPolygon:function(a,c,d,e){var f=d.f,g=d.c;if(g||f){var h=a.b(d.b,"Polygon");h.Ia(f,g);h.rc(c,e)}if(f=d.a)a=a.b(d.b,"Text"),a.Ja(f),d=wm(c),a.qb(d,0,d.length,2,c,e)},GeometryCollection:function(a,
-c,d,e){c=c.f;var f,g;f=0;for(g=c.length;f<g;++f)(0,qn[c[f].W()])(a,c[f],d,e)},Circle:function(a,c,d,e){var f=d.f,g=d.c;if(f||g){var h=a.b(d.b,"Polygon");h.Ia(f,g);h.pc(c,e)}if(f=d.a)a=a.b(d.b,"Text"),a.Ja(f),a.qb(c.$c(),0,2,2,c,e)}};function rn(a,c,d,e,f,g){this.f=void 0!==g?g:null;ik.call(this,a,c,d,void 0!==g?0:2,e);this.c=f;this.a=null}w(rn,ik);rn.prototype.getError=function(){return this.a};rn.prototype.i=function(a){a?(this.a=a,this.state=3):this.state=2;jk(this)};rn.prototype.load=function(){0==this.state&&(this.state=1,jk(this),this.f(qa(this.i,this)))};rn.prototype.b=function(){return this.c};function sn(a){Ch.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,projection:a.projection,state:a.state});this.A=void 0!==a.resolutions?a.resolutions:null}w(sn,Ch);function tn(a,c){if(a.A){var d=tb(a.A,c,0);c=a.A[d]}return c}sn.prototype.l=function(a){a=a.target;switch(a.state){case 1:C(this,new un(vn,a));break;case 2:C(this,new un(wn,a));break;case 3:C(this,new un(xn,a))}};function yn(a,c){a.b().src=c}function un(a,c){wc.call(this,a);this.image=c}w(un,wc);
-var vn="imageloadstart",wn="imageloadend",xn="imageloaderror";function zn(a){sn.call(this,{attributions:a.attributions,logo:a.logo,projection:a.projection,resolutions:a.resolutions,state:void 0!==a.state?a.state:void 0});this.$=a.canvasFunction;this.I=null;this.X=0;this.da=void 0!==a.ratio?a.ratio:1.5}w(zn,sn);zn.prototype.cc=function(a,c,d,e){c=tn(this,c);var f=this.I;if(f&&this.X==this.b&&f.aa()==c&&f.g==d&&Wd(f.R(),a))return f;a=a.slice();ne(a,this.da);(e=this.$(a,c,d,[le(a)/c*d,ie(a)/c*d],e))&&(f=new rn(a,c,d,this.j,e));this.I=f;this.X=this.b;return f};function Q(a){jd.call(this);this.ha=void 0;this.a="geometry";this.g=null;this.c=void 0;this.f=null;B(this,ld(this.a),this.Kd,!1,this);void 0!==a&&(a instanceof Ze||!a?this.za(a):this.H(a))}w(Q,jd);l=Q.prototype;l.clone=function(){var a=new Q(this.P());a.Dc(this.a);var c=this.V();c&&a.za(c.clone());(c=this.g)&&a.af(c);return a};l.V=function(){return this.get(this.a)};l.Qi=function(){return this.ha};l.Pi=function(){return this.a};l.wk=function(){return this.g};l.xk=function(){return this.c};l.yk=function(){this.s()};
-l.Kd=function(){this.f&&($c(this.f),this.f=null);var a=this.V();a&&(this.f=B(a,"change",this.yk,!1,this));this.s()};l.za=function(a){this.set(this.a,a)};l.af=function(a){this.c=(this.g=a)?An(a):void 0;this.s()};l.Mb=function(a){this.ha=a;this.s()};l.Dc=function(a){Zc(this,ld(this.a),this.Kd,!1,this);this.a=a;B(this,ld(this.a),this.Kd,!1,this);this.Kd()};function An(a){if(!ka(a)){var c;c=ga(a)?a:[a];a=function(){return c}}return a};function Bn(a){a.prototype.then=a.prototype.then;a.prototype.$goog_Thenable=!0}function Cn(a){if(!a)return!1;try{return!!a.$goog_Thenable}catch(c){return!1}};function Dn(a,c,d){this.f=d;this.c=a;this.g=c;this.a=0;this.b=null}Dn.prototype.get=function(){var a;0<this.a?(this.a--,a=this.b,this.b=a.next,a.next=null):a=this.c();return a};function En(a,c){a.g(c);a.a<a.f&&(a.a++,c.next=a.b,a.b=c)};function Fn(){this.a=this.b=null}var Hn=new Dn(function(){return new Gn},function(a){a.reset()},100);Fn.prototype.add=function(a,c){var d=Hn.get();d.set(a,c);this.a?this.a.next=d:this.b=d;this.a=d};Fn.prototype.remove=function(){var a=null;this.b&&(a=this.b,this.b=this.b.next,this.b||(this.a=null),a.next=null);return a};function Gn(){this.next=this.a=this.b=null}Gn.prototype.set=function(a,c){this.b=a;this.a=c;this.next=null};Gn.prototype.reset=function(){this.next=this.a=this.b=null};function In(a,c){Jn||Kn();Ln||(Jn(),Ln=!0);Mn.add(a,c)}var Jn;function Kn(){if(ba.Promise&&ba.Promise.resolve){var a=ba.Promise.resolve(void 0);Jn=function(){a.then(Nn)}}else Jn=function(){pi(Nn)}}var Ln=!1,Mn=new Fn;function Nn(){for(var a=null;a=Mn.remove();){try{a.b.call(a.a)}catch(c){oi(c)}En(Hn,a)}Ln=!1};function On(a,c){this.b=Pn;this.i=void 0;this.f=this.a=this.c=null;this.g=this.j=!1;if(a!=da)try{var d=this;a.call(c,function(a){Qn(d,Rn,a)},function(a){Qn(d,Sn,a)})}catch(e){Qn(this,Sn,e)}}var Pn=0,Rn=2,Sn=3;function Tn(){this.next=this.c=this.a=this.f=this.b=null;this.g=!1}Tn.prototype.reset=function(){this.c=this.a=this.f=this.b=null;this.g=!1};var Un=new Dn(function(){return new Tn},function(a){a.reset()},100);function Vn(a,c,d){var e=Un.get();e.f=a;e.a=c;e.c=d;return e}
-On.prototype.then=function(a,c,d){return Wn(this,ka(a)?a:null,ka(c)?c:null,d)};Bn(On);On.prototype.cancel=function(a){this.b==Pn&&In(function(){var c=new Xn(a);Yn(this,c)},this)};function Yn(a,c){if(a.b==Pn)if(a.c){var d=a.c;if(d.a){for(var e=0,f=null,g=null,h=d.a;h&&(h.g||(e++,h.b==a&&(f=h),!(f&&1<e)));h=h.next)f||(g=h);f&&(d.b==Pn&&1==e?Yn(d,c):(g?(e=g,e.next==d.f&&(d.f=e),e.next=e.next.next):Zn(d),$n(d,f,Sn,c)))}a.c=null}else Qn(a,Sn,c)}
-function ao(a,c){a.a||a.b!=Rn&&a.b!=Sn||bo(a);a.f?a.f.next=c:a.a=c;a.f=c}function Wn(a,c,d,e){var f=Vn(null,null,null);f.b=new On(function(a,h){f.f=c?function(d){try{var f=c.call(e,d);a(f)}catch(n){h(n)}}:a;f.a=d?function(c){try{var f=d.call(e,c);!ca(f)&&c instanceof Xn?h(c):a(f)}catch(n){h(n)}}:h});f.b.c=a;ao(a,f);return f.b}On.prototype.C=function(a){this.b=Pn;Qn(this,Rn,a)};On.prototype.A=function(a){this.b=Pn;Qn(this,Sn,a)};
-function Qn(a,c,d){if(a.b==Pn){a==d&&(c=Sn,d=new TypeError("Promise cannot resolve to itself"));a.b=1;var e;a:{var f=d,g=a.C,h=a.A;if(f instanceof On)ao(f,Vn(g||da,h||null,a)),e=!0;else if(Cn(f))f.then(g,h,a),e=!0;else{if(la(f))try{var k=f.then;if(ka(k)){co(f,k,g,h,a);e=!0;break a}}catch(m){h.call(a,m);e=!0;break a}e=!1}}e||(a.i=d,a.b=c,a.c=null,bo(a),c!=Sn||d instanceof Xn||eo(a,d))}}
-function co(a,c,d,e,f){function g(a){k||(k=!0,e.call(f,a))}function h(a){k||(k=!0,d.call(f,a))}var k=!1;try{c.call(a,h,g)}catch(m){g(m)}}function bo(a){a.j||(a.j=!0,In(a.l,a))}function Zn(a){var c=null;a.a&&(c=a.a,a.a=c.next,c.next=null);a.a||(a.f=null);return c}On.prototype.l=function(){for(var a=null;a=Zn(this);)$n(this,a,this.b,this.i);this.j=!1};
-function $n(a,c,d,e){if(d==Sn&&c.a&&!c.g)for(;a&&a.g;a=a.c)a.g=!1;if(c.b)c.b.c=null,fo(c,d,e);else try{c.g?c.f.call(c.c):fo(c,d,e)}catch(f){go.call(null,f)}En(Un,c)}function fo(a,c,d){c==Rn?a.f.call(a.c,d):a.a&&a.a.call(a.c,d)}function eo(a,c){a.g=!0;In(function(){a.g&&go.call(null,c)})}var go=oi;function Xn(a){xa.call(this,a)}w(Xn,xa);Xn.prototype.name="cancel";function ho(a,c,d){if(ka(a))d&&(a=qa(a,d));else if(a&&"function"==typeof a.handleEvent)a=qa(a.handleEvent,a);else throw Error("Invalid listener argument");return 2147483647<c?-1:ba.setTimeout(a,c||0)};var io=ba.JSON.parse,jo=ba.JSON.stringify;function ko(){}ko.prototype.b=null;function lo(a){var c;(c=a.b)||(c={},mo(a)&&(c[0]=!0,c[1]=!0),c=a.b=c);return c};var no;function oo(){}w(oo,ko);function po(a){return(a=mo(a))?new ActiveXObject(a):new XMLHttpRequest}function mo(a){if(!a.a&&"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),a.a=e}catch(f){}}throw Error("Could not create ActiveXObject. ActiveX might be disabled, or MSXML might not be installed");}return a.a}no=new oo;var qo=/^(?:([^:/?#.]+):)?(?:\/\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\?([^#]*))?(?:#(.*))?$/;function ro(a,c){if(a)for(var d=a.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 so(a){if(a[1]){var c=a[0],d=c.indexOf("#");0<=d&&(a.push(c.substr(d)),a[0]=c=c.substr(0,d));d=c.indexOf("?");0>d?a[1]="?":d==c.length-1&&(a[1]=void 0)}return a.join("")}function to(a,c,d){if(ga(c))for(var e=0;e<c.length;e++)to(a,String(c[e]),d);else null!=c&&d.push("&",a,""===c?"":"=",encodeURIComponent(String(c)))}function uo(a,c){for(var d in c)to(d,c[d],a);return a};function vo(a){dd.call(this);this.I=new ti;this.l=a||null;this.b=!1;this.i=this.ga=null;this.g=this.T=this.v="";this.a=this.A=this.f=this.C=!1;this.j=0;this.c=null;this.u=wo;this.B=this.$=!1}w(vo,dd);var wo="",xo=/^https?$/i,yo=["POST","PUT"];
-function zo(a,c){if(a.ga)throw Error("[goog.net.XhrIo] Object is active with another request="+a.v+"; newUri="+c);a.v=c;a.g="";a.T="GET";a.C=!1;a.b=!0;a.ga=a.l?po(a.l):po(no);a.i=a.l?lo(a.l):lo(no);a.ga.onreadystatechange=qa(a.N,a);try{a.A=!0,a.ga.open("GET",String(c),!0),a.A=!1}catch(g){Ao(a,g);return}var d=a.I.clone(),e=cb(d.O(),Bo),f=ba.FormData&&!1;!(0<=Xa(yo,"GET"))||e||f||d.set("Content-Type","application/x-www-form-urlencoded;charset=utf-8");d.forEach(function(a,c){this.ga.setRequestHeader(c,
-a)},a);a.u&&(a.ga.responseType=a.u);"withCredentials"in a.ga&&(a.ga.withCredentials=a.$);try{Co(a),0<a.j&&(a.B=Do(a.ga),a.B?(a.ga.timeout=a.j,a.ga.ontimeout=qa(a.jc,a)):a.c=ho(a.jc,a.j,a)),a.f=!0,a.ga.send(""),a.f=!1}catch(g){Ao(a,g)}}function Do(a){return $b&&lc(9)&&ja(a.timeout)&&ca(a.ontimeout)}function Bo(a){return"content-type"==a.toLowerCase()}
-vo.prototype.jc=function(){"undefined"!=typeof aa&&this.ga&&(this.g="Timed out after "+this.j+"ms, aborting",C(this,"timeout"),this.ga&&this.b&&(this.b=!1,this.a=!0,this.ga.abort(),this.a=!1,C(this,"complete"),C(this,"abort"),Eo(this)))};function Ao(a,c){a.b=!1;a.ga&&(a.a=!0,a.ga.abort(),a.a=!1);a.g=c;Fo(a);Eo(a)}function Fo(a){a.C||(a.C=!0,C(a,"complete"),C(a,"error"))}vo.prototype.Y=function(){this.ga&&(this.b&&(this.b=!1,this.a=!0,this.ga.abort(),this.a=!1),Eo(this,!0));vo.ba.Y.call(this)};
-vo.prototype.N=function(){this.ca||(this.A||this.f||this.a?Go(this):this.X())};vo.prototype.X=function(){Go(this)};function Go(a){if(a.b&&"undefined"!=typeof aa&&(!a.i[1]||4!=Ho(a)||2!=Io(a)))if(a.f&&4==Ho(a))ho(a.N,0,a);else if(C(a,"readystatechange"),4==Ho(a)){a.b=!1;try{if(Jo(a))C(a,"complete"),C(a,"success");else{var c;try{c=2<Ho(a)?a.ga.statusText:""}catch(d){c=""}a.g=c+" ["+Io(a)+"]";Fo(a)}}finally{Eo(a)}}}
-function Eo(a,c){if(a.ga){Co(a);var d=a.ga,e=a.i[0]?da:null;a.ga=null;a.i=null;c||C(a,"ready");try{d.onreadystatechange=e}catch(f){}}}function Co(a){a.ga&&a.B&&(a.ga.ontimeout=null);ja(a.c)&&(ba.clearTimeout(a.c),a.c=null)}
-function Jo(a){var c=Io(a),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)a=String(a.v).match(qo)[1]||null,!a&&ba.self&&ba.self.location&&(a=ba.self.location.protocol,a=a.substr(0,a.length-1)),c=!xo.test(a?a.toLowerCase():"");d=c}return d}function Ho(a){return a.ga?a.ga.readyState:0}function Io(a){try{return 2<Ho(a)?a.ga.status:-1}catch(c){return-1}}function Ko(a){try{return a.ga?a.ga.responseText:""}catch(c){return""}};function Lo(){if(!$b)return!1;try{return new ActiveXObject("MSXML2.DOMDocument"),!0}catch(a){return!1}}var Mo=$b&&Lo();function No(a){var c=a.xml;if(c)return c;if("undefined"!=typeof XMLSerializer)return(new XMLSerializer).serializeToString(a);throw Error("Your browser does not support serializing XML documents");};var Oo;a:if(document.implementation&&document.implementation.createDocument)Oo=document.implementation.createDocument("","",null);else{if(Mo){var Po=new ActiveXObject("MSXML2.DOMDocument");if(Po){Po.resolveExternals=!1;Po.validateOnParse=!1;try{Po.setProperty("ProhibitDTD",!0),Po.setProperty("MaxXMLSize",2048),Po.setProperty("MaxElementDepth",256)}catch(a){}}if(Po){Oo=Po;break a}}throw Error("Your browser does not support creating new documents");}var Qo=Oo;
-function Ro(a,c){return Qo.createElementNS(a,c)}function So(a,c){a||(a="");return Qo.createNode(1,c,a)}var To=document.implementation&&document.implementation.createDocument?Ro:So;function Uo(a,c){return Vo(a,c,[]).join("")}function Vo(a,c,d){if(4==a.nodeType||3==a.nodeType)c?d.push(String(a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):d.push(a.nodeValue);else for(a=a.firstChild;a;a=a.nextSibling)Vo(a,c,d);return d}function Wo(a){return a.localName}
-function Xo(a){var c=a.localName;return void 0!==c?c:a.baseName}var Yo=$b?Xo:Wo;function Zo(a){return a instanceof Document}function $o(a){return la(a)&&9==a.nodeType}var ap=$b?$o:Zo;function bp(a){return a instanceof Node}function cp(a){return la(a)&&void 0!==a.nodeType}var dp=$b?cp:bp;function ep(a,c,d){return a.getAttributeNS(c,d)||""}function fp(a,c,d){var e="";a=gp(a,c,d);void 0!==a&&(e=a.nodeValue);return e}var hp=document.implementation&&document.implementation.createDocument?ep:fp;
-function ip(a,c,d){return a.getAttributeNodeNS(c,d)}function jp(a,c,d){var e=null;a=a.attributes;for(var f,g,h=0,k=a.length;h<k;++h)if(f=a[h],f.namespaceURI==c&&(g=f.prefix?f.prefix+":"+d:d,g==f.nodeName)){e=f;break}return e}var gp=document.implementation&&document.implementation.createDocument?ip:jp;function kp(a,c,d,e){a.setAttributeNS(c,d,e)}function lp(a,c,d,e){c?(c=a.ownerDocument.createNode(2,d,c),c.nodeValue=e,a.setAttributeNode(c)):a.setAttribute(d,e)}
-var mp=document.implementation&&document.implementation.createDocument?kp:lp;function np(a){return(new DOMParser).parseFromString(a,"application/xml")}function op(a,c){return function(d,e){var f=a.call(c,d,e);void 0!==f&&hb(e[e.length-1],f)}}function pp(a,c){return function(d,e){var f=a.call(void 0!==c?c:this,d,e);void 0!==f&&e[e.length-1].push(f)}}function qp(a,c){return function(d,e){var f=a.call(void 0!==c?c:this,d,e);void 0!==f&&(e[e.length-1]=f)}}
-function rp(a){return function(c,d){var e=a.call(this,c,d);void 0!==e&&Ub(d[d.length-1],c.localName).push(e)}}function R(a,c){return function(d,e){var f=a.call(this,d,e);void 0!==f&&(e[e.length-1][void 0!==c?c:d.localName]=f)}}function S(a,c){return function(d,e,f){a.call(void 0!==c?c:this,d,e,f);f[f.length-1].node.appendChild(d)}}function sp(a){var c,d;return function(e,f,g){if(void 0===c){c={};var h={};h[e.localName]=a;c[e.namespaceURI]=h;d=tp(e.localName)}up(c,d,f,g)}}
-function tp(a,c){return function(d,e,f){d=e[e.length-1].node;e=a;void 0===e&&(e=f);f=c;void 0===c&&(f=d.namespaceURI);return To(f,e)}}var vp=tp();function wp(a,c){for(var d=c.length,e=Array(d),f=0;f<d;++f)e[f]=a[c[f]];return e}function T(a,c,d){d=void 0!==d?d:{};var e,f;e=0;for(f=a.length;e<f;++e)d[a[e]]=c;return d}function xp(a,c,d,e){for(c=c.firstElementChild;c;c=c.nextElementSibling){var f=a[c.namespaceURI];void 0!==f&&(f=f[c.localName],void 0!==f&&f.call(e,c,d))}}
-function U(a,c,d,e,f){e.push(a);xp(c,d,e,f);return e.pop()}function up(a,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&&a[m.namespaceURI][m.localName].call(g,m,k,e))}function yp(a,c,d,e,f,g,h){f.push(a);up(c,d,e,f,g,h);f.pop()};function zp(a,c,d){return function(e,f,g){var h=new vo;h.u="text";B(h,"complete",function(a){a=a.target;if(Jo(a)){var e=c.W(),f;if("json"==e)f=Ko(a);else if("text"==e)f=Ko(a);else if("xml"==e){if(!$b)try{f=a.ga?a.ga.responseXML:null}catch(h){f=null}f||(f=np(Ko(a)))}f&&(f=c.sa(f,{featureProjection:g}),d.call(this,f))}vc(a)},!1,this);ka(a)?zo(h,a(e,f,g)):zo(h,a)}}function Ap(a,c){return zp(a,c,function(a){this.Nb(a)})};function Bp(){return[[-Infinity,-Infinity,Infinity,Infinity]]};var Cp,Dp;
-(function(){var a={gb:{}};(function(){function c(a,d){if(!(this instanceof c))return new c(a,d);this.Ce=Math.max(4,a||9);this.Hf=Math.max(2,Math.ceil(.4*this.Ce));d&&this.ci(d);this.clear()}function d(a,c){a.bbox=e(a,0,a.children.length,c)}function e(a,c,d,e){for(var g=[Infinity,Infinity,-Infinity,-Infinity],h;c<d;c++)h=a.children[c],f(g,a.Da?e(h):h.bbox);return g}function f(a,c){a[0]=Math.min(a[0],c[0]);a[1]=Math.min(a[1],c[1]);a[2]=Math.max(a[2],c[2]);a[3]=Math.max(a[3],c[3])}function g(a,c){return a.bbox[0]-
-c.bbox[0]}function h(a,c){return a.bbox[1]-c.bbox[1]}function k(a){return(a[2]-a[0])*(a[3]-a[1])}function m(a){return a[2]-a[0]+(a[3]-a[1])}function n(a,c){return a[0]<=c[0]&&a[1]<=c[1]&&c[2]<=a[2]&&c[3]<=a[3]}function p(a,c){return c[0]<=a[2]&&c[1]<=a[3]&&c[2]>=a[0]&&c[3]>=a[1]}function q(a,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(a,c,d,h,f),g.push(c,h,h,d))}function r(a,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(a,k,h,e,f));g=a[e];h=c;m=d;u(a,c,e);for(0<f(a[d],g)&&u(a,c,d);h<m;){u(a,h,m);h++;for(m--;0>f(a[h],g);)h++;for(;0<f(a[m],g);)m--}0===f(a[c],g)?u(a,c,m):(m++,u(a,m,d));m<=e&&(c=m+1);e<=m&&(d=m-1)}}function u(a,c,d){var e=a[c];a[c]=a[d];a[d]=e}c.prototype={all:function(){return this.Cf(this.data,[])},search:function(a){var c=this.data,d=[],e=this.La;if(!p(a,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.Da?e(k):k.bbox,p(a,m)&&(c.Da?d.push(k):n(a,m)?this.Cf(k,d):f.push(k));c=f.pop()}return d},load:function(a){if(!a||!a.length)return this;if(a.length<this.Hf){for(var c=0,d=a.length;c<d;c++)this.qa(a[c]);return this}a=this.Ef(a.slice(),0,a.length-1,0);this.data.children.length?this.data.height===a.height?this.Jf(this.data,a):(this.data.height<a.height&&(c=this.data,this.data=a,a=c),this.Gf(a,this.data.height-a.height-1,
-!0)):this.data=a;return this},qa:function(a){a&&this.Gf(a,this.data.height-1);return this},clear:function(){this.data={children:[],height:1,bbox:[Infinity,Infinity,-Infinity,-Infinity],Da:!0};return this},remove:function(a){if(!a)return this;for(var c=this.data,d=this.La(a),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.Da&&(k=c.children.indexOf(a),-1!==k)){c.children.splice(k,1);e.push(c);this.ai(e);break}m||c.Da||!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},La:function(a){return a},Fe:function(a,c){return a[0]-c[0]},Ge:function(a,c){return a[1]-c[1]},toJSON:function(){return this.data},Cf:function(a,c){for(var d=[];a;)a.Da?c.push.apply(c,a.children):d.push.apply(d,a.children),a=d.pop();return c},Ef:function(a,c,e,f){var g=e-c+1,h=this.Ce,k;if(g<=h)return k={children:a.slice(c,e+1),height:1,bbox:null,Da:!0},d(k,this.La),k;f||(f=Math.ceil(Math.log(g)/Math.log(h)),h=Math.ceil(g/Math.pow(h,f-1)));
-k={children:[],height:f,bbox:null};var g=Math.ceil(g/h),h=g*Math.ceil(Math.sqrt(h)),m,n,p;for(q(a,c,e,h,this.Fe);c<=e;c+=h)for(n=Math.min(c+h-1,e),q(a,c,n,g,this.Ge),m=c;m<=n;m+=g)p=Math.min(m+g-1,n),k.children.push(this.Ef(a,m,p,f-1));d(k,this.La);return k},$h:function(a,c,d,e){for(var f,g,h,m,n,p,q,r;;){e.push(c);if(c.Da||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],a[2])-Math.min(p[0],a[0]))*(Math.max(p[3],a[3])-Math.min(p[1],
-a[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},Gf:function(a,c,d){var e=this.La;d=d?a.bbox:e(a);var e=[],g=this.$h(d,this.data,c,e);g.children.push(a);for(f(g.bbox,d);0<=c;)if(e[c].children.length>this.Ce)this.fi(e,c),c--;else break;this.Xh(d,e,c)},fi:function(a,c){var e=a[c],f=e.children.length,g=this.Hf;this.Yh(e,g,f);f={children:e.children.splice(this.Zh(e,g,f)),height:e.height};e.Da&&(f.Da=!0);d(e,this.La);d(f,this.La);c?a[c-1].children.push(f):this.Jf(e,f)},Jf:function(a,
-c){this.data={children:[a,c],height:a.height+1};d(this.data,this.La)},Zh:function(a,c,d){var f,g,h,m,n,p,q;n=p=Infinity;for(f=c;f<=d-c;f++)g=e(a,0,f,this.La),h=e(a,f,d,this.La),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},Yh:function(a,c,d){var e=a.Da?this.Fe:g,f=a.Da?this.Ge:h,k=this.Df(a,c,d,e);c=this.Df(a,c,d,f);k<c&&a.children.sort(e)},Df:function(a,c,d,g){a.children.sort(g);
-g=this.La;var h=e(a,0,c,g),k=e(a,d-c,d,g),n=m(h)+m(k),p,q;for(p=c;p<d-c;p++)q=a.children[p],f(h,a.Da?g(q):q.bbox),n+=m(h);for(p=d-c-1;p>=c;p--)q=a.children[p],f(k,a.Da?g(q):q.bbox),n+=m(k);return n},Xh:function(a,c,d){for(;0<=d;d--)f(c[d].bbox,a)},ai:function(a){for(var c=a.length-1,e;0<=c;c--)0===a[c].children.length?0<c?(e=a[c-1].children,e.splice(e.indexOf(a[c]),1)):this.clear():d(a[c],this.La)},ci:function(a){var c=["return a"," - b",";"];this.Fe=new Function("a","b",c.join(a[0]));this.Ge=new Function("a",
-"b",c.join(a[1]));this.La=new Function("a","return [a"+a.join(", a")+"];")}};"undefined"!==typeof a?a.gb=c:"undefined"!==typeof self?self.b=c:window.b=c})();Cp=a.gb})();function Ep(a){this.a=Cp(a);this.b={}}l=Ep.prototype;l.qa=function(a,c){var d=[a[0],a[1],a[2],a[3],c];this.a.qa(d);this.b[v(c)]=d};l.load=function(a,c){for(var d=Array(c.length),e=0,f=c.length;e<f;e++){var g=a[e],h=c[e],g=[g[0],g[1],g[2],g[3],h];d[e]=g;this.b[v(h)]=g}this.a.load(d)};l.remove=function(a){a=v(a);var c=this.b[a];delete this.b[a];return null!==this.a.remove(c)};function Fp(a,c,d){var e=v(d);Zd(a.b[e].slice(0,4),c)||(a.remove(d),a.qa(c,d))}
-function Gp(a){return a.a.all().map(function(a){return a[4]})}function Hp(a,c){return a.a.search(c).map(function(a){return a[4]})}l.forEach=function(a,c){return Ip(Gp(this),a,c)};function Jp(a,c,d,e){return Ip(Hp(a,c),d,e)}function Ip(a,c,d){for(var e,f=0,g=a.length;f<g&&!(e=c.call(d,a[f]));f++);return e}l.ya=function(){return Rb(this.b)};l.clear=function(){this.a.clear();this.b={}};l.R=function(){return this.a.data.bbox};function V(a){a=a||{};Ch.call(this,{attributions:a.attributions,logo:a.logo,projection:void 0,state:"ready",wrapX:void 0!==a.wrapX?a.wrapX:!0});this.X=wa;void 0!==a.loader?this.X=a.loader:void 0!==a.url&&(this.X=Ap(a.url,a.format));this.wa=void 0!==a.strategy?a.strategy:Bp;var c=void 0!==a.useSpatialIndex?a.useSpatialIndex:!0;this.a=c?new Ep:null;this.$=new Ep;this.f={};this.g={};this.l={};this.A={};this.c=null;var d,e;a.features instanceof tg?(d=a.features,e=d.a):ga(a.features)&&(e=a.features);c||
-void 0!==d||(d=new tg(e));void 0!==e&&Kp(this,e);void 0!==d&&Lp(this,d)}w(V,Ch);l=V.prototype;l.yc=function(a){var c=v(a).toString();if(Mp(this,c,a)){Np(this,c,a);var d=a.V();d?(c=d.R(),this.a&&this.a.qa(c,a)):this.f[c]=a;C(this,new Op("addfeature",a))}this.s()};function Np(a,c,d){a.A[c]=[B(d,"change",a.Eg,!1,a),B(d,"propertychange",a.Eg,!1,a)]}function Mp(a,c,d){var e=!0,f=d.ha;void 0!==f?f.toString()in a.g?e=!1:a.g[f.toString()]=d:a.l[c]=d;return e}l.Nb=function(a){Kp(this,a);this.s()};
-function Kp(a,c){var d,e,f,g,h=[],k=[],m=[];e=0;for(f=c.length;e<f;e++)g=c[e],d=v(g).toString(),Mp(a,d,g)&&k.push(g);e=0;for(f=k.length;e<f;e++){g=k[e];d=v(g).toString();Np(a,d,g);var n=g.V();n?(d=n.R(),h.push(d),m.push(g)):a.f[d]=g}a.a&&a.a.load(h,m);e=0;for(f=k.length;e<f;e++)C(a,new Op("addfeature",k[e]))}
-function Lp(a,c){var d=!1;B(a,"addfeature",function(a){d||(d=!0,c.push(a.feature),d=!1)});B(a,"removefeature",function(a){d||(d=!0,c.remove(a.feature),d=!1)});B(c,"add",function(a){d||(a=a.element,d=!0,this.yc(a),d=!1)},!1,a);B(c,"remove",function(a){d||(a=a.element,d=!0,this.dc(a),d=!1)},!1,a);a.c=c}
-l.clear=function(a){if(a){for(var c in this.A)this.A[c].forEach($c);this.c||(this.A={},this.g={},this.l={})}else a=this.Zg,this.a&&(this.a.forEach(a,this),Jb(this.f,a,this));this.c&&this.c.clear();this.a&&this.a.clear();this.$.clear();this.f={};C(this,new Op("clear"));this.s()};l.Le=function(a,c){if(this.a)return this.a.forEach(a,c);if(this.c)return this.c.forEach(a,c)};function Pp(a,c,d){a.sc([c[0],c[1],c[0],c[1]],function(a){if(a.V().He(c))return d.call(void 0,a)})}
-l.sc=function(a,c,d){if(this.a)return Jp(this.a,a,c,d);if(this.c)return this.c.forEach(c,d)};l.zb=function(a,c,d,e){return this.sc(a,d,e)};l.Me=function(a,c,d){return this.sc(a,function(e){if(e.V().ua(a)&&(e=c.call(d,e)))return e})};l.Re=function(){return this.c};l.zc=function(){var a;this.c?a=this.c.a:this.a&&(a=Gp(this.a),Rb(this.f)||hb(a,Mb(this.f)));return a};l.Qe=function(a){var c=[];Pp(this,a,function(a){c.push(a)});return c};l.Ad=function(a){return Hp(this.a,a)};
-l.Oe=function(a){var c=a[0],d=a[1],e=null,f=[NaN,NaN],g=Infinity,h=[-Infinity,-Infinity,Infinity,Infinity];Jp(this.a,h,function(a){var m=a.V(),n=g;g=m.Va(c,d,f,g);g<n&&(e=a,a=Math.sqrt(g),h[0]=c-a,h[1]=d-a,h[2]=c+a,h[3]=d+a)});return e};l.R=function(){return this.a.R()};l.Pe=function(a){a=this.g[a.toString()];return void 0!==a?a:null};
-l.Eg=function(a){a=a.target;var c=v(a).toString(),d=a.V();d?(d=d.R(),c in this.f?(delete this.f[c],this.a&&this.a.qa(d,a)):this.a&&Fp(this.a,d,a)):c in this.f||(this.a&&this.a.remove(a),this.f[c]=a);d=a.ha;void 0!==d?(d=d.toString(),c in this.l?(delete this.l[c],this.g[d]=a):this.g[d]!==a&&(Qp(this,a),this.g[d]=a)):c in this.l||(Qp(this,a),this.l[c]=a);this.s();C(this,new Op("changefeature",a))};l.ya=function(){return this.a.ya()&&Rb(this.f)};
-l.Wb=function(a,c,d){var e=this.$;a=this.wa(a,c);var f,g;f=0;for(g=a.length;f<g;++f){var h=a[f];Jp(e,h,function(a){return Wd(a.extent,h)})||(this.X.call(this,h,c,d),e.qa(h,{extent:h.slice()}))}};l.dc=function(a){var c=v(a).toString();c in this.f?delete this.f[c]:this.a&&this.a.remove(a);this.Zg(a);this.s()};l.Zg=function(a){var c=v(a).toString();this.A[c].forEach($c);delete this.A[c];var d=a.ha;void 0!==d?delete this.g[d.toString()]:delete this.l[c];C(this,new Op("removefeature",a))};
-function Qp(a,c){for(var d in a.g)if(a.g[d]===c){delete a.g[d];break}}function Op(a,c){wc.call(this,a);this.feature=c}w(Op,wc);function Rp(a){this.a=a.source;this.fa=Cd();this.c=Ri();this.f=[0,0];this.u=null;zn.call(this,{attributions:a.attributions,canvasFunction:qa(this.pi,this),logo:a.logo,projection:a.projection,ratio:a.ratio,resolutions:a.resolutions,state:this.a.v});this.B=null;this.g=void 0;this.Bg(a.style);B(this.a,"change",this.Nl,void 0,this)}w(Rp,zn);l=Rp.prototype;
-l.pi=function(a,c,d,e,f){var g=new Sm(.5*c/d,a,c);this.a.Wb(a,c,f);var h=!1;this.a.zb(a,c,function(a){var e;if(!(e=h)){var f;(e=a.c)?f=e.call(a,c):this.g&&(f=this.g(a,c));if(f){var p,q=!1;e=0;for(p=f.length;e<p;++e)q=pn(g,a,f[e],on(c,d),this.Ml,this)||q;e=q}else e=!1}h=e},this);Tm(g);if(h)return null;this.f[0]!=e[0]||this.f[1]!=e[1]?(this.c.canvas.width=e[0],this.c.canvas.height=e[1],this.f[0]=e[0],this.f[1]=e[1]):this.c.clearRect(0,0,e[0],e[1]);a=Sp(this,ge(a),c,d,e);Xm(g,this.c,d,a,0,{});this.u=
-g;return this.c.canvas};l.be=function(a,c,d,e,f){if(this.u){var g={};return Um(this.u,a,c,0,e,function(a){var c=v(a).toString();if(!(c in g))return g[c]=!0,f(a)})}};l.Jl=function(){return this.a};l.Kl=function(){return this.B};l.Ll=function(){return this.g};function Sp(a,c,d,e,f){return kk(a.fa,f[0]/2,f[1]/2,e/d,-e/d,0,-c[0],-c[1])}l.Ml=function(){this.s()};l.Nl=function(){Eh(this,this.a.v)};l.Bg=function(a){this.B=void 0!==a?a:Kl;this.g=a?Il(this.B):void 0;this.s()};function Tp(a){ym.call(this,a);this.g=null;this.j=Cd();this.c=this.f=null}w(Tp,ym);l=Tp.prototype;l.Ta=function(a,c,d,e){var f=this.a;return f.ea().be(a,c.viewState.resolution,c.viewState.rotation,c.skippedFeatureUids,function(a){return d.call(e,a,f)})};
-l.bc=function(a,c,d,e){if(this.cd())if(this.a.ea()instanceof Rp){if(a=a.slice(),mk(c.pixelToCoordinateMatrix,a,a),this.Ta(a,c,re,this))return d.call(e,this.a)}else if(this.f||(this.f=Cd(),Id(this.j,this.f)),c=Bm(a,this.f),this.c||(this.c=Ri(1,1)),this.c.clearRect(0,0,1,1),this.c.drawImage(this.cd(),c[0],c[1],1,1,0,0,1,1),0<this.c.getImageData(0,0,1,1).data[3])return d.call(e,this.a)};l.cd=function(){return this.g?this.g.b():null};l.Se=function(){return this.j};
-l.ae=function(a,c){var d=a.pixelRatio,e=a.viewState,f=e.center,g=e.resolution,h=e.rotation,k,m=this.a.ea(),n=a.viewHints;k=a.extent;void 0!==c.extent&&(k=je(k,c.extent));n[0]||n[1]||me(k)||(e=e.projection,(n=m.i)&&(e=n),(k=m.cc(k,g,d,e))&&pk(this,k)&&(this.g=k));if(this.g){k=this.g;var e=k.R(),n=k.aa(),p=k.g,g=d*n/(g*p);kk(this.j,d*a.size[0]/2,d*a.size[1]/2,g,g,h,p*(e[0]-f[0])/n,p*(f[1]-e[3])/n);this.f=null;rk(a.attributions,k.j);sk(a,m)}return!0};function Up(a){ym.call(this,a);this.c=this.j=null;this.C=!1;this.i=null;this.A=Cd();this.g=null;this.B=this.N=this.u=NaN;this.l=this.f=null;this.T=[0,0]}w(Up,ym);Up.prototype.cd=function(){return this.j};Up.prototype.Se=function(){return this.A};
-Up.prototype.ae=function(a,c){var d=a.pixelRatio,e=a.viewState,f=e.projection,g=this.a,h=g.ea(),k=Th(h,f),m=h.Bd(),n=Nh(k,e.resolution),p=h.Tb(n,a.pixelRatio,f),q=p[0]/pd(k.Ka(n),this.T)[0],r=k.aa(n),q=r/q,u=e.center,y;r==e.resolution?(u=uk(u,r,a.size),y=he(u,r,e.rotation,a.size)):y=a.extent;void 0!==c.extent&&(y=je(y,c.extent));if(me(y))return!1;var A=Lh(k,y,r),F=p[0]*pg(A),z=p[1]*og(A),x,K;this.j?(x=this.j,K=this.i,this.c[0]<F||this.c[1]<z||this.N!==p[0]||this.B!==p[1]||this.C&&(this.c[0]>F||this.c[1]>
-z)?(x.width=F,x.height=z,this.c=[F,z],this.C=!Cm(this.c),this.f=null):(F=this.c[0],z=this.c[1],(x=n!=this.u)||(x=this.f,x=!(x.b<=A.b&&A.f<=x.f&&x.a<=A.a&&A.c<=x.c)),x&&(this.f=null))):(K=Ri(F,z),this.j=K.canvas,this.c=[F,z],this.i=K,this.C=!Cm(this.c));var J,I;this.f?(z=this.f,F=pg(z)):(F/=p[0],z/=p[1],J=A.b-Math.floor((F-pg(A))/2),I=A.a-Math.floor((z-og(A))/2),this.u=n,this.N=p[0],this.B=p[1],this.f=new mg(J,J+F-1,I,I+z-1),this.l=Array(F*z),z=this.f);x={};x[n]={};var N=[],va=this.wd(h,x),Ra=g.c(),
-M=Nd(),Ia=new mg(0,0,0,0),pb,Ma,Eb;for(I=A.b;I<=A.f;++I)for(Eb=A.a;Eb<=A.c;++Eb)Ma=h.Sb(n,I,Eb,d,f),J=Ma.state,2==J||4==J||3==J&&!Ra?x[n][fg(Ma.b)]=Ma:(pb=Ih(k,Ma.b,va,Ia,M),pb||(N.push(Ma),(pb=Kh(k,Ma.b,Ia,M))&&va(n+1,pb)));va=0;for(pb=N.length;va<pb;++va)Ma=N[va],I=p[0]*(Ma.b[1]-z.b),Eb=p[1]*(z.c-Ma.b[2]),K.clearRect(I,Eb,p[0],p[1]);N=Object.keys(x).map(Number);kb(N);var $a=h.da,Ec=fe(Jh(k,[n,z.b,z.c],M)),jc,Ke,qj,Qh,Pf,mm,va=0;for(pb=N.length;va<pb;++va)if(jc=N[va],p=h.Tb(jc,d,f),Qh=x[jc],jc==
-n)for(qj in Qh)Ma=Qh[qj],Ke=(Ma.b[2]-z.a)*F+(Ma.b[1]-z.b),this.l[Ke]!=Ma&&(I=p[0]*(Ma.b[1]-z.b),Eb=p[1]*(z.c-Ma.b[2]),J=Ma.state,4!=J&&(3!=J||Ra)&&$a||K.clearRect(I,Eb,p[0],p[1]),2==J&&K.drawImage(Ma.Qa(),m,m,p[0],p[1],I,Eb,p[0],p[1]),this.l[Ke]=Ma);else for(qj in jc=k.aa(jc)/r,Qh)for(Ma=Qh[qj],Ke=Jh(k,Ma.b,M),I=(Ke[0]-Ec[0])/q,Eb=(Ec[1]-Ke[3])/q,mm=jc*p[0],Pf=jc*p[1],J=Ma.state,4!=J&&$a||K.clearRect(I,Eb,mm,Pf),2==J&&K.drawImage(Ma.Qa(),m,m,p[0],p[1],I,Eb,mm,Pf),Ma=kg(k,Ke,n,Ia),J=Math.max(Ma.b,
-z.b),Eb=Math.min(Ma.f,z.f),I=Math.max(Ma.a,z.a),Ma=Math.min(Ma.c,z.c);J<=Eb;++J)for(Pf=I;Pf<=Ma;++Pf)Ke=(Pf-z.a)*F+(J-z.b),this.l[Ke]=void 0;tk(a.usedTiles,h,n,A);vk(a,h,k,d,f,y,n,g.a());qk(a,h);sk(a,h);kk(this.A,d*a.size[0]/2,d*a.size[1]/2,d*q/e.resolution,d*q/e.resolution,e.rotation,(Ec[0]-u[0])/q,(u[1]-Ec[1])/q);this.g=null;return!0};
-Up.prototype.bc=function(a,c,d,e){if(this.i&&(this.g||(this.g=Cd(),Id(this.A,this.g)),a=Bm(a,this.g),0<this.i.getImageData(a[0],a[1],1,1).data[3]))return d.call(e,this.a)};function Vp(a){ym.call(this,a);this.f=!1;this.C=-1;this.l=NaN;this.j=Nd();this.c=this.i=null;this.g=Ri()}w(Vp,ym);
-Vp.prototype.v=function(a,c,d){var e=a.extent,f=a.pixelRatio,g=c.Fb?a.skippedFeatureUids:{},h=a.viewState,k=h.projection,h=h.rotation,m=k.R(),n=this.a.ea(),p=Am(this,a,0);zm(this,"precompose",d,a,p);var q=this.c;if(q&&!q.ya()){var r;fd(this.a,"render")?(this.g.canvas.width=d.canvas.width,this.g.canvas.height=d.canvas.height,r=this.g):r=d;var u=r.globalAlpha;r.globalAlpha=c.opacity;Xm(q,r,f,p,h,g);if(n.N&&k.c&&!Wd(m,e)){c=e[0];k=le(m);for(n=0;c<m[0];)--n,p=k*n,p=Am(this,a,p),Xm(q,r,f,p,h,g),c+=k;n=
-0;for(c=e[2];c>m[2];)++n,p=k*n,p=Am(this,a,p),Xm(q,r,f,p,h,g),c-=k;p=Am(this,a,0)}r!=d&&(zm(this,"render",r,a,p),d.drawImage(r.canvas,0,0));r.globalAlpha=u}zm(this,"postcompose",d,a,p)};Vp.prototype.Ta=function(a,c,d,e){if(this.c){var f=c.viewState.resolution,g=c.viewState.rotation,h=this.a,k=c.layerStates[v(h)],m={};return Um(this.c,a,f,g,k.Fb?c.skippedFeatureUids:{},function(a){var c=v(a).toString();if(!(c in m))return m[c]=!0,d.call(e,a,h)})}};Vp.prototype.A=function(){ok(this)};
-Vp.prototype.ae=function(a){function c(a){var c,e=a.c;e?c=e.call(a,n):(e=d.c)&&(c=e(a,n));if(c){if(c){var f,g=!1,e=0;for(f=c.length;e<f;++e)g=pn(r,a,c[e],on(n,p),this.A,this)||g;a=g}else a=!1;this.f=this.f||a}}var d=this.a,e=d.ea();rk(a.attributions,e.j);sk(a,e);var f=a.viewHints[0],g=a.viewHints[1],h=d.B,k=d.N;if(!this.f&&!h&&f||!k&&g)return!0;var m=a.extent,k=a.viewState,f=k.projection,n=k.resolution,p=a.pixelRatio,g=d.b,q=d.a,h=d.get("renderOrder");void 0===h&&(h=nn);m=Rd(m,q*n);q=k.projection.R();
-e.N&&k.projection.c&&!Wd(q,a.extent)&&(a=Math.max(le(m)/2,le(q)),m[0]=q[0]-a,m[2]=q[2]+a);if(!this.f&&this.l==n&&this.C==g&&this.i==h&&Wd(this.j,m))return!0;vc(this.c);this.c=null;this.f=!1;var r=new Sm(.5*n/p,m,n,d.a);e.Wb(m,n,f);if(h){var u=[];e.zb(m,n,function(a){u.push(a)},this);kb(u,h);u.forEach(c,this)}else e.zb(m,n,c,this);Tm(r);this.l=n;this.C=g;this.i=h;this.j=m;this.c=r;return!0};function Wp(a,c){Ck.call(this,0,c);this.c=Ri();this.b=this.c.canvas;this.b.style.width="100%";this.b.style.height="100%";this.b.className="ol-unselectable";Sg(a,this.b,0);this.a=!0;this.g=Cd()}w(Wp,Ck);Wp.prototype.Ie=function(a){return a instanceof jm?new Tp(a):a instanceof G?new Up(a):a instanceof H?new Vp(a):null};
-function Xp(a,c,d){var e=a.j,f=a.c;if(fd(e,c)){var g=d.extent,h=d.pixelRatio,k=d.viewState.rotation,m=d.pixelRatio,n=d.viewState,p=n.resolution;a=kk(a.g,a.b.width/2,a.b.height/2,m/p,-m/p,-n.rotation,-n.center[0],-n.center[1]);g=new km(f,h,g,a,k);C(e,new fk(c,e,g,d,f,null));xm(g)}}Wp.prototype.W=function(){return"canvas"};
-Wp.prototype.pe=function(a){if(a){var c=this.c,d=a.size[0]*a.pixelRatio,e=a.size[1]*a.pixelRatio;this.b.width!=d||this.b.height!=e?(this.b.width=d,this.b.height=e):c.clearRect(0,0,this.b.width,this.b.height);Dk(a);Xp(this,"precompose",a);d=a.layerStatesArray;mb(d);var e=a.viewState.resolution,f,g,h,k;f=0;for(g=d.length;f<g;++f)k=d[f],h=k.layer,h=Fk(this,h),hk(k,e)&&"ready"==k.v&&h.ae(a,k)&&h.v(a,k,c);Xp(this,"postcompose",a);this.a||(nh(this.b,!0),this.a=!0);Gk(this,a);a.postRenderFunctions.push(Ek)}else this.a&&
-(nh(this.b,!1),this.a=!1)};function Yp(a,c){nk.call(this,a);this.target=c}w(Yp,nk);Yp.prototype.g=wa;Yp.prototype.l=wa;function Zp(a){var c=Pg("DIV");c.style.position="absolute";Yp.call(this,a,c);this.c=null;this.f=Ed()}w(Zp,Yp);Zp.prototype.Ta=function(a,c,d,e){var f=this.a;return f.ea().be(a,c.viewState.resolution,c.viewState.rotation,c.skippedFeatureUids,function(a){return d.call(e,a,f)})};Zp.prototype.g=function(){Rg(this.target);this.c=null};
-Zp.prototype.j=function(a,c){var d=a.viewState,e=d.center,f=d.resolution,g=d.rotation,h=this.c,k=this.a.ea(),m=a.viewHints,n=a.extent;void 0!==c.extent&&(n=je(n,c.extent));m[0]||m[1]||me(n)||(d=d.projection,(m=k.i)&&(d=m),(n=k.cc(n,f,a.pixelRatio,d))&&pk(this,n)&&(h=n));h&&(d=h.R(),m=h.aa(),n=Cd(),kk(n,a.size[0]/2,a.size[1]/2,m/f,m/f,g,(d[0]-e[0])/m,(e[1]-d[3])/m),h!=this.c&&(e=h.b(this),e.style.maxWidth="none",e.style.position="absolute",Rg(this.target),this.target.appendChild(e),this.c=h),lk(n,
-this.f)||(Vi(this.target,n),Fd(this.f,n)),rk(a.attributions,h.j),sk(a,k));return!0};function $p(a){var c=Pg("DIV");c.style.position="absolute";Yp.call(this,a,c);this.f=!0;this.C=1;this.i=0;this.c={}}w($p,Yp);$p.prototype.g=function(){Rg(this.target);this.i=0};
-$p.prototype.j=function(a,c){if(!c.visible)return this.f&&(nh(this.target,!1),this.f=!1),!0;var d=a.pixelRatio,e=a.viewState,f=e.projection,g=this.a,h=g.ea(),k=Th(h,f),m=h.Bd(),n=Nh(k,e.resolution),p=k.aa(n),q=e.center,r;p==e.resolution?(q=uk(q,p,a.size),r=he(q,p,e.rotation,a.size)):r=a.extent;void 0!==c.extent&&(r=je(r,c.extent));var p=Lh(k,r,p),u={};u[n]={};var y=this.wd(h,u),A=g.c(),F=Nd(),z=new mg(0,0,0,0),x,K,J,I;for(J=p.b;J<=p.f;++J)for(I=p.a;I<=p.c;++I)x=h.Sb(n,J,I,d,f),K=x.state,2==K?u[n][fg(x.b)]=
-x:4==K||3==K&&!A||(K=Ih(k,x.b,y,z,F),K||(x=Kh(k,x.b,z,F))&&y(n+1,x));var N;if(this.i!=h.b){for(N in this.c)A=this.c[+N],Tg(A.target);this.c={};this.i=h.b}F=Object.keys(u).map(Number);kb(F);var y={},va;J=0;for(I=F.length;J<I;++J){N=F[J];N in this.c?A=this.c[N]:(A=k.Jd(q,N),A=new aq(k,A),y[N]=!0,this.c[N]=A);N=u[N];for(va in N){x=A;K=N[va];var Ra=m,M=K.b,Ia=M[0],pb=M[1],Ma=M[2],M=fg(M);if(!(M in x.a)){var Ia=pd(x.f.Ka(Ia),x.l),Eb=K.Qa(x),$a=Eb.style;$a.maxWidth="none";var Ec=void 0,jc=void 0;0<Ra?(Ec=
-Pg("DIV"),jc=Ec.style,jc.overflow="hidden",jc.width=Ia[0]+"px",jc.height=Ia[1]+"px",$a.position="absolute",$a.left=-Ra+"px",$a.top=-Ra+"px",$a.width=Ia[0]+2*Ra+"px",$a.height=Ia[1]+2*Ra+"px",Ec.appendChild(Eb)):($a.width=Ia[0]+"px",$a.height=Ia[1]+"px",Ec=Eb,jc=$a);jc.position="absolute";jc.left=(pb-x.c[1])*Ia[0]+"px";jc.top=(x.c[2]-Ma)*Ia[1]+"px";x.b||(x.b=document.createDocumentFragment());x.b.appendChild(Ec);x.a[M]=K}}A.b&&(A.target.appendChild(A.b),A.b=null)}m=Object.keys(this.c).map(Number);
-kb(m);J=Cd();va=0;for(F=m.length;va<F;++va)if(N=m[va],A=this.c[N],N in u)if(x=A.aa(),I=A.ta(),kk(J,a.size[0]/2,a.size[1]/2,x/e.resolution,x/e.resolution,e.rotation,(I[0]-q[0])/x,(q[1]-I[1])/x),A.setTransform(J),N in y){for(--N;0<=N;--N)if(N in this.c){I=this.c[N].target;I.parentNode&&I.parentNode.insertBefore(A.target,I.nextSibling);break}0>N&&Sg(this.target,A.target,0)}else{if(!a.viewHints[0]&&!a.viewHints[1]){K=kg(A.f,r,A.c[0],z);N=[];x=I=void 0;for(x in A.a)I=A.a[x],K.contains(I.b)||N.push(I);
-Ra=K=void 0;K=0;for(Ra=N.length;K<Ra;++K)I=N[K],x=fg(I.b),Tg(I.Qa(A)),delete A.a[x]}}else Tg(A.target),delete this.c[N];c.opacity!=this.C&&(this.C=this.target.style.opacity=c.opacity);c.visible&&!this.f&&(nh(this.target,!0),this.f=!0);tk(a.usedTiles,h,n,p);vk(a,h,k,d,f,r,n,g.a());qk(a,h);sk(a,h);return!0};
-function aq(a,c){this.target=Pg("DIV");this.target.style.position="absolute";this.target.style.width="100%";this.target.style.height="100%";this.f=a;this.c=c;this.j=fe(Jh(a,c));this.i=a.aa(c[0]);this.a={};this.b=null;this.g=Ed();this.l=[0,0]}aq.prototype.ta=function(){return this.j};aq.prototype.aa=function(){return this.i};aq.prototype.setTransform=function(a){lk(a,this.g)||(Vi(this.target,a),Fd(this.g,a))};function bq(a){this.i=Ri();var c=this.i.canvas;c.style.maxWidth="none";c.style.position="absolute";Yp.call(this,a,c);this.f=!1;this.u=-1;this.v=NaN;this.C=Nd();this.c=this.A=null;this.N=Cd();this.B=Cd()}w(bq,Yp);
-bq.prototype.l=function(a,c){var d=a.viewState,e=d.center,f=d.rotation,g=d.resolution,d=a.pixelRatio,h=a.size[0],k=a.size[1],m=h*d,n=k*d,e=kk(this.N,d*h/2,d*k/2,d/g,-d/g,-f,-e[0],-e[1]),g=this.i;g.canvas.width=m;g.canvas.height=n;h=kk(this.B,0,0,1/d,1/d,0,-(m-h)/2*d,-(n-k)/2*d);Vi(g.canvas,h);cq(this,"precompose",a,e);(h=this.c)&&!h.ya()&&(g.globalAlpha=c.opacity,Xm(h,g,d,e,f,c.Fb?a.skippedFeatureUids:{}),cq(this,"render",a,e));cq(this,"postcompose",a,e)};
-function cq(a,c,d,e){var f=a.i;a=a.a;fd(a,c)&&(e=new km(f,d.pixelRatio,d.extent,e,d.viewState.rotation),C(a,new fk(c,a,e,d,f,null)),xm(e))}bq.prototype.Ta=function(a,c,d,e){if(this.c){var f=c.viewState.resolution,g=c.viewState.rotation,h=this.a,k=c.layerStates[v(h)],m={};return Um(this.c,a,f,g,k.Fb?c.skippedFeatureUids:{},function(a){var c=v(a).toString();if(!(c in m))return m[c]=!0,d.call(e,a,h)})}};bq.prototype.I=function(){ok(this)};
-bq.prototype.j=function(a){function c(a){var c,e=a.c;e?c=e.call(a,m):(e=d.c)&&(c=e(a,m));if(c){if(c){var f,g=!1,e=0;for(f=c.length;e<f;++e)g=pn(p,a,c[e],on(m,n),this.I,this)||g;a=g}else a=!1;this.f=this.f||a}}var d=this.a,e=d.ea();rk(a.attributions,e.j);sk(a,e);var f=a.viewHints[0],g=a.viewHints[1],h=d.B,k=d.N;if(!this.f&&!h&&f||!k&&g)return!0;var g=a.extent,h=a.viewState,f=h.projection,m=h.resolution,n=a.pixelRatio;a=d.b;k=d.a;h=d.get("renderOrder");void 0===h&&(h=nn);g=Rd(g,k*m);if(!this.f&&this.v==
-m&&this.u==a&&this.A==h&&Wd(this.C,g))return!0;vc(this.c);this.c=null;this.f=!1;var p=new Sm(.5*m/n,g,m,d.a);e.Wb(g,m,f);if(h){var q=[];e.zb(g,m,function(a){q.push(a)},this);kb(q,h);q.forEach(c,this)}else e.zb(g,m,c,this);Tm(p);this.v=m;this.u=a;this.A=h;this.C=g;this.c=p;return!0};function dq(a,c){Ck.call(this,0,c);this.c=Ri();var d=this.c.canvas;d.style.position="absolute";d.style.width="100%";d.style.height="100%";d.className="ol-unselectable";Sg(a,d,0);this.g=Cd();this.b=Pg("DIV");this.b.className="ol-unselectable";d=this.b.style;d.position="absolute";d.width="100%";d.height="100%";B(this.b,"touchstart",yc);Sg(a,this.b,0);this.a=!0}w(dq,Ck);dq.prototype.Y=function(){Tg(this.b);dq.ba.Y.call(this)};
-dq.prototype.Ie=function(a){if(a instanceof jm)a=new Zp(a);else if(a instanceof G)a=new $p(a);else if(a instanceof H)a=new bq(a);else return null;return a};function eq(a,c,d){var e=a.j;if(fd(e,c)){var f=d.extent,g=d.pixelRatio,h=d.viewState,k=h.rotation,m=a.c,n=m.canvas;kk(a.g,n.width/2,n.height/2,g/h.resolution,-g/h.resolution,-h.rotation,-h.center[0],-h.center[1]);a=new km(m,g,f,a.g,k);C(e,new fk(c,e,a,d,m,null));xm(a)}}dq.prototype.W=function(){return"dom"};
-dq.prototype.pe=function(a){if(a){var c=this.j;if(fd(c,"precompose")||fd(c,"postcompose")){var c=this.c.canvas,d=a.pixelRatio;c.width=a.size[0]*d;c.height=a.size[1]*d}eq(this,"precompose",a);c=a.layerStatesArray;mb(c);var d=a.viewState.resolution,e,f,g,h;e=0;for(f=c.length;e<f;++e)h=c[e],g=h.layer,g=Fk(this,g),Sg(this.b,g.target,e),hk(h,d)&&"ready"==h.v?g.j(a,h)&&g.l(a,h):g.g();var c=a.layerStates,k;for(k in this.f)k in c||(g=this.f[k],Tg(g.target));this.a||(nh(this.b,!0),this.a=!0);Dk(a);Gk(this,
-a);a.postRenderFunctions.push(Ek);eq(this,"postcompose",a)}else this.a&&(nh(this.b,!1),this.a=!1)};function fq(a){this.b=a}function gq(a){this.b=a}w(gq,fq);gq.prototype.W=function(){return 35632};function hq(a){this.b=a}w(hq,fq);hq.prototype.W=function(){return 35633};function iq(){this.b="precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}"}w(iq,gq);ea(iq);
-function jq(){this.b="varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}"}w(jq,hq);ea(jq);
-function kq(a,c){this.l=a.getUniformLocation(c,"j");this.C=a.getUniformLocation(c,"i");this.j=a.getUniformLocation(c,"k");this.i=a.getUniformLocation(c,"h");this.b=a.getAttribLocation(c,"e");this.a=a.getAttribLocation(c,"f");this.f=a.getAttribLocation(c,"c");this.c=a.getAttribLocation(c,"g");this.g=a.getAttribLocation(c,"d")};function lq(a){this.b=void 0!==a?a:[]};function mq(a,c){this.A=a;this.b=c;this.a={};this.j={};this.g={};this.l=this.C=this.f=this.i=null;(this.c=sb(ua,"OES_element_index_uint"))&&c.getExtension("OES_element_index_uint");B(this.A,"webglcontextlost",this.Nm,!1,this);B(this.A,"webglcontextrestored",this.Om,!1,this)}
-function nq(a,c,d){var e=a.b,f=d.b,g=v(d);if(g in a.a)e.bindBuffer(c,a.a[g].buffer);else{var h=e.createBuffer();e.bindBuffer(c,h);var k;34962==c?k=new Float32Array(f):34963==c&&(k=a.c?new Uint32Array(f):new Uint16Array(f));e.bufferData(c,k,35044);a.a[g]={a:d,buffer:h}}}function oq(a,c){var d=a.b,e=v(c),f=a.a[e];d.isContextLost()||d.deleteBuffer(f.buffer);delete a.a[e]}l=mq.prototype;
-l.Y=function(){var a=this.b;a.isContextLost()||(Jb(this.a,function(c){a.deleteBuffer(c.buffer)}),Jb(this.g,function(c){a.deleteProgram(c)}),Jb(this.j,function(c){a.deleteShader(c)}),a.deleteFramebuffer(this.f),a.deleteRenderbuffer(this.l),a.deleteTexture(this.C))};l.Mm=function(){return this.b};
-function pq(a){if(!a.f){var c=a.b,d=c.createFramebuffer();c.bindFramebuffer(c.FRAMEBUFFER,d);var e=qq(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);a.f=d;
-a.C=e;a.l=f}return a.f}function rq(a,c){var d=v(c);if(d in a.j)return a.j[d];var e=a.b,f=e.createShader(c.W());e.shaderSource(f,c.b);e.compileShader(f);return a.j[d]=f}function sq(a,c,d){var e=v(c)+"/"+v(d);if(e in a.g)return a.g[e];var f=a.b,g=f.createProgram();f.attachShader(g,rq(a,c));f.attachShader(g,rq(a,d));f.linkProgram(g);return a.g[e]=g}l.Nm=function(){Sb(this.a);Sb(this.j);Sb(this.g);this.l=this.C=this.f=this.i=null};l.Om=function(){};
-l.je=function(a){if(a==this.i)return!1;this.b.useProgram(a);this.i=a;return!0};function tq(a,c,d){var e=a.createTexture();a.bindTexture(a.TEXTURE_2D,e);a.texParameteri(a.TEXTURE_2D,a.TEXTURE_MAG_FILTER,a.LINEAR);a.texParameteri(a.TEXTURE_2D,a.TEXTURE_MIN_FILTER,a.LINEAR);void 0!==c&&a.texParameteri(3553,10242,c);void 0!==d&&a.texParameteri(3553,10243,d);return e}function qq(a,c,d){var e=tq(a,void 0,void 0);a.texImage2D(a.TEXTURE_2D,0,a.RGBA,c,d,0,a.RGBA,a.UNSIGNED_BYTE,null);return e}
-function uq(a,c){var d=tq(a,33071,33071);a.texImage2D(a.TEXTURE_2D,0,a.RGBA,a.RGBA,a.UNSIGNED_BYTE,c);return d};function vq(a,c){this.N=this.B=void 0;this.C=ge(c);this.u=[];this.j=[];this.ka=void 0;this.g=[];this.f=[];this.T=this.I=void 0;this.a=[];this.ca=this.l=null;this.X=void 0;this.lb=Ed();this.mb=Ed();this.da=this.$=void 0;this.nb=Ed();this.ra=this.Ma=this.fa=void 0;this.Na=[];this.i=[];this.b=[];this.v=null;this.c=[];this.A=[];this.wa=void 0}w(vq,ek);
-function wq(a,c){var d=a.v,e=a.l,f=a.Na,g=a.i,h=c.b;return function(){if(!h.isContextLost()){var a,m;a=0;for(m=f.length;a<m;++a)h.deleteTexture(f[a]);a=0;for(m=g.length;a<m;++a)h.deleteTexture(g[a])}oq(c,d);oq(c,e)}}
-function xq(a,c,d,e){var f=a.B,g=a.N,h=a.ka,k=a.I,m=a.T,n=a.X,p=a.$,q=a.da,r=a.fa?1:0,u=a.Ma,y=a.ra,A=a.wa,F=Math.cos(u),u=Math.sin(u),z=a.a.length,x=a.b.length,K,J,I,N,va,Ra;for(K=0;K<d;K+=e)va=c[K]-a.C[0],Ra=c[K+1]-a.C[1],J=x/8,I=-y*f,N=-y*(h-g),a.b[x++]=va,a.b[x++]=Ra,a.b[x++]=I*F-N*u,a.b[x++]=I*u+N*F,a.b[x++]=p/m,a.b[x++]=(q+h)/k,a.b[x++]=n,a.b[x++]=r,I=y*(A-f),N=-y*(h-g),a.b[x++]=va,a.b[x++]=Ra,a.b[x++]=I*F-N*u,a.b[x++]=I*u+N*F,a.b[x++]=(p+A)/m,a.b[x++]=(q+h)/k,a.b[x++]=n,a.b[x++]=r,I=y*(A-f),
-N=y*g,a.b[x++]=va,a.b[x++]=Ra,a.b[x++]=I*F-N*u,a.b[x++]=I*u+N*F,a.b[x++]=(p+A)/m,a.b[x++]=q/k,a.b[x++]=n,a.b[x++]=r,I=-y*f,N=y*g,a.b[x++]=va,a.b[x++]=Ra,a.b[x++]=I*F-N*u,a.b[x++]=I*u+N*F,a.b[x++]=p/m,a.b[x++]=q/k,a.b[x++]=n,a.b[x++]=r,a.a[z++]=J,a.a[z++]=J+1,a.a[z++]=J+2,a.a[z++]=J,a.a[z++]=J+2,a.a[z++]=J+3}vq.prototype.ob=function(a,c){this.c.push(this.a.length);this.A.push(c);var d=a.o;xq(this,d,d.length,a.G)};
-vq.prototype.pb=function(a,c){this.c.push(this.a.length);this.A.push(c);var d=a.o;xq(this,d,d.length,a.G)};function yq(a,c){var d=c.b;a.u.push(a.a.length);a.j.push(a.a.length);a.v=new lq(a.b);nq(c,34962,a.v);a.l=new lq(a.a);nq(c,34963,a.l);var e={};zq(a.Na,a.g,e,d);zq(a.i,a.f,e,d);a.B=void 0;a.N=void 0;a.ka=void 0;a.g=null;a.f=null;a.I=void 0;a.T=void 0;a.a=null;a.X=void 0;a.$=void 0;a.da=void 0;a.fa=void 0;a.Ma=void 0;a.ra=void 0;a.b=null;a.wa=void 0}
-function zq(a,c,d,e){var f,g,h,k=c.length;for(h=0;h<k;++h)f=c[h],g=v(f).toString(),g in d?f=d[g]:(f=uq(e,f),d[g]=f),a[h]=f}
-function Aq(a,c,d,e,f,g,h,k,m,n,p){var q=c.b;nq(c,34962,a.v);nq(c,34963,a.l);var r=iq.Bb(),u=jq.Bb(),u=sq(c,r,u);a.ca?r=a.ca:(r=new kq(q,u),a.ca=r);c.je(u);q.enableVertexAttribArray(r.f);q.vertexAttribPointer(r.f,2,5126,!1,32,0);q.enableVertexAttribArray(r.b);q.vertexAttribPointer(r.b,2,5126,!1,32,8);q.enableVertexAttribArray(r.g);q.vertexAttribPointer(r.g,2,5126,!1,32,16);q.enableVertexAttribArray(r.a);q.vertexAttribPointer(r.a,1,5126,!1,32,24);q.enableVertexAttribArray(r.c);q.vertexAttribPointer(r.c,
-1,5126,!1,32,28);u=a.nb;kk(u,0,0,2/(e*g[0]),2/(e*g[1]),-f,-(d[0]-a.C[0]),-(d[1]-a.C[1]));d=a.mb;e=2/g[0];g=2/g[1];Gd(d);d[0]=e;d[5]=g;d[10]=1;d[15]=1;g=a.lb;Gd(g);0!==f&&Ld(g,-f);q.uniformMatrix4fv(r.i,!1,u);q.uniformMatrix4fv(r.C,!1,d);q.uniformMatrix4fv(r.l,!1,g);q.uniform1f(r.j,h);var y;if(void 0===m)Bq(a,q,c,k,a.Na,a.u);else{if(n)a:{f=c.c?5125:5123;c=c.c?4:2;g=a.c.length-1;for(h=a.i.length-1;0<=h;--h)for(q.bindTexture(3553,a.i[h]),n=0<h?a.j[h-1]:0,u=a.j[h];0<=g&&a.c[g]>=n;){y=a.c[g];d=a.A[g];
-e=v(d).toString();if(void 0===k[e]&&d.V()&&(void 0===p||ke(p,d.V().R()))&&(q.clear(q.COLOR_BUFFER_BIT|q.DEPTH_BUFFER_BIT),q.drawElements(4,u-y,f,y*c),u=m(d))){a=u;break a}u=y;g--}a=void 0}else q.clear(q.COLOR_BUFFER_BIT|q.DEPTH_BUFFER_BIT),Bq(a,q,c,k,a.i,a.j),a=(a=m(null))?a:void 0;y=a}q.disableVertexAttribArray(r.f);q.disableVertexAttribArray(r.b);q.disableVertexAttribArray(r.g);q.disableVertexAttribArray(r.a);q.disableVertexAttribArray(r.c);return y}
-function Bq(a,c,d,e,f,g){var h=d.c?5125:5123;d=d.c?4:2;if(Rb(e)){var k;a=0;e=f.length;for(k=0;a<e;++a){c.bindTexture(3553,f[a]);var m=g[a];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<a.c.length&&a.c[k]<=q;){var u=v(a.A[k]).toString();void 0!==e[u]?(r!==p&&c.drawElements(4,p-r,h,r*d),p=r=k===a.c.length-1?q:a.c[k+1]):p=k===a.c.length-1?q:a.c[k+1];k++}r!==p&&c.drawElements(4,p-r,h,r*d)}}}
-vq.prototype.bb=function(a){var c=a.Ab(),d=a.Jb(1),e=a.Cd(),f=a.ce(1),g=a.v,h=a.ta(),k=a.B,m=a.C,n=a.kb();a=a.A;var p;0===this.g.length?this.g.push(d):(p=this.g[this.g.length-1],v(p)!=v(d)&&(this.u.push(this.a.length),this.g.push(d)));0===this.f.length?this.f.push(f):(p=this.f[this.f.length-1],v(p)!=v(f)&&(this.j.push(this.a.length),this.f.push(f)));this.B=c[0];this.N=c[1];this.ka=n[1];this.I=e[1];this.T=e[0];this.X=g;this.$=h[0];this.da=h[1];this.Ma=m;this.fa=k;this.ra=a;this.wa=n[0]};
-function Cq(a,c,d){this.f=c;this.g=a;this.c=d;this.a={}}function Dq(a,c){var d=[],e;for(e in a.a)d.push(wq(a.a[e],c));return ve.apply(null,d)}function Eq(a,c){for(var d in a.a)yq(a.a[d],c)}Cq.prototype.b=function(a,c){var d=this.a[c];void 0===d&&(d=new Fq[c](this.g,this.f),this.a[c]=d);return d};Cq.prototype.ya=function(){return Rb(this.a)};function Gq(a,c,d,e,f,g,h,k,m,n){var p=Hq,q,r;for(q=Dm.length-1;0<=q;--q)if(r=a.a[Dm[q]],void 0!==r&&(r=Aq(r,c,d,e,f,p,g,h,k,m,n)))return r}
-function Iq(a,c,d,e,f,g,h,k){var m=d.b;m.bindFramebuffer(m.FRAMEBUFFER,pq(d));var n;void 0!==a.c&&(n=Rd(Yd(c),e*a.c));return Gq(a,d,c,e,f,g,h,function(a){var c=new Uint8Array(4);m.readPixels(0,0,1,1,m.RGBA,m.UNSIGNED_BYTE,c);if(0<c[3]&&(a=k(a)))return a},!0,n)}function Jq(a,c,d,e,f,g,h){var k=d.b;k.bindFramebuffer(k.FRAMEBUFFER,pq(d));return void 0!==Gq(a,d,c,e,f,g,h,function(){var a=new Uint8Array(4);k.readPixels(0,0,1,1,k.RGBA,k.UNSIGNED_BYTE,a);return 0<a[3]},!1)}var Fq={Image:vq},Hq=[1,1];function Kq(a,c,d,e,f,g){this.a=a;this.g=c;this.f=g;this.l=f;this.i=e;this.j=d;this.c=null;this.b={}}w(Kq,ek);l=Kq.prototype;l.oc=function(a,c){var d=a.toString(),e=this.b[d];void 0!==e?e.push(c):this.b[d]=[c]};l.pc=function(){};l.Je=function(a,c){var d=(0,c.g)(a);if(d&&ke(this.f,d.R())){var e=c.b;void 0===e&&(e=0);this.oc(e,function(a){a.Ia(c.f,c.c);a.bb(c.j);a.Ja(c.a);var e=Lq[d.W()];e&&e.call(a,d,null)})}};
-l.xd=function(a,c){var d=a.f,e,f;e=0;for(f=d.length;e<f;++e){var g=d[e],h=Lq[g.W()];h&&h.call(this,g,c)}};l.pb=function(a,c){var d=this.a,e=(new Cq(1,this.f)).b(0,"Image");e.bb(this.c);e.pb(a,c);yq(e,d);Aq(e,this.a,this.g,this.j,this.i,this.l,1,{},void 0,!1);wq(e,d)()};l.yb=function(){};l.qc=function(){};l.ob=function(a,c){var d=this.a,e=(new Cq(1,this.f)).b(0,"Image");e.bb(this.c);e.ob(a,c);yq(e,d);Aq(e,this.a,this.g,this.j,this.i,this.l,1,{},void 0,!1);wq(e,d)()};l.rc=function(){};l.Pb=function(){};
-l.qb=function(){};l.Ia=function(){};l.bb=function(a){this.c=a};l.Ja=function(){};var Lq={Point:Kq.prototype.pb,MultiPoint:Kq.prototype.ob,GeometryCollection:Kq.prototype.xd};function Mq(){this.b="precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}"}w(Mq,gq);ea(Mq);function Nq(){this.b="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}"}w(Nq,hq);ea(Nq);
-function Oq(a,c){this.c=a.getUniformLocation(c,"f");this.f=a.getUniformLocation(c,"e");this.j=a.getUniformLocation(c,"d");this.g=a.getUniformLocation(c,"g");this.b=a.getAttribLocation(c,"b");this.a=a.getAttribLocation(c,"c")};function Pq(a,c){nk.call(this,c);this.c=a;this.T=new lq([-1,-1,0,0,1,-1,1,0,-1,1,0,1,1,1,1,1]);this.g=this.Ua=null;this.j=void 0;this.C=Cd();this.v=Ed();this.A=null}w(Pq,nk);
-function Qq(a,c,d){var e=a.c.c;if(void 0===a.j||a.j!=d){c.postRenderFunctions.push(ra(function(a,c,d){a.isContextLost()||(a.deleteFramebuffer(c),a.deleteTexture(d))},e,a.g,a.Ua));c=qq(e,d,d);var f=e.createFramebuffer();e.bindFramebuffer(36160,f);e.framebufferTexture2D(36160,36064,3553,c,0);a.Ua=c;a.g=f;a.j=d}else e.bindFramebuffer(36160,a.g)}
-Pq.prototype.Ag=function(a,c,d){Rq(this,"precompose",d,a);nq(d,34962,this.T);var e=d.b,f=Mq.Bb(),g=Nq.Bb(),f=sq(d,f,g);this.A?g=this.A:this.A=g=new Oq(e,f);d.je(f)&&(e.enableVertexAttribArray(g.b),e.vertexAttribPointer(g.b,2,5126,!1,16,0),e.enableVertexAttribArray(g.a),e.vertexAttribPointer(g.a,2,5126,!1,16,8),e.uniform1i(g.g,0));e.uniformMatrix4fv(g.j,!1,this.C);e.uniformMatrix4fv(g.f,!1,this.v);e.uniform1f(g.c,c.opacity);e.bindTexture(3553,this.Ua);e.drawArrays(5,0,4);Rq(this,"postcompose",d,a)};
-function Rq(a,c,d,e){a=a.a;if(fd(a,c)){var f=e.viewState;C(a,new fk(c,a,new Kq(d,f.center,f.resolution,f.rotation,e.size,e.extent),e,null,d))}}Pq.prototype.gf=function(){this.g=this.Ua=null;this.j=void 0};function Sq(a,c){Pq.call(this,a,c);this.l=this.i=this.f=null}w(Sq,Pq);function Tq(a,c){var d=c.b();return uq(a.c.c,d)}Sq.prototype.Ta=function(a,c,d,e){var f=this.a;return f.ea().be(a,c.viewState.resolution,c.viewState.rotation,c.skippedFeatureUids,function(a){return d.call(e,a,f)})};
-Sq.prototype.hf=function(a,c){var d=this.c.c,e=a.pixelRatio,f=a.viewState,g=f.center,h=f.resolution,k=f.rotation,m=this.f,n=this.Ua,p=this.a.ea(),q=a.viewHints,r=a.extent;void 0!==c.extent&&(r=je(r,c.extent));q[0]||q[1]||me(r)||(f=f.projection,(q=p.i)&&(f=q),(r=p.cc(r,h,e,f))&&pk(this,r)&&(m=r,n=Tq(this,r),this.Ua&&a.postRenderFunctions.push(ra(function(a,c){a.isContextLost()||a.deleteTexture(c)},d,this.Ua))));m&&(d=this.c.g.A,Uq(this,d.width,d.height,e,g,h,k,m.R()),this.l=null,e=this.C,Gd(e),Kd(e,
-1,-1),Jd(e,0,-1),this.f=m,this.Ua=n,rk(a.attributions,m.j),sk(a,p));return!0};function Uq(a,c,d,e,f,g,h,k){c*=g;d*=g;a=a.v;Gd(a);Kd(a,2*e/c,2*e/d);Ld(a,-h);Jd(a,k[0]-f[0],k[1]-f[1]);Kd(a,(k[2]-k[0])/2,(k[3]-k[1])/2);Jd(a,1,1)}Sq.prototype.$d=function(a,c){return void 0!==this.Ta(a,c,re,this)};
-Sq.prototype.bc=function(a,c,d,e){if(this.f&&this.f.b())if(this.a.ea()instanceof Rp){if(a=a.slice(),mk(c.pixelToCoordinateMatrix,a,a),this.Ta(a,c,re,this))return d.call(e,this.a)}else{var f=[this.f.b().width,this.f.b().height];if(!this.l){var g=c.size;c=Cd();Gd(c);Jd(c,-1,-1);Kd(c,2/g[0],2/g[1]);Jd(c,0,g[1]);Kd(c,1,-1);g=Cd();Id(this.v,g);var h=Cd();Gd(h);Jd(h,0,f[1]);Kd(h,1,-1);Kd(h,f[0]/2,f[1]/2);Jd(h,1,1);var k=Cd();Hd(h,g,k);Hd(k,c,k);this.l=k}c=[0,0];mk(this.l,a,c);if(!(0>c[0]||c[0]>f[0]||0>
-c[1]||c[1]>f[1])&&(this.i||(this.i=Ri(1,1)),this.i.clearRect(0,0,1,1),this.i.drawImage(this.f.b(),c[0],c[1],1,1,0,0,1,1),0<this.i.getImageData(0,0,1,1).data[3]))return d.call(e,this.a)}};function Vq(){this.b="precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}"}w(Vq,gq);ea(Vq);function Wq(){this.b="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}"}w(Wq,hq);ea(Wq);function Xq(a,c){this.c=a.getUniformLocation(c,"e");this.f=a.getUniformLocation(c,"d");this.b=a.getAttribLocation(c,"b");this.a=a.getAttribLocation(c,"c")};function Yq(a,c){Pq.call(this,a,c);this.N=Vq.Bb();this.X=Wq.Bb();this.f=null;this.B=new lq([0,0,0,1,1,0,1,1,0,1,0,0,1,1,1,0]);this.u=this.i=null;this.l=-1;this.I=[0,0]}w(Yq,Pq);l=Yq.prototype;l.Y=function(){oq(this.c.g,this.B);Yq.ba.Y.call(this)};l.wd=function(a,c){var d=this.c;return function(e,f){return Sh(a,e,f,function(a){var f=xh(d.a,a.jb());f&&(c[e]||(c[e]={}),c[e][a.b.toString()]=a);return f})}};l.gf=function(){Yq.ba.gf.call(this);this.f=null};
-l.hf=function(a,c,d){var e=this.c,f=d.b,g=a.viewState,h=g.projection,k=this.a,m=k.ea(),n=Th(m,h),p=Nh(n,g.resolution),q=n.aa(p),r=m.Tb(p,a.pixelRatio,h),u=r[0]/pd(n.Ka(p),this.I)[0],y=q/u,A=m.Bd(),F=g.center,z;q==g.resolution?(F=uk(F,q,a.size),z=he(F,q,g.rotation,a.size)):z=a.extent;q=Lh(n,z,q);if(this.i&&ng(this.i,q)&&this.l==m.b)y=this.u;else{var x=[pg(q),og(q)],K=Math.pow(2,Math.ceil(Math.log(Math.max(x[0]*r[0],x[1]*r[1]))/Math.LN2)),x=y*K,J=n.ta(p),I=J[0]+q.b*r[0]*y,y=J[1]+q.a*r[1]*y,y=[I,y,I+
-x,y+x];Qq(this,a,K);f.viewport(0,0,K,K);f.clearColor(0,0,0,0);f.clear(16384);f.disable(3042);K=sq(d,this.N,this.X);d.je(K);this.f||(this.f=new Xq(f,K));nq(d,34962,this.B);f.enableVertexAttribArray(this.f.b);f.vertexAttribPointer(this.f.b,2,5126,!1,16,0);f.enableVertexAttribArray(this.f.a);f.vertexAttribPointer(this.f.a,2,5126,!1,16,8);f.uniform1i(this.f.c,0);d={};d[p]={};var N=this.wd(m,d),va=k.c(),K=!0,I=Nd(),Ra=new mg(0,0,0,0),M,Ia,pb;for(Ia=q.b;Ia<=q.f;++Ia)for(pb=q.a;pb<=q.c;++pb){J=m.Sb(p,Ia,
-pb,u,h);if(void 0!==c.extent&&(M=Jh(n,J.b,I),!ke(M,c.extent)))continue;M=J.state;if(2==M){if(xh(e.a,J.jb())){d[p][fg(J.b)]=J;continue}}else if(4==M||3==M&&!va)continue;K=!1;M=Ih(n,J.b,N,Ra,I);M||(J=Kh(n,J.b,Ra,I))&&N(p+1,J)}c=Object.keys(d).map(Number);kb(c);for(var N=new Float32Array(4),Ma,Eb,$a,va=0,Ra=c.length;va<Ra;++va)for(Ma in Eb=d[c[va]],Eb)J=Eb[Ma],M=Jh(n,J.b,I),Ia=2*(M[2]-M[0])/x,pb=2*(M[3]-M[1])/x,$a=2*(M[0]-y[0])/x-1,M=2*(M[1]-y[1])/x-1,Bd(N,Ia,pb,$a,M),f.uniform4fv(this.f.f,N),Zq(e,J,
-r,A*u),f.drawArrays(5,0,4);K?(this.i=q,this.u=y,this.l=m.b):(this.u=this.i=null,this.l=-1,a.animate=!0)}tk(a.usedTiles,m,p,q);var Ec=e.l;vk(a,m,n,u,h,z,p,k.a(),function(a){var c;(c=2!=a.state||xh(e.a,a.jb()))||(c=a.jb()in Ec.c);c||wk(Ec,[a,hg(n,a.b),n.aa(a.b[0]),r,A*u])},this);qk(a,m);sk(a,m);f=this.C;Gd(f);Jd(f,(F[0]-y[0])/(y[2]-y[0]),(F[1]-y[1])/(y[3]-y[1]));0!==g.rotation&&Ld(f,g.rotation);Kd(f,a.size[0]*g.resolution/(y[2]-y[0]),a.size[1]*g.resolution/(y[3]-y[1]));Jd(f,-.5,-.5);return!0};
-l.bc=function(a,c,d,e){if(this.g){var f=[0,0];mk(this.C,[a[0]/c.size[0],(c.size[1]-a[1])/c.size[1]],f);a=[f[0]*this.j,f[1]*this.j];c=this.c.g.b;c.bindFramebuffer(c.FRAMEBUFFER,this.g);f=new Uint8Array(4);c.readPixels(a[0],a[1],1,1,c.RGBA,c.UNSIGNED_BYTE,f);if(0<f[3])return d.call(e,this.a)}};function $q(a,c){Pq.call(this,a,c);this.l=!1;this.I=-1;this.N=NaN;this.u=Nd();this.i=this.f=this.B=null}w($q,Pq);l=$q.prototype;l.Ag=function(a,c,d){this.i=c;var e=a.viewState,f=this.f;if(f&&!f.ya()){var g=e.center,h=e.resolution,e=e.rotation,k=a.size,m=c.opacity;a=c.Fb?a.skippedFeatureUids:{};var n,p;c=0;for(n=Dm.length;c<n;++c)p=f.a[Dm[c]],void 0!==p&&Aq(p,d,g,h,e,k,m,a,void 0,!1)}};l.Y=function(){var a=this.f;a&&(Dq(a,this.c.g)(),this.f=null);$q.ba.Y.call(this)};
-l.Ta=function(a,c,d,e){if(this.f&&this.i){var f=c.viewState,g=this.a,h=this.i,k={};return Iq(this.f,a,this.c.g,f.resolution,f.rotation,h.opacity,h.Fb?c.skippedFeatureUids:{},function(a){var c=v(a).toString();if(!(c in k))return k[c]=!0,d.call(e,a,g)})}};l.$d=function(a,c){if(this.f&&this.i){var d=c.viewState;return Jq(this.f,a,this.c.g,d.resolution,d.rotation,this.i.opacity,c.skippedFeatureUids)}return!1};
-l.bc=function(a,c,d,e){a=a.slice();mk(c.pixelToCoordinateMatrix,a,a);if(this.$d(a,c))return d.call(e,this.a)};l.Dl=function(){ok(this)};
-l.hf=function(a,c,d){function e(a){var c,d=a.c;d?c=d.call(a,n):(d=f.c)&&(c=d(a,n));if(c){if(c){var e,g=!1,d=0;for(e=c.length;d<e;++d)g=pn(r,a,c[d],on(n,p),this.Dl,this)||g;a=g}else a=!1;this.l=this.l||a}}var f=this.a;c=f.ea();rk(a.attributions,c.j);sk(a,c);var g=a.viewHints[0],h=a.viewHints[1],k=f.B,m=f.N;if(!this.l&&!k&&g||!m&&h)return!0;var h=a.extent,k=a.viewState,g=k.projection,n=k.resolution,p=a.pixelRatio,k=f.b,q=f.a,m=f.get("renderOrder");void 0===m&&(m=nn);h=Rd(h,q*n);if(!this.l&&this.N==
-n&&this.I==k&&this.B==m&&Wd(this.u,h))return!0;this.f&&a.postRenderFunctions.push(Dq(this.f,d));this.l=!1;var r=new Cq(.5*n/p,h,f.a);c.Wb(h,n,g);if(m){var u=[];c.zb(h,n,function(a){u.push(a)},this);kb(u,m);u.forEach(e,this)}else c.zb(h,n,e,this);Eq(r,d);this.N=n;this.I=k;this.B=m;this.u=h;this.f=r;return!0};function ar(a,c){Ck.call(this,0,c);this.b=Pg("CANVAS");this.b.style.width="100%";this.b.style.height="100%";this.b.className="ol-unselectable";Sg(a,this.b,0);this.u=this.B=0;this.N=Ri();this.C=!0;this.c=Xi(this.b,{antialias:!0,depth:!1,failIfMajorPerformanceCaveat:!0,preserveDrawingBuffer:!1,stencil:!0});this.g=new mq(this.b,this.c);B(this.b,"webglcontextlost",this.Bl,!1,this);B(this.b,"webglcontextrestored",this.Cl,!1,this);this.a=new wh;this.v=null;this.l=new Hk(qa(function(a){var c=a[1];a=a[2];
-var f=c[0]-this.v[0],c=c[1]-this.v[1];return 65536*Math.log(a)+Math.sqrt(f*f+c*c)/a},this),function(a){return a[0].jb()});this.I=qa(function(){if(!this.l.ya()){Lk(this.l);var a=Ik(this.l);Zq(this,a[0],a[3],a[4])}},this);this.i=0;br(this)}w(ar,Ck);
-function Zq(a,c,d,e){var f=a.c,g=c.jb();if(xh(a.a,g))a=a.a.get(g),f.bindTexture(3553,a.Ua),9729!=a.gg&&(f.texParameteri(3553,10240,9729),a.gg=9729),9729!=a.hg&&(f.texParameteri(3553,10240,9729),a.hg=9729);else{var h=f.createTexture();f.bindTexture(3553,h);if(0<e){var k=a.N.canvas,m=a.N;a.B!==d[0]||a.u!==d[1]?(k.width=d[0],k.height=d[1],a.B=d[0],a.u=d[1]):m.clearRect(0,0,d[0],d[1]);m.drawImage(c.Qa(),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.Qa());f.texParameteri(3553,10240,9729);f.texParameteri(3553,10241,9729);f.texParameteri(3553,10242,33071);f.texParameteri(3553,10243,33071);a.a.set(g,{Ua:h,gg:9729,hg:9729})}}l=ar.prototype;l.Ie=function(a){return a instanceof jm?new Sq(this,a):a instanceof G?new Yq(this,a):a instanceof H?new $q(this,a):null};
-function cr(a,c,d){var e=a.j;if(fd(e,c)){var f=a.g;a=d.viewState;a=new Kq(f,a.center,a.resolution,a.rotation,d.size,d.extent);C(e,new fk(c,e,a,d,null,f));c=Object.keys(a.b).map(Number);kb(c);var g,h;d=0;for(e=c.length;d<e;++d)for(f=a.b[c[d].toString()],g=0,h=f.length;g<h;++g)f[g](a)}}l.Y=function(){var a=this.c;a.isContextLost()||this.a.forEach(function(c){c&&a.deleteTexture(c.Ua)});vc(this.g);ar.ba.Y.call(this)};
-l.ui=function(a,c){for(var d=this.c,e;1024<this.a.Qb()-this.i;){if(e=this.a.b.kc)d.deleteTexture(e.Ua);else if(+this.a.b.Rd==c.index)break;else--this.i;this.a.pop()}};l.W=function(){return"webgl"};l.Bl=function(a){a.preventDefault();this.a.clear();this.i=0;Jb(this.f,function(a){a.gf()})};l.Cl=function(){br(this);this.j.render()};function br(a){a=a.c;a.activeTexture(33984);a.blendFuncSeparate(770,771,1,771);a.disable(2884);a.disable(2929);a.disable(3089);a.disable(2960)}
-l.pe=function(a){var c=this.g,d=this.c;if(d.isContextLost())return!1;if(!a)return this.C&&(nh(this.b,!1),this.C=!1),!1;this.v=a.focus;this.a.set((-a.index).toString(),null);++this.i;cr(this,"precompose",a);var e=[],f=a.layerStatesArray;mb(f);var g=a.viewState.resolution,h,k,m,n;h=0;for(k=f.length;h<k;++h)n=f[h],hk(n,g)&&"ready"==n.v&&(m=Fk(this,n.layer),m.hf(a,n,c)&&e.push(n));f=a.size[0]*a.pixelRatio;g=a.size[1]*a.pixelRatio;if(this.b.width!=f||this.b.height!=g)this.b.width=f,this.b.height=g;d.bindFramebuffer(36160,
-null);d.clearColor(0,0,0,0);d.clear(16384);d.enable(3042);d.viewport(0,0,this.b.width,this.b.height);h=0;for(k=e.length;h<k;++h)n=e[h],m=Fk(this,n.layer),m.Ag(a,n,c);this.C||(nh(this.b,!0),this.C=!0);Dk(a);1024<this.a.Qb()-this.i&&a.postRenderFunctions.push(qa(this.ui,this));this.l.ya()||(a.postRenderFunctions.push(this.I),a.animate=!0);cr(this,"postcompose",a);Gk(this,a);a.postRenderFunctions.push(Ek)};
-l.ff=function(a,c,d,e,f,g){var h;if(this.c.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(hk(h,k.resolution)&&f.call(g,p)&&(h=Fk(this,p).Ta(a,c,d,e)))return h}};l.zg=function(a,c,d,e){var f=!1;if(this.c.isContextLost())return!1;var g=c.viewState,h=c.layerStatesArray,k;for(k=h.length-1;0<=k;--k){var m=h[k],n=m.layer;if(hk(m,g.resolution)&&d.call(e,n)&&(f=Fk(this,n).$d(a,c)))return!0}return f};
-l.yg=function(a,c,d,e,f){if(this.c.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(hk(h,g.resolution)&&f.call(e,n)&&(h=Fk(this,n).bc(a,c,d,e)))return h}};var dr=["canvas","webgl","dom"];
-function W(a){jd.call(this);var c=er(a);this.nb=void 0!==a.loadTilesWhileAnimating?a.loadTilesWhileAnimating:!1;this.lc=void 0!==a.loadTilesWhileInteracting?a.loadTilesWhileInteracting:!1;this.Ic=void 0!==a.pixelRatio?a.pixelRatio:Zi;this.mc=c.logos;this.A=new ki(this.Hn,void 0,this);uc(this,this.A);this.lb=Cd();this.ze=Cd();this.mb=0;this.c=null;this.wa=Nd();this.u=this.I=null;this.a=Mg("DIV","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.a="none";dj&&ah(this.a,"ol-touch");this.fa=Mg("DIV","ol-overlaycontainer");this.a.appendChild(this.fa);this.B=Mg("DIV","ol-overlaycontainer-stopevent");B(this.B,["click","dblclick","mousedown","touchstart","MSPointerDown",Yj,bc?"DOMMouseScroll":"mousewheel"],xc);this.a.appendChild(this.B);a=new Qj(this);B(a,Mb(bk),this.$f,!1,this);uc(this,a);this.$=c.keyboardEventTarget;this.v=new Ci;B(this.v,"key",this.Zf,!1,this);uc(this,
-this.v);a=new Ki(this.a);B(a,"mousewheel",this.Zf,!1,this);uc(this,a);this.g=c.controls;this.f=c.interactions;this.j=c.overlays;this.i=new c.Jn(this.a,this);uc(this,this.i);this.Na=new xi;uc(this,this.Na);this.T=this.l=null;this.N=[];this.ra=[];this.da=new Mk(qa(this.nj,this),qa(this.Ik,this));this.X={};B(this,ld("layergroup"),this.Bj,!1,this);B(this,ld("view"),this.Wj,!1,this);B(this,ld("size"),this.Tj,!1,this);B(this,ld("target"),this.Vj,!1,this);this.H(c.values);this.g.forEach(function(a){a.setMap(this)},
-this);B(this.g,"add",function(a){a.element.setMap(this)},!1,this);B(this.g,"remove",function(a){a.element.setMap(null)},!1,this);this.f.forEach(function(a){a.setMap(this)},this);B(this.f,"add",function(a){a.element.setMap(this)},!1,this);B(this.f,"remove",function(a){a.element.setMap(null)},!1,this);this.j.forEach(function(a){a.setMap(this)},this);B(this.j,"add",function(a){a.element.setMap(this)},!1,this);B(this.j,"remove",function(a){a.element.setMap(null)},!1,this)}w(W,jd);l=W.prototype;l.hi=function(a){this.g.push(a)};
-l.ii=function(a){this.f.push(a)};l.Kf=function(a){this.Rb().xc().push(a)};l.Lf=function(a){this.j.push(a)};l.Aa=function(a){this.render();Array.prototype.push.apply(this.N,arguments)};l.Y=function(){Tg(this.a);W.ba.Y.call(this)};l.Wc=function(a,c,d,e,f){if(this.c)return a=this.xa(a),this.i.ff(a,this.c,c,void 0!==d?d:null,void 0!==e?e:re,void 0!==f?f:null)};l.Hk=function(a,c,d,e,f){if(this.c)return this.i.yg(a,this.c,c,void 0!==d?d:null,void 0!==e?e:re,void 0!==f?f:null)};
-l.Yj=function(a,c,d){if(!this.c)return!1;a=this.xa(a);return this.i.zg(a,this.c,void 0!==c?c:re,void 0!==d?d:null)};l.Ji=function(a){return this.xa(this.zd(a))};l.zd=function(a){var c;c=this.a;a=kh(a);c=kh(c);c=new Dg(a.x-c.x,a.y-c.y);return[c.x,c.y]};l.bf=function(){return this.get("target")};l.uc=function(){var a=this.bf();return void 0!==a?Ig(a):null};l.xa=function(a){var c=this.c;return c?(a=a.slice(),mk(c.pixelToCoordinateMatrix,a,a)):null};l.Hi=function(){return this.g};l.cj=function(){return this.j};
-l.Ri=function(){return this.f};l.Rb=function(){return this.get("layergroup")};l.ng=function(){return this.Rb().xc()};l.Ba=function(a){var c=this.c;return c?(a=a.slice(0,2),mk(c.coordinateToPixelMatrix,a,a)):null};l.Ea=function(){return this.get("size")};l.Z=function(){return this.get("view")};l.pj=function(){return this.a};
-l.nj=function(a,c,d,e){var f=this.c;if(!(f&&c in f.wantedTiles&&f.wantedTiles[c][fg(a.b)]))return Infinity;a=d[0]-f.focus[0];d=d[1]-f.focus[1];return 65536*Math.log(e)+Math.sqrt(a*a+d*d)/e};l.Zf=function(a,c){var d=new Oj(c||a.type,this,a);this.$f(d)};l.$f=function(a){if(this.c){this.T=a.coordinate;a.frameState=this.c;var c=this.f.a,d;if(!1!==C(this,a))for(d=c.length-1;0<=d;d--){var e=c[d];if(e.c()&&!e.handleEvent(a))break}}};
-l.Rj=function(){var a=this.c,c=this.da;if(!c.ya()){var d=16,e=d,f=0;a&&(f=a.viewHints,f[0]&&(d=this.nb?8:0,e=2),f[1]&&(d=this.lc?8:0,e=2),f=Lb(a.wantedTiles));d*=f;e*=f;c.f<d&&(Lk(c),Nk(c,d,e))}c=this.ra;d=0;for(e=c.length;d<e;++d)c[d](this,a);c.length=0};l.Tj=function(){this.render()};l.Vj=function(){var a=this.uc();Ji(this.v);a?(a.appendChild(this.a),Di(this.v,this.$?this.$:a),this.l||(this.l=B(this.Na,"resize",this.Fc,!1,this))):(Tg(this.a),this.l&&($c(this.l),this.l=null));this.Fc()};l.Ik=function(){this.render()};
-l.Xj=function(){this.render()};l.Wj=function(){this.I&&($c(this.I),this.I=null);var a=this.Z();a&&(this.I=B(a,"propertychange",this.Xj,!1,this));this.render()};l.Cj=function(){this.render()};l.Dj=function(){this.render()};l.Bj=function(){this.u&&(this.u.forEach($c),this.u=null);var a=this.Rb();a&&(this.u=[B(a,"propertychange",this.Dj,!1,this),B(a,"change",this.Cj,!1,this)]);this.render()};l.In=function(){var a=this.A;li(a);a.f()};l.render=function(){null!=this.A.ha||this.A.start()};l.Bn=function(a){return this.g.remove(a)};
-l.Cn=function(a){return this.f.remove(a)};l.En=function(a){return this.Rb().xc().remove(a)};l.Fn=function(a){return this.j.remove(a)};
-l.Hn=function(a){var c,d,e,f=this.Ea(),g=this.Z(),h=null;if(void 0!==f&&0<f[0]&&0<f[1]&&g&&Tf(g)){var h=g.c.slice(),k=this.Rb().Te(),m={};c=0;for(d=k.length;c<d;++c)m[v(k[c].layer)]=k[c];e=Sf(g);h={animate:!1,attributions:{},coordinateToPixelMatrix:this.lb,extent:null,focus:this.T?this.T:e.center,index:this.mb++,layerStates:m,layerStatesArray:k,logos:Vb(this.mc),pixelRatio:this.Ic,pixelToCoordinateMatrix:this.ze,postRenderFunctions:[],size:f,skippedFeatureUids:this.X,tileQueue:this.da,time:a,usedTiles:{},
-viewState:e,viewHints:h,wantedTiles:{}}}if(h){a=this.N;c=f=0;for(d=a.length;c<d;++c)g=a[c],g(this,h)&&(a[f++]=g);a.length=f;h.extent=he(e.center,e.resolution,e.rotation,h.size)}this.c=h;this.i.pe(h);h&&(h.animate&&this.render(),Array.prototype.push.apply(this.ra,h.postRenderFunctions),0!==this.N.length||h.viewHints[0]||h.viewHints[1]||Zd(h.extent,this.wa)||(C(this,new uh("moveend",this,h)),Sd(h.extent,this.wa)));C(this,new uh("postrender",this,h));pi(this.Rj,this)};
-l.ih=function(a){this.set("layergroup",a)};l.vf=function(a){this.set("size",a)};l.Jk=function(a){this.set("target",a)};l.Yn=function(a){this.set("view",a)};l.nh=function(a){a=v(a).toString();this.X[a]=!0;this.render()};
-l.Fc=function(){var a=this.uc();if(a){var c=Hg(a),d=$b&&a.currentStyle;d&&Xg(Fg(c))&&"auto"!=d.width&&"auto"!=d.height&&!d.boxSizing?(c=oh(a,d.width,"width","pixelWidth"),a=oh(a,d.height,"height","pixelHeight"),a=new Eg(c,a)):(d=new Eg(a.offsetWidth,a.offsetHeight),c=qh(a,"padding"),a=th(a),a=new Eg(d.width-a.left-c.left-c.right-a.right,d.height-a.top-c.top-c.bottom-a.bottom));this.vf([a.width,a.height])}else this.vf(void 0)};l.qh=function(a){a=v(a).toString();delete this.X[a];this.render()};
-function er(a){var c=null;void 0!==a.keyboardEventTarget&&(c=ia(a.keyboardEventTarget)?document.getElementById(a.keyboardEventTarget):a.keyboardEventTarget);var d={},e={};if(void 0===a.logo||"boolean"==typeof a.logo&&a.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=a.logo;ia(f)?e[f]="":la(f)&&(e[f.src]=f.href)}f=a.layers instanceof bm?a.layers:new bm({layers:a.layers});d.layergroup=f;d.target=a.target;d.view=void 0!==a.view?a.view:new Of;var f=Ck,g;void 0!==a.renderer?ga(a.renderer)?g=a.renderer:ia(a.renderer)&&(g=[a.renderer]):g=dr;var h,k;h=0;for(k=g.length;h<k;++h){var m=g[h];if("canvas"==m){if(aj){f=Wp;break}}else if("dom"==m){f=dq;break}else if("webgl"==m&&Yi){f=ar;break}}var n;void 0!==a.controls?n=ga(a.controls)?new tg(a.controls.slice()):
-a.controls:n=bi();var p;void 0!==a.interactions?p=ga(a.interactions)?new tg(a.interactions.slice()):a.interactions:p=am();a=void 0!==a.overlays?ga(a.overlays)?new tg(a.overlays.slice()):a.overlays:new tg;return{controls:n,interactions:p,keyboardEventTarget:c,logos:e,overlays:a,Jn:f,values:d}}im();function fr(a){jd.call(this);this.i=void 0!==a.insertFirst?a.insertFirst:!0;this.l=void 0!==a.stopEvent?a.stopEvent:!0;this.c=Mg("DIV",{"class":"ol-overlay-container"});this.c.style.position="absolute";this.autoPan=void 0!==a.autoPan?a.autoPan:!1;this.g=void 0!==a.autoPanAnimation?a.autoPanAnimation:{};this.j=void 0!==a.autoPanMargin?a.autoPanMargin:20;this.a={sd:"",Sd:"",qe:"",re:"",visible:!0};this.f=null;B(this,ld("element"),this.xj,!1,this);B(this,ld("map"),this.Ij,!1,this);B(this,ld("offset"),
-this.Nj,!1,this);B(this,ld("position"),this.Pj,!1,this);B(this,ld("positioning"),this.Qj,!1,this);void 0!==a.element&&this.fh(a.element);this.kh(void 0!==a.offset?a.offset:[0,0]);this.lh(void 0!==a.positioning?a.positioning:"top-left");void 0!==a.position&&this.cf(a.position)}w(fr,jd);l=fr.prototype;l.Vd=function(){return this.get("element")};l.Wd=function(){return this.get("map")};l.Wf=function(){return this.get("offset")};l.og=function(){return this.get("position")};l.Xf=function(){return this.get("positioning")};
-l.xj=function(){Rg(this.c);var a=this.Vd();a&&Qg(this.c,a)};l.Ij=function(){this.f&&(Tg(this.c),$c(this.f),this.f=null);var a=this.Wd();a&&(this.f=B(a,"postrender",this.render,!1,this),gr(this),a=this.l?a.B:a.fa,this.i?Sg(a,this.c,0):Qg(a,this.c))};l.render=function(){gr(this)};l.Nj=function(){gr(this)};
-l.Pj=function(){gr(this);if(void 0!==this.get("position")&&this.autoPan){var a=this.Wd();if(void 0!==a&&a.uc()){var c=hr(a.uc(),a.Ea()),d=this.Vd(),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=hr(d,[e,f]),d=this.j;Wd(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=a.Z().Fa(),e=a.Ba(d),c=[e[0]+c[0],e[1]+c[1]],this.g&&(this.g.source=d,a.Aa($f(this.g))),a.Z().Ra(a.xa(c)))}}};l.Qj=function(){gr(this)};l.fh=function(a){this.set("element",a)};l.setMap=function(a){this.set("map",a)};l.kh=function(a){this.set("offset",a)};l.cf=function(a){this.set("position",a)};
-function hr(a,c){var d=Hg(a),e=new Dg(0,0),f;f=d?Hg(d):document;f=!$b||9<=nc||Xg(Fg(f))?f.documentElement:f.body;a!=f&&(f=jh(a),d=Yg(Fg(d)),e.x=f.left+d.x,e.y=f.top+d.y);return[e.x,e.y,e.x+c[0],e.y+c[1]]}l.lh=function(a){this.set("positioning",a)};function ir(a,c){a.a.visible!==c&&(nh(a.c,c),a.a.visible=c)}
-function gr(a){var c=a.Wd(),d=a.og();if(void 0!==c&&c.c&&void 0!==d){var d=c.Ba(d),e=c.Ea(),c=a.c.style,f=a.Wf(),g=a.Xf(),h=f[0],f=f[1];if("bottom-right"==g||"center-right"==g||"top-right"==g)""!==a.a.Sd&&(a.a.Sd=c.left=""),h=Math.round(e[0]-d[0]-h)+"px",a.a.qe!=h&&(a.a.qe=c.right=h);else{""!==a.a.qe&&(a.a.qe=c.right="");if("bottom-center"==g||"center-center"==g||"top-center"==g)h-=lh(a.c).width/2;h=Math.round(d[0]+h)+"px";a.a.Sd!=h&&(a.a.Sd=c.left=h)}if("bottom-left"==g||"bottom-center"==g||"bottom-right"==
-g)""!==a.a.re&&(a.a.re=c.top=""),d=Math.round(e[1]-d[1]-f)+"px",a.a.sd!=d&&(a.a.sd=c.bottom=d);else{""!==a.a.sd&&(a.a.sd=c.bottom="");if("center-left"==g||"center-center"==g||"center-right"==g)f-=lh(a.c).height/2;d=Math.round(d[1]+f)+"px";a.a.re!=d&&(a.a.re=c.top=d)}ir(a,!0)}else ir(a,!1)};function jr(a){a=a?a:{};this.j=void 0!==a.collapsed?a.collapsed:!0;this.i=void 0!==a.collapsible?a.collapsible:!0;this.i||(this.j=!1);var c=a.className?a.className:"ol-overviewmap",d=a.tipLabel?a.tipLabel:"Overview map",e=a.collapseLabel?a.collapseLabel:"\u00ab";this.v=ia(e)?Mg("SPAN",{},e):e;e=a.label?a.label:"\u00bb";this.u=ia(e)?Mg("SPAN",{},e):e;d=Mg("BUTTON",{type:"button",title:d},this.i&&!this.j?this.v:this.u);B(d,"click",this.Tk,!1,this);var e=Mg("DIV","ol-overviewmap-map"),f=this.f=new W({controls:new tg,
-interactions:new tg,target:e,view:a.view});a.layers&&a.layers.forEach(function(a){f.Kf(a)},this);var g=Mg("DIV","ol-overviewmap-box");this.l=new fr({position:[0,0],positioning:"bottom-left",element:g});this.f.Lf(this.l);c=Mg("DIV",c+" ol-unselectable ol-control"+(this.j&&this.i?" ol-collapsed":"")+(this.i?"":" ol-uncollapsible"),e,d);vh.call(this,{element:c,render:a.render?a.render:kr,target:a.target})}w(jr,vh);l=jr.prototype;
-l.setMap=function(a){var c=this.a;a!==c&&(c&&(c=c.Z())&&Zc(c,ld("rotation"),this.Md,!1,this),jr.ba.setMap.call(this,a),a&&(this.A.push(B(a,"propertychange",this.Jj,!1,this)),0===this.f.ng().Gb()&&this.f.ih(a.Rb()),a=a.Z()))&&(B(a,ld("rotation"),this.Md,!1,this),Tf(a)&&(this.f.Fc(),lr(this)))};l.Jj=function(a){"view"===a.key&&((a=a.oldValue)&&Zc(a,ld("rotation"),this.Md,!1,this),a=this.a.Z(),B(a,ld("rotation"),this.Md,!1,this))};l.Md=function(){this.f.Z().Xd(this.a.Z().va())};
-function kr(){var a=this.a,c=this.f;if(a.c&&c.c){var d=a.Ea(),a=a.Z().Kc(d),e=c.Ea(),d=c.Z().Kc(e),f=c.Ba(fe(a)),c=c.Ba(de(a)),c=new Eg(Math.abs(f[0]-c[0]),Math.abs(f[1]-c[1])),f=e[0],e=e[1];c.width<.1*f||c.height<.1*e||c.width>.75*f||c.height>.75*e?lr(this):Wd(d,a)||(a=this.f,d=this.a.Z(),a.Z().Ra(d.Fa()))}mr(this)}function lr(a){var c=a.a;a=a.f;var d=c.Ea(),c=c.Z().Kc(d),d=a.Ea();a=a.Z();ne(c,1/(.1*Math.pow(2,Math.log(7.5)/Math.LN2/2)));a.Ke(c,d)}
-function mr(a){var c=a.a,d=a.f;if(c.c&&d.c){var e=c.Ea(),f=c.Z(),g=d.Z();d.Ea();var c=f.va(),h=a.l,d=a.l.Vd(),f=f.Kc(e),e=g.aa(),g=ce(f),f=ee(f),k;if(a=a.a.Z().Fa())k=[g[0]-a[0],g[1]-a[1]],vd(k,c),qd(k,a);h.cf(k);d&&(k=new Eg(Math.abs((g[0]-f[0])/e),Math.abs((f[1]-g[1])/e)),c=Xg(Fg(Hg(d))),!$b||lc("10")||c&&lc("8")?(d=d.style,bc?d.MozBoxSizing="border-box":cc?d.WebkitBoxSizing="border-box":d.boxSizing="border-box",d.width=Math.max(k.width,0)+"px",d.height=Math.max(k.height,0)+"px"):(a=d.style,c?(c=
-qh(d,"padding"),d=th(d),a.pixelWidth=k.width-d.left-c.left-c.right-d.right,a.pixelHeight=k.height-d.top-c.top-c.bottom-d.bottom):(a.pixelWidth=k.width,a.pixelHeight=k.height)))}}l.Tk=function(a){a.preventDefault();nr(this)};function nr(a){ch(a.element,"ol-collapsed");a.j?Ug(a.v,a.u):Ug(a.u,a.v);a.j=!a.j;var c=a.f;a.j||c.c||(c.Fc(),lr(a),Yc(c,"postrender",function(){mr(this)},!1,a))}l.Sk=function(){return this.i};
-l.Vk=function(a){this.i!==a&&(this.i=a,ch(this.element,"ol-uncollapsible"),!a&&this.j&&nr(this))};l.Uk=function(a){this.i&&this.j!==a&&nr(this)};l.Rk=function(){return this.j};function or(a){a=a?a:{};var c=a.className?a.className:"ol-scale-line";this.l=Mg("DIV",c+"-inner");this.i=Mg("DIV",c+" ol-unselectable",this.l);this.u=null;this.v=void 0!==a.minWidth?a.minWidth:64;this.f=!1;this.I=void 0;this.B="";this.j=null;vh.call(this,{element:this.i,render:a.render?a.render:pr,target:a.target});B(this,ld("units"),this.$,!1,this);this.X(a.units||"metric")}w(or,vh);var qr=[1,2,5];or.prototype.N=function(){return this.get("units")};
-function pr(a){(a=a.frameState)?this.u=a.viewState:this.u=null;rr(this)}or.prototype.$=function(){rr(this)};or.prototype.X=function(a){this.set("units",a)};
-function rr(a){var c=a.u;if(c){var d=c.center,e=c.projection,c=e.getPointResolution(c.resolution,d),f=e.a,g=a.N();"degrees"!=f||"metric"!=g&&"imperial"!=g&&"us"!=g&&"nautical"!=g?"degrees"!=f&&"degrees"==g?(a.j||(a.j=Ge(e,Ce("EPSG:4326"))),d=Math.cos(yb(a.j(d)[1])),e=ye.radius,e/=ze[f],c*=180/(Math.PI*d*e)):a.j=null:(a.j=null,d=Math.cos(yb(d[1])),c*=Math.PI*d*ye.radius/180);d=a.v*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(a.v*c)/Math.log(10));;){e=qr[d%3]*Math.pow(10,Math.floor(d/3));g=Math.round(e/c);if(isNaN(g)){nh(a.i,!1);a.f=!1;return}if(g>=a.v)break;++d}c=e+" "+f;a.B!=c&&(a.l.innerHTML=c,a.B=c);a.I!=g&&(a.l.style.width=g+"px",a.I=g);a.f||(nh(a.i,!0),a.f=
-!0)}else a.f&&(nh(a.i,!1),a.f=!1)};function sr(a){rc.call(this);this.a=a;this.b={}}w(sr,rc);var tr=[];sr.prototype.Pa=function(a,c,d,e){ga(c)||(c&&(tr[0]=c.toString()),c=tr);for(var f=0;f<c.length;f++){var g=B(a,c[f],d||this.handleEvent,e||!1,this.a||this);if(!g)break;this.b[g.key]=g}return this};
-sr.prototype.wf=function(a,c,d,e,f){if(ga(c))for(var g=0;g<c.length;g++)this.wf(a,c[g],d,e,f);else d=d||this.handleEvent,f=f||this.a||this,d=Sc(d),e=!!e,c=Gc(a)?Nc(a.fb,String(c),d,e,f):a?(a=Uc(a))?Nc(a,c,d,e,f):null:null,c&&($c(c),delete this.b[c.key]);return this};function ur(a){Jb(a.b,function(a,d){this.b.hasOwnProperty(d)&&$c(a)},a);a.b={}}sr.prototype.Y=function(){sr.ba.Y.call(this);ur(this)};sr.prototype.handleEvent=function(){throw Error("EventHandler.handleEvent not implemented");};function vr(a,c,d){dd.call(this);this.target=a;this.handle=c||a;this.b=d||new eh(NaN,NaN,NaN,NaN);this.j=Hg(a);this.a=new sr(this);uc(this,this.a);this.g=this.f=this.C=this.l=this.screenY=this.screenX=this.clientY=this.clientX=0;this.c=!1;B(this.handle,["touchstart","mousedown"],this.oh,!1,this)}w(vr,dd);var wr=$b||bc&&lc("1.9.3");l=vr.prototype;
-l.Y=function(){vr.ba.Y.call(this);Zc(this.handle,["touchstart","mousedown"],this.oh,!1,this);ur(this.a);wr&&this.j.releaseCapture();this.handle=this.target=null};
-l.oh=function(a){var c="mousedown"==a.type;if(this.c||c&&!Dc(a))C(this,"earlycancel");else if(C(this,new xr("start",this,a.clientX,a.clientY))){this.c=!0;a.preventDefault();var c=this.j,d=c.documentElement,e=!wr;this.a.Pa(c,["touchmove","mousemove"],this.Mj,e);this.a.Pa(c,["touchend","mouseup"],this.yd,e);wr?(d.setCapture(!1),this.a.Pa(d,"losecapture",this.yd)):this.a.Pa(c?c.parentWindow||c.defaultView:window,"blur",this.yd);this.A&&this.a.Pa(this.A,"scroll",this.Tm,e);this.clientX=this.l=a.clientX;
-this.clientY=this.C=a.clientY;this.screenX=a.screenX;this.screenY=a.screenY;this.f=this.target.offsetLeft;this.g=this.target.offsetTop;this.i=Yg(Fg(this.j))}};l.yd=function(a){ur(this.a);wr&&this.j.releaseCapture();this.c?(this.c=!1,C(this,new xr("end",this,a.clientX,a.clientY,0,yr(this,this.f),zr(this,this.g)))):C(this,"earlycancel")};
-l.Mj=function(a){var c=1*(a.clientX-this.clientX),d=a.clientY-this.clientY;this.clientX=a.clientX;this.clientY=a.clientY;this.screenX=a.screenX;this.screenY=a.screenY;if(!this.c){var e=this.l-this.clientX,f=this.C-this.clientY;if(0<e*e+f*f)if(C(this,new xr("start",this,a.clientX,a.clientY)))this.c=!0;else{this.ca||this.yd(a);return}}d=Ar(this,c,d);c=d.x;d=d.y;this.c&&C(this,new xr("beforedrag",this,a.clientX,a.clientY,0,c,d))&&(Br(this,a,c,d),a.preventDefault())};
-function Ar(a,c,d){var e=Yg(Fg(a.j));c+=e.x-a.i.x;d+=e.y-a.i.y;a.i=e;a.f+=c;a.g+=d;return new Dg(yr(a,a.f),zr(a,a.g))}l.Tm=function(a){var c=Ar(this,0,0);a.clientX=this.clientX;a.clientY=this.clientY;Br(this,a,c.x,c.y)};function Br(a,c,d,e){a.target.style.left=d+"px";a.target.style.top=e+"px";C(a,new xr("drag",a,c.clientX,c.clientY,0,d,e))}function yr(a,c){var d=a.b,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 zr(a,c){var d=a.b,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 xr(a,c,d,e,f,g,h){wc.call(this,a);this.clientX=d;this.clientY=e;this.left=ca(g)?g:c.f;this.top=ca(h)?h:c.g}w(xr,wc);function Cr(a){a=a?a:{};this.j=void 0;this.i=Dr;this.l=null;this.u=!1;this.v=a.duration?a.duration:200;var c=a.className?a.className:"ol-zoomslider",d=Mg("DIV",[c+"-thumb","ol-unselectable"]),c=Mg("DIV",[c,"ol-unselectable","ol-control"],d);this.f=new vr(d);uc(this,this.f);B(this.f,"start",this.wj,!1,this);B(this.f,"drag",this.uj,!1,this);B(this.f,"end",this.vj,!1,this);B(c,"click",this.tj,!1,this);B(d,"click",xc);vh.call(this,{element:c,render:a.render?a.render:Er})}w(Cr,vh);var Dr=0;l=Cr.prototype;
-l.setMap=function(a){Cr.ba.setMap.call(this,a);a&&a.render()};
-function Er(a){if(a.frameState){if(!this.u){var c=this.element,d=lh(c),e=Vg(c),c=qh(e,"margin"),f=new Eg(e.offsetWidth,e.offsetHeight),e=f.width+c.right+c.left,c=f.height+c.top+c.bottom;this.l=[e,c];e=d.width-e;c=d.height-c;d.width>d.height?(this.i=1,d=new eh(0,0,e,0)):(this.i=Dr,d=new eh(0,0,0,c));this.f.b=d||new eh(NaN,NaN,NaN,NaN);this.u=!0}a=a.frameState.viewState.resolution;a!==this.j&&(this.j=a,a=1-Rf(this.a.Z())(a),d=this.f,c=Vg(this.element),1==this.i?hh(c,d.b.left+d.b.width*a):hh(c,d.b.left,
-d.b.top+d.b.height*a))}}l.tj=function(a){var c=this.a,d=c.Z(),e=d.aa();c.Aa(bg({resolution:e,duration:this.v,easing:Wf}));a=Fr(this,Gr(this,a.offsetX-this.l[0]/2,a.offsetY-this.l[1]/2));d.wb(d.constrainResolution(a))};l.wj=function(){Uf(this.a.Z(),1)};l.uj=function(a){this.j=Fr(this,Gr(this,a.left,a.top));this.a.Z().wb(this.j)};l.vj=function(){var a=this.a,c=a.Z();Uf(c,-1);a.Aa(bg({resolution:this.j,duration:this.v,easing:Wf}));a=c.constrainResolution(this.j);c.wb(a)};
-function Gr(a,c,d){var e=a.f.b;return Qa(1===a.i?(c-e.left)/e.width:(d-e.top)/e.height,0,1)}function Fr(a,c){return Qf(a.a.Z())(1-c)};function Hr(a){a=a?a:{};this.f=a.extent?a.extent:null;var c=a.className?a.className:"ol-zoom-extent",d=Mg("BUTTON",{type:"button",title:a.tipLabel?a.tipLabel:"Fit to extent"},a.label?a.label:"E");B(d,"click",this.j,!1,this);c=Mg("DIV",c+" ol-unselectable ol-control",d);vh.call(this,{element:c,target:a.target})}w(Hr,vh);Hr.prototype.j=function(a){a.preventDefault();var c=this.a;a=c.Z();var d=this.f?this.f:a.g.R(),c=c.Ea();a.Ke(d,c)};function Ir(a){jd.call(this);a=a?a:{};this.a=null;B(this,ld("tracking"),this.vk,!1,this);this.$e(void 0!==a.tracking?a.tracking:!1)}w(Ir,jd);l=Ir.prototype;l.Y=function(){this.$e(!1);Ir.ba.Y.call(this)};
-l.Um=function(a){a=a.b;if(null!==a.alpha){var c=yb(a.alpha);this.set("alpha",c);"boolean"==typeof a.absolute&&a.absolute?this.set("heading",c):ja(a.webkitCompassHeading)&&-1!=a.webkitCompassAccuracy&&this.set("heading",yb(a.webkitCompassHeading))}null!==a.beta&&this.set("beta",yb(a.beta));null!==a.gamma&&this.set("gamma",yb(a.gamma));this.s()};l.Ci=function(){return this.get("alpha")};l.Fi=function(){return this.get("beta")};l.Ni=function(){return this.get("gamma")};l.uk=function(){return this.get("heading")};
-l.jg=function(){return this.get("tracking")};l.vk=function(){if(bj){var a=this.jg();a&&!this.a?this.a=B(ba,"deviceorientation",this.Um,!1,this):!a&&this.a&&($c(this.a),this.a=null)}};l.$e=function(a){this.set("tracking",a)};function Jr(){this.defaultDataProjection=null}function Kr(a,c,d){var e;d&&(e={dataProjection:d.dataProjection?d.dataProjection:a.Ha(c),featureProjection:d.featureProjection});return Lr(a,e)}function Lr(a,c){var d;c&&(d={featureProjection:c.featureProjection,dataProjection:c.dataProjection?c.dataProjection:a.defaultDataProjection,rightHanded:c.rightHanded});return d}
-function Mr(a,c,d){var e=d?Ce(d.featureProjection):null;d=d?Ce(d.dataProjection):null;return e&&d&&!Ue(e,d)?a instanceof Ze?(c?a.clone():a).Sa(c?e:d,c?d:e):Ye(c?a.slice():a,c?e:d,c?d:e):a};function Nr(){this.defaultDataProjection=null}w(Nr,Jr);function Or(a){return la(a)?a:ia(a)?(a=io(a))?a:null:null}l=Nr.prototype;l.W=function(){return"json"};l.vb=function(a,c){return this.Ac(Or(a),Kr(this,a,c))};l.sa=function(a,c){return this.of(Or(a),Kr(this,a,c))};l.Bc=function(a,c){return this.Rg(Or(a),Kr(this,a,c))};l.Ha=function(a){return this.Xg(Or(a))};l.kd=function(a,c){return jo(this.Gc(a,c))};l.xb=function(a,c){return jo(this.ue(a,c))};l.Hc=function(a,c){return jo(this.we(a,c))};function Pr(a){a=a?a:{};this.defaultDataProjection=null;this.b=a.geometryName}w(Pr,Nr);
-function Qr(a,c){if(!a)return null;var d;if(ja(a.x)&&ja(a.y))d="Point";else if(a.points)d="MultiPoint";else if(a.paths)d=1===a.paths.length?"LineString":"MultiLineString";else if(a.rings){var e=a.rings,f=Rr(a),g=[];d=[];var h,k;h=0;for(k=e.length;h<k;++h){var m=rb(e[h]);Df(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(Wd((new tf(g[h][0])).R(),(new tf(e)).R())){g[h].push(e);f=!0;break}f||g.push([e.reverse()])}a=Vb(a);1===g.length?(d=
-"Polygon",a.rings=g[0]):(d="MultiPolygon",a.rings=g)}return Mr((0,Sr[d])(a),!1,c)}function Rr(a){var c="XY";!0===a.hasZ&&!0===a.hasM?c="XYZM":!0===a.hasZ?c="XYZ":!0===a.hasM&&(c="XYM");return c}function Tr(a){a=a.a;return{hasZ:"XYZ"===a||"XYZM"===a,hasM:"XYM"===a||"XYZM"===a}}
-var Sr={Point:function(a){return void 0!==a.m&&void 0!==a.z?new D([a.x,a.y,a.z,a.m],"XYZM"):void 0!==a.z?new D([a.x,a.y,a.z],"XYZ"):void 0!==a.m?new D([a.x,a.y,a.m],"XYM"):new D([a.x,a.y])},LineString:function(a){return new L(a.paths[0],Rr(a))},Polygon:function(a){return new E(a.rings,Rr(a))},MultiPoint:function(a){return new kn(a.points,Rr(a))},MultiLineString:function(a){return new O(a.paths,Rr(a))},MultiPolygon:function(a){return new P(a.rings,Rr(a))}},Ur={Point:function(a){var c=a.U();a=a.a;if("XYZ"===
-a)return{x:c[0],y:c[1],z:c[2]};if("XYM"===a)return{x:c[0],y:c[1],m:c[2]};if("XYZM"===a)return{x:c[0],y:c[1],z:c[2],m:c[3]};if("XY"===a)return{x:c[0],y:c[1]}},LineString:function(a){var c=Tr(a);return{hasZ:c.hasZ,hasM:c.hasM,paths:[a.U()]}},Polygon:function(a){var c=Tr(a);return{hasZ:c.hasZ,hasM:c.hasM,rings:a.U(!1)}},MultiPoint:function(a){var c=Tr(a);return{hasZ:c.hasZ,hasM:c.hasM,points:a.U()}},MultiLineString:function(a){var c=Tr(a);return{hasZ:c.hasZ,hasM:c.hasM,paths:a.U()}},MultiPolygon:function(a){var c=
-Tr(a);a=a.U(!1);for(var d=[],e=0;e<a.length;e++)for(var f=a[e].length-1;0<=f;f--)d.push(a[e][f]);return{hasZ:c.hasZ,hasM:c.hasM,rings:d}}};l=Pr.prototype;l.Ac=function(a,c){var d=Qr(a.geometry,c),e=new Q;this.b&&e.Dc(this.b);e.za(d);c&&c.Ve&&a.attributes[c.Ve]&&e.Mb(a.attributes[c.Ve]);a.attributes&&e.H(a.attributes);return e};
-l.of=function(a,c){var d=c?c:{};if(a.features){var e=[],f=a.features,g,h;d.Ve=a.objectIdFieldName;g=0;for(h=f.length;g<h;++g)e.push(this.Ac(f[g],d));return e}return[this.Ac(a,d)]};l.Rg=function(a,c){return Qr(a,c)};l.Xg=function(a){return a.spatialReference&&a.spatialReference.wkid?Ce("EPSG:"+a.spatialReference.wkid):null};function Vr(a,c){return(0,Ur[a.W()])(Mr(a,!0,c),c)}l.we=function(a,c){return Vr(a,Lr(this,c))};
-l.Gc=function(a,c){c=Lr(this,c);var d={},e=a.V();e&&(d.geometry=Vr(e,c));e=a.P();delete e[a.a];d.attributes=Rb(e)?{}:e;c&&c.featureProjection&&(d.spatialReference={wkid:Ce(c.featureProjection).b.split(":").pop()});return d};l.ue=function(a,c){c=Lr(this,c);var d=[],e,f;e=0;for(f=a.length;e<f;++e)d.push(this.Gc(a[e],c));return{features:d}};function Wr(a){a=a?a:{};this.defaultDataProjection=null;this.defaultDataProjection=Ce(a.defaultDataProjection?a.defaultDataProjection:"EPSG:4326");this.b=a.geometryName}w(Wr,Nr);function Xr(a,c){return a?Mr((0,Yr[a.type])(a),!1,c):null}function Zr(a,c){return(0,$r[a.W()])(Mr(a,!0,c),c)}
-var Yr={Point:function(a){return new D(a.coordinates)},LineString:function(a){return new L(a.coordinates)},Polygon:function(a){return new E(a.coordinates)},MultiPoint:function(a){return new kn(a.coordinates)},MultiLineString:function(a){return new O(a.coordinates)},MultiPolygon:function(a){return new P(a.coordinates)},GeometryCollection:function(a,c){var d=a.geometries.map(function(a){return Xr(a,c)});return new $m(d)}},$r={Point:function(a){return{type:"Point",coordinates:a.U()}},LineString:function(a){return{type:"LineString",
-coordinates:a.U()}},Polygon:function(a,c){var d;c&&(d=c.rightHanded);return{type:"Polygon",coordinates:a.U(d)}},MultiPoint:function(a){return{type:"MultiPoint",coordinates:a.U()}},MultiLineString:function(a){return{type:"MultiLineString",coordinates:a.U()}},MultiPolygon:function(a,c){var d;c&&(d=c.rightHanded);return{type:"MultiPolygon",coordinates:a.U(d)}},GeometryCollection:function(a,c){return{type:"GeometryCollection",geometries:a.f.map(function(a){return Zr(a,c)})}},Circle:function(){return{type:"GeometryCollection",
-geometries:[]}}};l=Wr.prototype;l.Ac=function(a,c){var d=Xr(a.geometry,c),e=new Q;this.b&&e.Dc(this.b);e.za(d);a.id&&e.Mb(a.id);a.properties&&e.H(a.properties);return e};l.of=function(a,c){if("Feature"==a.type)return[this.Ac(a,c)];if("FeatureCollection"==a.type){var d=[],e=a.features,f,g;f=0;for(g=e.length;f<g;++f)d.push(this.Ac(e[f],c));return d}return[]};l.Rg=function(a,c){return Xr(a,c)};
-l.Xg=function(a){return(a=a.crs)?"name"==a.type?Ce(a.properties.name):"EPSG"==a.type?Ce("EPSG:"+a.properties.code):null:this.defaultDataProjection};l.Gc=function(a,c){c=Lr(this,c);var d={type:"Feature"},e=a.ha;e&&(d.id=e);e=a.V();d.geometry=e?Zr(e,c):null;e=a.P();delete e[a.a];d.properties=Rb(e)?null:e;return d};l.ue=function(a,c){c=Lr(this,c);var d=[],e,f;e=0;for(f=a.length;e<f;++e)d.push(this.Gc(a[e],c));return{type:"FeatureCollection",features:d}};l.we=function(a,c){return Zr(a,Lr(this,c))};function as(){this.defaultDataProjection=null}w(as,Jr);l=as.prototype;l.W=function(){return"xml"};l.vb=function(a,c){if(ap(a))return bs(this,a,c);if(dp(a))return this.Pg(a,c);if(ia(a)){var d=np(a);return bs(this,d,c)}return null};function bs(a,c,d){a=cs(a,c,d);return 0<a.length?a[0]:null}l.sa=function(a,c){if(ap(a))return cs(this,a,c);if(dp(a))return this.Lb(a,c);if(ia(a)){var d=np(a);return cs(this,d,c)}return[]};
-function cs(a,c,d){var e=[];for(c=c.firstChild;c;c=c.nextSibling)1==c.nodeType&&hb(e,a.Lb(c,d));return e}l.Bc=function(a,c){if(ap(a))return this.A(a,c);if(dp(a)){var d=this.le(a,[Kr(this,a,c?c:{})]);return d?d:null}return ia(a)?(d=np(a),this.A(d,c)):null};l.Ha=function(a){return ap(a)?this.sf(a):dp(a)?this.oe(a):ia(a)?(a=np(a),this.sf(a)):null};l.sf=function(){return this.defaultDataProjection};l.oe=function(){return this.defaultDataProjection};l.kd=function(a,c){var d=this.u(a,c);return No(d)};
-l.xb=function(a,c){var d=this.a(a,c);return No(d)};l.Hc=function(a,c){var d=this.C(a,c);return No(d)};function ds(a){a=a?a:{};this.featureType=a.featureType;this.featureNS=a.featureNS;this.srsName=a.srsName;this.schemaLocation="";this.b={};this.b["http://www.opengis.net/gml"]={featureMember:qp(ds.prototype.fd),featureMembers:qp(ds.prototype.fd)};this.defaultDataProjection=null}w(ds,as);l=ds.prototype;
-l.fd=function(a,c){var d=Yo(a),e;if("FeatureCollection"==d)"http://www.opengis.net/wfs"===a.namespaceURI?e=U([],this.b,a,c,this):e=U(null,this.b,a,c,this);else if("featureMembers"==d||"featureMember"==d){var f=c[0],g=f.featureType;e=f.featureNS;var h,k;if(!g&&a.childNodes){g=[];e={};h=0;for(k=a.childNodes.length;h<k;++h){var m=a.childNodes[h];if(1===m.nodeType){var n=m.nodeName.split(":").pop();if(-1===g.indexOf(n)){var p;Pb(e,m.namespaceURI)?p=Qb(e,function(a){return a===m.namespaceURI}):(p="p"+
-Lb(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?pp(this.nf,this):qp(this.nf,this));f[e[q]]=n}e=U([],f,a,c)}e||(e=[]);return e};l.le=function(a,c){var d=c[0];d.srsName=a.firstElementChild.getAttribute("srsName");var e=U(null,this.Bf,a,c,this);if(e)return Mr(e,!1,d)};
-l.nf=function(a,c){var d,e=a.getAttribute("fid")||hp(a,"http://www.opengis.net/gml","id"),f={},g;for(d=a.firstElementChild;d;d=d.nextElementSibling){var h=Yo(d);if(0===d.childNodes.length||1===d.childNodes.length&&(3===d.firstChild.nodeType||4===d.firstChild.nodeType)){var k=Uo(d,!1);/^[\s\xa0]*$/.test(k)&&(k=void 0);f[h]=k}else"boundedBy"!==h&&(g=h),f[h]=this.le(d,c)}d=new Q(f);g&&d.Dc(g);e&&d.Mb(e);return d};l.Wg=function(a,c){var d=this.ke(a,c);if(d){var e=new D(null);vf(e,"XYZ",d);return e}};
-l.Ug=function(a,c){var d=U([],this.Lh,a,c,this);if(d)return new kn(d)};l.Tg=function(a,c){var d=U([],this.Kh,a,c,this);if(d){var e=new O(null);jn(e,d);return e}};l.Vg=function(a,c){var d=U([],this.Mh,a,c,this);if(d){var e=new P(null);mn(e,d);return e}};l.Mg=function(a,c){xp(this.Ph,a,c,this)};l.fg=function(a,c){xp(this.Ih,a,c,this)};l.Ng=function(a,c){xp(this.Qh,a,c,this)};l.me=function(a,c){var d=this.ke(a,c);if(d){var e=new L(null);gn(e,"XYZ",d);return e}};
-l.mn=function(a,c){var d=U(null,this.md,a,c,this);if(d)return d};l.Sg=function(a,c){var d=this.ke(a,c);if(d){var e=new tf(null);uf(e,"XYZ",d);return e}};l.ne=function(a,c){var d=U([null],this.ye,a,c,this);if(d&&d[0]){var e=new E(null),f=d[0],g=[f.length],h,k;h=1;for(k=d.length;h<k;++h)hb(f,d[h]),g.push(f.length);Hf(e,"XYZ",f,g);return e}};l.ke=function(a,c){return U(null,this.md,a,c,this)};l.Lh=Object({"http://www.opengis.net/gml":{pointMember:pp(ds.prototype.Mg),pointMembers:pp(ds.prototype.Mg)}});
-l.Kh=Object({"http://www.opengis.net/gml":{lineStringMember:pp(ds.prototype.fg),lineStringMembers:pp(ds.prototype.fg)}});l.Mh=Object({"http://www.opengis.net/gml":{polygonMember:pp(ds.prototype.Ng),polygonMembers:pp(ds.prototype.Ng)}});l.Ph=Object({"http://www.opengis.net/gml":{Point:pp(ds.prototype.ke)}});l.Ih=Object({"http://www.opengis.net/gml":{LineString:pp(ds.prototype.me)}});l.Qh=Object({"http://www.opengis.net/gml":{Polygon:pp(ds.prototype.ne)}});l.od=Object({"http://www.opengis.net/gml":{LinearRing:qp(ds.prototype.mn)}});
-l.Lb=function(a,c){var d={featureType:this.featureType,featureNS:this.featureNS};c&&Yb(d,Kr(this,a,c));return this.fd(a,[d])};l.oe=function(a){return Ce(this.v?this.v:a.firstElementChild.getAttribute("srsName"))};function es(a){a=Uo(a,!1);return fs(a)}function fs(a){if(a=/^\s*(true|1)|(false|0)\s*$/.exec(a))return void 0!==a[1]||!1}
-function gs(a){a=Uo(a,!1);if(a=/^\s*(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?))\s*$/.exec(a)){var c=Date.UTC(parseInt(a[1],10),parseInt(a[2],10)-1,parseInt(a[3],10),parseInt(a[4],10),parseInt(a[5],10),parseInt(a[6],10))/1E3;if("Z"!=a[7]){var d="-"==a[8]?-1:1,c=c+60*d*parseInt(a[9],10);void 0!==a[10]&&(c+=3600*d*parseInt(a[10],10))}return c}}function hs(a){a=Uo(a,!1);return is(a)}
-function is(a){if(a=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(a))return parseFloat(a[1])}function js(a){a=Uo(a,!1);return ks(a)}function ks(a){if(a=/^\s*(\d+)\s*$/.exec(a))return parseInt(a[1],10)}function X(a){return Uo(a,!1).trim()}function ls(a,c){ms(a,c?"1":"0")}function ns(a,c){a.appendChild(Qo.createTextNode(c.toPrecision()))}function os(a,c){a.appendChild(Qo.createTextNode(c.toString()))}function ms(a,c){a.appendChild(Qo.createTextNode(c))};function ps(a){a=a?a:{};ds.call(this,a);this.b["http://www.opengis.net/gml"].featureMember=pp(ds.prototype.fd);this.schemaLocation=a.schemaLocation?a.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd"}w(ps,ds);l=ps.prototype;
-l.Qg=function(a,c){var d=Uo(a,!1).replace(/^\s*|\s*$/g,""),e=c[0].srsName,f=a.parentNode.getAttribute("srsDimension"),g="enu";e&&(g=Fe(Ce(e)));d=d.split(/[\s,]+/);e=2;a.getAttribute("srsDimension")?e=ks(a.getAttribute("srsDimension")):a.getAttribute("dimension")?e=ks(a.getAttribute("dimension")):f&&(e=ks(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.kn=function(a,c){var d=U([null],this.Eh,a,c,this);return Qd(d[1][0],d[1][1],d[1][3],d[1][4])};l.$j=function(a,c){var d=U(void 0,this.od,a,c,this);d&&c[c.length-1].push(d)};l.Vm=function(a,c){var d=U(void 0,this.od,a,c,this);d&&(c[c.length-1][0]=d)};l.md=Object({"http://www.opengis.net/gml":{coordinates:qp(ps.prototype.Qg)}});l.ye=Object({"http://www.opengis.net/gml":{innerBoundaryIs:ps.prototype.$j,outerBoundaryIs:ps.prototype.Vm}});l.Eh=Object({"http://www.opengis.net/gml":{coordinates:pp(ps.prototype.Qg)}});
-l.Bf=Object({"http://www.opengis.net/gml":{Point:qp(ds.prototype.Wg),MultiPoint:qp(ds.prototype.Ug),LineString:qp(ds.prototype.me),MultiLineString:qp(ds.prototype.Tg),LinearRing:qp(ds.prototype.Sg),Polygon:qp(ds.prototype.ne),MultiPolygon:qp(ds.prototype.Vg),Box:qp(ps.prototype.kn)}});function qs(a){a=a?a:{};ds.call(this,a);this.l=void 0!==a.surface?a.surface:!1;this.g=void 0!==a.curve?a.curve:!1;this.j=void 0!==a.multiCurve?a.multiCurve:!0;this.i=void 0!==a.multiSurface?a.multiSurface:!0;this.schemaLocation=a.schemaLocation?a.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd"}w(qs,ds);l=qs.prototype;l.qn=function(a,c){var d=U([],this.Jh,a,c,this);if(d){var e=new O(null);jn(e,d);return e}};
-l.rn=function(a,c){var d=U([],this.Nh,a,c,this);if(d){var e=new P(null);mn(e,d);return e}};l.Of=function(a,c){xp(this.Fh,a,c,this)};l.ph=function(a,c){xp(this.Uh,a,c,this)};l.vn=function(a,c){return U([null],this.Oh,a,c,this)};l.xn=function(a,c){return U([null],this.Th,a,c,this)};l.wn=function(a,c){return U([null],this.ye,a,c,this)};l.pn=function(a,c){return U([null],this.md,a,c,this)};l.bk=function(a,c){var d=U(void 0,this.od,a,c,this);d&&c[c.length-1].push(d)};
-l.vi=function(a,c){var d=U(void 0,this.od,a,c,this);d&&(c[c.length-1][0]=d)};l.Yg=function(a,c){var d=U([null],this.Vh,a,c,this);if(d&&d[0]){var e=new E(null),f=d[0],g=[f.length],h,k;h=1;for(k=d.length;h<k;++h)hb(f,d[h]),g.push(f.length);Hf(e,"XYZ",f,g);return e}};l.Og=function(a,c){var d=U([null],this.Gh,a,c,this);if(d){var e=new L(null);gn(e,"XYZ",d);return e}};l.ln=function(a,c){var d=U([null],this.Hh,a,c,this);return Qd(d[1][0],d[1][1],d[2][0],d[2][1])};
-l.nn=function(a,c){for(var d=Uo(a,!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=Fe(Ce(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.qf=function(a,c){var d=Uo(a,!1).replace(/^\s*|\s*$/g,""),e=c[0].srsName,f=a.parentNode.getAttribute("srsDimension"),g="enu";e&&(g=Fe(Ce(e)));d=d.split(/\s+/);e=2;a.getAttribute("srsDimension")?e=ks(a.getAttribute("srsDimension")):a.getAttribute("dimension")?e=ks(a.getAttribute("dimension")):f&&(e=ks(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:qp(qs.prototype.nn),posList:qp(qs.prototype.qf)}});l.ye=Object({"http://www.opengis.net/gml":{interior:qs.prototype.bk,exterior:qs.prototype.vi}});
-l.Bf=Object({"http://www.opengis.net/gml":{Point:qp(ds.prototype.Wg),MultiPoint:qp(ds.prototype.Ug),LineString:qp(ds.prototype.me),MultiLineString:qp(ds.prototype.Tg),LinearRing:qp(ds.prototype.Sg),Polygon:qp(ds.prototype.ne),MultiPolygon:qp(ds.prototype.Vg),Surface:qp(qs.prototype.Yg),MultiSurface:qp(qs.prototype.rn),Curve:qp(qs.prototype.Og),MultiCurve:qp(qs.prototype.qn),Envelope:qp(qs.prototype.ln)}});l.Jh=Object({"http://www.opengis.net/gml":{curveMember:pp(qs.prototype.Of),curveMembers:pp(qs.prototype.Of)}});
-l.Nh=Object({"http://www.opengis.net/gml":{surfaceMember:pp(qs.prototype.ph),surfaceMembers:pp(qs.prototype.ph)}});l.Fh=Object({"http://www.opengis.net/gml":{LineString:pp(ds.prototype.me),Curve:pp(qs.prototype.Og)}});l.Uh=Object({"http://www.opengis.net/gml":{Polygon:pp(ds.prototype.ne),Surface:pp(qs.prototype.Yg)}});l.Vh=Object({"http://www.opengis.net/gml":{patches:qp(qs.prototype.vn)}});l.Gh=Object({"http://www.opengis.net/gml":{segments:qp(qs.prototype.xn)}});
-l.Hh=Object({"http://www.opengis.net/gml":{lowerCorner:pp(qs.prototype.qf),upperCorner:pp(qs.prototype.qf)}});l.Oh=Object({"http://www.opengis.net/gml":{PolygonPatch:qp(qs.prototype.wn)}});l.Th=Object({"http://www.opengis.net/gml":{LineStringSegment:qp(qs.prototype.pn)}});function rs(a,c,d){d=d[d.length-1].srsName;c=c.U();for(var e=c.length,f=Array(e),g,h=0;h<e;++h){g=c[h];var k=h,m="enu";d&&(m=Fe(Ce(d)));f[k]="en"===m.substr(0,2)?g[0]+" "+g[1]:g[1]+" "+g[0]}ms(a,f.join(" "))}
-l.Ah=function(a,c,d){var e=d[d.length-1].srsName;e&&a.setAttribute("srsName",e);e=To(a.namespaceURI,"pos");a.appendChild(e);d=d[d.length-1].srsName;a="enu";d&&(a=Fe(Ce(d)));c=c.U();ms(e,"en"===a.substr(0,2)?c[0]+" "+c[1]:c[1]+" "+c[0])};var ss={"http://www.opengis.net/gml":{lowerCorner:S(ms),upperCorner:S(ms)}};l=qs.prototype;l.ko=function(a,c,d){var e=d[d.length-1].srsName;e&&a.setAttribute("srsName",e);yp({node:a},ss,vp,[c[0]+" "+c[1],c[2]+" "+c[3]],d,["lowerCorner","upperCorner"],this)};
-l.xh=function(a,c,d){var e=d[d.length-1].srsName;e&&a.setAttribute("srsName",e);e=To(a.namespaceURI,"posList");a.appendChild(e);rs(e,c,d)};l.Sh=function(a,c){var d=c[c.length-1],e=d.node,f=d.exteriorWritten;void 0===f&&(d.exteriorWritten=!0);return To(e.namespaceURI,void 0!==f?"interior":"exterior")};
-l.xe=function(a,c,d){var e=d[d.length-1].srsName;"PolygonPatch"!==a.nodeName&&e&&a.setAttribute("srsName",e);"Polygon"===a.nodeName||"PolygonPatch"===a.nodeName?(c=c.Dd(),yp({node:a,srsName:e},ts,this.Sh,c,d,void 0,this)):"Surface"===a.nodeName&&(e=To(a.namespaceURI,"patches"),a.appendChild(e),a=To(e.namespaceURI,"PolygonPatch"),e.appendChild(a),this.xe(a,c,d))};
-l.se=function(a,c,d){var e=d[d.length-1].srsName;"LineStringSegment"!==a.nodeName&&e&&a.setAttribute("srsName",e);"LineString"===a.nodeName||"LineStringSegment"===a.nodeName?(e=To(a.namespaceURI,"posList"),a.appendChild(e),rs(e,c,d)):"Curve"===a.nodeName&&(e=To(a.namespaceURI,"segments"),a.appendChild(e),a=To(e.namespaceURI,"LineStringSegment"),e.appendChild(a),this.se(a,c,d))};
-l.zh=function(a,c,d){var e=d[d.length-1],f=e.srsName,e=e.surface;f&&a.setAttribute("srsName",f);c=c.Fd();yp({node:a,srsName:f,surface:e},us,this.f,c,d,void 0,this)};l.lo=function(a,c,d){var e=d[d.length-1].srsName;e&&a.setAttribute("srsName",e);c=c.Yd();yp({node:a,srsName:e},vs,tp("pointMember"),c,d,void 0,this)};l.yh=function(a,c,d){var e=d[d.length-1],f=e.srsName,e=e.curve;f&&a.setAttribute("srsName",f);c=c.Xc();yp({node:a,srsName:f,curve:e},ws,this.f,c,d,void 0,this)};
-l.Bh=function(a,c,d){var e=To(a.namespaceURI,"LinearRing");a.appendChild(e);this.xh(e,c,d)};l.Ch=function(a,c,d){var e=this.c(c,d);e&&(a.appendChild(e),this.xe(e,c,d))};l.mo=function(a,c,d){var e=To(a.namespaceURI,"Point");a.appendChild(e);this.Ah(e,c,d)};l.wh=function(a,c,d){var e=this.c(c,d);e&&(a.appendChild(e),this.se(e,c,d))};
-l.ve=function(a,c,d){var e=d[d.length-1],f=Vb(e);f.node=a;var g;ga(c)?e.dataProjection?g=Ye(c,e.featureProjection,e.dataProjection):g=c:g=Mr(c,!0,e);yp(f,xs,this.c,[g],d,void 0,this)};
-l.uh=function(a,c,d){var e=c.ha;e&&a.setAttribute("fid",e);var e=d[d.length-1],f=e.featureNS,g=c.a;e.fc||(e.fc={},e.fc[f]={});var h=c.P();c=[];var k=[],m;for(m in h){var n=h[m];null!==n&&(c.push(m),k.push(n),m==g||n instanceof Ze?m in e.fc[f]||(e.fc[f][m]=S(this.ve,this)):m in e.fc[f]||(e.fc[f][m]=S(ms)))}m=Vb(e);m.node=a;yp(m,e.fc,tp(void 0,f),k,d,c)};
-var us={"http://www.opengis.net/gml":{surfaceMember:S(qs.prototype.Ch),polygonMember:S(qs.prototype.Ch)}},vs={"http://www.opengis.net/gml":{pointMember:S(qs.prototype.mo)}},ws={"http://www.opengis.net/gml":{lineStringMember:S(qs.prototype.wh),curveMember:S(qs.prototype.wh)}},ts={"http://www.opengis.net/gml":{exterior:S(qs.prototype.Bh),interior:S(qs.prototype.Bh)}},xs={"http://www.opengis.net/gml":{Curve:S(qs.prototype.se),MultiCurve:S(qs.prototype.yh),Point:S(qs.prototype.Ah),MultiPoint:S(qs.prototype.lo),
-LineString:S(qs.prototype.se),MultiLineString:S(qs.prototype.yh),LinearRing:S(qs.prototype.xh),Polygon:S(qs.prototype.xe),MultiPolygon:S(qs.prototype.zh),Surface:S(qs.prototype.xe),MultiSurface:S(qs.prototype.zh),Envelope:S(qs.prototype.ko)}},ys={MultiLineString:"lineStringMember",MultiCurve:"curveMember",MultiPolygon:"polygonMember",MultiSurface:"surfaceMember"};qs.prototype.f=function(a,c){return To("http://www.opengis.net/gml",ys[c[c.length-1].node.nodeName])};
-qs.prototype.c=function(a,c){var d=c[c.length-1],e=d.multiSurface,f=d.surface,g=d.curve,d=d.multiCurve,h;ga(a)?h="Envelope":(h=a.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 To("http://www.opengis.net/gml",h)};
-qs.prototype.C=function(a,c){c=Lr(this,c);var d=To("http://www.opengis.net/gml","geom"),e={node:d,srsName:this.srsName,curve:this.g,surface:this.l,multiSurface:this.i,multiCurve:this.j};c&&Yb(e,c);this.ve(d,a,[e]);return d};
-qs.prototype.a=function(a,c){c=Lr(this,c);var d=To("http://www.opengis.net/gml","featureMembers");mp(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.i,multiCurve:this.j,featureNS:this.featureNS,featureType:this.featureType};c&&Yb(e,c);var e=[e],f=e[e.length-1],g=f.featureType,h=f.featureNS,k={};k[h]={};k[h][g]=S(this.uh,this);f=Vb(f);f.node=d;yp(f,k,tp(g,h),a,e);return d};function zs(a){a=a?a:{};this.defaultDataProjection=null;this.defaultDataProjection=Ce("EPSG:4326");this.b=a.readExtensions}w(zs,as);var As=[null,"http://www.topografix.com/GPX/1/0","http://www.topografix.com/GPX/1/1"];function Bs(a,c,d){a.push(parseFloat(c.getAttribute("lon")),parseFloat(c.getAttribute("lat")));"ele"in d?(a.push(d.ele),delete d.ele):a.push(0);"time"in d?(a.push(d.time),delete d.time):a.push(0);return a}
-function Cs(a,c){var d=c[c.length-1],e=a.getAttribute("href");null!==e&&(d.link=e);xp(Ds,a,c)}function Es(a,c){c[c.length-1].extensionsNode_=a}function Fs(a,c){var d=c[0],e=U({flatCoordinates:[]},Gs,a,c);if(e){var f=e.flatCoordinates;delete e.flatCoordinates;var g=new L(null);gn(g,"XYZM",f);Mr(g,!1,d);d=new Q(g);d.H(e);return d}}
-function Hs(a,c){var d=c[0],e=U({flatCoordinates:[],ends:[]},Is,a,c);if(e){var f=e.flatCoordinates;delete e.flatCoordinates;var g=e.ends;delete e.ends;var h=new O(null);hn(h,"XYZM",f,g);Mr(h,!1,d);d=new Q(h);d.H(e);return d}}function Js(a,c){var d=c[0],e=U({},Ks,a,c);if(e){var f=Bs([],a,e),f=new D(f,"XYZM");Mr(f,!1,d);d=new Q(f);d.H(e);return d}}
-var Ls={rte:Fs,trk:Hs,wpt:Js},Ms=T(As,{rte:pp(Fs),trk:pp(Hs),wpt:pp(Js)}),Ds=T(As,{text:R(X,"linkText"),type:R(X,"linkType")}),Gs=T(As,{name:R(X),cmt:R(X),desc:R(X),src:R(X),link:Cs,number:R(js),extensions:Es,type:R(X),rtept:function(a,c){var d=U({},Ns,a,c);d&&Bs(c[c.length-1].flatCoordinates,a,d)}}),Ns=T(As,{ele:R(hs),time:R(gs)}),Is=T(As,{name:R(X),cmt:R(X),desc:R(X),src:R(X),link:Cs,number:R(js),type:R(X),extensions:Es,trkseg:function(a,c){var d=c[c.length-1];xp(Os,a,c);d.ends.push(d.flatCoordinates.length)}}),
-Os=T(As,{trkpt:function(a,c){var d=U({},Ps,a,c);d&&Bs(c[c.length-1].flatCoordinates,a,d)}}),Ps=T(As,{ele:R(hs),time:R(gs)}),Ks=T(As,{ele:R(hs),time:R(gs),magvar:R(hs),geoidheight:R(hs),name:R(X),cmt:R(X),desc:R(X),src:R(X),link:Cs,sym:R(X),type:R(X),fix:R(X),sat:R(js),hdop:R(hs),vdop:R(hs),pdop:R(hs),ageofdgpsdata:R(hs),dgpsid:R(js),extensions:Es});
-function Qs(a,c){c||(c=[]);for(var d=0,e=c.length;d<e;++d){var f=c[d];if(a.b){var g=f.get("extensionsNode_")||null;a.b(f,g)}f.set("extensionsNode_",void 0)}}zs.prototype.Pg=function(a,c){if(!sb(As,a.namespaceURI))return null;var d=Ls[a.localName];if(!d)return null;d=d(a,[Kr(this,a,c)]);if(!d)return null;Qs(this,[d]);return d};zs.prototype.Lb=function(a,c){if(!sb(As,a.namespaceURI))return[];if("gpx"==a.localName){var d=U([],Ms,a,[Kr(this,a,c)]);if(d)return Qs(this,d),d}return[]};
-function Rs(a,c,d){a.setAttribute("href",c);c=d[d.length-1].properties;yp({node:a},Ss,vp,[c.linkText,c.linkType],d,Ts)}function Us(a,c,d){var e=d[d.length-1],f=e.node.namespaceURI,g=e.properties;mp(a,null,"lat",c[1]);mp(a,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=Vs[f];e=wp(g,c);yp({node:a,properties:g},Ws,vp,e,d,c)}
-var Ts=["text","type"],Ss=T(As,{text:S(ms),type:S(ms)}),Xs=T(As,"name cmt desc src link number type rtept".split(" ")),Ys=T(As,{name:S(ms),cmt:S(ms),desc:S(ms),src:S(ms),link:S(Rs),number:S(os),type:S(ms),rtept:sp(S(Us))}),Zs=T(As,"name cmt desc src link number type trkseg".split(" ")),bt=T(As,{name:S(ms),cmt:S(ms),desc:S(ms),src:S(ms),link:S(Rs),number:S(os),type:S(ms),trkseg:sp(S(function(a,c,d){yp({node:a,geometryLayout:c.a,properties:{}},$s,at,c.U(),d)}))}),at=tp("trkpt"),$s=T(As,{trkpt:S(Us)}),
-Vs=T(As,"ele time magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid".split(" ")),Ws=T(As,{ele:S(ns),time:S(function(a,c){var d=new Date(1E3*c),d=d.getUTCFullYear()+"-"+Na(d.getUTCMonth()+1)+"-"+Na(d.getUTCDate())+"T"+Na(d.getUTCHours())+":"+Na(d.getUTCMinutes())+":"+Na(d.getUTCSeconds())+"Z";a.appendChild(Qo.createTextNode(d))}),magvar:S(ns),geoidheight:S(ns),name:S(ms),cmt:S(ms),desc:S(ms),src:S(ms),link:S(Rs),sym:S(ms),type:S(ms),fix:S(ms),sat:S(os),
-hdop:S(ns),vdop:S(ns),pdop:S(ns),ageofdgpsdata:S(ns),dgpsid:S(os)}),ct={Point:"wpt",LineString:"rte",MultiLineString:"trk"};function dt(a,c){var d=a.V();if(d)return To(c[c.length-1].node.namespaceURI,ct[d.W()])}
-var et=T(As,{rte:S(function(a,c,d){var e=d[0],f=c.P();a={node:a,properties:f};if(c=c.V())c=Mr(c,!0,e),a.geometryLayout=c.a,f.rtept=c.U();e=Xs[d[d.length-1].node.namespaceURI];f=wp(f,e);yp(a,Ys,vp,f,d,e)}),trk:S(function(a,c,d){var e=d[0],f=c.P();a={node:a,properties:f};if(c=c.V())c=Mr(c,!0,e),f.trkseg=c.Xc();e=Zs[d[d.length-1].node.namespaceURI];f=wp(f,e);yp(a,bt,vp,f,d,e)}),wpt:S(function(a,c,d){var e=d[0],f=d[d.length-1];f.properties=c.P();if(c=c.V())c=Mr(c,!0,e),f.geometryLayout=c.a,Us(a,c.U(),
-d)})});zs.prototype.a=function(a,c){c=Lr(this,c);var d=To("http://www.topografix.com/GPX/1/1","gpx");yp({node:d},et,dt,a,[c]);return d};function ft(a){a=gt(a);return ab(a,function(a){return a.c.substring(a.a,a.b)})}function ht(a,c,d){this.c=a;this.a=c;this.b=d}function gt(a){for(var c=RegExp("\r\n|\r|\n","g"),d=0,e,f=[];e=c.exec(a);)d=new ht(a,d,e.index),f.push(d),d=c.lastIndex;d<a.length&&(d=new ht(a,d,a.length),f.push(d));return f};function it(){this.defaultDataProjection=null}w(it,Jr);l=it.prototype;l.W=function(){return"text"};l.vb=function(a,c){return this.ed(ia(a)?a:"",Lr(this,c))};l.sa=function(a,c){return this.pf(ia(a)?a:"",Lr(this,c))};l.Bc=function(a,c){return this.gd(ia(a)?a:"",Lr(this,c))};l.Ha=function(){return this.defaultDataProjection};l.kd=function(a,c){return this.te(a,Lr(this,c))};l.xb=function(a,c){return this.vh(a,Lr(this,c))};l.Hc=function(a,c){return this.ld(a,Lr(this,c))};function jt(a){a=a?a:{};this.defaultDataProjection=null;this.defaultDataProjection=Ce("EPSG:4326");this.b=a.altitudeMode?a.altitudeMode:"none"}w(jt,it);var kt=/^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/,lt=/^H.([A-Z]{3}).*?:(.*)/,mt=/^HFDTE(\d{2})(\d{2})(\d{2})/;
-jt.prototype.ed=function(a,c){var d=this.b,e=ft(a),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=kt.exec(q)){var q=parseInt(r[1],10),u=parseInt(r[2],10),y=parseInt(r[3],10),A=parseInt(r[4],10)+parseInt(r[5],10)/6E4;"S"==r[6]&&(A=-A);var F=parseInt(r[7],10)+parseInt(r[8],10)/6E4;"W"==r[9]&&(F=-F);g.push(F,A);"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,u,y)/1E3)}}else if("H"==q.charAt(0))if(r=
-mt.exec(q))m=parseInt(r[1],10),k=parseInt(r[2],10)-1,h=2E3+parseInt(r[3],10);else if(r=lt.exec(q))f[r[1]]=r[2].trim(),mt.exec(q)}if(0===g.length)return null;e=new L(null);gn(e,"none"==d?"XYM":"XYZM",g);d=new Q(Mr(e,!1,c));d.H(f);return d};jt.prototype.pf=function(a,c){var d=this.ed(a,c);return d?[d]:[]};function nt(a,c){this.a=this.i=this.f="";this.l=null;this.g=this.b="";this.j=!1;var d;a instanceof nt?(this.j=ca(c)?c:a.j,ot(this,a.f),this.i=a.i,this.a=a.a,pt(this,a.l),this.b=a.b,qt(this,a.c.clone()),this.g=a.g):a&&(d=String(a).match(qo))?(this.j=!!c,ot(this,d[1]||"",!0),this.i=rt(d[2]||""),this.a=rt(d[3]||"",!0),pt(this,d[4]),this.b=rt(d[5]||"",!0),qt(this,d[6]||"",!0),this.g=rt(d[7]||"")):(this.j=!!c,this.c=new st(null,0,this.j))}
-nt.prototype.toString=function(){var a=[],c=this.f;c&&a.push(tt(c,ut,!0),":");var d=this.a;if(d||"file"==c)a.push("//"),(c=this.i)&&a.push(tt(c,ut,!0),"@"),a.push(encodeURIComponent(String(d)).replace(/%25([0-9a-fA-F]{2})/g,"%$1")),d=this.l,null!=d&&a.push(":",String(d));if(d=this.b)this.a&&"/"!=d.charAt(0)&&a.push("/"),a.push(tt(d,"/"==d.charAt(0)?vt:wt,!0));(d=this.c.toString())&&a.push("?",d);(d=this.g)&&a.push("#",tt(d,xt));return a.join("")};nt.prototype.clone=function(){return new nt(this)};
-function ot(a,c,d){a.f=d?rt(c,!0):c;a.f&&(a.f=a.f.replace(/:$/,""))}function pt(a,c){if(c){c=Number(c);if(isNaN(c)||0>c)throw Error("Bad port number "+c);a.l=c}else a.l=null}function qt(a,c,d){c instanceof st?(a.c=c,yt(a.c,a.j)):(d||(c=tt(c,zt)),a.c=new st(c,0,a.j))}function At(a){return a instanceof nt?a.clone():new nt(a,void 0)}
-function Bt(a,c){a instanceof nt||(a=At(a));c instanceof nt||(c=At(c));var d=a,e=c,f=d.clone(),g=!!e.f;g?ot(f,e.f):g=!!e.i;g?f.i=e.i:g=!!e.a;g?f.a=e.a:g=null!=e.l;var h=e.b;if(g)pt(f,e.l);else if(g=!!e.b)if("/"!=h.charAt(0)&&(d.a&&!d.b?h="/"+h:(d=f.b.lastIndexOf("/"),-1!=d&&(h=f.b.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.b=h:g=""!==e.c.toString();g?qt(f,rt(e.c.toString())):g=!!e.g;g&&(f.g=e.g);return f}function rt(a,c){return a?c?decodeURI(a.replace(/%25/g,"%2525")):decodeURIComponent(a):""}function tt(a,c,d){return ia(a)?(a=encodeURI(a).replace(c,Ct),d&&(a=a.replace(/%25([0-9a-fA-F]{2})/g,"%$1")),a):null}function Ct(a){a=a.charCodeAt(0);return"%"+(a>>4&15).toString(16)+(a&15).toString(16)}
-var ut=/[#\/\?@]/g,wt=/[\#\?:]/g,vt=/[\#\?]/g,zt=/[\#\?@]/g,xt=/#/g;function st(a,c,d){this.a=this.b=null;this.c=a||null;this.f=!!d}function Dt(a){a.b||(a.b=new ti,a.a=0,a.c&&ro(a.c,function(c,d){a.add(decodeURIComponent(c.replace(/\+/g," ")),d)}))}l=st.prototype;l.Qb=function(){Dt(this);return this.a};l.add=function(a,c){Dt(this);this.c=null;a=Et(this,a);var d=this.b.get(a);d||this.b.set(a,d=[]);d.push(c);this.a++;return this};
-l.remove=function(a){Dt(this);a=Et(this,a);return vi(this.b.a,a)?(this.c=null,this.a-=this.b.get(a).length,this.b.remove(a)):!1};l.clear=function(){this.b=this.c=null;this.a=0};l.ya=function(){Dt(this);return 0==this.a};function Ft(a,c){Dt(a);c=Et(a,c);return vi(a.b.a,c)}l.O=function(){Dt(this);for(var a=this.b.Ub(),c=this.b.O(),d=[],e=0;e<c.length;e++)for(var f=a[e],g=0;g<f.length;g++)d.push(c[e]);return d};
-l.Ub=function(a){Dt(this);var c=[];if(ia(a))Ft(this,a)&&(c=fb(c,this.b.get(Et(this,a))));else{a=this.b.Ub();for(var d=0;d<a.length;d++)c=fb(c,a[d])}return c};l.set=function(a,c){Dt(this);this.c=null;a=Et(this,a);Ft(this,a)&&(this.a-=this.b.get(a).length);this.b.set(a,[c]);this.a++;return this};l.get=function(a,c){var d=a?this.Ub(a):[];return 0<d.length?String(d[0]):c};function Gt(a,c,d){a.remove(c);0<d.length&&(a.c=null,a.b.set(Et(a,c),gb(d)),a.a+=d.length)}
-l.toString=function(){if(this.c)return this.c;if(!this.b)return"";for(var a=[],c=this.b.O(),d=0;d<c.length;d++)for(var e=c[d],f=encodeURIComponent(String(e)),e=this.Ub(e),g=0;g<e.length;g++){var h=f;""!==e[g]&&(h+="="+encodeURIComponent(String(e[g])));a.push(h)}return this.c=a.join("&")};l.clone=function(){var a=new st;a.c=this.c;this.b&&(a.b=this.b.clone(),a.a=this.a);return a};function Et(a,c){var d=String(c);a.f&&(d=d.toLowerCase());return d}
-function yt(a,c){c&&!a.f&&(Dt(a),a.c=null,a.b.forEach(function(a,c){var f=c.toLowerCase();c!=f&&(this.remove(c),Gt(this,f,a))},a));a.f=c};function Ht(a){a=a||{};this.f=a.font;this.g=a.rotation;this.a=a.scale;this.c=a.text;this.i=a.textAlign;this.l=a.textBaseline;this.b=void 0!==a.fill?a.fill:new El({color:"#333"});this.j=void 0!==a.stroke?a.stroke:null;this.C=void 0!==a.offsetX?a.offsetX:0;this.A=void 0!==a.offsetY?a.offsetY:0}l=Ht.prototype;l.Li=function(){return this.f};l.aj=function(){return this.C};l.bj=function(){return this.A};l.Fm=function(){return this.b};l.Gm=function(){return this.g};l.Hm=function(){return this.a};l.Im=function(){return this.j};
-l.Jm=function(){return this.c};l.lj=function(){return this.i};l.mj=function(){return this.l};l.On=function(a){this.f=a};l.Nn=function(a){this.b=a};l.Km=function(a){this.g=a};l.Lm=function(a){this.a=a};l.Un=function(a){this.j=a};l.Vn=function(a){this.c=a};l.Wn=function(a){this.i=a};l.Xn=function(a){this.l=a};function It(a){a=a?a:{};this.defaultDataProjection=null;this.defaultDataProjection=Ce("EPSG:4326");this.c=a.defaultStyle?a.defaultStyle:Jt;this.f=void 0!==a.extractStyles?a.extractStyles:!0;this.b={}}w(It,as);
-var Kt=["http://www.google.com/kml/ext/2.2"],Lt=[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"],Mt=[255,255,255,1],Nt=new El({color:Mt}),Ot=[20,2],Pt=[64,64],Qt=new yk({anchor:Ot,anchorOrigin:"bottom-left",anchorXUnits:"pixels",anchorYUnits:"pixels",crossOrigin:"anonymous",rotation:0,scale:.5,size:Pt,src:"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"}),Rt=new Al({color:Mt,width:1}),St=new Ht({font:"normal 16px Helvetica",
-fill:Nt,stroke:Rt,scale:1}),Jt=[new Gl({fill:Nt,image:Qt,text:St,stroke:Rt,zIndex:0})],Tt={fraction:"fraction",pixels:"pixels"};function Ut(a,c,d,e){return function(){return a?a:c?Vt(c,d,e):d}}function Vt(a,c,d){return ga(a)?a:ia(a)?(!(a in d)&&"#"+a in d&&(a="#"+a),Vt(d[a],c,d)):c}function Wt(a){a=Uo(a,!1);if(a=/^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(a))return a=a[1],[parseInt(a.substr(6,2),16),parseInt(a.substr(4,2),16),parseInt(a.substr(2,2),16),parseInt(a.substr(0,2),16)/255]}
-function Xt(a){a=Uo(a,!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(a);)c.push(parseFloat(e[1]),parseFloat(e[2]),e[3]?parseFloat(e[3]):0),a=a.substr(e[0].length);return""!==a?void 0:c}function Yt(a){var c=Uo(a,!1);return a.baseURI?Bt(a.baseURI,c.trim()).toString():c.trim()}function Zt(a){a=hs(a);if(void 0!==a)return Math.sqrt(a)}function $t(a,c){return U(null,au,a,c)}
-function bu(a,c){var d=U({o:[],th:[]},cu,a,c);if(d){var e=d.o,d=d.th,f,g;f=0;for(g=Math.min(e.length,d.length);f<g;++f)e[4*f+3]=d[f];d=new L(null);gn(d,"XYZM",e);return d}}function du(a,c){var d=U({},eu,a,c),e=U(null,fu,a,c);if(e){var f=new L(null);gn(f,"XYZ",e);f.H(d);return f}}function gu(a,c){var d=U({},eu,a,c),e=U(null,fu,a,c);if(e){var f=new E(null);Hf(f,"XYZ",e,[e.length]);f.H(d);return f}}
-function hu(a,c){var d=U([],iu,a,c);if(!d)return null;if(0===d.length)return new $m(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.a;f=g.o;h=1;for(k=d.length;h<k;++h)g=d[h],hb(f,g.o);g=new kn(null);cf(g,e,f);g.s();ju(g,d);return g}return"LineString"==f?(g=new O(null),jn(g,d),ju(g,d),g):"Polygon"==f?(g=new P(null),mn(g,d),ju(g,d),g):"GeometryCollection"==f?new $m(d):null}return new $m(d)}
-function ku(a,c){var d=U({},eu,a,c),e=U(null,fu,a,c);if(e){var f=new D(null);vf(f,"XYZ",e);f.H(d);return f}}function lu(a,c){var d=U({},eu,a,c),e=U([null],mu,a,c);if(e&&e[0]){var f=new E(null),g=e[0],h=[g.length],k,m;k=1;for(m=e.length;k<m;++k)hb(g,e[k]),h.push(g.length);Hf(f,"XYZ",g,h);f.H(d);return f}}
-function nu(a,c){var d=U({},ou,a,c);if(!d)return null;var e="fillStyle"in d?d.fillStyle:Nt,f=d.fill;void 0===f||f||(e=null);var f="imageStyle"in d?d.imageStyle:Qt,g="textStyle"in d?d.textStyle:St,h="strokeStyle"in d?d.strokeStyle:Rt,d=d.outline;void 0===d||d||(h=null);return[new Gl({fill:e,image:f,stroke:h,text:g,zIndex:void 0})]}
-function ju(a,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&&a.set("extrude",e);m&&a.set("altitudeMode",f)}function pu(a,c){xp(qu,a,c)}
-var ru=T(Lt,{value:qp(X)}),qu=T(Lt,{Data:function(a,c){var d=a.getAttribute("name");if(null!==d){var e=U(void 0,ru,a,c);e&&(c[c.length-1][d]=e)}},SchemaData:function(a,c){xp(su,a,c)}}),eu=T(Lt,{extrude:R(es),altitudeMode:R(X)}),au=T(Lt,{coordinates:qp(Xt)}),mu=T(Lt,{innerBoundaryIs:function(a,c){var d=U(void 0,tu,a,c);d&&c[c.length-1].push(d)},outerBoundaryIs:function(a,c){var d=U(void 0,uu,a,c);d&&(c[c.length-1][0]=d)}}),cu=T(Lt,{when:function(a,c){var d=c[c.length-1].th,e=Uo(a,!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)}},T(Kt,{coord:function(a,c){var d=c[c.length-1].o,e=Uo(a,!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)}})),fu=T(Lt,{coordinates:qp(Xt)}),vu=T(Lt,{href:R(Yt)},T(Kt,{x:R(hs),y:R(hs),w:R(hs),h:R(hs)})),wu=T(Lt,{Icon:R(function(a,c){var d=U({},vu,a,c);return d?d:null}),heading:R(hs),hotSpot:R(function(a){var c=a.getAttribute("xunits"),d=a.getAttribute("yunits");return{x:parseFloat(a.getAttribute("x")),zf:Tt[c],y:parseFloat(a.getAttribute("y")),Af:Tt[d]}}),scale:R(Zt)}),tu=T(Lt,{LinearRing:qp($t)}),xu=T(Lt,{color:R(Wt),scale:R(Zt)}),yu=T(Lt,{color:R(Wt),
-width:R(hs)}),iu=T(Lt,{LineString:pp(du),LinearRing:pp(gu),MultiGeometry:pp(hu),Point:pp(ku),Polygon:pp(lu)}),zu=T(Kt,{Track:pp(bu)}),Bu=T(Lt,{ExtendedData:pu,Link:function(a,c){xp(Au,a,c)},address:R(X),description:R(X),name:R(X),open:R(es),phoneNumber:R(X),visibility:R(es)}),Au=T(Lt,{href:R(Yt)}),uu=T(Lt,{LinearRing:qp($t)}),Cu=T(Lt,{Style:R(nu),key:R(X),styleUrl:R(function(a){var c=Uo(a,!1).trim();return a.baseURI?Bt(a.baseURI,c).toString():c})}),Eu=T(Lt,{ExtendedData:pu,MultiGeometry:R(hu,"geometry"),
-LineString:R(du,"geometry"),LinearRing:R(gu,"geometry"),Point:R(ku,"geometry"),Polygon:R(lu,"geometry"),Style:R(nu),StyleMap:function(a,c){var d=U(void 0,Du,a,c);if(d){var e=c[c.length-1];ga(d)?e.Style=d:ia(d)&&(e.styleUrl=d)}},address:R(X),description:R(X),name:R(X),open:R(es),phoneNumber:R(X),styleUrl:R(Yt),visibility:R(es)},T(Kt,{MultiTrack:R(function(a,c){var d=U([],zu,a,c);if(d){var e=new O(null);jn(e,d);return e}},"geometry"),Track:R(bu,"geometry")})),Fu=T(Lt,{color:R(Wt),fill:R(es),outline:R(es)}),
-su=T(Lt,{SimpleData:function(a,c){var d=a.getAttribute("name");if(null!==d){var e=X(a);c[c.length-1][d]=e}}}),ou=T(Lt,{IconStyle:function(a,c){var d=U({},wu,a,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.zf,m=n.Af):"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"===g?(h=Ot,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 u,f=d.heading;void 0!==f&&(u=yb(f));d=d.scale;"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"==g&&(r=Pt);h=new yk({anchor:h,anchorOrigin:"bottom-left",anchorXUnits:k,anchorYUnits:m,crossOrigin:"anonymous",offset:p,offsetOrigin:"bottom-left",rotation:u,scale:d,size:r,src:g});e.imageStyle=h}},LabelStyle:function(a,c){var d=U({},xu,a,c);d&&(c[c.length-1].textStyle=new Ht({fill:new El({color:"color"in
-d?d.color:Mt}),scale:d.scale}))},LineStyle:function(a,c){var d=U({},yu,a,c);d&&(c[c.length-1].strokeStyle=new Al({color:"color"in d?d.color:Mt,width:"width"in d?d.width:1}))},PolyStyle:function(a,c){var d=U({},Fu,a,c);if(d){var e=c[c.length-1];e.fillStyle=new El({color:"color"in d?d.color:Mt});var f=d.fill;void 0!==f&&(e.fill=f);d=d.outline;void 0!==d&&(e.outline=d)}}}),Du=T(Lt,{Pair:function(a,c){var d=U({},Cu,a,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=It.prototype;l.mf=function(a,c){Yo(a);var d=T(Lt,{Document:op(this.mf,this),Folder:op(this.mf,this),Placemark:pp(this.rf,this),Style:qa(this.zn,this),StyleMap:qa(this.yn,this)});if(d=U([],d,a,c,this))return d};l.rf=function(a,c){var d=U({geometry:null},Eu,a,c);if(d){var e=new Q,f=a.getAttribute("id");null!==f&&e.Mb(f);var f=c[0],g=d.geometry;g&&Mr(g,!1,f);e.za(g);delete d.geometry;this.f&&e.af(Ut(d.Style,d.styleUrl,this.c,this.b));delete d.Style;e.H(d);return e}};
-l.zn=function(a,c){var d=a.getAttribute("id");if(null!==d){var e=nu(a,c);e&&(d=a.baseURI?Bt(a.baseURI,"#"+d).toString():"#"+d,this.b[d]=e)}};l.yn=function(a,c){var d=a.getAttribute("id");if(null!==d){var e=U(void 0,Du,a,c);e&&(d=a.baseURI?Bt(a.baseURI,"#"+d).toString():"#"+d,this.b[d]=e)}};l.Pg=function(a,c){if(!sb(Lt,a.namespaceURI))return null;var d=this.rf(a,[Kr(this,a,c)]);return d?d:null};
-l.Lb=function(a,c){if(!sb(Lt,a.namespaceURI))return[];var d;d=Yo(a);if("Document"==d||"Folder"==d)return(d=this.mf(a,[Kr(this,a,c)]))?d:[];if("Placemark"==d)return(d=this.rf(a,[Kr(this,a,c)]))?[d]:[];if("kml"==d){d=[];var e;for(e=a.firstElementChild;e;e=e.nextElementSibling){var f=this.Lb(e,c);f&&hb(d,f)}return d}return[]};l.sn=function(a){if(ap(a))return Gu(this,a);if(dp(a))return Hu(this,a);if(ia(a))return a=np(a),Gu(this,a)};
-function Gu(a,c){var d;for(d=c.firstChild;d;d=d.nextSibling)if(1==d.nodeType){var e=Hu(a,d);if(e)return e}}function Hu(a,c){var d;for(d=c.firstElementChild;d;d=d.nextElementSibling)if(sb(Lt,d.namespaceURI)&&"name"==d.localName)return X(d);for(d=c.firstElementChild;d;d=d.nextElementSibling){var e=Yo(d);if(sb(Lt,d.namespaceURI)&&("Document"==e||"Folder"==e||"Placemark"==e||"kml"==e)&&(e=Hu(a,d)))return e}}
-l.tn=function(a){var c=[];ap(a)?hb(c,Iu(this,a)):dp(a)?hb(c,Ju(this,a)):ia(a)&&(a=np(a),hb(c,Iu(this,a)));return c};function Iu(a,c){var d,e=[];for(d=c.firstChild;d;d=d.nextSibling)1==d.nodeType&&hb(e,Ju(a,d));return e}
-function Ju(a,c){var d,e=[];for(d=c.firstElementChild;d;d=d.nextElementSibling)if(sb(Lt,d.namespaceURI)&&"NetworkLink"==d.localName){var f=U({},Bu,d,[]);e.push(f)}for(d=c.firstElementChild;d;d=d.nextElementSibling)f=Yo(d),!sb(Lt,d.namespaceURI)||"Document"!=f&&"Folder"!=f&&"kml"!=f||hb(e,Ju(a,d));return e}function Ku(a,c){var d=yg(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}ms(a,d.join(""))}
-function Lu(a,c,d){yp({node:a},Mu,Nu,[c],d)}function Ou(a,c,d){var e={node:a};c.ha&&a.setAttribute("id",c.ha);a=c.P();var f=c.c;f&&(f=f.call(c,0))&&0<f.length&&(a.Style=f[0],(f=f[0].a)&&(a.name=f.c));f=Pu[d[d.length-1].node.namespaceURI];a=wp(a,f);yp(e,Qu,vp,a,d,f);a=d[0];(c=c.V())&&(c=Mr(c,!0,a));yp(e,Qu,Ru,[c],d)}function Su(a,c,d){var e=c.o;a={node:a};a.layout=c.a;a.stride=c.G;yp(a,Tu,Uu,[e],d)}function Vu(a,c,d){c=c.Dd();var e=c.shift();a={node:a};yp(a,Wu,Xu,c,d);yp(a,Wu,Yu,[e],d)}
-function Zu(a,c){ns(a,c*c)}
-var $u=T(Lt,["Document","Placemark"]),cv=T(Lt,{Document:S(function(a,c,d){yp({node:a},av,bv,c,d)}),Placemark:S(Ou)}),av=T(Lt,{Placemark:S(Ou)}),dv={Point:"Point",LineString:"LineString",LinearRing:"LinearRing",Polygon:"Polygon",MultiPoint:"MultiGeometry",MultiLineString:"MultiGeometry",MultiPolygon:"MultiGeometry"},ev=T(Lt,["href"],T(Kt,["x","y","w","h"])),fv=T(Lt,{href:S(ms)},T(Kt,{x:S(ns),y:S(ns),w:S(ns),h:S(ns)})),gv=T(Lt,["scale","heading","Icon","hotSpot"]),iv=T(Lt,{Icon:S(function(a,c,d){a=
-{node:a};var e=ev[d[d.length-1].node.namespaceURI],f=wp(c,e);yp(a,fv,vp,f,d,e);e=ev[Kt[0]];f=wp(c,e);yp(a,fv,hv,f,d,e)}),heading:S(ns),hotSpot:S(function(a,c){a.setAttribute("x",c.x);a.setAttribute("y",c.y);a.setAttribute("xunits",c.zf);a.setAttribute("yunits",c.Af)}),scale:S(Zu)}),jv=T(Lt,["color","scale"]),kv=T(Lt,{color:S(Ku),scale:S(Zu)}),lv=T(Lt,["color","width"]),mv=T(Lt,{color:S(Ku),width:S(ns)}),Mu=T(Lt,{LinearRing:S(Su)}),nv=T(Lt,{LineString:S(Su),Point:S(Su),Polygon:S(Vu)}),Pu=T(Lt,"name open visibility address phoneNumber description styleUrl Style".split(" ")),
-Qu=T(Lt,{MultiGeometry:S(function(a,c,d){a={node:a};var e=c.W(),f,g;"MultiPoint"==e?(f=c.Yd(),g=ov):"MultiLineString"==e?(f=c.Xc(),g=pv):"MultiPolygon"==e&&(f=c.Fd(),g=qv);yp(a,nv,g,f,d)}),LineString:S(Su),LinearRing:S(Su),Point:S(Su),Polygon:S(Vu),Style:S(function(a,c,d){a={node:a};var e={},f=c.f,g=c.c,h=c.j;c=c.a;h&&(e.IconStyle=h);c&&(e.LabelStyle=c);g&&(e.LineStyle=g);f&&(e.PolyStyle=f);c=rv[d[d.length-1].node.namespaceURI];e=wp(e,c);yp(a,sv,vp,e,d,c)}),address:S(ms),description:S(ms),name:S(ms),
-open:S(ls),phoneNumber:S(ms),styleUrl:S(ms),visibility:S(ls)}),Tu=T(Lt,{coordinates:S(function(a,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]}ms(a,k)})}),Wu=T(Lt,{outerBoundaryIs:S(Lu),innerBoundaryIs:S(Lu)}),tv=T(Lt,{color:S(Ku)}),rv=T(Lt,["IconStyle","LabelStyle","LineStyle","PolyStyle"]),sv=T(Lt,{IconStyle:S(function(a,
-c,d){a={node:a};var e={},f=c.kb(),g=c.Cd(),h={href:c.b.j};if(f){h.w=f[0];h.h=f[1];var k=c.Ab(),m=c.ta();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],zf:"pixels",y:f[1]-k[1],Af:"pixels"})}e.Icon=h;f=c.A;1!==f&&(e.scale=f);c=c.C;0!==c&&(e.heading=c);c=gv[d[d.length-1].node.namespaceURI];e=wp(e,c);yp(a,iv,vp,e,d,c)}),LabelStyle:S(function(a,c,d){a={node:a};var e={},f=c.b;f&&(e.color=f.b);(c=c.a)&&1!==c&&(e.scale=c);c=jv[d[d.length-1].node.namespaceURI];
-e=wp(e,c);yp(a,kv,vp,e,d,c)}),LineStyle:S(function(a,c,d){a={node:a};var e=lv[d[d.length-1].node.namespaceURI];c=wp({color:c.b,width:c.a},e);yp(a,mv,vp,c,d,e)}),PolyStyle:S(function(a,c,d){yp({node:a},tv,uv,[c.b],d)})});function hv(a,c,d){return To(Kt[0],"gx:"+d)}function bv(a,c){return To(c[c.length-1].node.namespaceURI,"Placemark")}function Ru(a,c){if(a)return To(c[c.length-1].node.namespaceURI,dv[a.W()])}
-var uv=tp("color"),Uu=tp("coordinates"),Xu=tp("innerBoundaryIs"),ov=tp("Point"),pv=tp("LineString"),Nu=tp("LinearRing"),qv=tp("Polygon"),Yu=tp("outerBoundaryIs");
-It.prototype.a=function(a,c){c=Lr(this,c);var d=To(Lt[4],"kml");mp(d,"http://www.w3.org/2000/xmlns/","xmlns:gx",Kt[0]);mp(d,"http://www.w3.org/2000/xmlns/","xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");mp(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<a.length?f.Document=a:1==a.length&&(f.Placemark=a[0]);var g=$u[d.namespaceURI],f=wp(f,g);yp(e,cv,vp,f,[c],g);
-return d};function vv(){this.defaultDataProjection=null;this.defaultDataProjection=Ce("EPSG:4326")}w(vv,as);function wv(a,c){c[c.length-1].jd[a.getAttribute("k")]=a.getAttribute("v")}
-var xv=[null],yv=T(xv,{nd:function(a,c){c[c.length-1].vc.push(a.getAttribute("ref"))},tag:wv}),Av=T(xv,{node:function(a,c){var d=c[0],e=c[c.length-1],f=a.getAttribute("id"),g=[parseFloat(a.getAttribute("lon")),parseFloat(a.getAttribute("lat"))];e.ig[f]=g;var h=U({jd:{}},zv,a,c);Rb(h.jd)||(g=new D(g),Mr(g,!1,d),d=new Q(g),d.Mb(f),d.H(h.jd),e.features.push(d))},way:function(a,c){for(var d=c[0],e=a.getAttribute("id"),f=U({vc:[],jd:{}},yv,a,c),g=c[c.length-1],h=[],k=0,m=f.vc.length;k<m;k++)hb(h,g.ig[f.vc[k]]);
-f.vc[0]==f.vc[f.vc.length-1]?(k=new E(null),Hf(k,"XY",h,[h.length])):(k=new L(null),gn(k,"XY",h));Mr(k,!1,d);d=new Q(k);d.Mb(e);d.H(f.jd);g.features.push(d)}}),zv=T(xv,{tag:wv});vv.prototype.Lb=function(a,c){var d=Kr(this,a,c);return"osm"==a.localName&&(d=U({ig:{},features:[]},Av,a,[d]),d.features)?d.features:[]};function Bv(a){return a.getAttributeNS("http://www.w3.org/1999/xlink","href")};function Cv(){}Cv.prototype.c=function(a){return ap(a)?this.a(a):dp(a)?this.b(a):ia(a)?(a=np(a),this.a(a)):null};function Dv(){}w(Dv,Cv);Dv.prototype.a=function(a){for(a=a.firstChild;a;a=a.nextSibling)if(1==a.nodeType)return this.b(a);return null};Dv.prototype.b=function(a){return(a=U({},Ev,a,[]))?a:null};
-var Fv=[null,"http://www.opengis.net/ows/1.1"],Ev=T(Fv,{ServiceIdentification:R(function(a,c){return U({},Gv,a,c)}),ServiceProvider:R(function(a,c){return U({},Hv,a,c)}),OperationsMetadata:R(function(a,c){return U({},Iv,a,c)})}),Jv=T(Fv,{DeliveryPoint:R(X),City:R(X),AdministrativeArea:R(X),PostalCode:R(X),Country:R(X),ElectronicMailAddress:R(X)}),Kv=T(Fv,{Value:rp(function(a){return X(a)})}),Lv=T(Fv,{AllowedValues:R(function(a,c){return U({},Kv,a,c)})}),Nv=T(Fv,{Phone:R(function(a,c){return U({},
-Mv,a,c)}),Address:R(function(a,c){return U({},Jv,a,c)})}),Pv=T(Fv,{HTTP:R(function(a,c){return U({},Ov,a,c)})}),Ov=T(Fv,{Get:rp(function(a,c){var d=Bv(a);return d?U({href:d},Qv,a,c):void 0}),Post:void 0}),Rv=T(Fv,{DCP:R(function(a,c){return U({},Pv,a,c)})}),Iv=T(Fv,{Operation:function(a,c){var d=a.getAttribute("name"),e=U({},Rv,a,c);e&&(c[c.length-1][d]=e)}}),Mv=T(Fv,{Voice:R(X),Facsimile:R(X)}),Qv=T(Fv,{Constraint:rp(function(a,c){var d=a.getAttribute("name");return d?U({name:d},Lv,a,c):void 0})}),
-Sv=T(Fv,{IndividualName:R(X),PositionName:R(X),ContactInfo:R(function(a,c){return U({},Nv,a,c)})}),Gv=T(Fv,{Title:R(X),ServiceTypeVersion:R(X),ServiceType:R(X)}),Hv=T(Fv,{ProviderName:R(X),ProviderSite:R(Bv),ServiceContact:R(function(a,c){return U({},Sv,a,c)})});function Tv(a,c,d,e){var f;void 0!==e?f=e:f=[];e=0;var g,h;for(g=0;g<c;)for(h=a[g++],f[e++]=a[g++],f[e++]=h,h=2;h<d;++h)f[e++]=a[g++];f.length=e};function Uv(a){a=a?a:{};this.defaultDataProjection=null;this.defaultDataProjection=Ce("EPSG:4326");this.b=a.factor?a.factor:1E5;this.a=a.geometryLayout?a.geometryLayout:"XY"}w(Uv,it);function Vv(a,c,d){var e,f=Array(c);for(e=0;e<c;++e)f[e]=0;var g,h;g=0;for(h=a.length;g<h;)for(e=0;e<c;++e,++g){var k=a[g],m=k-f[e];f[e]=k;a[g]=m}return Wv(a,d?d:1E5)}
-function Xv(a,c,d){var e,f=Array(c);for(e=0;e<c;++e)f[e]=0;a=Yv(a,d?d:1E5);var g;d=0;for(g=a.length;d<g;)for(e=0;e<c;++e,++d)f[e]+=a[d],a[d]=f[e];return a}function Wv(a,c){var d=c?c:1E5,e,f;e=0;for(f=a.length;e<f;++e)a[e]=Math.round(a[e]*d);d=0;for(e=a.length;d<e;++d)f=a[d],a[d]=0>f?~(f<<1):f<<1;d="";e=0;for(f=a.length;e<f;++e){for(var g=a[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 Yv(a,c){var d=c?c:1E5,e=[],f=0,g=0,h,k;h=0;for(k=a.length;h<k;++h){var m=a.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=Uv.prototype;l.ed=function(a,c){var d=this.gd(a,c);return new Q(d)};l.pf=function(a,c){return[this.ed(a,c)]};l.gd=function(a,c){var d=bf(this.a),e=Xv(a,d,this.b);Tv(e,e.length,d,e);d=pf(e,0,e.length,d);return Mr(new L(d,this.a),!1,Lr(this,c))};
-l.te=function(a,c){var d=a.V();return d?this.ld(d,c):""};l.vh=function(a,c){return this.te(a[0],c)};l.ld=function(a,c){a=Mr(a,!0,Lr(this,c));var d=a.o,e=a.G;Tv(d,d.length,e,d);return Vv(d,e,this.b)};function Zv(a){a=a?a:{};this.defaultDataProjection=null;this.defaultDataProjection=Ce(a.defaultDataProjection?a.defaultDataProjection:"EPSG:4326")}w(Zv,Nr);function $v(a,c){var d=[],e,f,g,h;g=0;for(h=a.length;g<h;++g)e=a[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 aw(a,c,d,e,f){a=a.geometries;var g=[],h,k;h=0;for(k=a.length;h<k;++h)g[h]=bw(a[h],c,d,e,f);return g}
-function bw(a,c,d,e,f){var g=a.type,h=cw[g];c="Point"===g||"MultiPoint"===g?h(a,d,e):h(a,c);d=new Q;d.za(Mr(c,!1,f));a.id&&d.Mb(a.id);a.properties&&d.H(a.properties);return d}
-Zv.prototype.of=function(a,c){if("Topology"==a.type){var d,e=null,f=null;a.transform&&(d=a.transform,e=d.scale,f=d.translate);var g=a.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,u=0,y=void 0,A=void 0,F=void 0,A=0,F=n.length;A<F;++A)y=n[A],r+=y[0],u+=y[1],y[0]=r,y[1]=u,dw(y,p,q)}d=[];h=Mb(a.objects);k=0;for(m=h.length;k<m;++k)"GeometryCollection"===h[k].type?(n=h[k],d.push.apply(d,aw(n,g,e,f,c))):(n=h[k],d.push(bw(n,g,e,f,c)));return d}return[]};
-function dw(a,c,d){a[0]=a[0]*c[0]+d[0];a[1]=a[1]*c[1]+d[1]}Zv.prototype.Ha=function(){return this.defaultDataProjection};
-var cw={Point:function(a,c,d){a=a.coordinates;c&&d&&dw(a,c,d);return new D(a)},LineString:function(a,c){var d=$v(a.arcs,c);return new L(d)},Polygon:function(a,c){var d=[],e,f;e=0;for(f=a.arcs.length;e<f;++e)d[e]=$v(a.arcs[e],c);return new E(d)},MultiPoint:function(a,c,d){a=a.coordinates;var e,f;if(c&&d)for(e=0,f=a.length;e<f;++e)dw(a[e],c,d);return new kn(a)},MultiLineString:function(a,c){var d=[],e,f;e=0;for(f=a.arcs.length;e<f;++e)d[e]=$v(a.arcs[e],c);return new O(d)},MultiPolygon:function(a,c){var d=
-[],e,f,g,h,k,m;k=0;for(m=a.arcs.length;k<m;++k){e=a.arcs[k];f=[];g=0;for(h=e.length;g<h;++g)f[g]=$v(e[g],c);d[k]=f}return new P(d)}};function ew(a){a=a?a:{};this.g=a.featureType;this.c=a.featureNS;this.b=a.gmlFormat?a.gmlFormat:new qs;this.f=a.schemaLocation?a.schemaLocation:"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";this.defaultDataProjection=null}w(ew,as);ew.prototype.Lb=function(a,c){var d={featureType:this.g,featureNS:this.c};Yb(d,Kr(this,a,c?c:{}));d=[d];this.b.b["http://www.opengis.net/gml"].featureMember=pp(ds.prototype.fd);(d=U([],this.b.b,a,d,this.b))||(d=[]);return d};
-ew.prototype.i=function(a){if(ap(a))return fw(a);if(dp(a))return U({},gw,a,[]);if(ia(a))return a=np(a),fw(a)};ew.prototype.j=function(a){if(ap(a))return hw(this,a);if(dp(a))return iw(this,a);if(ia(a))return a=np(a),hw(this,a)};function hw(a,c){for(var d=c.firstChild;d;d=d.nextSibling)if(1==d.nodeType)return iw(a,d)}var jw={"http://www.opengis.net/gml":{boundedBy:R(ds.prototype.le,"bounds")}};
-function iw(a,c){var d={},e=ks(c.getAttribute("numberOfFeatures"));d.numberOfFeatures=e;return U(d,jw,c,[],a.b)}
-var kw={"http://www.opengis.net/wfs":{totalInserted:R(js),totalUpdated:R(js),totalDeleted:R(js)}},lw={"http://www.opengis.net/ogc":{FeatureId:pp(function(a){return a.getAttribute("fid")})}},mw={"http://www.opengis.net/wfs":{Feature:function(a,c){xp(lw,a,c)}}},gw={"http://www.opengis.net/wfs":{TransactionSummary:R(function(a,c){return U({},kw,a,c)},"transactionSummary"),InsertResults:R(function(a,c){return U([],mw,a,c)},"insertIds")}};
-function fw(a){for(a=a.firstChild;a;a=a.nextSibling)if(1==a.nodeType)return U({},gw,a,[])}var nw={"http://www.opengis.net/wfs":{PropertyName:S(ms)}};function ow(a,c){var d=To("http://www.opengis.net/ogc","Filter"),e=To("http://www.opengis.net/ogc","FeatureId");d.appendChild(e);e.setAttribute("fid",c);a.appendChild(d)}
-var pw={"http://www.opengis.net/wfs":{Insert:S(function(a,c,d){var e=d[d.length-1],e=To(e.featureNS,e.featureType);a.appendChild(e);qs.prototype.uh(e,c,d)}),Update:S(function(a,c,d){var e=d[d.length-1],f=e.featureType,g=e.featurePrefix,g=g?g:"feature",h=e.featureNS;a.setAttribute("typeName",g+":"+f);mp(a,"http://www.w3.org/2000/xmlns/","xmlns:"+g,h);if(f=c.ha){for(var g=c.O(),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})}yp({node:a,srsName:e.srsName},pw,tp("Property"),
-h,d);ow(a,f)}}),Delete:S(function(a,c,d){var e=d[d.length-1];d=e.featureType;var f=e.featurePrefix,f=f?f:"feature",e=e.featureNS;a.setAttribute("typeName",f+":"+d);mp(a,"http://www.w3.org/2000/xmlns/","xmlns:"+f,e);(c=c.ha)&&ow(a,c)}),Property:S(function(a,c,d){var e=To("http://www.opengis.net/wfs","Name");a.appendChild(e);ms(e,c.name);void 0!==c.value&&null!==c.value&&(e=To("http://www.opengis.net/wfs","Value"),a.appendChild(e),c.value instanceof Ze?qs.prototype.ve(e,c.value,d):ms(e,c.value))}),
-Native:S(function(a,c){c.jo&&a.setAttribute("vendorId",c.jo);void 0!==c.Ln&&a.setAttribute("safeToIgnore",c.Ln);void 0!==c.value&&ms(a,c.value)})}},qw={"http://www.opengis.net/wfs":{Query:S(function(a,c,d){var e=d[d.length-1],f=e.featurePrefix,g=e.featureNS,h=e.propertyNames,k=e.srsName;a.setAttribute("typeName",(f?f+":":"")+c);k&&a.setAttribute("srsName",k);g&&mp(a,"http://www.w3.org/2000/xmlns/","xmlns:"+f,g);c=Vb(e);c.node=a;yp(c,nw,tp("PropertyName"),h,d);if(e=e.bbox)h=To("http://www.opengis.net/ogc",
-"Filter"),c=d[d.length-1].geometryName,f=To("http://www.opengis.net/ogc","BBOX"),h.appendChild(f),g=To("http://www.opengis.net/ogc","PropertyName"),ms(g,c),f.appendChild(g),qs.prototype.ve(f,e,d),a.appendChild(h)})}};
-ew.prototype.l=function(a){var c=To("http://www.opengis.net/wfs","GetFeature");c.setAttribute("service","WFS");c.setAttribute("version","1.1.0");a&&(a.handle&&c.setAttribute("handle",a.handle),a.outputFormat&&c.setAttribute("outputFormat",a.outputFormat),void 0!==a.maxFeatures&&c.setAttribute("maxFeatures",a.maxFeatures),a.resultType&&c.setAttribute("resultType",a.resultType),void 0!==a.startIndex&&c.setAttribute("startIndex",a.startIndex),void 0!==a.count&&c.setAttribute("count",a.count));mp(c,"http://www.w3.org/2001/XMLSchema-instance",
-"xsi:schemaLocation",this.f);var d=a.featureTypes;a=[{node:c,srsName:a.srsName,featureNS:a.featureNS?a.featureNS:this.c,featurePrefix:a.featurePrefix,geometryName:a.geometryName,bbox:a.bbox,propertyNames:a.propertyNames?a.propertyNames:[]}];var e=Vb(a[a.length-1]);e.node=c;yp(e,qw,tp("Query"),d,a);return c};
-ew.prototype.v=function(a,c,d,e){var f=[],g=To("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));mp(g,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.f);a&&(k={node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},Yb(k,h),yp(k,pw,tp("Insert"),a,f));c&&(k={node:g,featureNS:e.featureNS,featureType:e.featureType,
-featurePrefix:e.featurePrefix},Yb(k,h),yp(k,pw,tp("Update"),c,f));d&&yp({node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},pw,tp("Delete"),d,f);e.nativeElements&&yp({node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},pw,tp("Native"),e.nativeElements,f);return g};ew.prototype.sf=function(a){for(a=a.firstChild;a;a=a.nextSibling)if(1==a.nodeType)return this.oe(a);return null};
-ew.prototype.oe=function(a){if(a.firstElementChild&&a.firstElementChild.firstElementChild)for(a=a.firstElementChild.firstElementChild,a=a.firstElementChild;a;a=a.nextElementSibling)if(0!==a.childNodes.length&&(1!==a.childNodes.length||3!==a.firstChild.nodeType)){var c=[{}];this.b.le(a,c);return Ce(c.pop().srsName)}return null};function rw(a){a=a?a:{};this.defaultDataProjection=null;this.b=void 0!==a.splitCollection?a.splitCollection:!1}w(rw,it);function sw(a){a=a.U();return 0===a.length?"":a[0]+" "+a[1]}function tw(a){a=a.U();for(var c=[],d=0,e=a.length;d<e;++d)c.push(a[d][0]+" "+a[d][1]);return c.join(",")}function uw(a){var c=[];a=a.Dd();for(var d=0,e=a.length;d<e;++d)c.push("("+tw(a[d])+")");return c.join(",")}function vw(a){var c=a.W();a=(0,ww[c])(a);c=c.toUpperCase();return 0===a.length?c+" EMPTY":c+"("+a+")"}
-var ww={Point:sw,LineString:tw,Polygon:uw,MultiPoint:function(a){var c=[];a=a.Yd();for(var d=0,e=a.length;d<e;++d)c.push("("+sw(a[d])+")");return c.join(",")},MultiLineString:function(a){var c=[];a=a.Xc();for(var d=0,e=a.length;d<e;++d)c.push("("+tw(a[d])+")");return c.join(",")},MultiPolygon:function(a){var c=[];a=a.Fd();for(var d=0,e=a.length;d<e;++d)c.push("("+uw(a[d])+")");return c.join(",")},GeometryCollection:function(a){var c=[];a=a.Rf();for(var d=0,e=a.length;d<e;++d)c.push(vw(a[d]));return c.join(",")}};
-l=rw.prototype;l.ed=function(a,c){var d=this.gd(a,c);if(d){var e=new Q;e.za(d);return e}return null};l.pf=function(a,c){var d=[],e=this.gd(a,c);this.b&&"GeometryCollection"==e.W()?d=e.f:d=[e];for(var f=[],g=0,h=d.length;g<h;++g)e=new Q,e.za(d[g]),f.push(e);return f};l.gd=function(a,c){var d;d=new xw(new yw(a));d.b=zw(d.a);return(d=Aw(d))?Mr(d,!1,c):null};l.te=function(a,c){var d=a.V();return d?this.ld(d,c):""};
-l.vh=function(a,c){if(1==a.length)return this.te(a[0],c);for(var d=[],e=0,f=a.length;e<f;++e)d.push(a[e].V());d=new $m(d);return this.ld(d,c)};l.ld=function(a,c){return vw(Mr(a,!0,c))};function yw(a){this.a=a;this.b=-1}function Bw(a,c){return"0"<=a&&"9">=a||"."==a&&!(void 0!==c&&c)}
-function zw(a){var c=a.a.charAt(++a.b),d={position:a.b,value:c};if("("==c)d.type=2;else if(","==c)d.type=5;else if(")"==c)d.type=3;else if(Bw(c)||"-"==c){d.type=4;var e,c=a.b,f=!1,g=!1;do{if("."==e)f=!0;else if("e"==e||"E"==e)g=!0;e=a.a.charAt(++a.b)}while(Bw(e,f)||!g&&("e"==e||"E"==e)||g&&("-"==e||"+"==e));a=parseFloat(a.a.substring(c,a.b--));d.value=a}else if("a"<=c&&"z">=c||"A"<=c&&"Z">=c){d.type=1;c=a.b;do e=a.a.charAt(++a.b);while("a"<=e&&"z">=e||"A"<=e&&"Z">=e);a=a.a.substring(c,a.b--).toUpperCase();
-d.value=a}else{if(" "==c||"\t"==c||"\r"==c||"\n"==c)return zw(a);if(""===c)d.type=6;else throw Error("Unexpected character: "+c);}return d}function xw(a){this.a=a}l=xw.prototype;l.match=function(a){if(a=this.b.type==a)this.b=zw(this.a);return a};
-function Aw(a){var c=a.b;if(a.match(1)){var d=c.value;if("GEOMETRYCOLLECTION"==d){a:{if(a.match(2)){c=[];do c.push(Aw(a));while(a.match(5));if(a.match(3)){a=c;break a}}else if(Cw(a)){a=[];break a}throw Error(Dw(a));}return new $m(a)}var e=Ew[d],c=Fw[d];if(!e||!c)throw Error("Invalid geometry type: "+d);a=e.call(a);return new c(a)}throw Error(Dw(a));}l.kf=function(){if(this.match(2)){var a=Gw(this);if(this.match(3))return a}else if(Cw(this))return null;throw Error(Dw(this));};
-l.jf=function(){if(this.match(2)){var a=Hw(this);if(this.match(3))return a}else if(Cw(this))return[];throw Error(Dw(this));};l.lf=function(){if(this.match(2)){var a=Iw(this);if(this.match(3))return a}else if(Cw(this))return[];throw Error(Dw(this));};l.Ym=function(){if(this.match(2)){var a;if(2==this.b.type)for(a=[this.kf()];this.match(5);)a.push(this.kf());else a=Hw(this);if(this.match(3))return a}else if(Cw(this))return[];throw Error(Dw(this));};
-l.Xm=function(){if(this.match(2)){var a=Iw(this);if(this.match(3))return a}else if(Cw(this))return[];throw Error(Dw(this));};l.Zm=function(){if(this.match(2)){for(var a=[this.lf()];this.match(5);)a.push(this.lf());if(this.match(3))return a}else if(Cw(this))return[];throw Error(Dw(this));};function Gw(a){for(var c=[],d=0;2>d;++d){var e=a.b;if(a.match(4))c.push(e.value);else break}if(2==c.length)return c;throw Error(Dw(a));}function Hw(a){for(var c=[Gw(a)];a.match(5);)c.push(Gw(a));return c}
-function Iw(a){for(var c=[a.jf()];a.match(5);)c.push(a.jf());return c}function Cw(a){var c=1==a.b.type&&"EMPTY"==a.b.value;c&&(a.b=zw(a.a));return c}function Dw(a){return"Unexpected `"+a.b.value+"` at position "+a.b.position+" in `"+a.a.a+"`"}var Fw={POINT:D,LINESTRING:L,POLYGON:E,MULTIPOINT:kn,MULTILINESTRING:O,MULTIPOLYGON:P},Ew={POINT:xw.prototype.kf,LINESTRING:xw.prototype.jf,POLYGON:xw.prototype.lf,MULTIPOINT:xw.prototype.Ym,MULTILINESTRING:xw.prototype.Xm,MULTIPOLYGON:xw.prototype.Zm};function Jw(){this.version=void 0}w(Jw,Cv);Jw.prototype.a=function(a){for(a=a.firstChild;a;a=a.nextSibling)if(1==a.nodeType)return this.b(a);return null};Jw.prototype.b=function(a){this.version=a.getAttribute("version").trim();return(a=U({version:this.version},Kw,a,[]))?a:null};function Lw(a,c){return U({},Mw,a,c)}function Nw(a,c){return U({},Ow,a,c)}function Pw(a,c){var d=Lw(a,c);if(d){var e=[ks(a.getAttribute("width")),ks(a.getAttribute("height"))];d.size=e;return d}}
-function Qw(a,c){return U([],Rw,a,c)}
-var Sw=[null,"http://www.opengis.net/wms"],Kw=T(Sw,{Service:R(function(a,c){return U({},Tw,a,c)}),Capability:R(function(a,c){return U({},Uw,a,c)})}),Uw=T(Sw,{Request:R(function(a,c){return U({},Vw,a,c)}),Exception:R(function(a,c){return U([],Ww,a,c)}),Layer:R(function(a,c){return U({},Xw,a,c)})}),Tw=T(Sw,{Name:R(X),Title:R(X),Abstract:R(X),KeywordList:R(Qw),OnlineResource:R(Bv),ContactInformation:R(function(a,c){return U({},Yw,a,c)}),Fees:R(X),AccessConstraints:R(X),LayerLimit:R(js),MaxWidth:R(js),
-MaxHeight:R(js)}),Yw=T(Sw,{ContactPersonPrimary:R(function(a,c){return U({},Zw,a,c)}),ContactPosition:R(X),ContactAddress:R(function(a,c){return U({},$w,a,c)}),ContactVoiceTelephone:R(X),ContactFacsimileTelephone:R(X),ContactElectronicMailAddress:R(X)}),Zw=T(Sw,{ContactPerson:R(X),ContactOrganization:R(X)}),$w=T(Sw,{AddressType:R(X),Address:R(X),City:R(X),StateOrProvince:R(X),PostCode:R(X),Country:R(X)}),Ww=T(Sw,{Format:pp(X)}),Xw=T(Sw,{Name:R(X),Title:R(X),Abstract:R(X),KeywordList:R(Qw),CRS:rp(X),
-EX_GeographicBoundingBox:R(function(a,c){var d=U({},ax,a,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:rp(function(a){var c=[is(a.getAttribute("minx")),is(a.getAttribute("miny")),is(a.getAttribute("maxx")),is(a.getAttribute("maxy"))],d=[is(a.getAttribute("resx")),is(a.getAttribute("resy"))];return{crs:a.getAttribute("CRS"),extent:c,res:d}}),Dimension:rp(function(a){return{name:a.getAttribute("name"),
-units:a.getAttribute("units"),unitSymbol:a.getAttribute("unitSymbol"),"default":a.getAttribute("default"),multipleValues:fs(a.getAttribute("multipleValues")),nearestValue:fs(a.getAttribute("nearestValue")),current:fs(a.getAttribute("current")),values:X(a)}}),Attribution:R(function(a,c){return U({},bx,a,c)}),AuthorityURL:rp(function(a,c){var d=Lw(a,c);if(d)return d.name=a.getAttribute("name"),d}),Identifier:rp(X),MetadataURL:rp(function(a,c){var d=Lw(a,c);if(d)return d.type=a.getAttribute("type"),
-d}),DataURL:rp(Lw),FeatureListURL:rp(Lw),Style:rp(function(a,c){return U({},cx,a,c)}),MinScaleDenominator:R(hs),MaxScaleDenominator:R(hs),Layer:rp(function(a,c){var d=c[c.length-1],e=U({},Xw,a,c);if(e){var f=fs(a.getAttribute("queryable"));void 0===f&&(f=d.queryable);e.queryable=void 0!==f?f:!1;f=ks(a.getAttribute("cascaded"));void 0===f&&(f=d.cascaded);e.cascaded=f;f=fs(a.getAttribute("opaque"));void 0===f&&(f=d.opaque);e.opaque=void 0!==f?f:!1;f=fs(a.getAttribute("noSubsets"));void 0===f&&(f=d.noSubsets);
-e.noSubsets=void 0!==f?f:!1;(f=is(a.getAttribute("fixedWidth")))||(f=d.fixedWidth);e.fixedWidth=f;(f=is(a.getAttribute("fixedHeight")))||(f=d.fixedHeight);e.fixedHeight=f;["Style","CRS","AuthorityURL"].forEach(function(a){if(a in d){var c=Ub(e,a),c=c.concat(d[a]);e[a]=c}});"EX_GeographicBoundingBox BoundingBox Dimension Attribution MinScaleDenominator MaxScaleDenominator".split(" ").forEach(function(a){a in e||(e[a]=d[a])});return e}})}),bx=T(Sw,{Title:R(X),OnlineResource:R(Bv),LogoURL:R(Pw)}),ax=
-T(Sw,{westBoundLongitude:R(hs),eastBoundLongitude:R(hs),southBoundLatitude:R(hs),northBoundLatitude:R(hs)}),Vw=T(Sw,{GetCapabilities:R(Nw),GetMap:R(Nw),GetFeatureInfo:R(Nw)}),Ow=T(Sw,{Format:rp(X),DCPType:rp(function(a,c){return U({},dx,a,c)})}),dx=T(Sw,{HTTP:R(function(a,c){return U({},ex,a,c)})}),ex=T(Sw,{Get:R(Lw),Post:R(Lw)}),cx=T(Sw,{Name:R(X),Title:R(X),Abstract:R(X),LegendURL:rp(Pw),StyleSheetURL:R(Lw),StyleURL:R(Lw)}),Mw=T(Sw,{Format:R(X),OnlineResource:R(Bv)}),Rw=T(Sw,{Keyword:pp(X)});function fx(){this.c="http://mapserver.gis.umn.edu/mapserver";this.b=new ps;this.defaultDataProjection=null}w(fx,as);
-fx.prototype.Lb=function(a,c){var d={featureType:this.featureType,featureNS:this.featureNS};c&&Yb(d,Kr(this,a,c));var e=[d];a.namespaceURI=this.c;var f=Yo(a),d=[];if(0!==a.childNodes.length){if("msGMLOutput"==f)for(var g=0,h=a.childNodes.length;g<h;g++){var k=a.childNodes[g];if(1===k.nodeType){var m=e[0],n=k.localName.replace("_layer","")+"_feature";m.featureType=n;m.featureNS=this.c;var p={};p[n]=pp(this.b.nf,this.b);m=T([m.featureNS,null],p);k.namespaceURI=this.c;(k=U([],m,k,e,this.b))&&hb(d,k)}}"FeatureCollection"==
-f&&(e=U([],this.b.b,a,[{}],this.b))&&(d=e)}return d};function gx(){this.f=new Dv}w(gx,Cv);gx.prototype.a=function(a){for(a=a.firstChild;a;a=a.nextSibling)if(1==a.nodeType)return this.b(a);return null};gx.prototype.b=function(a){this.version=a.getAttribute("version").trim();var c=this.f.b(a);if(!c)return null;c.version=this.version;return(c=U(c,hx,a,[]))?c:null};function ix(a){var c=X(a).split(" ");if(c&&2==c.length)return a=+c[0],c=+c[1],isNaN(a)||isNaN(c)?void 0:[a,c]}
-var jx=[null,"http://www.opengis.net/wmts/1.0"],kx=[null,"http://www.opengis.net/ows/1.1"],hx=T(jx,{Contents:R(function(a,c){return U({},lx,a,c)})}),lx=T(jx,{Layer:rp(function(a,c){return U({},mx,a,c)}),TileMatrixSet:rp(function(a,c){return U({},nx,a,c)})}),mx=T(jx,{Style:rp(function(a,c){var d=U({},ox,a,c);if(d){var e="true"===a.getAttribute("isDefault");d.isDefault=e;return d}}),Format:rp(X),TileMatrixSetLink:rp(function(a,c){return U({},px,a,c)}),ResourceURL:rp(function(a){var c=a.getAttribute("format"),
-d=a.getAttribute("template");a=a.getAttribute("resourceType");var e={};c&&(e.format=c);d&&(e.template=d);a&&(e.resourceType=a);return e})},T(kx,{Title:R(X),Abstract:R(X),WGS84BoundingBox:R(function(a,c){var d=U([],qx,a,c);return 2!=d.length?void 0:Md(d)}),Identifier:R(X)})),ox=T(jx,{LegendURL:rp(function(a){var c={};c.format=a.getAttribute("format");c.href=Bv(a);return c})},T(kx,{Title:R(X),Identifier:R(X)})),px=T(jx,{TileMatrixSet:R(X)}),qx=T(kx,{LowerCorner:pp(ix),UpperCorner:pp(ix)}),nx=T(jx,{WellKnownScaleSet:R(X),
-TileMatrix:rp(function(a,c){return U({},rx,a,c)})},T(kx,{SupportedCRS:R(X),Identifier:R(X)})),rx=T(jx,{TopLeftCorner:R(ix),ScaleDenominator:R(hs),TileWidth:R(js),TileHeight:R(js),MatrixWidth:R(js),MatrixHeight:R(js)},T(kx,{Identifier:R(X)}));var sx=new xe(6378137);function tx(a){jd.call(this);a=a||{};this.a=null;this.f=We;this.c=void 0;B(this,ld("projection"),this.Bk,!1,this);B(this,ld("tracking"),this.Ck,!1,this);void 0!==a.projection&&this.mg(Ce(a.projection));void 0!==a.trackingOptions&&this.mh(a.trackingOptions);this.Ud(void 0!==a.tracking?a.tracking:!1)}w(tx,jd);l=tx.prototype;l.Y=function(){this.Ud(!1);tx.ba.Y.call(this)};l.Bk=function(){var a=this.kg();a&&(this.f=Ge(Ce("EPSG:4326"),a),this.a&&this.set("position",this.f(this.a)))};
-l.Ck=function(){if(cj){var a=this.lg();a&&void 0===this.c?this.c=ba.navigator.geolocation.watchPosition(qa(this.fn,this),qa(this.gn,this),this.Yf()):a||void 0===this.c||(ba.navigator.geolocation.clearWatch(this.c),this.c=void 0)}};
-l.fn=function(a){a=a.coords;this.set("accuracy",a.accuracy);this.set("altitude",null===a.altitude?void 0:a.altitude);this.set("altitudeAccuracy",null===a.altitudeAccuracy?void 0:a.altitudeAccuracy);this.set("heading",null===a.heading?void 0:yb(a.heading));this.a?(this.a[0]=a.longitude,this.a[1]=a.latitude):this.a=[a.longitude,a.latitude];var c=this.f(this.a);this.set("position",c);this.set("speed",null===a.speed?void 0:a.speed);a=Kf(sx,this.a,a.accuracy);a.Ob(this.f);this.set("accuracyGeometry",a);
-this.s()};l.gn=function(a){a.type="error";this.Ud(!1);C(this,a)};l.Ai=function(){return this.get("accuracy")};l.Bi=function(){return this.get("accuracyGeometry")||null};l.Di=function(){return this.get("altitude")};l.Ei=function(){return this.get("altitudeAccuracy")};l.zk=function(){return this.get("heading")};l.Ak=function(){return this.get("position")};l.kg=function(){return this.get("projection")};l.jj=function(){return this.get("speed")};l.lg=function(){return this.get("tracking")};l.Yf=function(){return this.get("trackingOptions")};
-l.mg=function(a){this.set("projection",a)};l.Ud=function(a){this.set("tracking",a)};l.mh=function(a){this.set("trackingOptions",a)};function ux(a,c,d){for(var e=[],f=a(0),g=a(1),h=c(f),k=c(g),m=[g,f],n=[k,h],p=[1,0],q={},r=1E5,u,y,A,F,z;0<--r&&0<p.length;)A=p.pop(),f=m.pop(),h=n.pop(),g=A.toString(),g in q||(e.push(h[0],h[1]),q[g]=!0),F=p.pop(),g=m.pop(),k=n.pop(),z=(A+F)/2,u=a(z),y=c(u),Sa(y[0],y[1],h[0],h[1],k[0],k[1])<d?(e.push(k[0],k[1]),g=F.toString(),q[g]=!0):(p.push(F,z,z,A),n.push(k,y,y,h),m.push(g,u,u,f));return e}function vx(a,c,d,e,f){var g=Ce("EPSG:4326");return ux(function(e){return[a,c+(d-c)*e]},Ve(g,e),f)}
-function wx(a,c,d,e,f){var g=Ce("EPSG:4326");return ux(function(e){return[c+(d-c)*e,a]},Ve(g,e),f)};function xx(a){a=a||{};this.g=this.l=null;this.c=this.j=Infinity;this.f=this.i=-Infinity;this.B=this.u=Infinity;this.ca=this.N=-Infinity;this.T=void 0!==a.targetSize?a.targetSize:100;this.ka=void 0!==a.maxLines?a.maxLines:100;this.b=[];this.a=[];this.I=void 0!==a.strokeStyle?a.strokeStyle:yx;this.v=this.C=void 0;this.A=null;this.setMap(void 0!==a.map?a.map:null)}var yx=new Al({color:"rgba(0,0,0,0.2)"}),zx=[90,45,30,20,10,5,2,1,.5,.2,.1,.05,.01,.005,.002,.001];
-function Ax(a,c,d,e,f,g,h){var k=h;c=vx(c,d,e,a.g,f);k=void 0!==a.b[k]?a.b[k]:new L(null);gn(k,"XY",c);ke(k.R(),g)&&(a.b[h++]=k);return h}function Bx(a,c,d,e,f){var g=f;c=wx(c,a.f,a.c,a.g,d);g=void 0!==a.a[g]?a.a[g]:new L(null);gn(g,"XY",c);ke(g.R(),e)&&(a.a[f++]=g);return f}l=xx.prototype;l.Dk=function(){return this.l};l.Zi=function(){return this.b};l.dj=function(){return this.a};
-l.bg=function(a){var c=a.vectorContext,d=a.frameState,e=d.extent;a=d.viewState;var f=a.center,g=a.projection,h=a.resolution;a=d.pixelRatio;a=h*h/(4*a*a);if(!this.g||!Ue(this.g,g)){var k=Ce("EPSG:4326"),m=g.R(),n=g.i,p=Ye(n,k,g),q=n[2],r=n[1],u=n[0],y=p[3],A=p[2],F=p[1],p=p[0];this.j=n[3];this.c=q;this.i=r;this.f=u;this.u=y;this.B=A;this.N=F;this.ca=p;this.C=Ve(k,g);this.v=Ve(g,k);this.A=this.v(ge(m));this.g=g}k=0;g.c&&(g=g.R(),k=le(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.A[0];g=this.A[1];k=-1;n=Math.pow(this.T*h,2);q=[];r=[];h=0;for(m=zx.length;h<m;++h){u=zx[h]/2;q[0]=d-u;q[1]=g-u;r[0]=d+u;r[1]=g+u;this.C(q,q);this.C(r,r);u=Math.pow(r[0]-q[0],2)+Math.pow(r[1]-q[1],2);if(u<=n)break;k=zx[h]}h=k;if(-1==h)this.b.length=this.a.length=0;else{d=this.v(f);f=d[0];d=d[1];g=this.ka;k=[Math.max(e[0],this.ca),Math.max(e[1],this.N),Math.min(e[2],this.B),Math.min(e[3],this.u)];k=Ye(k,this.g,"EPSG:4326");n=k[3];r=k[1];f=Math.floor(f/h)*h;q=Qa(f,this.f,
-this.c);m=Ax(this,q,r,n,a,e,0);for(k=0;q!=this.f&&k++<g;)q=Math.max(q-h,this.f),m=Ax(this,q,r,n,a,e,m);q=Qa(f,this.f,this.c);for(k=0;q!=this.c&&k++<g;)q=Math.min(q+h,this.c),m=Ax(this,q,r,n,a,e,m);this.b.length=m;d=Math.floor(d/h)*h;f=Qa(d,this.i,this.j);m=Bx(this,f,a,e,0);for(k=0;f!=this.i&&k++<g;)f=Math.max(f-h,this.i),m=Bx(this,f,a,e,m);f=Qa(d,this.i,this.j);for(k=0;f!=this.j&&k++<g;)f=Math.min(f+h,this.j),m=Bx(this,f,a,e,m);this.a.length=m}c.Ia(null,this.I);a=0;for(f=this.b.length;a<f;++a)h=this.b[a],
-c.yb(h,null);a=0;for(f=this.a.length;a<f;++a)h=this.a[a],c.yb(h,null)};l.setMap=function(a){this.l&&(this.l.J("postcompose",this.bg,this),this.l.render());a&&(a.D("postcompose",this.bg,this),a.render());this.l=a};function Cx(a,c,d,e,f,g,h){ik.call(this,a,c,d,0,e);this.l=f;this.a=new Image;g&&(this.a.crossOrigin=g);this.f={};this.c=null;this.state=0;this.i=h}w(Cx,ik);Cx.prototype.b=function(a){if(void 0!==a){var c=v(a);if(c in this.f)return this.f[c];a=Rb(this.f)?this.a:this.a.cloneNode(!1);return this.f[c]=a}return this.a};Cx.prototype.C=function(){this.state=3;this.c.forEach($c);this.c=null;jk(this)};
-Cx.prototype.A=function(){void 0===this.resolution&&(this.resolution=ie(this.extent)/this.a.height);this.state=2;this.c.forEach($c);this.c=null;jk(this)};Cx.prototype.load=function(){0==this.state&&(this.state=1,jk(this),this.c=[Yc(this.a,"error",this.C,!1,this),Yc(this.a,"load",this.A,!1,this)],this.i(this,this.l))};function Dx(a,c,d,e,f){Ah.call(this,a,c);this.i=d;this.a=new Image;e&&(this.a.crossOrigin=e);this.c={};this.g=null;this.l=f}w(Dx,Ah);l=Dx.prototype;l.Y=function(){1==this.state&&Ex(this);Dx.ba.Y.call(this)};l.Qa=function(a){if(void 0!==a){var c=v(a);if(c in this.c)return this.c[c];a=Rb(this.c)?this.a:this.a.cloneNode(!1);return this.c[c]=a}return this.a};l.jb=function(){return this.i};l.Ek=function(){this.state=3;Ex(this);Bh(this)};
-l.Fk=function(){this.state=this.a.naturalWidth&&this.a.naturalHeight?2:4;Ex(this);Bh(this)};l.load=function(){0==this.state&&(this.state=1,Bh(this),this.g=[Yc(this.a,"error",this.Ek,!1,this),Yc(this.a,"load",this.Fk,!1,this)],this.l(this,this.i))};function Ex(a){a.g.forEach($c);a.g=null};function Fx(a,c,d){return function(e,f,g){return d(a,c,e,f,g)}}function Gx(){};function Hx(a,c){dd.call(this);this.b=new sr(this);var d=a;c&&(d=Hg(a));this.b.Pa(d,"dragenter",this.Pm);d!=a&&this.b.Pa(d,"dragover",this.Qm);this.b.Pa(a,"dragover",this.Rm);this.b.Pa(a,"drop",this.Sm)}w(Hx,dd);l=Hx.prototype;l.Uc=!1;l.Y=function(){Hx.ba.Y.call(this);this.b.Tc()};l.Pm=function(a){var c=a.b.dataTransfer;(this.Uc=!(!c||!(c.types&&(0<=Xa(c.types,"Files")||0<=Xa(c.types,"public.file-url"))||c.files&&0<c.files.length)))&&a.preventDefault()};
-l.Qm=function(a){this.Uc&&(a.preventDefault(),a.b.dataTransfer.dropEffect="none")};l.Rm=function(a){if(this.Uc){a.preventDefault();a.c();a=a.b.dataTransfer;try{a.effectAllowed="all"}catch(c){}a.dropEffect="copy"}};l.Sm=function(a){this.Uc&&(a.preventDefault(),a.c(),a=new Bc(a.b),a.type="drop",C(this,a))};/*
- 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 Ix(a,c){this.g=[];this.u=a;this.v=c||null;this.f=this.b=!1;this.c=void 0;this.C=this.B=this.i=!1;this.j=0;this.a=null;this.l=0}Ix.prototype.cancel=function(a){if(this.b)this.c instanceof Ix&&this.c.cancel();else{if(this.a){var c=this.a;delete this.a;a?c.cancel(a):(c.l--,0>=c.l&&c.cancel())}this.u?this.u.call(this.v,this):this.C=!0;this.b||(a=new Jx,Kx(this),Lx(this,!1,a))}};Ix.prototype.A=function(a,c){this.i=!1;Lx(this,a,c)};function Lx(a,c,d){a.b=!0;a.c=d;a.f=!c;Mx(a)}
-function Kx(a){if(a.b){if(!a.C)throw new Nx;a.C=!1}}Ix.prototype.Lc=function(a){Kx(this);Lx(this,!0,a)};function Ox(a,c,d,e){a.g.push([c,d,e]);a.b&&Mx(a)}Ix.prototype.then=function(a,c,d){var e,f,g=new On(function(a,c){e=a;f=c});Ox(this,e,function(a){a instanceof Jx?g.cancel():f(a)});return g.then(a,c,d)};Bn(Ix);function Px(a){return bb(a.g,function(a){return ka(a[1])})}
-function Mx(a){if(a.j&&a.b&&Px(a)){var c=a.j,d=Qx[c];d&&(ba.clearTimeout(d.ha),delete Qx[c]);a.j=0}a.a&&(a.a.l--,delete a.a);for(var c=a.c,e=d=!1;a.g.length&&!a.i;){var f=a.g.shift(),g=f[0],h=f[1],f=f[2];if(g=a.f?h:g)try{var k=g.call(f||a.v,c);ca(k)&&(a.f=a.f&&(k==c||k instanceof Error),a.c=c=k);if(Cn(c)||"function"===typeof ba.Promise&&c instanceof ba.Promise)e=!0,a.i=!0}catch(m){c=m,a.f=!0,Px(a)||(d=!0)}}a.c=c;e&&(k=qa(a.A,a,!0),e=qa(a.A,a,!1),c instanceof Ix?(Ox(c,k,e),c.B=!0):c.then(k,e));d&&
-(c=new Rx(c),Qx[c.ha]=c,a.j=c.ha)}function Nx(){xa.call(this)}w(Nx,xa);Nx.prototype.message="Deferred has already fired";Nx.prototype.name="AlreadyCalledError";function Jx(){xa.call(this)}w(Jx,xa);Jx.prototype.message="Deferred was canceled";Jx.prototype.name="CanceledError";function Rx(a){this.ha=ba.setTimeout(qa(this.a,this),0);this.b=a}Rx.prototype.a=function(){delete Qx[this.ha];throw this.b;};var Qx={};function Sx(a,c){ca(a.name)?(this.name=a.name,this.code=Tx[a.name]):(this.code=a.code,this.name=Ux(a.code));xa.call(this,Aa("%s %s",this.name,c))}w(Sx,xa);function Ux(a){var c=Qb(Tx,function(c){return a==c});if(!ca(c))throw Error("Invalid code: "+a);return c}var Tx={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 Vx(a,c){wc.call(this,a.type,c)}w(Vx,wc);function Wx(){dd.call(this);this.ab=new FileReader;this.ab.onloadstart=qa(this.b,this);this.ab.onprogress=qa(this.b,this);this.ab.onload=qa(this.b,this);this.ab.onabort=qa(this.b,this);this.ab.onerror=qa(this.b,this);this.ab.onloadend=qa(this.b,this)}w(Wx,dd);Wx.prototype.getError=function(){return this.ab.error&&new Sx(this.ab.error,"reading file")};Wx.prototype.b=function(a){C(this,new Vx(a,this))};Wx.prototype.Y=function(){Wx.ba.Y.call(this);delete this.ab};
-function Xx(a){var c=new Ix;a.Pa("loadend",ra(function(a,c){var f=c.ab.result,g=c.getError();null==f||g?(Kx(a),Lx(a,!1,g)):a.Lc(f);c.Tc()},c,a));return c};function Yx(a){a=a?a:{};Qk.call(this,{handleEvent:re});this.j=a.formatConstructors?a.formatConstructors:[];this.v=a.projection?Ce(a.projection):null;this.f=null;this.a=void 0}w(Yx,Qk);Yx.prototype.Y=function(){this.a&&$c(this.a);Yx.ba.Y.call(this)};Yx.prototype.i=function(a){a=a.b.dataTransfer.files;var c,d,e;c=0;for(d=a.length;c<d;++c){var f=e=a[c],g=new Wx,h=Xx(g);g.ab.readAsText(f,"");Ox(h,ra(this.l,e),null,this)}};
-Yx.prototype.l=function(a,c){var d=this.A,e=this.v;e||(e=d.Z().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.sa(c)}catch(u){m=null}if(m){var k=k.Ha(c),k=Ve(k,e),n,p;n=0;for(p=m.length;n<p;++n){var q=m[n],r=q.V();r&&r.Ob(k);f.push(q)}}}C(this,new Zx($x,this,a,f,e))};Yx.prototype.setMap=function(a){this.a&&($c(this.a),this.a=void 0);this.f&&(vc(this.f),this.f=null);Yx.ba.setMap.call(this,a);a&&(this.f=new Hx(a.a),this.a=B(this.f,"drop",this.i,!1,this))};var $x="addfeatures";
-function Zx(a,c,d,e,f){wc.call(this,a,c);this.features=e;this.file=d;this.projection=f}w(Zx,wc);function ay(a,c){this.x=a;this.y=c}w(ay,Dg);ay.prototype.clone=function(){return new ay(this.x,this.y)};ay.prototype.scale=Dg.prototype.scale;ay.prototype.add=function(a){this.x+=a.x;this.y+=a.y;return this};ay.prototype.rotate=function(a){var c=Math.cos(a);a=Math.sin(a);var d=this.y*c+this.x*a;this.x=this.x*c-this.y*a;this.y=d;return this};function by(a){a=a?a:{};cl.call(this,{handleDownEvent:cy,handleDragEvent:dy,handleUpEvent:ey});this.l=a.condition?a.condition:$k;this.a=this.f=void 0;this.i=0;this.v=a.duration?a.duration:400}w(by,cl);
-function dy(a){if(bl(a)){var c=a.map,d=c.Ea();a=a.pixel;a=new ay(a[0]-d[0]/2,d[1]/2-a[1]);d=Math.atan2(a.y,a.x);a=Math.sqrt(a.x*a.x+a.y*a.y);var e=c.Z();c.render();if(void 0!==this.f){var f=d-this.f;Rk(c,e,e.va()-f)}this.f=d;void 0!==this.a&&(d=this.a*(e.aa()/a),Tk(c,e,d));void 0!==this.a&&(this.i=this.a/a);this.a=a}}
-function ey(a){if(!bl(a))return!0;a=a.map;var c=a.Z();Uf(c,-1);var d=this.i-1,e=c.va(),e=c.constrainRotation(e,0);Rk(a,c,e,void 0,void 0);var e=c.aa(),f=this.v,e=c.constrainResolution(e,0,d);Tk(a,c,e,void 0,f);this.i=0;return!1}function cy(a){return bl(a)&&this.l(a)?(Uf(a.map.Z(),1),this.a=this.f=void 0,!0):!1};function fy(a,c){wc.call(this,a);this.feature=c}w(fy,wc);
-function gy(a){cl.call(this,{handleDownEvent:hy,handleEvent:iy,handleUpEvent:jy});this.da=null;this.T=!1;this.nb=a.source?a.source:null;this.lb=a.features?a.features:null;this.Dh=a.snapTolerance?a.snapTolerance:12;this.$=a.type;this.f=ky(this.$);this.Na=a.minPoints?a.minPoints:this.f===ly?3:2;this.wa=a.maxPoints?a.maxPoints:Infinity;var c=a.geometryFunction;if(!c)if("Circle"===this.$)c=function(a,c){var d=c?c:new Ym([NaN,NaN]);d.uf(a[0],Math.sqrt(wd(a[0],a[1])));return d};else{var d,c=this.f;c===
-my?d=D:c===ny?d=L:c===ly&&(d=E);c=function(a,c){var g=c;g?g.ja(a):g=new d(a);return g}}this.B=c;this.I=this.v=this.a=this.N=this.i=this.l=null;this.gi=a.clickTolerance?a.clickTolerance*a.clickTolerance:36;this.fa=new H({source:new V({useSpatialIndex:!1,wrapX:a.wrapX?a.wrapX:!1}),style:a.style?a.style:oy()});this.mb=a.geometryName;this.Wh=a.condition?a.condition:Zk;this.ra=a.freehandCondition?a.freehandCondition:$k;B(this,ld("active"),this.sh,!1,this)}w(gy,cl);
-function oy(){var a=Ll();return function(c){return a[c.V().W()]}}l=gy.prototype;l.setMap=function(a){gy.ba.setMap.call(this,a);this.sh()};function iy(a){var c=!this.T;this.T&&a.type===ak?(py(this,a),c=!1):a.type===Zj?c=qy(this,a):a.type===Tj&&(c=!1);return dl.call(this,a)&&c}function hy(a){if(this.Wh(a))return this.da=a.pixel,!0;if(this.f!==ny&&this.f!==ly||!this.ra(a))return!1;this.da=a.pixel;this.T=!0;this.l||ry(this,a);return!0}
-function jy(a){this.T=!1;var c=this.da,d=a.pixel,e=c[0]-d[0],c=c[1]-d[1],d=!0;e*e+c*c<=this.gi&&(qy(this,a),this.l?this.f===sy?this.Vc():ty(this,a)?this.Vc():py(this,a):(ry(this,a),this.f===my&&this.Vc()),d=!1);return d}
-function qy(a,c){if(a.l){var d=c.coordinate,e=a.i.V(),f;a.f===my?f=a.a:a.f===ly?(f=a.a[0],f=f[f.length-1],ty(a,c)&&(d=a.l.slice())):(f=a.a,f=f[f.length-1]);f[0]=d[0];f[1]=d[1];a.B(a.a,e);a.N&&a.N.V().ja(d);e instanceof E&&a.f!==ly?(a.v||(a.v=new Q(new L(null))),e=e.Tf(0),d=a.v.V(),gn(d,e.a,e.o)):a.I&&(d=a.v.V(),d.ja(a.I));uy(a)}else d=c.coordinate.slice(),a.N?a.N.V().ja(d):(a.N=new Q(new D(d)),uy(a));return!0}
-function ty(a,c){var d=!1;if(a.i){var e=!1,f=[a.l];a.f===ny?e=a.a.length>a.Na:a.f===ly&&(e=a.a[0].length>a.Na,f=[a.a[0][0],a.a[0][a.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.Ba(k),n=c.pixel,d=n[0]-m[0],m=n[1]-m[1],n=a.T&&a.ra(c)?1:a.Dh;if(d=Math.sqrt(d*d+m*m)<=n){a.l=k;break}}}return d}
-function ry(a,c){var d=c.coordinate;a.l=d;a.f===my?a.a=d.slice():a.f===ly?(a.a=[[d.slice(),d.slice()]],a.I=a.a[0]):(a.a=[d.slice(),d.slice()],a.f===sy&&(a.I=a.a));a.I&&(a.v=new Q(new L(a.I)));d=a.B(a.a);a.i=new Q;a.mb&&a.i.Dc(a.mb);a.i.za(d);uy(a);C(a,new fy("drawstart",a.i))}
-function py(a,c){var d=c.coordinate,e=a.i.V(),f,g;if(a.f===ny)a.l=d.slice(),g=a.a,g.push(d.slice()),f=g.length>a.wa,a.B(g,e);else if(a.f===ly){g=a.a[0];g.push(d.slice());if(f=g.length>a.wa)a.l=g[0];a.B(a.a,e)}uy(a);f&&a.Vc()}l.Dn=function(){var a=this.i.V(),c,d;this.f===ny?(c=this.a,c.splice(-2,1),this.B(c,a)):this.f===ly&&(c=this.a[0],c.splice(-2,1),d=this.v.V(),d.ja(c),this.B(this.a,a));0===c.length&&(this.l=null);uy(this)};
-l.Vc=function(){var a=vy(this),c=this.a,d=a.V();this.f===ny?(c.pop(),this.B(c,d)):this.f===ly&&(c[0].pop(),c[0].push(c[0][0]),this.B(c,d));"MultiPoint"===this.$?a.za(new kn([c])):"MultiLineString"===this.$?a.za(new O([c])):"MultiPolygon"===this.$&&a.za(new P([c]));C(this,new fy("drawend",a));this.lb&&this.lb.push(a);this.nb&&this.nb.yc(a)};function vy(a){a.l=null;var c=a.i;c&&(a.i=null,a.N=null,a.v=null,a.fa.ea().clear(!0));return c}
-l.dl=function(a){var c=a.V();this.i=a;this.a=c.U();a=this.a[this.a.length-1];this.l=a.slice();this.a.push(a.slice());uy(this);C(this,new fy("drawstart",this.i))};l.ic=qe;function uy(a){var c=[];a.i&&c.push(a.i);a.v&&c.push(a.v);a.N&&c.push(a.N);a=a.fa.ea();a.clear(!0);a.Nb(c)}l.sh=function(){var a=this.A,c=this.c();a&&c||vy(this);this.fa.setMap(c?a:null)};
-function ky(a){var c;"Point"===a||"MultiPoint"===a?c=my:"LineString"===a||"MultiLineString"===a?c=ny:"Polygon"===a||"MultiPolygon"===a?c=ly:"Circle"===a&&(c=sy);return c}var my="Point",ny="LineString",ly="Polygon",sy="Circle";function wy(a,c,d){wc.call(this,a);this.features=c;this.mapBrowserPointerEvent=d}w(wy,wc);
-function xy(a){cl.call(this,{handleDownEvent:yy,handleDragEvent:zy,handleEvent:Ay,handleUpEvent:By});this.wa=a.deleteCondition?a.deleteCondition:we(Zk,Yk);this.ra=this.f=null;this.da=[0,0];this.B=this.T=!1;this.a=new Ep;this.N=void 0!==a.pixelTolerance?a.pixelTolerance:10;this.l=this.fa=!1;this.i=null;this.I=new H({source:new V({useSpatialIndex:!1,wrapX:!!a.wrapX}),style:a.style?a.style:Cy(),updateWhileAnimating:!0,updateWhileInteracting:!0});this.$={Point:this.kl,LineString:this.sg,LinearRing:this.sg,
-Polygon:this.ll,MultiPoint:this.il,MultiLineString:this.hl,MultiPolygon:this.jl,GeometryCollection:this.gl};this.v=a.features;this.v.forEach(this.ef,this);B(this.v,"add",this.el,!1,this);B(this.v,"remove",this.fl,!1,this)}w(xy,cl);l=xy.prototype;l.ef=function(a){var c=a.V();c.W()in this.$&&this.$[c.W()].call(this,a,c);(c=this.A)&&Dy(this,this.da,c);B(a,"change",this.rg,!1,this)};function Ey(a,c){a.B||(a.B=!0,C(a,new wy("modifystart",a.v,c)))}
-function Fy(a,c){Gy(a,c);a.f&&0===a.v.Gb()&&(a.I.ea().dc(a.f),a.f=null);Zc(c,"change",a.rg,!1,a)}function Gy(a,c){var d=a.a,e=[];d.forEach(function(a){c===a.feature&&e.push(a)});for(var f=e.length-1;0<=f;--f)d.remove(e[f])}l.setMap=function(a){this.I.setMap(a);xy.ba.setMap.call(this,a)};l.el=function(a){this.ef(a.element)};l.rg=function(a){this.l||(a=a.target,Fy(this,a),this.ef(a))};l.fl=function(a){Fy(this,a.element)};
-l.kl=function(a,c){var d=c.U(),d={feature:a,geometry:c,ia:[d,d]};this.a.qa(c.R(),d)};l.il=function(a,c){var d=c.U(),e,f,g;f=0;for(g=d.length;f<g;++f)e=d[f],e={feature:a,geometry:c,depth:[f],index:f,ia:[e,e]},this.a.qa(c.R(),e)};l.sg=function(a,c){var d=c.U(),e,f,g,h;e=0;for(f=d.length-1;e<f;++e)g=d.slice(e,e+2),h={feature:a,geometry:c,index:e,ia:g},this.a.qa(Md(g),h)};
-l.hl=function(a,c){var d=c.U(),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:a,geometry:c,depth:[h],index:f,ia:m},this.a.qa(Md(m),n)};l.ll=function(a,c){var d=c.U(),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:a,geometry:c,depth:[h],index:f,ia:m},this.a.qa(Md(m),n)};
-l.jl=function(a,c){var d=c.U(),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:a,geometry:c,depth:[h,m],index:f,ia:q},this.a.qa(Md(q),r)};l.gl=function(a,c){var d,e=c.f;for(d=0;d<e.length;++d)this.$[e[d].W()].call(this,a,e[d])};function Hy(a,c){var d=a.f;d?d.V().ja(c):(d=new Q(new D(c)),a.f=d,a.I.ea().yc(d))}function Iy(a,c){return a.index-c.index}
-function yy(a){Dy(this,a.pixel,a.map);this.i=[];this.B=!1;var c=this.f;if(c){var d=[],c=c.V().U(),e=Md([c]),e=Hp(this.a,e),f={};e.sort(Iy);for(var g=0,h=e.length;g<h;++g){var k=e[g],m=k.ia,n=v(k.feature),p=k.depth;p&&(n+="-"+p.join("-"));f[n]||(f[n]=Array(2));if(ud(m[0],c)&&!f[n][0])this.i.push([k,0]),f[n][0]=k;else if(ud(m[1],c)&&!f[n][1]){if("LineString"!==k.geometry.W()&&"MultiLineString"!==k.geometry.W()||!f[n][0]||0!==f[n][0].index)this.i.push([k,1]),f[n][1]=k}else v(m)in this.ra&&!f[n][0]&&
-!f[n][1]&&d.push([k,c])}d.length&&Ey(this,a);for(g=d.length-1;0<=g;--g)this.ak.apply(this,d[g])}return!!this.f}
-function zy(a){this.T=!1;Ey(this,a);a=a.coordinate;for(var c=0,d=this.i.length;c<d;++c){for(var e=this.i[c],f=e[0],g=f.depth,h=f.geometry,k=h.U(),m=f.ia,e=e[1];a.length<h.G;)a.push(0);switch(h.W()){case "Point":k=a;m[0]=m[1]=a;break;case "MultiPoint":k[f.index]=a;m[0]=m[1]=a;break;case "LineString":k[f.index+e]=a;m[e]=a;break;case "MultiLineString":k[g[0]][f.index+e]=a;m[e]=a;break;case "Polygon":k[g[0]][f.index+e]=a;m[e]=a;break;case "MultiPolygon":k[g[1]][g[0]][f.index+e]=a,m[e]=a}f=h;this.l=!0;
-f.ja(k);this.l=!1}Hy(this,a)}function By(a){for(var c,d=this.i.length-1;0<=d;--d)c=this.i[d][0],Fp(this.a,Md(c.ia),c);this.B&&(C(this,new wy("modifyend",this.v,a)),this.B=!1);return!1}
-function Ay(a){if(!(a instanceof Pj))return!0;var c;a.map.Z().c.slice()[1]||a.type!=Zj||this.u||(this.da=a.pixel,Dy(this,a.pixel,a.map));if(this.f&&this.wa(a))if(a.type==Uj&&this.T)c=!0;else{this.f.V();Ey(this,a);c=this.i;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.U(),q=v(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.l=!0,e.ja(f),this.l=!1,f={depth:p.depth,feature:p.feature,geometry:p.geometry,index:n,ia:[g[0].ia[0],g[1].ia[1]]},this.a.qa(Md(f.ia),
-f),Jy(this,h,m,p.depth,-1),this.f&&(this.I.ea().dc(this.f),this.f=null))}c=!0;C(this,new wy("modifyend",this.v,a));this.B=!1}a.type==Uj&&(this.T=!1);return dl.call(this,a)&&!c}
-function Dy(a,c,d){function e(a,c){return xd(f,a.ia)-xd(f,c.ia)}var f=d.xa(c),g=d.xa([c[0]-a.N,c[1]+a.N]),h=d.xa([c[0]+a.N,c[1]-a.N]),g=Md([g,h]),g=Hp(a.a,g);if(0<g.length){g.sort(e);var h=g[0].ia,k=rd(f,h),m=d.Ba(k);if(Math.sqrt(wd(c,m))<=a.N){c=d.Ba(h[0]);d=d.Ba(h[1]);c=wd(m,c);d=wd(m,d);a.fa=Math.sqrt(Math.min(c,d))<=a.N;a.fa&&(k=c>d?h[1]:h[0]);Hy(a,k);d={};d[v(h)]=!0;c=1;for(m=g.length;c<m;++c)if(k=g[c].ia,ud(h[0],k[0])&&ud(h[1],k[1])||ud(h[0],k[1])&&ud(h[1],k[0]))d[v(k)]=!0;else break;a.ra=d;
-return}}a.f&&(a.I.ea().dc(a.f),a.f=null)}
-l.ak=function(a,c){for(var d=a.ia,e=a.feature,f=a.geometry,g=a.depth,h=a.index,k;c.length<f.G;)c.push(0);switch(f.W()){case "MultiLineString":k=f.U();k[g[0]].splice(h+1,0,c);break;case "Polygon":k=f.U();k[g[0]].splice(h+1,0,c);break;case "MultiPolygon":k=f.U();k[g[1]][g[0]].splice(h+1,0,c);break;case "LineString":k=f.U();k.splice(h+1,0,c);break;default:return}this.l=!0;f.ja(k);this.l=!1;k=this.a;k.remove(a);Jy(this,f,h,g,1);var m={ia:[d[0],c],feature:e,geometry:f,depth:g,index:h};k.qa(Md(m.ia),m);
-this.i.push([m,1]);d={ia:[c,d[1]],feature:e,geometry:f,depth:g,index:h+1};k.qa(Md(d.ia),d);this.i.push([d,0]);this.T=!0};function Jy(a,c,d,e,f){Jp(a.a,c.R(),function(a){a.geometry===c&&(void 0===e||void 0===a.depth||ob(a.depth,e))&&a.index>d&&(a.index+=f)})}function Cy(){var a=Ll();return function(){return a.Point}};function Ky(a,c,d,e){wc.call(this,a);this.selected=c;this.deselected=d;this.mapBrowserEvent=e}w(Ky,wc);
-function Ly(a){Qk.call(this,{handleEvent:My});a=a?a:{};this.u=a.condition?a.condition:Yk;this.l=a.addCondition?a.addCondition:qe;this.B=a.removeCondition?a.removeCondition:qe;this.N=a.toggleCondition?a.toggleCondition:$k;this.v=a.multi?a.multi:!1;this.j=a.filter?a.filter:re;var c;if(a.layers)if(ka(a.layers))c=a.layers;else{var d=a.layers;c=function(a){return sb(d,a)}}else c=re;this.i=c;this.a={};this.f=new H({source:new V({useSpatialIndex:!1,features:a.features,wrapX:a.wrapX}),style:a.style?a.style:
-Ny(),updateWhileAnimating:!0,updateWhileInteracting:!0});a=this.f.ea().c;B(a,"add",this.ml,!1,this);B(a,"remove",this.pl,!1,this)}w(Ly,Qk);l=Ly.prototype;l.nl=function(){return this.f.ea().c};l.ol=function(a){a=v(a);return this.a[a]};
-function My(a){if(!this.u(a))return!0;var c=this.l(a),d=this.B(a),e=this.N(a),f=!c&&!d&&!e,g=a.map,h=this.f.ea().c,k=[],m=[],n=!1;if(f)g.Wc(a.pixel,function(a,c){if(this.j(a,c)){m.push(a);var d=v(a);this.a[d]=c;return!this.v}},this,this.i),0<m.length&&1==h.Gb()&&h.item(0)==m[0]||(n=!0,0!==h.Gb()&&(k=Array.prototype.concat(h.a),h.clear()),h.Ze(m),0===m.length?Sb(this.a):0<k.length&&k.forEach(function(a){a=v(a);delete this.a[a]},this));else{g.Wc(a.pixel,function(a,f){if(!sb(h.a,a)){if((c||e)&&this.j(a,
-f)){m.push(a);var g=v(a);this.a[g]=f}}else if(d||e)k.push(a),g=v(a),delete this.a[g]},this,this.i);for(f=k.length-1;0<=f;--f)h.remove(k[f]);h.Ze(m);if(0<m.length||0<k.length)n=!0}n&&C(this,new Ky("select",m,k,a));return Xk(a)}l.setMap=function(a){var c=this.A,d=this.f.ea().c;null===c||d.forEach(c.qh,c);Ly.ba.setMap.call(this,a);this.f.setMap(a);null===a||d.forEach(a.nh,a)};
-function Ny(){var a=Ll();hb(a.Polygon,a.LineString);hb(a.GeometryCollection,a.LineString);return function(c){return a[c.V().W()]}}l.ml=function(a){a=a.element;var c=this.A;null===c||c.nh(a)};l.pl=function(a){a=a.element;var c=this.A;null===c||c.qh(a)};function Oy(a){cl.call(this,{handleEvent:Py,handleDownEvent:re,handleUpEvent:Qy});a=a?a:{};this.l=a.source?a.source:null;this.i=a.features?a.features:null;this.da=[];this.B={};this.N={};this.T={};this.v={};this.I=null;this.f=void 0!==a.pixelTolerance?a.pixelTolerance:10;this.fa=qa(Ry,this);this.a=new Ep;this.$={Point:this.vl,LineString:this.vg,LinearRing:this.vg,Polygon:this.wl,MultiPoint:this.tl,MultiLineString:this.sl,MultiPolygon:this.ul,GeometryCollection:this.rl}}w(Oy,cl);l=Oy.prototype;
-l.ad=function(a,c){var d=void 0!==c?c:!0,e=a.V(),f=this.$[e.W()];if(f){var g=v(a);this.T[g]=e.R(Nd());f.call(this,a,e);d&&(this.N[g]=e.D("change",qa(this.zj,this,a),this),this.B[g]=a.D(ld(a.a),this.ql,this))}};l.xi=function(a){this.ad(a)};l.yi=function(a){this.bd(a)};l.tg=function(a){var c;a instanceof Op?c=a.feature:a instanceof sg&&(c=a.element);this.ad(c)};l.ug=function(a){var c;a instanceof Op?c=a.feature:a instanceof sg&&(c=a.element);this.bd(c)};
-l.ql=function(a){a=a.g;this.bd(a,!0);this.ad(a,!0)};l.zj=function(a){if(this.u){var c=v(a);c in this.v||(this.v[c]=a)}else this.rh(a)};l.bd=function(a,c){var d=void 0!==c?c:!0,e=v(a),f=this.T[e];if(f){var g=this.a,h=[];Jp(g,f,function(c){a===c.feature&&h.push(c)});for(f=h.length-1;0<=f;--f)g.remove(h[f]);d&&($c(this.N[e]),delete this.N[e],$c(this.B[e]),delete this.B[e])}};
-l.setMap=function(a){var c=this.A,d=this.da,e;this.i?e=this.i:this.l&&(e=this.l.zc());c&&(d.forEach(hd),d.length=0,e.forEach(this.yi,this));Oy.ba.setMap.call(this,a);a&&(this.i?(d.push(this.i.D("add",this.tg,this)),d.push(this.i.D("remove",this.ug,this))):this.l&&(d.push(this.l.D("addfeature",this.tg,this)),d.push(this.l.D("removefeature",this.ug,this))),e.forEach(this.xi,this))};l.ic=qe;l.rh=function(a){this.bd(a,!1);this.ad(a,!1)};
-l.rl=function(a,c){var d,e=c.f;for(d=0;d<e.length;++d)this.$[e[d].W()].call(this,a,e[d])};l.vg=function(a,c){var d=c.U(),e,f,g,h;e=0;for(f=d.length-1;e<f;++e)g=d.slice(e,e+2),h={feature:a,ia:g},this.a.qa(Md(g),h)};l.sl=function(a,c){var d=c.U(),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:a,ia:m},this.a.qa(Md(m),n)};l.tl=function(a,c){var d=c.U(),e,f,g;f=0;for(g=d.length;f<g;++f)e=d[f],e={feature:a,ia:[e,e]},this.a.qa(c.R(),e)};
-l.ul=function(a,c){var d=c.U(),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:a,ia:q},this.a.qa(Md(q),r)};l.vl=function(a,c){var d=c.U(),d={feature:a,ia:[d,d]};this.a.qa(c.R(),d)};l.wl=function(a,c){var d=c.U(),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:a,ia:m},this.a.qa(Md(m),n)};
-function Py(a){var c,d,e=a.pixel,f=a.coordinate;c=a.map;var g=c.xa([e[0]-this.f,e[1]+this.f]);d=c.xa([e[0]+this.f,e[1]-this.f]);var g=Md([g,d]),h=Hp(this.a,g),k=!1,g=!1,m=null;d=null;0<h.length&&(this.I=f,h.sort(this.fa),h=h[0].ia,m=rd(f,h),d=c.Ba(m),Math.sqrt(wd(e,d))<=this.f&&(g=!0,e=c.Ba(h[0]),f=c.Ba(h[1]),e=wd(d,e),f=wd(d,f),k=Math.sqrt(Math.min(e,f))<=this.f))&&(m=e>f?h[1]:h[0],d=c.Ba(m),d=[Math.round(d[0]),Math.round(d[1])]);c=m;g&&(a.coordinate=c.slice(0,2),a.pixel=d);return dl.call(this,a)}
-function Qy(){var a=Mb(this.v);a.length&&(a.forEach(this.rh,this),this.v={});return!1}function Ry(a,c){return xd(this.I,a.ia)-xd(this.I,c.ia)};function Sy(a){cl.call(this,{handleDownEvent:Ty,handleDragEvent:Uy,handleMoveEvent:Vy,handleUpEvent:Wy});this.l=void 0;this.a=null;this.f=void 0!==a.features?a.features:null;this.i=null}w(Sy,cl);function Ty(a){this.i=Xy(this,a.pixel,a.map);return!this.a&&this.i?(this.a=a.coordinate,Vy.call(this,a),!0):!1}function Wy(a){return this.a?(this.a=null,Vy.call(this,a),!0):!1}
-function Uy(a){if(this.a){a=a.coordinate;var c=a[0]-this.a[0],d=a[1]-this.a[1];if(this.f)this.f.forEach(function(a){var e=a.V();e.wc(c,d);a.za(e)});else if(this.i){var e=this.i.V();e.wc(c,d);this.i.za(e)}this.a=a}}
-function Vy(a){var c=a.map.uc();if(a=a.map.Wc(a.pixel,function(a){return a})){var d=!1;this.f&&sb(this.f.a,a)&&(d=!0);this.l=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.l?this.l:"",this.l=void 0}function Xy(a,c,d){var e=null;c=d.Wc(c,function(a){return a});a.f&&sb(a.f.a,c)&&(e=c);return e};function Y(a){a=a?a:{};var c=Vb(a);delete c.gradient;delete c.radius;delete c.blur;delete c.shadow;delete c.weight;H.call(this,c);this.f=null;this.$=void 0!==a.shadow?a.shadow:250;this.I=void 0;this.u=null;B(this,ld("gradient"),this.Aj,!1,this);this.hh(a.gradient?a.gradient:Yy);this.dh(void 0!==a.blur?a.blur:15);this.xg(void 0!==a.radius?a.radius:8);B(this,[ld("blur"),ld("radius")],this.cg,!1,this);this.cg();var d=a.weight?a.weight:"weight",e;ia(d)?e=function(a){return a.get(d)}:e=d;this.g(qa(function(a){a=
-e(a);a=void 0!==a?Qa(a,0,1):1;var c=255*a|0,d=this.u[c];d||(d=[new Gl({image:new yk({opacity:a,src:this.I})})],this.u[c]=d);return d},this));this.set("renderOrder",null);B(this,"render",this.Sj,!1,this)}w(Y,H);var Yy=["#00f","#0ff","#0f0","#ff0","#f00"];l=Y.prototype;l.Pf=function(){return this.get("blur")};l.Sf=function(){return this.get("gradient")};l.wg=function(){return this.get("radius")};
-l.Aj=function(){for(var a=this.Sf(),c=Ri(1,256),d=c.createLinearGradient(0,0,1,256),e=1/(a.length-1),f=0,g=a.length;f<g;++f)d.addColorStop(f*e,a[f]);c.fillStyle=d;c.fillRect(0,0,1,256);this.f=c.getImageData(0,0,1,256).data};l.cg=function(){var a=this.wg(),c=this.Pf(),d=a+c+1,e=2*d,e=Ri(e,e);e.shadowOffsetX=e.shadowOffsetY=this.$;e.shadowBlur=c;e.shadowColor="#000";e.beginPath();c=d-this.$;e.arc(c,c,a,0,2*Math.PI,!0);e.fill();this.I=e.canvas.toDataURL();this.u=Array(256);this.s()};
-l.Sj=function(a){a=a.context;var c=a.canvas,c=a.getImageData(0,0,c.width,c.height),d=c.data,e,f,g;e=0;for(f=d.length;e<f;e+=4)if(g=4*d[e+3])d[e]=this.f[g],d[e+1]=this.f[g+1],d[e+2]=this.f[g+2];a.putImageData(c,0,0)};l.dh=function(a){this.set("blur",a)};l.hh=function(a){this.set("gradient",a)};l.xg=function(a){this.set("radius",a)};function Zy(a,c){var d=c||{},e=d.document||document,f=Pg("SCRIPT"),g={bh:f,jc:void 0},h=new Ix($y,g),k=null,m=null!=d.timeout?d.timeout:5E3;0<m&&(k=window.setTimeout(function(){az(f,!0);var c=new bz(cz,"Timeout reached for loading script "+a);Kx(h);Lx(h,!1,c)},m),g.jc=k);f.onload=f.onreadystatechange=function(){f.readyState&&"loaded"!=f.readyState&&"complete"!=f.readyState||(az(f,d.si||!1,k),h.Lc(null))};f.onerror=function(){az(f,!0,k);var c=new bz(dz,"Error while loading script "+a);Kx(h);Lx(h,!1,
-c)};g=d.attributes||{};Yb(g,{type:"text/javascript",charset:"UTF-8",src:a});Jg(f,g);ez(e).appendChild(f);return h}function ez(a){var c=a.getElementsByTagName("HEAD");return c&&0!=c.length?c[0]:a.documentElement}function $y(){if(this&&this.bh){var a=this.bh;a&&"SCRIPT"==a.tagName&&az(a,!0,this.jc)}}function az(a,c,d){null!=d&&ba.clearTimeout(d);a.onload=da;a.onerror=da;a.onreadystatechange=da;c&&window.setTimeout(function(){Tg(a)},0)}var dz=0,cz=1;
-function bz(a,c){var d="Jsloader error (code #"+a+")";c&&(d+=": "+c);xa.call(this,d);this.code=a}w(bz,xa);function fz(a,c){this.a=new nt(a);this.b=c?c:"callback";this.jc=5E3}var gz=0;function hz(a,c,d,e){c=c||null;var f="_"+(gz++).toString(36)+sa().toString(36);ba._callbacks_||(ba._callbacks_={});var g=a.a.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)]);Gt(k.c,m,n)}d&&(ba._callbacks_[f]=iz(f,d),d=a.b,h="_callbacks_."+f,ga(h)||(h=[String(h)]),Gt(g.c,d,h));a=Zy(g.toString(),{timeout:a.jc,si:!0});Ox(a,null,jz(f,c,e),void 0)}
-fz.prototype.cancel=function(a){a&&(a.ti&&a.ti.cancel(),a.ha&&kz(a.ha,!1))};function jz(a,c,d){return function(){kz(a,!1);d&&d(c)}}function iz(a,c){return function(d){kz(a,!0);c.apply(void 0,arguments)}}function kz(a,c){ba._callbacks_[a]&&(c?delete ba._callbacks_[a]:ba._callbacks_[a]=da)};function lz(a,c){var d=/\{z\}/g,e=/\{x\}/g,f=/\{y\}/g,g=/\{-y\}/g;return function(h){if(h)return a.replace(d,h[0].toString()).replace(e,h[1].toString()).replace(f,function(){return(-h[2]-1).toString()}).replace(g,function(){return(og(c.a?c.a[h[0]]:null)+h[2]).toString()})}}function mz(a,c){for(var d=a.length,e=Array(d),f=0;f<d;++f)e[f]=lz(a[f],c);return nz(e)}function nz(a){return 1===a.length?a[0]:function(c,d,e){if(c)return a[wb((c[1]<<c[0])+c[2],a.length)](c,d,e)}}function oz(){}
-function pz(a){var c=[],d=/\{(\d)-(\d)\}/.exec(a)||/\{([a-z])-([a-z])\}/.exec(a);if(d){var e=d[2].charCodeAt(0),f;for(f=d[1].charCodeAt(0);f<=e;++f)c.push(a.replace(d[0],String.fromCharCode(f)))}else c.push(a);return c};function qz(a){Rh.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,opaque:a.opaque,projection:a.projection,state:void 0!==a.state?a.state:void 0,tileGrid:a.tileGrid,tilePixelRatio:a.tilePixelRatio,wrapX:a.wrapX});this.tileUrlFunction=void 0!==a.tileUrlFunction?a.tileUrlFunction:oz;this.crossOrigin=void 0!==a.crossOrigin?a.crossOrigin:null;this.tileLoadFunction=void 0!==a.tileLoadFunction?a.tileLoadFunction:rz;this.tileClass=void 0!==a.tileClass?a.tileClass:Dx}w(qz,Rh);
-function rz(a,c){a.Qa().src=c}l=qz.prototype;l.Sb=function(a,c,d,e,f){var g=this.hb(a,c,d);if(xh(this.a,g))return this.a.get(g);a=[a,c,d];e=(c=Uh(this,a,f))?this.tileUrlFunction(c,e,f):void 0;e=new this.tileClass(a,void 0!==e?0:4,void 0!==e?e:"",this.crossOrigin,this.tileLoadFunction);B(e,"change",this.$l,!1,this);this.a.set(g,e);return e};l.Ya=function(){return this.tileLoadFunction};l.Za=function(){return this.tileUrlFunction};
-l.$l=function(a){a=a.target;switch(a.state){case 1:C(this,new Vh("tileloadstart",a));break;case 2:C(this,new Vh("tileloadend",a));break;case 3:C(this,new Vh("tileloaderror",a))}};l.cb=function(a){this.a.clear();this.tileLoadFunction=a;this.s()};l.Ga=function(a){this.a.clear();this.tileUrlFunction=a;this.s()};l.yf=function(a,c,d){a=this.hb(a,c,d);xh(this.a,a)&&this.a.get(a)};function sz(a){qz.call(this,{crossOrigin:"anonymous",opaque:!0,projection:Ce("EPSG:3857"),state:"loading",tileLoadFunction:a.tileLoadFunction,wrapX:void 0!==a.wrapX?a.wrapX:!0});this.g=void 0!==a.culture?a.culture:"en-us";this.f=void 0!==a.maxZoom?a.maxZoom:-1;var c=new nt("https://dev.virtualearth.net/REST/v1/Imagery/Metadata/"+a.imagerySet);hz(new fz(c,"jsonp"),{include:"ImageryProviders",uriScheme:"https",key:a.key},qa(this.l,this))}w(sz,qz);var tz=new rg({html:'<a class="ol-attribution-bing-tos" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'});
-sz.prototype.l=function(a){if(200!=a.statusCode||"OK"!=a.statusDescription||"ValidCredentials"!=a.authenticationResultCode||1!=a.resourceSets.length||1!=a.resourceSets[0].resources.length)Eh(this,"error");else{var c=a.brandLogoUri;-1==c.indexOf("https")&&(c=c.replace("http","https"));var d=a.resourceSets[0].resources[0],e=-1==this.f?d.zoomMax:this.f;a=ig(this.i);var f=Oh({extent:a,minZoom:d.zoomMin,maxZoom:e,tileSize:d.imageWidth==d.imageHeight?d.imageWidth:[d.imageWidth,d.imageHeight]});this.tileGrid=
-f;var g=this.g;this.tileUrlFunction=nz(d.imageUrlSubdomains.map(function(a){var c=[0,0,0],e=d.imageUrl.replace("{subdomain}",a).replace("{culture}",g);return function(a){if(a)return cg(a[0],a[1],-a[2]-1,c),e.replace("{quadkey}",eg(c))}}));if(d.imageryProviders){var h=Ge(Ce("EPSG:4326"),this.i);a=d.imageryProviders.map(function(a){var c=a.attribution,d={};a.coverageAreas.forEach(function(a){var c=a.zoomMin,g=Math.min(a.zoomMax,e);a=a.bbox;a=oe([a[1],a[0],a[3],a[2]],h);var k,m;for(k=c;k<=g;++k)m=k.toString(),
-c=kg(f,a,k),m in d?d[m].push(c):d[m]=[c]});return new rg({html:c,tileRanges:d})});a.push(tz);this.la(a)}this.T=c;Eh(this,"ready")}};function Z(a){V.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,projection:a.projection});this.I=void 0;this.da=void 0!==a.distance?a.distance:20;this.B=[];this.u=a.source;this.u.D("change",Z.prototype.ra,this)}w(Z,V);Z.prototype.fa=function(){return this.u};Z.prototype.Wb=function(a,c,d){this.u.Wb(a,c,d);c!==this.I&&(this.clear(),this.I=c,uz(this),this.Nb(this.B))};Z.prototype.ra=function(){this.clear();uz(this);this.Nb(this.B);this.s()};
-function uz(a){if(void 0!==a.I){a.B.length=0;for(var c=Nd(),d=a.da*a.I,e=a.u.zc(),f={},g=0,h=e.length;g<h;g++){var k=e[g];Ob(f,v(k).toString())||(k=k.V().U(),Yd(k,c),Rd(c,d,c),k=a.u.Ad(c),k=k.filter(function(a){a=v(a).toString();return a in f?!1:f[a]=!0}),a.B.push(vz(k)))}}}function vz(a){for(var c=a.length,d=[0,0],e=0;e<c;e++){var f=a[e].V().U();qd(d,f)}c=1/c;d[0]*=c;d[1]*=c;d=new Q(new D(d));d.set("features",a);return d};function wz(a){sn.call(this,{projection:a.projection,resolutions:a.resolutions});this.$=void 0!==a.crossOrigin?a.crossOrigin:null;this.g=void 0!==a.displayDpi?a.displayDpi:96;this.f=void 0!==a.params?a.params:{};var c;void 0!==a.url?c=Fx(a.url,this.f,qa(this.Gl,this)):c=Gx;this.I=c;this.a=void 0!==a.imageLoadFunction?a.imageLoadFunction:yn;this.da=void 0!==a.hidpi?a.hidpi:!0;this.X=void 0!==a.metersPerUnit?a.metersPerUnit:1;this.u=void 0!==a.ratio?a.ratio:1;this.fa=void 0!==a.useOverlay?a.useOverlay:
-!1;this.c=null;this.B=0}w(wz,sn);l=wz.prototype;l.Fl=function(){return this.f};l.cc=function(a,c,d,e){c=tn(this,c);d=this.da?d:1;var f=this.c;if(f&&this.B==this.b&&f.aa()==c&&f.g==d&&Wd(f.R(),a))return f;1!=this.u&&(a=a.slice(),ne(a,this.u));e=this.I(a,[le(a)/c*d,ie(a)/c*d],e);void 0!==e?(f=new Cx(a,c,d,this.j,e,this.$,this.a),B(f,"change",this.l,!1,this)):f=null;this.c=f;this.B=this.b;return f};l.El=function(){return this.a};l.Il=function(a){Yb(this.f,a);this.s()};
-l.Gl=function(a,c,d,e){var f=ge(d),g=this.X,h=le(d);d=ie(d);var k=e[0],m=e[1],n=.0254/this.g;e={OPERATION:this.fa?"GETDYNAMICMAPOVERLAYIMAGE":"GETMAPIMAGE",VERSION:"2.0.0",LOCALE:"en",CLIENTAGENT:"ol.source.ImageMapGuide source",CLIP:"1",SETDISPLAYDPI:this.g,SETDISPLAYWIDTH:Math.round(e[0]),SETDISPLAYHEIGHT:Math.round(e[1]),SETVIEWSCALE:m*h>k*d?h*g/(k*n):d*g/(m*n),SETVIEWCENTERX:f[0],SETVIEWCENTERY:f[1]};Yb(e,c);return so(uo([a],e))};l.Hl=function(a){this.c=null;this.a=a;this.s()};function xz(a){var c=void 0!==a.attributions?a.attributions:null,d=a.imageExtent,e,f;void 0!==a.imageSize&&(e=ie(d)/a.imageSize[1],f=[e]);var g=void 0!==a.crossOrigin?a.crossOrigin:null,h=void 0!==a.imageLoadFunction?a.imageLoadFunction:yn;sn.call(this,{attributions:c,logo:a.logo,projection:Ce(a.projection),resolutions:f});this.a=new Cx(d,e,1,c,a.url,g,h);B(this.a,"change",this.l,!1,this)}w(xz,sn);xz.prototype.cc=function(a){return ke(a,this.a.R())?this.a:null};function yz(a){a=a||{};sn.call(this,{attributions:a.attributions,logo:a.logo,projection:a.projection,resolutions:a.resolutions});this.da=void 0!==a.crossOrigin?a.crossOrigin:null;this.f=a.url;this.u=void 0!==a.imageLoadFunction?a.imageLoadFunction:yn;this.c=a.params;this.g=!0;zz(this);this.$=a.serverType;this.fa=void 0!==a.hidpi?a.hidpi:!0;this.a=null;this.B=[0,0];this.X=0;this.I=void 0!==a.ratio?a.ratio:1.5}w(yz,sn);var Az=[101,101];l=yz.prototype;
-l.Ol=function(a,c,d,e){if(void 0!==this.f){var f=he(a,c,0,Az),g={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:this.c.LAYERS};Yb(g,this.c,e);e=Math.floor((f[3]-a[1])/c);g[this.g?"I":"X"]=Math.floor((a[0]-f[0])/c);g[this.g?"J":"Y"]=e;return Bz(this,f,Az,1,Ce(d),g)}};l.Ql=function(){return this.c};
-l.cc=function(a,c,d,e){if(void 0===this.f)return null;c=tn(this,c);1==d||this.fa&&void 0!==this.$||(d=1);a=a.slice();var f=(a[0]+a[2])/2,g=(a[1]+a[3])/2;if(1!=this.I){var h=this.I*le(a)/2,k=this.I*ie(a)/2;a[0]=f-h;a[1]=g-k;a[2]=f+h;a[3]=g+k}var m=c/d,h=Math.ceil(le(a)/m),k=Math.ceil(ie(a)/m);a[0]=f-m*h/2;a[2]=f+m*h/2;a[1]=g-m*k/2;a[3]=g+m*k/2;if((f=this.a)&&this.X==this.b&&f.aa()==c&&f.g==d&&Wd(f.R(),a))return f;f={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};
-Yb(f,this.c);this.B[0]=h;this.B[1]=k;e=Bz(this,a,this.B,d,e,f);this.a=new Cx(a,c,d,this.j,e,this.da,this.u);this.X=this.b;B(this.a,"change",this.l,!1,this);return this.a};l.Pl=function(){return this.u};
-function Bz(a,c,d,e,f,g){g[a.g?"CRS":"SRS"]=f.b;"STYLES"in a.c||(g.STYLES=new String(""));if(1!=e)switch(a.$){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;a.g&&"ne"==d.substr(0,2)?h=[c[1],c[0],c[3],c[2]]:h=c;g.BBOX=h.join(",");return so(uo([a.f],g))}l.Rl=function(){return this.f};
-l.Sl=function(a){this.a=null;this.u=a;this.s()};l.Tl=function(a){a!=this.f&&(this.f=a,this.a=null,this.s())};l.Ul=function(a){Yb(this.c,a);zz(this);this.a=null;this.s()};function zz(a){a.g=0<=Oa(Tb(a.c,"VERSION","1.3.0"),"1.3")};function Cz(a){var c=void 0!==a.projection?a.projection:"EPSG:3857",d=void 0!==a.tileGrid?a.tileGrid:Oh({extent:ig(c),maxZoom:a.maxZoom,tileSize:a.tileSize});this.l=null;qz.call(this,{attributions:a.attributions,crossOrigin:a.crossOrigin,logo:a.logo,projection:c,tileGrid:d,tileLoadFunction:a.tileLoadFunction,tilePixelRatio:a.tilePixelRatio,tileUrlFunction:oz,wrapX:void 0!==a.wrapX?a.wrapX:!0});void 0!==a.tileUrlFunction?this.Ga(a.tileUrlFunction):void 0!==a.urls?(a=a.urls,this.Ga(mz(a,this.tileGrid)),
-this.l=a):void 0!==a.url&&this.f(a.url)}w(Cz,qz);Cz.prototype.g=function(){return this.l};Cz.prototype.f=function(a){this.Ga(mz(pz(a),this.tileGrid));this.l=[a]};function Dz(a){a=a||{};var c;void 0!==a.attributions?c=a.attributions:c=[Ez];Cz.call(this,{attributions:c,crossOrigin:void 0!==a.crossOrigin?a.crossOrigin:"anonymous",opaque:!0,maxZoom:void 0!==a.maxZoom?a.maxZoom:19,tileLoadFunction:a.tileLoadFunction,url:void 0!==a.url?a.url:"https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png",wrapX:a.wrapX})}w(Dz,Cz);var Ez=new rg({html:'&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.'});function Fz(a){a=a||{};var c=Gz[a.layer];this.A=a.layer;Cz.call(this,{attributions:c.attributions,crossOrigin:"anonymous",logo:"https://developer.mapquest.com/content/osm/mq_logo.png",maxZoom:c.maxZoom,opaque:!0,tileLoadFunction:a.tileLoadFunction,url:void 0!==a.url?a.url:"https://otile{1-4}-s.mqcdn.com/tiles/1.0.0/"+this.A+"/{z}/{x}/{y}.jpg"})}w(Fz,Cz);
-var Hz=new rg({html:'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a>'}),Gz={osm:{maxZoom:19,attributions:[Hz,Ez]},sat:{maxZoom:18,attributions:[Hz,new rg({html:"Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency"})]},hyb:{maxZoom:18,attributions:[Hz,Ez]}};Fz.prototype.u=function(){return this.A};(function(){var a={},c={gb:a};(function(d){if("object"===typeof a&&"undefined"!==typeof c)c.gb=d();else{var e;"undefined"!==typeof window?e=window:"undefined"!==typeof global?e=global:"undefined"!==typeof self?e=self:e=this;e.Bo=d()}})(function(){return function e(a,c,h){function k(n,q){if(!c[n]){if(!a[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]={gb:{}};a[n][0].call(r.gb,function(c){var e=
-a[n][1][c];return k(e?e:c)},r,r.gb,e,a,c,h)}return c[n].gb}for(var m="function"==typeof require&&require,n=0;n<h.length;n++)k(h[n]);return k}({1:[function(a,c,g){a=a("./processor");g.Rh=a},{"./processor":2}],2:[function(a,c){function g(a){return function(c){var e=c.buffers,f=c.meta,g=c.width,h=c.height,k=e.length,m=e[0].byteLength,z;if(c.imageOps){m=Array(k);for(z=0;z<k;++z)m[z]=new ImageData(new Uint8ClampedArray(e[z]),g,h);g=a(m,f).data}else{g=new Uint8ClampedArray(m);h=Array(k);c=Array(k);for(z=
-0;z<k;++z)h[z]=new Uint8ClampedArray(e[z]),c[z]=[0,0,0,0];for(e=0;e<m;e+=4){for(z=0;z<k;++z){var x=h[z];c[z][0]=x[e];c[z][1]=x[e+1];c[z][2]=x[e+2];c[z][3]=x[e+3]}z=a(c,f);g[e]=z[0];g[e+1]=z[1];g[e+2]=z[2];g[e+3]=z[3]}}return g.buffer}}function h(a,c){var e=Object.keys(a.lib||{}).map(function(c){return"var "+c+" = "+a.lib[c].toString()+";"}).concat(["var __minion__ = ("+g.toString()+")(",a.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(a,c){var e=g(a.operation);return{postMessage:function(a){setTimeout(function(){c({data:{buffer:e(a),Td:a.Td}})},0)}}}function m(a){this.Ae=!!a.Zj;var c;0===a.threads?c=0:this.Ae?c=1:c=a.threads||1;var e=[];if(c)for(var f=0;f<c;++f)e[f]=h(a,this.If.bind(this,f));else e[0]=k(a,this.If.bind(this,
-0));this.rd=e;this.Jc=[];this.di=a.jn||Infinity;this.qd=0;this.nc={};this.Be=null}m.prototype.hn=function(a,c,e){this.bi({Vb:a,Td:c,Lc:e});this.Ff()};m.prototype.bi=function(a){for(this.Jc.push(a);this.Jc.length>this.di;)this.Jc.shift().Lc(null,null)};m.prototype.Ff=function(){if(0===this.qd&&0<this.Jc.length){var a=this.Be=this.Jc.shift(),c=a.Vb[0].width,e=a.Vb[0].height,f=a.Vb.map(function(a){return a.data.buffer}),g=this.rd.length;this.qd=g;if(1===g)this.rd[0].postMessage({buffers:f,meta:a.Td,
-imageOps:this.Ae,width:c,height:e},f);else for(var h=4*Math.ceil(a.Vb[0].data.length/4/g),k=0;k<g;++k){for(var m=k*h,z=[],x=0,K=f.length;x<K;++x)z.push(f[k].slice(m,m+h));this.rd[k].postMessage({buffers:z,meta:a.Td,imageOps:this.Ae,width:c,height:e},z)}}};m.prototype.If=function(a,c){this.zo||(this.nc[a]=c.data,--this.qd,0===this.qd&&this.ei())};m.prototype.ei=function(){var a=this.Be,c=this.rd.length,e,f;if(1===c)e=new Uint8ClampedArray(this.nc[0].buffer),f=this.nc[0].meta;else{var g=a.Vb[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.nc[h].buffer),k);f[h]=this.nc[h].meta}}this.Be=null;this.nc={};a.Lc(null,new ImageData(e,a.Vb[0].width,a.Vb[0].height),f);this.Ff()};c.gb=m},{}]},{},[1])(1)});Dp=c.gb})();function Iz(a){this.B=null;this.fa=void 0!==a.operationType?a.operationType:"pixel";this.ra=void 0!==a.threads?a.threads:1;this.a=Jz(a.sources);for(var c=0,d=this.a.length;c<d;++c)B(this.a[c],"change",this.s,!1,this);this.c=Ri();this.$=new Mk(function(){return 1},qa(this.s,this));for(var c=Kz(this.a),d={},e=0,f=c.length;e<f;++e)d[v(c[e].layer)]=c[e];this.f=this.g=null;this.X={animate:!1,attributions:{},coordinateToPixelMatrix:Cd(),extent:null,focus:null,index:0,layerStates:d,layerStatesArray:c,logos:{},
-pixelRatio:1,pixelToCoordinateMatrix:Cd(),postRenderFunctions:[],size:[0,0],skippedFeatureUids:{},tileQueue:this.$,time:Date.now(),usedTiles:{},viewState:{rotation:0},viewHints:[],wantedTiles:{}};sn.call(this,{});void 0!==a.operation&&this.u(a.operation,a.lib)}w(Iz,sn);Iz.prototype.u=function(a,c){this.B=new Dp.Rh({operation:a,Zj:"image"===this.fa,jn:1,lib:c,threads:this.ra});this.s()};function Lz(a,c,d){var e=a.g;return!e||a.b!==e.Kn||d!==e.resolution||!Zd(c,e.extent)}
-Iz.prototype.cc=function(a,c,d,e){d=!0;for(var f,g=0,h=this.a.length;g<h;++g)if(f=this.a[g].a.ea(),"ready"!==f.v){d=!1;break}if(!d)return null;if(!Lz(this,a,c))return this.f;d=this.c.canvas;f=Math.round(le(a)/c);g=Math.round(ie(a)/c);if(f!==d.width||g!==d.height)d.width=f,d.height=g;f=Vb(this.X);f.viewState=Vb(f.viewState);var g=ge(a),h=Math.round(le(a)/c),k=Math.round(ie(a)/c);f.extent=a;f.focus=ge(a);f.size[0]=h;f.size[1]=k;h=f.viewState;h.center=g;h.projection=e;h.resolution=c;this.f=e=new rn(a,
-c,1,this.j,d,this.I.bind(this,f));this.g={extent:a,resolution:c,Kn:this.b};return e};
-Iz.prototype.I=function(a,c){for(var d=this.a.length,e=Array(d),f=0;f<d;++f){var g;var h=this.a[f],k=a;h.ae(k,a.layerStatesArray[f]);if(g=h.cd()){var h=h.Se(),m=Math.round(h[12]),n=Math.round(h[13]),p=k.size[0],k=k.size[1];if(g instanceof Image){if(Mz){var q=Mz.canvas;q.width!==p||q.height!==k?Mz=Ri(p,k):Mz.clearRect(0,0,p,k)}else Mz=Ri(p,k);Mz.drawImage(g,m,n,Math.round(g.width*h[0]),Math.round(g.height*h[5]));g=Mz.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={};C(this,new Nz(Oz,a,d));this.B.hn(e,d,this.da.bind(this,a,c));Nk(a.tileQueue,16,16)};Iz.prototype.da=function(a,c,d,e,f){d?c(d):e&&(C(this,new Nz(Pz,a,f)),Lz(this,a.extent,a.viewState.resolution/a.pixelRatio)||this.c.putImageData(e,0,0),c(null))};var Mz=null;function Kz(a){return a.map(function(a){return dk(a.a)})}
-function Jz(a){for(var c=a.length,d=Array(c),e=0;e<c;++e){var f=e,g=a[e],h=null;g instanceof Rh?(g=new G({source:g}),h=new Up(g)):g instanceof sn&&(g=new jm({source:g}),h=new Tp(g));d[f]=h}return d}function Nz(a,c,d){wc.call(this,a);this.extent=c.extent;this.resolution=c.viewState.resolution/c.pixelRatio;this.data=d}w(Nz,wc);var Oz="beforeoperations",Pz="afteroperations";var Qz={terrain:{Wa:"jpg",opaque:!0},"terrain-background":{Wa:"jpg",opaque:!0},"terrain-labels":{Wa:"png",opaque:!1},"terrain-lines":{Wa:"png",opaque:!1},"toner-background":{Wa:"png",opaque:!0},toner:{Wa:"png",opaque:!0},"toner-hybrid":{Wa:"png",opaque:!1},"toner-labels":{Wa:"png",opaque:!1},"toner-lines":{Wa:"png",opaque:!1},"toner-lite":{Wa:"png",opaque:!0},watercolor:{Wa:"jpg",opaque:!0}},Rz={terrain:{minZoom:4,maxZoom:18},toner:{minZoom:0,maxZoom:20},watercolor:{minZoom:3,maxZoom:16}};
-function Sz(a){var c=a.layer.indexOf("-"),d=Qz[a.layer];Cz.call(this,{attributions:Tz,crossOrigin:"anonymous",maxZoom:Rz[-1==c?a.layer:a.layer.slice(0,c)].maxZoom,opaque:d.opaque,tileLoadFunction:a.tileLoadFunction,url:void 0!==a.url?a.url:"https://stamen-tiles-{a-d}.a.ssl.fastly.net/"+a.layer+"/{z}/{x}/{y}."+d.Wa})}w(Sz,Cz);var Tz=[new rg({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>.'}),Ez];function Uz(a){a=a||{};var c=void 0!==a.params?a.params:{};qz.call(this,{attributions:a.attributions,crossOrigin:a.crossOrigin,logo:a.logo,projection:a.projection,tileGrid:a.tileGrid,tileLoadFunction:a.tileLoadFunction,tileUrlFunction:qa(this.Yl,this),wrapX:void 0!==a.wrapX?a.wrapX:!0});var d=a.urls;void 0===d&&void 0!==a.url&&(d=pz(a.url));this.g=d||[];this.f=c;this.l=Nd()}w(Uz,qz);l=Uz.prototype;l.Vl=function(){return this.f};
-l.Tb=function(a,c,d){a=Uz.ba.Tb.call(this,a,c,d);return 1==c?a:od(a,c,this.c)};l.Wl=function(){return this.g};l.Xl=function(a){a=void 0!==a?pz(a):null;this.Cg(a)};l.Cg=function(a){this.g=a||[];this.s()};
-l.Yl=function(a,c,d){var e=this.tileGrid;e||(e=Th(this,d));if(!(e.b.length<=a[0])){var f=Jh(e,a,this.l),g=pd(e.Ka(a[0]),this.c);1!=c&&(g=od(g,c,this.c));e={F:"image",FORMAT:"PNG32",TRANSPARENT:!0};Yb(e,this.f);var h=this.g;0!==h.length?(d=d.b.split(":").pop(),e.SIZE=g[0]+","+g[1],e.BBOX=f.join(","),e.BBOXSR=d,e.IMAGESR=d,e.DPI=Math.round(90*c),a=1==h.length?h[0]:h[wb((a[1]<<a[0])+a[2],h.length)],za(a,"/")||(a+="/"),za(a,"MapServer/")?a+="export":za(a,"ImageServer/")&&(a+="exportImage"),a=so(uo([a],
-e))):a=void 0;return a}};l.Zl=function(a){Yb(this.f,a);this.s()};function Vz(a,c,d){Ah.call(this,a,2);this.f=c;this.c=d;this.a={}}w(Vz,Ah);Vz.prototype.Qa=function(a){a=void 0!==a?v(a):-1;if(a in this.a)return this.a[a];var c=this.f,d=Ri(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.a[a]=d.canvas};
-function Wz(a){Rh.call(this,{opaque:!1,projection:a.projection,tileGrid:a.tileGrid,wrapX:void 0!==a.wrapX?a.wrapX:!0})}w(Wz,Rh);Wz.prototype.Sb=function(a,c,d){var e=this.hb(a,c,d);if(xh(this.a,e))return this.a.get(e);var f=pd(this.tileGrid.Ka(a));a=[a,c,d];c=(c=Uh(this,a))?fg(Uh(this,c)):"";f=new Vz(a,f,c);this.a.set(e,f);return f};function Xz(a){qz.call(this,{attributions:a.attributions,crossOrigin:a.crossOrigin,projection:Ce("EPSG:3857"),state:"loading",tileLoadFunction:a.tileLoadFunction,wrapX:void 0!==a.wrapX?a.wrapX:!0});hz(new fz(a.url),void 0,qa(this.g,this),qa(this.f,this))}w(Xz,qz);
-Xz.prototype.g=function(a){var c=Ce("EPSG:4326"),d=this.i,e;void 0!==a.bounds&&(e=oe(a.bounds,Ge(c,d)));var f=a.minzoom||0,g=a.maxzoom||22;this.tileGrid=d=Oh({extent:ig(d),maxZoom:g,minZoom:f});this.tileUrlFunction=mz(a.tiles,d);if(void 0!==a.attribution&&!this.j){c=void 0!==e?e:c.R();e={};for(var h;f<=g;++f)h=f.toString(),e[h]=[kg(d,c,f)];this.la([new rg({html:a.attribution,tileRanges:e})])}Eh(this,"ready")};Xz.prototype.f=function(){Eh(this,"error")};function Yz(a){Rh.call(this,{projection:Ce("EPSG:3857"),state:"loading"});this.l=void 0!==a.preemptive?a.preemptive:!0;this.f=oz;this.g=void 0;hz(new fz(a.url),void 0,qa(this.bm,this))}w(Yz,Rh);l=Yz.prototype;l.kj=function(){return this.g};l.wi=function(a,c,d,e,f){this.tileGrid?(c=this.tileGrid.Yc(a,c),Zz(this.Sb(c[0],c[1],c[2],1,this.i),a,d,e,f)):!0===f?pi(function(){d.call(e,null)}):d.call(e,null)};
-l.bm=function(a){var c=Ce("EPSG:4326"),d=this.i,e;void 0!==a.bounds&&(e=oe(a.bounds,Ge(c,d)));var f=a.minzoom||0,g=a.maxzoom||22;this.tileGrid=d=Oh({extent:ig(d),maxZoom:g,minZoom:f});this.g=a.template;var h=a.grids;if(h){this.f=mz(h,d);if(void 0!==a.attribution){c=void 0!==e?e:c.R();for(e={};f<=g;++f)h=f.toString(),e[h]=[kg(d,c,f)];this.la([new rg({html:a.attribution,tileRanges:e})])}Eh(this,"ready")}else Eh(this,"error")};
-l.Sb=function(a,c,d,e,f){var g=this.hb(a,c,d);if(xh(this.a,g))return this.a.get(g);a=[a,c,d];c=Uh(this,a,f);e=this.f(c,e,f);e=new $z(a,void 0!==e?0:4,void 0!==e?e:"",Jh(this.tileGrid,a),this.l);this.a.set(g,e);return e};l.yf=function(a,c,d){a=this.hb(a,c,d);xh(this.a,a)&&this.a.get(a)};function $z(a,c,d,e,f){Ah.call(this,a,c);this.i=d;this.a=e;this.l=f;this.g=this.f=this.c=null}w($z,Ah);l=$z.prototype;l.Qa=function(){return null};
-function aA(a,c){if(!a.c||!a.f||!a.g)return null;var d=a.c[Math.floor((1-(c[1]-a.a[1])/(a.a[3]-a.a[1]))*a.c.length)];if(!ia(d))return null;d=d.charCodeAt(Math.floor((c[0]-a.a[0])/(a.a[2]-a.a[0])*d.length));93<=d&&d--;35<=d&&d--;d-=32;return d in a.f?a.g[a.f[d]]:null}function Zz(a,c,d,e,f){0==a.state&&!0===f?(Yc(a,"change",function(){d.call(e,aA(this,c))},!1,a),bA(a)):!0===f?pi(function(){d.call(e,aA(this,c))},a):d.call(e,aA(a,c))}l.jb=function(){return this.i};l.yj=function(){this.state=3;Bh(this)};
-l.am=function(a){this.c=a.grid;this.f=a.keys;this.g=a.data;this.state=4;Bh(this)};function bA(a){0==a.state&&(a.state=1,hz(new fz(a.i),void 0,qa(a.am,a),qa(a.yj,a)))}l.load=function(){this.l&&bA(this)};function cA(a){V.call(this,{attributions:a.attributions,logo:a.logo,projection:void 0,state:"ready",wrapX:a.wrapX});this.fa=void 0!==a.format?a.format:null;this.u=a.tileGrid;this.I=oz;this.da=void 0!==a.tileLoadFunction?a.tileLoadFunction:null;this.B={};void 0!==a.tileUrlFunction?(this.I=a.tileUrlFunction,this.s()):void 0!==a.urls?(this.I=mz(a.urls,this.u),this.s()):void 0!==a.url&&(this.I=mz(pz(a.url),this.u),this.s())}w(cA,V);l=cA.prototype;l.clear=function(){Sb(this.B)};
-function dA(a,c,d,e){var f=a.B;a=a.u.Yc(c,d);f=f[a[0]+"/"+a[1]+"/"+a[2]];if(void 0!==f)for(a=0,d=f.length;a<d;++a){var g=f[a];if(g.V().He(c)&&e.call(void 0,g))break}}l.zb=function(a,c,d,e){var f=this.u,g=this.B;c=Nh(f,c);a=kg(f,a,c);for(var h,f=a.b;f<=a.f;++f)for(h=a.a;h<=a.c;++h){var k=g[c+"/"+f+"/"+h];if(void 0!==k){var m,n;m=0;for(n=k.length;m<n;++m){var p=d.call(e,k[m]);if(p)return p}}}};l.zc=function(){var a=this.B,c=[],d;for(d in a)hb(c,a[d]);return c};
-l.Ki=function(a,c){var d=[];dA(this,a,c,function(a){d.push(a)});return d};function eA(a,c,d){var e=a.u;a.N&&d.f&&(c=gg(c,e,d));return jg(c,e)?c:null}l.Wb=function(a,c,d){function e(a,c){h[a]=c;this.s()}var f=this.u,g=this.I,h=this.B,k=Nh(f,c),f=kg(f,a,k),m=[k,0,0],n,p;for(n=f.b;n<=f.f;++n)for(p=f.a;p<=f.c;++p){var q=k+"/"+n+"/"+p;if(!(q in h)){m[1]=n;m[2]=p;var r=eA(this,m,d),r=r?g(r,1,d):void 0;void 0!==r&&(h[q]=[],q=ra(e,q),this.da?this.da(r,qa(q,this)):zp(r,this.fa,q).call(this,a,c,d))}}};function fA(a){a=a||{};var c=void 0!==a.params?a.params:{};qz.call(this,{attributions:a.attributions,crossOrigin:a.crossOrigin,logo:a.logo,opaque:!Tb(c,"TRANSPARENT",!0),projection:a.projection,tileGrid:a.tileGrid,tileLoadFunction:a.tileLoadFunction,tileUrlFunction:qa(this.gm,this),wrapX:void 0!==a.wrapX?a.wrapX:!0});var d=a.urls;void 0===d&&void 0!==a.url&&(d=pz(a.url));this.g=d||[];this.A=void 0!==a.gutter?a.gutter:0;this.f=c;this.l=!0;this.u=a.serverType;this.I=void 0!==a.hidpi?a.hidpi:!0;this.B=
-"";gA(this);this.X=Nd();hA(this)}w(fA,qz);l=fA.prototype;
-l.cm=function(a,c,d,e){d=Ce(d);var f=this.tileGrid;f||(f=Th(this,d));c=f.Yc(a,c);if(!(f.b.length<=c[0])){var g=f.aa(c[0]),h=Jh(f,c,this.X),f=pd(f.Ka(c[0]),this.c),k=this.A;0!==k&&(f=nd(f,k,this.c),h=Rd(h,g*k,h));k={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:this.f.LAYERS};Yb(k,this.f,e);e=Math.floor((h[3]-a[1])/g);k[this.l?"I":"X"]=Math.floor((a[0]-h[0])/g);k[this.l?"J":"Y"]=e;return iA(this,c,f,h,1,d,k)}};l.Bd=function(){return this.A};
-l.hb=function(a,c,d){return this.B+fA.ba.hb.call(this,a,c,d)};l.dm=function(){return this.f};
-function iA(a,c,d,e,f,g,h){var k=a.g;if(0!==k.length){h.WIDTH=d[0];h.HEIGHT=d[1];h[a.l?"CRS":"SRS"]=g.b;"STYLES"in a.f||(h.STYLES=new String(""));if(1!=f)switch(a.u){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;a.l&&"ne"==g.substr(0,2)&&(a=e[0],e[0]=e[1],e[1]=a,a=e[2],e[2]=e[3],e[3]=a);h.BBOX=e.join(",");return so(uo([1==k.length?k[0]:k[wb((c[1]<<
-c[0])+c[2],k.length)]],h))}}l.Tb=function(a,c,d){a=fA.ba.Tb.call(this,a,c,d);return 1!=c&&this.I&&void 0!==this.u?od(a,c,this.c):a};l.em=function(){return this.g};function gA(a){var c=0,d=[],e,f;e=0;for(f=a.g.length;e<f;++e)d[c++]=a.g[e];for(var g in a.f)d[c++]=g+"-"+a.f[g];a.B=d.join("#")}l.fm=function(a){a=void 0!==a?pz(a):null;this.Dg(a)};l.Dg=function(a){this.g=a||[];gA(this);this.s()};
-l.gm=function(a,c,d){var e=this.tileGrid;e||(e=Th(this,d));if(!(e.b.length<=a[0])){1==c||this.I&&void 0!==this.u||(c=1);var f=e.aa(a[0]),g=Jh(e,a,this.X),e=pd(e.Ka(a[0]),this.c),h=this.A;0!==h&&(e=nd(e,h,this.c),g=Rd(g,f*h,g));1!=c&&(e=od(e,c,this.c));f={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};Yb(f,this.f);return iA(this,a,e,g,c,d,f)}};l.hm=function(a){Yb(this.f,a);gA(this);hA(this);this.s()};function hA(a){a.l=0<=Oa(Tb(a.f,"VERSION","1.3.0"),"1.3")};function jA(a){this.i=a.matrixIds;Fh.call(this,{extent:a.extent,origin:a.origin,origins:a.origins,resolutions:a.resolutions,tileSize:a.tileSize,tileSizes:a.tileSizes,sizes:a.sizes})}w(jA,Fh);jA.prototype.C=function(){return this.i};
-function kA(a,c){var d=[],e=[],f=[],g=[],h=[],k;k=Ce(a.SupportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"));var m=k.Ed(),n="ne"==k.g.substr(0,2);kb(a.TileMatrix,function(a,c){return c.ScaleDenominator-a.ScaleDenominator});a.TileMatrix.forEach(function(a){e.push(a.Identifier);var c=2.8E-4*a.ScaleDenominator/m,k=a.TileWidth,u=a.TileHeight;n?f.push([a.TopLeftCorner[1],a.TopLeftCorner[0]]):f.push(a.TopLeftCorner);d.push(c);g.push(k==u?k:[k,u]);h.push([a.MatrixWidth,-a.MatrixHeight])});
-return new jA({extent:c,origins:f,resolutions:d,matrixIds:e,tileSizes:g,sizes:h})};function lA(a){function c(a){a="KVP"==e?so(uo([a],g)):a.replace(/\{(\w+?)\}/g,function(a,c){return c.toLowerCase()in g?g[c.toLowerCase()]:a});return function(c){if(c){var d={TileMatrix:f.i[c[0]],TileCol:c[1],TileRow:-c[2]-1};Yb(d,h);c=a;return c="KVP"==e?so(uo([c],d)):c.replace(/\{(\w+?)\}/g,function(a,c){return d[c]})}}}this.X=void 0!==a.version?a.version:"1.0.0";this.u=void 0!==a.format?a.format:"image/jpeg";this.f=void 0!==a.dimensions?a.dimensions:{};this.A="";mA(this);this.B=a.layer;this.l=a.matrixSet;
-this.I=a.style;var d=a.urls;void 0===d&&void 0!==a.url&&(d=pz(a.url));this.g=d||[];var e=this.$=void 0!==a.requestEncoding?a.requestEncoding:"KVP",f=a.tileGrid,g={layer:this.B,style:this.I,tilematrixset:this.l};"KVP"==e&&Yb(g,{Service:"WMTS",Request:"GetTile",Version:this.X,Format:this.u});var h=this.f,d=0<this.g.length?nz(this.g.map(c)):oz;qz.call(this,{attributions:a.attributions,crossOrigin:a.crossOrigin,logo:a.logo,projection:a.projection,tileClass:a.tileClass,tileGrid:f,tileLoadFunction:a.tileLoadFunction,
-tilePixelRatio:a.tilePixelRatio,tileUrlFunction:d,wrapX:void 0!==a.wrapX?a.wrapX:!1})}w(lA,qz);l=lA.prototype;l.Ii=function(){return this.f};l.Mi=function(){return this.u};l.hb=function(a,c,d){return this.A+lA.ba.hb.call(this,a,c,d)};l.im=function(){return this.B};l.Yi=function(){return this.l};l.ij=function(){return this.$};l.jm=function(){return this.I};l.km=function(){return this.g};l.oj=function(){return this.X};function mA(a){var c=0,d=[],e;for(e in a.f)d[c++]=e+"-"+a.f[e];a.A=d.join("/")}
-l.io=function(a){Yb(this.f,a);mA(this);this.s()};function nA(a){a=a||{};var c=a.size,d=c[0],e=c[1],f=[],g=256;switch(void 0!==a.tierSizeCalculation?a.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 Fh({extent:c,origin:fe(c),resolutions:g}),k=a.url;
-qz.call(this,{attributions:a.attributions,crossOrigin:a.crossOrigin,logo:a.logo,tileClass:oA,tileGrid:c,tileUrlFunction:function(a){if(a){var c=a[0],d=a[1];a=-a[2]-1;return k+"TileGroup"+((d+a*f[c][0]+h[c])/256|0)+"/"+c+"-"+d+"-"+a+".jpg"}}})}w(nA,qz);function oA(a,c,d,e,f){Dx.call(this,a,c,d,e,f);this.f={}}w(oA,Dx);
-oA.prototype.Qa=function(a){var c=void 0!==a?v(a).toString():"";if(c in this.f)return this.f[c];a=oA.ba.Qa.call(this,a);if(2==this.state){if(256==a.width&&256==a.height)return this.f[c]=a;var d=Ri(256,256);d.drawImage(a,0,0);return this.f[c]=d.canvas}return a};function pA(a){a=a||{};this.a=void 0!==a.initialSize?a.initialSize:256;this.c=void 0!==a.maxSize?a.maxSize:void 0!==ta?ta:2048;this.b=void 0!==a.space?a.space:1;this.g=[new qA(this.a,this.b)];this.f=this.a;this.j=[new qA(this.f,this.b)]}pA.prototype.add=function(a,c,d,e,f,g){if(c+this.b>this.c||d+this.b>this.c)return null;e=rA(this,!1,a,c,d,e,g);if(!e)return null;a=rA(this,!0,a,c,d,void 0!==f?f:se,g);return{offsetX:e.offsetX,offsetY:e.offsetY,image:e.image,dg:a.image}};
-function rA(a,c,d,e,f,g,h){var k=c?a.j:a.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*a.f,a.c),a.f=m):(m=Math.min(2*a.a,a.c),a.a=m),m=new qA(m,a.b),k.push(m),++p)}}function qA(a,c){this.b=c;this.a=[{x:0,y:0,width:a,height:a}];this.f={};this.c=Pg("CANVAS");this.c.width=a;this.c.height=a;this.g=this.c.getContext("2d")}qA.prototype.get=function(a){return Tb(this.f,a,null)};
-qA.prototype.add=function(a,c,d,e,f){var g,h,k;h=0;for(k=this.a.length;h<k;++h)if(g=this.a[h],g.width>=c+this.b&&g.height>=d+this.b)return k={offsetX:g.x+this.b,offsetY:g.y+this.b,image:this.c},this.f[a]=k,e.call(f,this.g,g.x+this.b,g.y+this.b),a=h,c=c+this.b,d=d+this.b,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},sA(this,a,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},sA(this,a,e,f)),k;return null};function sA(a,c,d,e){c=[c,1];0<d.width&&0<d.height&&c.push(d);0<e.width&&0<e.height&&c.push(e);a.a.splice.apply(a.a,c)};function tA(a){this.u=this.f=this.g=null;this.l=void 0!==a.fill?a.fill:null;this.I=[0,0];this.b=a.points;this.c=void 0!==a.radius?a.radius:a.radius1;this.j=void 0!==a.radius2?a.radius2:this.c;this.i=void 0!==a.angle?a.angle:0;this.a=void 0!==a.stroke?a.stroke:null;this.ka=this.T=this.ca=null;var c=a.atlasManager,d="",e="",f=0,g=null,h,k=0;this.a&&(h=Ag(this.a.b),k=this.a.a,void 0===k&&(k=1),g=this.a.c,$i||(g=null),e=this.a.g,void 0===e&&(e="round"),d=this.a.f,void 0===d&&(d="round"),f=this.a.j,void 0===
-f&&(f=10));var m=2*(this.c+k)+1,d={strokeStyle:h,hd:k,size:m,lineCap:d,lineDash:g,lineJoin:e,miterLimit:f};if(void 0===c){this.f=Pg("CANVAS");this.f.height=m;this.f.width=m;var c=m=this.f.width,n=this.f.getContext("2d");this.Ig(d,n,0,0);this.l?this.u=this.f:(n=this.u=Pg("CANVAS"),n.height=d.size,n.width=d.size,n=n.getContext("2d"),this.Hg(d,n,0,0))}else m=Math.round(m),(e=!this.l)&&(n=qa(this.Hg,this,d)),f=this.rb(),n=c.add(f,m,m,qa(this.Ig,this,d),n),this.f=n.image,this.I=[n.offsetX,n.offsetY],c=
-n.image.width,this.u=e?n.dg:this.f;this.ca=[m/2,m/2];this.T=[m,m];this.ka=[c,c];xk.call(this,{opacity:1,rotateWithView:!1,rotation:void 0!==a.rotation?a.rotation:0,scale:1,snapToPixel:void 0!==a.snapToPixel?a.snapToPixel:!0})}w(tA,xk);l=tA.prototype;l.Ab=function(){return this.ca};l.pm=function(){return this.i};l.qm=function(){return this.l};l.ce=function(){return this.u};l.Jb=function(){return this.f};l.Cd=function(){return this.ka};l.dd=function(){return 2};l.ta=function(){return this.I};l.rm=function(){return this.b};
-l.sm=function(){return this.c};l.hj=function(){return this.j};l.kb=function(){return this.T};l.tm=function(){return this.a};l.Ye=wa;l.load=wa;l.xf=wa;
-l.Ig=function(a,c,d,e){var f;c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();this.j!==this.c&&(this.b*=2);for(d=0;d<=this.b;d++)e=2*d*Math.PI/this.b-Math.PI/2+this.i,f=0===d%2?this.c:this.j,c.lineTo(a.size/2+f*Math.cos(e),a.size/2+f*Math.sin(e));this.l&&(c.fillStyle=Ag(this.l.b),c.fill());this.a&&(c.strokeStyle=a.strokeStyle,c.lineWidth=a.hd,a.lineDash&&c.setLineDash(a.lineDash),c.lineCap=a.lineCap,c.lineJoin=a.lineJoin,c.miterLimit=a.miterLimit,c.stroke());c.closePath()};
-l.Hg=function(a,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();this.j!==this.c&&(this.b*=2);var f;for(d=0;d<=this.b;d++)f=2*d*Math.PI/this.b-Math.PI/2+this.i,e=0===d%2?this.c:this.j,c.lineTo(a.size/2+e*Math.cos(f),a.size/2+e*Math.sin(f));c.fillStyle=Bl;c.fill();this.a&&(c.strokeStyle=a.strokeStyle,c.lineWidth=a.hd,a.lineDash&&c.setLineDash(a.lineDash),c.stroke());c.closePath()};
-l.rb=function(){var a=this.a?this.a.rb():"-",c=this.l?this.l.rb():"-";this.g&&a==this.g[1]&&c==this.g[2]&&this.c==this.g[3]&&this.j==this.g[4]&&this.i==this.g[5]&&this.b==this.g[6]||(this.g=["r"+a+c+(void 0!==this.c?this.c.toString():"-")+(void 0!==this.j?this.j.toString():"-")+(void 0!==this.i?this.i.toString():"-")+(void 0!==this.b?this.b.toString():"-"),a,c,this.c,this.j,this.i,this.b]);return this.g[0]};t("ol.animation.bounce",function(a){var c=a.resolution,d=a.start?a.start:Date.now(),e=void 0!==a.duration?a.duration:1E3,f=a.easing?a.easing:Zf;return function(a,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);t("ol.animation.pan",$f,OPENLAYERS);t("ol.animation.rotate",ag,OPENLAYERS);t("ol.animation.zoom",bg,OPENLAYERS);
-t("ol.Attribution",rg,OPENLAYERS);rg.prototype.getHTML=rg.prototype.c;sg.prototype.element=sg.prototype.element;t("ol.Collection",tg,OPENLAYERS);tg.prototype.clear=tg.prototype.clear;tg.prototype.extend=tg.prototype.Ze;tg.prototype.forEach=tg.prototype.forEach;tg.prototype.getArray=tg.prototype.tk;tg.prototype.item=tg.prototype.item;tg.prototype.getLength=tg.prototype.Gb;tg.prototype.insertAt=tg.prototype.Od;tg.prototype.pop=tg.prototype.pop;tg.prototype.push=tg.prototype.push;
-tg.prototype.remove=tg.prototype.remove;tg.prototype.removeAt=tg.prototype.tf;tg.prototype.setAt=tg.prototype.Mn;t("ol.coordinate.add",qd,OPENLAYERS);t("ol.coordinate.createStringXY",function(a){return function(c){return yd(c,a)}},OPENLAYERS);t("ol.coordinate.format",td,OPENLAYERS);t("ol.coordinate.rotate",vd,OPENLAYERS);t("ol.coordinate.toStringHDMS",function(a){return a?sd(a[1],"NS")+" "+sd(a[0],"EW"):""},OPENLAYERS);t("ol.coordinate.toStringXY",yd,OPENLAYERS);t("ol.DeviceOrientation",Ir,OPENLAYERS);
-Ir.prototype.getAlpha=Ir.prototype.Ci;Ir.prototype.getBeta=Ir.prototype.Fi;Ir.prototype.getGamma=Ir.prototype.Ni;Ir.prototype.getHeading=Ir.prototype.uk;Ir.prototype.getTracking=Ir.prototype.jg;Ir.prototype.setTracking=Ir.prototype.$e;t("ol.easing.easeIn",Vf,OPENLAYERS);t("ol.easing.easeOut",Wf,OPENLAYERS);t("ol.easing.inAndOut",Xf,OPENLAYERS);t("ol.easing.linear",Yf,OPENLAYERS);t("ol.easing.upAndDown",Zf,OPENLAYERS);t("ol.extent.boundingExtent",Md,OPENLAYERS);t("ol.extent.buffer",Rd,OPENLAYERS);
-t("ol.extent.containsCoordinate",Ud,OPENLAYERS);t("ol.extent.containsExtent",Wd,OPENLAYERS);t("ol.extent.containsXY",Vd,OPENLAYERS);t("ol.extent.createEmpty",Nd,OPENLAYERS);t("ol.extent.equals",Zd,OPENLAYERS);t("ol.extent.extend",$d,OPENLAYERS);t("ol.extent.getBottomLeft",ce,OPENLAYERS);t("ol.extent.getBottomRight",de,OPENLAYERS);t("ol.extent.getCenter",ge,OPENLAYERS);t("ol.extent.getHeight",ie,OPENLAYERS);t("ol.extent.getIntersection",je,OPENLAYERS);
-t("ol.extent.getSize",function(a){return[a[2]-a[0],a[3]-a[1]]},OPENLAYERS);t("ol.extent.getTopLeft",fe,OPENLAYERS);t("ol.extent.getTopRight",ee,OPENLAYERS);t("ol.extent.getWidth",le,OPENLAYERS);t("ol.extent.intersects",ke,OPENLAYERS);t("ol.extent.isEmpty",me,OPENLAYERS);t("ol.extent.applyTransform",oe,OPENLAYERS);t("ol.Feature",Q,OPENLAYERS);Q.prototype.clone=Q.prototype.clone;Q.prototype.getGeometry=Q.prototype.V;Q.prototype.getId=Q.prototype.Qi;Q.prototype.getGeometryName=Q.prototype.Pi;
-Q.prototype.getStyle=Q.prototype.wk;Q.prototype.getStyleFunction=Q.prototype.xk;Q.prototype.setGeometry=Q.prototype.za;Q.prototype.setStyle=Q.prototype.af;Q.prototype.setId=Q.prototype.Mb;Q.prototype.setGeometryName=Q.prototype.Dc;t("ol.featureloader.xhr",Ap,OPENLAYERS);t("ol.Geolocation",tx,OPENLAYERS);tx.prototype.getAccuracy=tx.prototype.Ai;tx.prototype.getAccuracyGeometry=tx.prototype.Bi;tx.prototype.getAltitude=tx.prototype.Di;tx.prototype.getAltitudeAccuracy=tx.prototype.Ei;
-tx.prototype.getHeading=tx.prototype.zk;tx.prototype.getPosition=tx.prototype.Ak;tx.prototype.getProjection=tx.prototype.kg;tx.prototype.getSpeed=tx.prototype.jj;tx.prototype.getTracking=tx.prototype.lg;tx.prototype.getTrackingOptions=tx.prototype.Yf;tx.prototype.setProjection=tx.prototype.mg;tx.prototype.setTracking=tx.prototype.Ud;tx.prototype.setTrackingOptions=tx.prototype.mh;t("ol.Graticule",xx,OPENLAYERS);xx.prototype.getMap=xx.prototype.Dk;xx.prototype.getMeridians=xx.prototype.Zi;
-xx.prototype.getParallels=xx.prototype.dj;xx.prototype.setMap=xx.prototype.setMap;t("ol.has.DEVICE_PIXEL_RATIO",Zi,OPENLAYERS);t("ol.has.CANVAS",aj,OPENLAYERS);t("ol.has.DEVICE_ORIENTATION",bj,OPENLAYERS);t("ol.has.GEOLOCATION",cj,OPENLAYERS);t("ol.has.TOUCH",dj,OPENLAYERS);t("ol.has.WEBGL",Yi,OPENLAYERS);Cx.prototype.getImage=Cx.prototype.b;Dx.prototype.getImage=Dx.prototype.Qa;t("ol.Kinetic",Ok,OPENLAYERS);t("ol.loadingstrategy.all",Bp,OPENLAYERS);
-t("ol.loadingstrategy.bbox",function(a){return[a]},OPENLAYERS);t("ol.loadingstrategy.tile",function(a){return function(c,d){var e=Nh(a,d),f=kg(a,c,e),g=[],e=[e,0,0];for(e[1]=f.b;e[1]<=f.f;++e[1])for(e[2]=f.a;e[2]<=f.c;++e[2])g.push(Jh(a,e));return g}},OPENLAYERS);t("ol.Map",W,OPENLAYERS);W.prototype.addControl=W.prototype.hi;W.prototype.addInteraction=W.prototype.ii;W.prototype.addLayer=W.prototype.Kf;W.prototype.addOverlay=W.prototype.Lf;W.prototype.beforeRender=W.prototype.Aa;
-W.prototype.forEachFeatureAtPixel=W.prototype.Wc;W.prototype.forEachLayerAtPixel=W.prototype.Hk;W.prototype.hasFeatureAtPixel=W.prototype.Yj;W.prototype.getEventCoordinate=W.prototype.Ji;W.prototype.getEventPixel=W.prototype.zd;W.prototype.getTarget=W.prototype.bf;W.prototype.getTargetElement=W.prototype.uc;W.prototype.getCoordinateFromPixel=W.prototype.xa;W.prototype.getControls=W.prototype.Hi;W.prototype.getOverlays=W.prototype.cj;W.prototype.getInteractions=W.prototype.Ri;
-W.prototype.getLayerGroup=W.prototype.Rb;W.prototype.getLayers=W.prototype.ng;W.prototype.getPixelFromCoordinate=W.prototype.Ba;W.prototype.getSize=W.prototype.Ea;W.prototype.getView=W.prototype.Z;W.prototype.getViewport=W.prototype.pj;W.prototype.renderSync=W.prototype.In;W.prototype.render=W.prototype.render;W.prototype.removeControl=W.prototype.Bn;W.prototype.removeInteraction=W.prototype.Cn;W.prototype.removeLayer=W.prototype.En;W.prototype.removeOverlay=W.prototype.Fn;
-W.prototype.setLayerGroup=W.prototype.ih;W.prototype.setSize=W.prototype.vf;W.prototype.setTarget=W.prototype.Jk;W.prototype.setView=W.prototype.Yn;W.prototype.updateSize=W.prototype.Fc;Oj.prototype.originalEvent=Oj.prototype.originalEvent;Oj.prototype.pixel=Oj.prototype.pixel;Oj.prototype.coordinate=Oj.prototype.coordinate;Oj.prototype.dragging=Oj.prototype.dragging;Oj.prototype.preventDefault=Oj.prototype.preventDefault;Oj.prototype.stopPropagation=Oj.prototype.c;uh.prototype.map=uh.prototype.map;
-uh.prototype.frameState=uh.prototype.frameState;id.prototype.key=id.prototype.key;id.prototype.oldValue=id.prototype.oldValue;t("ol.Object",jd,OPENLAYERS);jd.prototype.get=jd.prototype.get;jd.prototype.getKeys=jd.prototype.O;jd.prototype.getProperties=jd.prototype.P;jd.prototype.set=jd.prototype.set;jd.prototype.setProperties=jd.prototype.H;jd.prototype.unset=jd.prototype.S;t("ol.Observable",gd,OPENLAYERS);t("ol.Observable.unByKey",hd,OPENLAYERS);gd.prototype.changed=gd.prototype.s;
-gd.prototype.getRevision=gd.prototype.K;gd.prototype.on=gd.prototype.D;gd.prototype.once=gd.prototype.L;gd.prototype.un=gd.prototype.J;gd.prototype.unByKey=gd.prototype.M;t("ol.inherits",w,OPENLAYERS);t("ol.Overlay",fr,OPENLAYERS);fr.prototype.getElement=fr.prototype.Vd;fr.prototype.getMap=fr.prototype.Wd;fr.prototype.getOffset=fr.prototype.Wf;fr.prototype.getPosition=fr.prototype.og;fr.prototype.getPositioning=fr.prototype.Xf;fr.prototype.setElement=fr.prototype.fh;fr.prototype.setMap=fr.prototype.setMap;
-fr.prototype.setOffset=fr.prototype.kh;fr.prototype.setPosition=fr.prototype.cf;fr.prototype.setPositioning=fr.prototype.lh;t("ol.size.toSize",pd,OPENLAYERS);Ah.prototype.getTileCoord=Ah.prototype.j;t("ol.View",Of,OPENLAYERS);Of.prototype.constrainCenter=Of.prototype.vd;Of.prototype.constrainResolution=Of.prototype.constrainResolution;Of.prototype.constrainRotation=Of.prototype.constrainRotation;Of.prototype.getCenter=Of.prototype.Fa;Of.prototype.calculateExtent=Of.prototype.Kc;
-Of.prototype.getProjection=Of.prototype.Kk;Of.prototype.getResolution=Of.prototype.aa;Of.prototype.getRotation=Of.prototype.va;Of.prototype.getZoom=Of.prototype.rj;Of.prototype.fit=Of.prototype.Ke;Of.prototype.centerOn=Of.prototype.ri;Of.prototype.rotate=Of.prototype.rotate;Of.prototype.setCenter=Of.prototype.Ra;Of.prototype.setResolution=Of.prototype.wb;Of.prototype.setRotation=Of.prototype.Xd;Of.prototype.setZoom=Of.prototype.ao;t("ol.xml.getAllTextContent",Uo,OPENLAYERS);t("ol.xml.parse",np,OPENLAYERS);
-t("ol.webgl.Context",mq,OPENLAYERS);mq.prototype.getGL=mq.prototype.Mm;mq.prototype.useProgram=mq.prototype.je;t("ol.tilegrid.TileGrid",Fh,OPENLAYERS);Fh.prototype.getMaxZoom=Fh.prototype.Uf;Fh.prototype.getMinZoom=Fh.prototype.Vf;Fh.prototype.getOrigin=Fh.prototype.ta;Fh.prototype.getResolution=Fh.prototype.aa;Fh.prototype.getResolutions=Fh.prototype.Kg;Fh.prototype.getTileCoordForCoordAndResolution=Fh.prototype.Yc;Fh.prototype.getTileCoordForCoordAndZ=Fh.prototype.Jd;Fh.prototype.getTileSize=Fh.prototype.Ka;
-t("ol.tilegrid.createXYZ",Oh,OPENLAYERS);t("ol.tilegrid.WMTS",jA,OPENLAYERS);jA.prototype.getMatrixIds=jA.prototype.C;t("ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet",kA,OPENLAYERS);t("ol.style.AtlasManager",pA,OPENLAYERS);t("ol.style.Circle",Fl,OPENLAYERS);Fl.prototype.getFill=Fl.prototype.lm;Fl.prototype.getImage=Fl.prototype.Jb;Fl.prototype.getRadius=Fl.prototype.mm;Fl.prototype.getStroke=Fl.prototype.nm;t("ol.style.Fill",El,OPENLAYERS);El.prototype.getColor=El.prototype.c;
-El.prototype.setColor=El.prototype.f;t("ol.style.Icon",yk,OPENLAYERS);yk.prototype.getAnchor=yk.prototype.Ab;yk.prototype.getImage=yk.prototype.Jb;yk.prototype.getOrigin=yk.prototype.ta;yk.prototype.getSrc=yk.prototype.om;yk.prototype.getSize=yk.prototype.kb;yk.prototype.load=yk.prototype.load;t("ol.style.Image",xk,OPENLAYERS);xk.prototype.getOpacity=xk.prototype.de;xk.prototype.getRotateWithView=xk.prototype.Gd;xk.prototype.getRotation=xk.prototype.ee;xk.prototype.getScale=xk.prototype.fe;
-xk.prototype.getSnapToPixel=xk.prototype.Id;xk.prototype.setOpacity=xk.prototype.ge;xk.prototype.setRotation=xk.prototype.he;xk.prototype.setScale=xk.prototype.ie;t("ol.style.RegularShape",tA,OPENLAYERS);tA.prototype.getAnchor=tA.prototype.Ab;tA.prototype.getAngle=tA.prototype.pm;tA.prototype.getFill=tA.prototype.qm;tA.prototype.getImage=tA.prototype.Jb;tA.prototype.getOrigin=tA.prototype.ta;tA.prototype.getPoints=tA.prototype.rm;tA.prototype.getRadius=tA.prototype.sm;tA.prototype.getRadius2=tA.prototype.hj;
-tA.prototype.getSize=tA.prototype.kb;tA.prototype.getStroke=tA.prototype.tm;t("ol.style.Stroke",Al,OPENLAYERS);Al.prototype.getColor=Al.prototype.um;Al.prototype.getLineCap=Al.prototype.Ui;Al.prototype.getLineDash=Al.prototype.vm;Al.prototype.getLineJoin=Al.prototype.Vi;Al.prototype.getMiterLimit=Al.prototype.$i;Al.prototype.getWidth=Al.prototype.wm;Al.prototype.setColor=Al.prototype.xm;Al.prototype.setLineCap=Al.prototype.Rn;Al.prototype.setLineDash=Al.prototype.ym;Al.prototype.setLineJoin=Al.prototype.Sn;
-Al.prototype.setMiterLimit=Al.prototype.Tn;Al.prototype.setWidth=Al.prototype.Zn;t("ol.style.Style",Gl,OPENLAYERS);Gl.prototype.getGeometry=Gl.prototype.V;Gl.prototype.getGeometryFunction=Gl.prototype.Oi;Gl.prototype.getFill=Gl.prototype.zm;Gl.prototype.getImage=Gl.prototype.Am;Gl.prototype.getStroke=Gl.prototype.Bm;Gl.prototype.getText=Gl.prototype.Cm;Gl.prototype.getZIndex=Gl.prototype.Dm;Gl.prototype.setGeometry=Gl.prototype.Jg;Gl.prototype.setZIndex=Gl.prototype.Em;t("ol.style.Text",Ht,OPENLAYERS);
-Ht.prototype.getFont=Ht.prototype.Li;Ht.prototype.getOffsetX=Ht.prototype.aj;Ht.prototype.getOffsetY=Ht.prototype.bj;Ht.prototype.getFill=Ht.prototype.Fm;Ht.prototype.getRotation=Ht.prototype.Gm;Ht.prototype.getScale=Ht.prototype.Hm;Ht.prototype.getStroke=Ht.prototype.Im;Ht.prototype.getText=Ht.prototype.Jm;Ht.prototype.getTextAlign=Ht.prototype.lj;Ht.prototype.getTextBaseline=Ht.prototype.mj;Ht.prototype.setFont=Ht.prototype.On;Ht.prototype.setFill=Ht.prototype.Nn;Ht.prototype.setRotation=Ht.prototype.Km;
-Ht.prototype.setScale=Ht.prototype.Lm;Ht.prototype.setStroke=Ht.prototype.Un;Ht.prototype.setText=Ht.prototype.Vn;Ht.prototype.setTextAlign=Ht.prototype.Wn;Ht.prototype.setTextBaseline=Ht.prototype.Xn;t("ol.Sphere",xe,OPENLAYERS);xe.prototype.geodesicArea=xe.prototype.a;xe.prototype.haversineDistance=xe.prototype.b;t("ol.source.BingMaps",sz,OPENLAYERS);t("ol.source.BingMaps.TOS_ATTRIBUTION",tz,OPENLAYERS);t("ol.source.Cluster",Z,OPENLAYERS);Z.prototype.getSource=Z.prototype.fa;
-t("ol.source.ImageCanvas",zn,OPENLAYERS);t("ol.source.ImageMapGuide",wz,OPENLAYERS);wz.prototype.getParams=wz.prototype.Fl;wz.prototype.getImageLoadFunction=wz.prototype.El;wz.prototype.updateParams=wz.prototype.Il;wz.prototype.setImageLoadFunction=wz.prototype.Hl;t("ol.source.Image",sn,OPENLAYERS);un.prototype.image=un.prototype.image;t("ol.source.ImageStatic",xz,OPENLAYERS);t("ol.source.ImageVector",Rp,OPENLAYERS);Rp.prototype.getSource=Rp.prototype.Jl;Rp.prototype.getStyle=Rp.prototype.Kl;
-Rp.prototype.getStyleFunction=Rp.prototype.Ll;Rp.prototype.setStyle=Rp.prototype.Bg;t("ol.source.ImageWMS",yz,OPENLAYERS);yz.prototype.getGetFeatureInfoUrl=yz.prototype.Ol;yz.prototype.getParams=yz.prototype.Ql;yz.prototype.getImageLoadFunction=yz.prototype.Pl;yz.prototype.getUrl=yz.prototype.Rl;yz.prototype.setImageLoadFunction=yz.prototype.Sl;yz.prototype.setUrl=yz.prototype.Tl;yz.prototype.updateParams=yz.prototype.Ul;t("ol.source.MapQuest",Fz,OPENLAYERS);Fz.prototype.getLayer=Fz.prototype.u;
-t("ol.source.OSM",Dz,OPENLAYERS);t("ol.source.OSM.ATTRIBUTION",Ez,OPENLAYERS);t("ol.source.Raster",Iz,OPENLAYERS);Iz.prototype.setOperation=Iz.prototype.u;Nz.prototype.extent=Nz.prototype.extent;Nz.prototype.resolution=Nz.prototype.resolution;Nz.prototype.data=Nz.prototype.data;t("ol.source.Source",Ch,OPENLAYERS);Ch.prototype.getAttributions=Ch.prototype.na;Ch.prototype.getLogo=Ch.prototype.ma;Ch.prototype.getProjection=Ch.prototype.oa;Ch.prototype.getState=Ch.prototype.pa;
-Ch.prototype.setAttributions=Ch.prototype.la;t("ol.source.Stamen",Sz,OPENLAYERS);t("ol.source.TileArcGISRest",Uz,OPENLAYERS);Uz.prototype.getParams=Uz.prototype.Vl;Uz.prototype.getUrls=Uz.prototype.Wl;Uz.prototype.setUrl=Uz.prototype.Xl;Uz.prototype.setUrls=Uz.prototype.Cg;Uz.prototype.updateParams=Uz.prototype.Zl;t("ol.source.TileDebug",Wz,OPENLAYERS);t("ol.source.TileImage",qz,OPENLAYERS);qz.prototype.getTileLoadFunction=qz.prototype.Ya;qz.prototype.getTileUrlFunction=qz.prototype.Za;
-qz.prototype.setTileLoadFunction=qz.prototype.cb;qz.prototype.setTileUrlFunction=qz.prototype.Ga;t("ol.source.TileJSON",Xz,OPENLAYERS);t("ol.source.Tile",Rh,OPENLAYERS);Rh.prototype.getTileGrid=Rh.prototype.Ca;Vh.prototype.tile=Vh.prototype.tile;t("ol.source.TileUTFGrid",Yz,OPENLAYERS);Yz.prototype.getTemplate=Yz.prototype.kj;Yz.prototype.forDataAtCoordinateAndResolution=Yz.prototype.wi;t("ol.source.TileVector",cA,OPENLAYERS);cA.prototype.getFeaturesAtCoordinateAndResolution=cA.prototype.Ki;
-t("ol.source.TileWMS",fA,OPENLAYERS);fA.prototype.getGetFeatureInfoUrl=fA.prototype.cm;fA.prototype.getParams=fA.prototype.dm;fA.prototype.getUrls=fA.prototype.em;fA.prototype.setUrl=fA.prototype.fm;fA.prototype.setUrls=fA.prototype.Dg;fA.prototype.updateParams=fA.prototype.hm;t("ol.source.Vector",V,OPENLAYERS);V.prototype.addFeature=V.prototype.yc;V.prototype.addFeatures=V.prototype.Nb;V.prototype.clear=V.prototype.clear;V.prototype.forEachFeature=V.prototype.Le;
-V.prototype.forEachFeatureInExtent=V.prototype.sc;V.prototype.forEachFeatureIntersectingExtent=V.prototype.Me;V.prototype.getFeaturesCollection=V.prototype.Re;V.prototype.getFeatures=V.prototype.zc;V.prototype.getFeaturesAtCoordinate=V.prototype.Qe;V.prototype.getFeaturesInExtent=V.prototype.Ad;V.prototype.getClosestFeatureToCoordinate=V.prototype.Oe;V.prototype.getExtent=V.prototype.R;V.prototype.getFeatureById=V.prototype.Pe;V.prototype.removeFeature=V.prototype.dc;Op.prototype.feature=Op.prototype.feature;
-t("ol.source.WMTS",lA,OPENLAYERS);lA.prototype.getDimensions=lA.prototype.Ii;lA.prototype.getFormat=lA.prototype.Mi;lA.prototype.getLayer=lA.prototype.im;lA.prototype.getMatrixSet=lA.prototype.Yi;lA.prototype.getRequestEncoding=lA.prototype.ij;lA.prototype.getStyle=lA.prototype.jm;lA.prototype.getUrls=lA.prototype.km;lA.prototype.getVersion=lA.prototype.oj;lA.prototype.updateDimensions=lA.prototype.io;
-t("ol.source.WMTS.optionsFromCapabilities",function(a,c){var d=cb(a.Contents.Layer,function(a){return a.Identifier==c.layer}),e=a.Contents.TileMatrixSet,f,g;f=1<d.TileMatrixSetLink.length?"projection"in c?db(d.TileMatrixSetLink,function(a){return cb(e,function(c){return c.Identifier==a.TileMatrixSet}).SupportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3")==c.projection}):db(d.TileMatrixSetLink,function(a){return a.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=db(d.Style,function(a){return"style"in c?a.Title==c.style:a.isDefault});0>f&&(f=0);f=d.Style[f].Identifier;var k={};"Dimension"in d&&d.Dimension.forEach(function(a){var c=a.Identifier,d=a["default"];void 0===d&&(d=a.values[0]);k[c]=d});var m=cb(a.Contents.TileMatrixSet,function(a){return a.Identifier==g}),n;n="projection"in c?Ce(c.projection):Ce(m.SupportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"));var p=d.WGS84BoundingBox,q,r;void 0!==
-p&&(r=Ce("EPSG:4326").R(),r=p[0]==r[0]&&p[2]==r[2],q=Ye(p,"EPSG:4326",n),(p=n.R())&&(Wd(p,q)||(q=void 0)));var m=kA(m,q),u=[];q=c.requestEncoding;q=void 0!==q?q:"";if(a.hasOwnProperty("OperationsMetadata")&&a.OperationsMetadata.hasOwnProperty("GetTile")&&0!==q.indexOf("REST"))for(var d=a.OperationsMetadata.GetTile.DCP.HTTP.Get,p=0,y=d.length;p<y;++p){var A=cb(d[p].Constraint,function(a){return"GetEncoding"==a.name}).AllowedValues.Value;0<A.length&&sb(A,"KVP")&&(q="KVP",u.push(d[p].href))}else q="REST",
-d.ResourceURL.forEach(function(a){"tile"==a.resourceType&&(h=a.format,u.push(a.template))});return{urls:u,layer:c.layer,matrixSet:g,format:h,projection:n,requestEncoding:q,tileGrid:m,style:f,dimensions:k,wrapX:r}},OPENLAYERS);t("ol.source.XYZ",Cz,OPENLAYERS);Cz.prototype.getUrls=Cz.prototype.g;Cz.prototype.setUrl=Cz.prototype.f;t("ol.source.Zoomify",nA,OPENLAYERS);fk.prototype.vectorContext=fk.prototype.vectorContext;fk.prototype.frameState=fk.prototype.frameState;fk.prototype.context=fk.prototype.context;
-fk.prototype.glContext=fk.prototype.glContext;t("ol.render.VectorContext",ek,OPENLAYERS);Kq.prototype.drawAsync=Kq.prototype.oc;Kq.prototype.drawCircleGeometry=Kq.prototype.pc;Kq.prototype.drawFeature=Kq.prototype.Je;Kq.prototype.drawGeometryCollectionGeometry=Kq.prototype.xd;Kq.prototype.drawPointGeometry=Kq.prototype.pb;Kq.prototype.drawLineStringGeometry=Kq.prototype.yb;Kq.prototype.drawMultiLineStringGeometry=Kq.prototype.qc;Kq.prototype.drawMultiPointGeometry=Kq.prototype.ob;
-Kq.prototype.drawMultiPolygonGeometry=Kq.prototype.rc;Kq.prototype.drawPolygonGeometry=Kq.prototype.Pb;Kq.prototype.drawText=Kq.prototype.qb;Kq.prototype.setFillStrokeStyle=Kq.prototype.Ia;Kq.prototype.setImageStyle=Kq.prototype.bb;Kq.prototype.setTextStyle=Kq.prototype.Ja;km.prototype.drawAsync=km.prototype.oc;km.prototype.drawCircleGeometry=km.prototype.pc;km.prototype.drawFeature=km.prototype.Je;km.prototype.drawPointGeometry=km.prototype.pb;km.prototype.drawMultiPointGeometry=km.prototype.ob;
-km.prototype.drawLineStringGeometry=km.prototype.yb;km.prototype.drawMultiLineStringGeometry=km.prototype.qc;km.prototype.drawPolygonGeometry=km.prototype.Pb;km.prototype.drawMultiPolygonGeometry=km.prototype.rc;km.prototype.setFillStrokeStyle=km.prototype.Ia;km.prototype.setImageStyle=km.prototype.bb;km.prototype.setTextStyle=km.prototype.Ja;t("ol.proj.common.add",im,OPENLAYERS);t("ol.proj.METERS_PER_UNIT",ze,OPENLAYERS);t("ol.proj.Projection",Ae,OPENLAYERS);Ae.prototype.getCode=Ae.prototype.Gi;
-Ae.prototype.getExtent=Ae.prototype.R;Ae.prototype.getUnits=Ae.prototype.yl;Ae.prototype.getMetersPerUnit=Ae.prototype.Ed;Ae.prototype.getWorldExtent=Ae.prototype.qj;Ae.prototype.isGlobal=Ae.prototype.ck;Ae.prototype.setGlobal=Ae.prototype.Qn;Ae.prototype.setExtent=Ae.prototype.zl;Ae.prototype.setWorldExtent=Ae.prototype.$n;Ae.prototype.setGetPointResolution=Ae.prototype.Pn;Ae.prototype.getPointResolution=Ae.prototype.getPointResolution;t("ol.proj.addEquivalentProjections",De,OPENLAYERS);
-t("ol.proj.addProjection",Re,OPENLAYERS);t("ol.proj.addCoordinateTransforms",Ee,OPENLAYERS);t("ol.proj.fromLonLat",function(a,c){return Xe(a,"EPSG:4326",void 0!==c?c:"EPSG:3857")},OPENLAYERS);t("ol.proj.toLonLat",function(a,c){return Xe(a,void 0!==c?c:"EPSG:3857","EPSG:4326")},OPENLAYERS);t("ol.proj.get",Ce,OPENLAYERS);t("ol.proj.getTransform",Ve,OPENLAYERS);t("ol.proj.transform",Xe,OPENLAYERS);t("ol.proj.transformExtent",Ye,OPENLAYERS);t("ol.layer.Heatmap",Y,OPENLAYERS);Y.prototype.getBlur=Y.prototype.Pf;
-Y.prototype.getGradient=Y.prototype.Sf;Y.prototype.getRadius=Y.prototype.wg;Y.prototype.setBlur=Y.prototype.dh;Y.prototype.setGradient=Y.prototype.hh;Y.prototype.setRadius=Y.prototype.xg;t("ol.layer.Image",jm,OPENLAYERS);jm.prototype.getSource=jm.prototype.ea;t("ol.layer.Layer",gk,OPENLAYERS);gk.prototype.getSource=gk.prototype.ea;gk.prototype.setMap=gk.prototype.setMap;gk.prototype.setSource=gk.prototype.Ec;t("ol.layer.Base",ck,OPENLAYERS);ck.prototype.getExtent=ck.prototype.R;
-ck.prototype.getMaxResolution=ck.prototype.Cb;ck.prototype.getMinResolution=ck.prototype.Db;ck.prototype.getOpacity=ck.prototype.Hb;ck.prototype.getVisible=ck.prototype.ib;ck.prototype.getZIndex=ck.prototype.Ib;ck.prototype.setExtent=ck.prototype.Yb;ck.prototype.setMaxResolution=ck.prototype.gc;ck.prototype.setMinResolution=ck.prototype.hc;ck.prototype.setOpacity=ck.prototype.Zb;ck.prototype.setVisible=ck.prototype.$b;ck.prototype.setZIndex=ck.prototype.ac;t("ol.layer.Group",bm,OPENLAYERS);
-bm.prototype.getLayers=bm.prototype.xc;bm.prototype.setLayers=bm.prototype.jh;t("ol.layer.Tile",G,OPENLAYERS);G.prototype.getPreload=G.prototype.a;G.prototype.getSource=G.prototype.ea;G.prototype.setPreload=G.prototype.f;G.prototype.getUseInterimTilesOnError=G.prototype.c;G.prototype.setUseInterimTilesOnError=G.prototype.g;t("ol.layer.Vector",H,OPENLAYERS);H.prototype.getSource=H.prototype.ea;H.prototype.getStyle=H.prototype.T;H.prototype.getStyleFunction=H.prototype.X;H.prototype.setStyle=H.prototype.g;
-t("ol.interaction.DoubleClickZoom",Uk,OPENLAYERS);t("ol.interaction.DoubleClickZoom.handleEvent",Vk,OPENLAYERS);t("ol.interaction.DragAndDrop",Yx,OPENLAYERS);t("ol.interaction.DragAndDrop.handleEvent",re,OPENLAYERS);Zx.prototype.features=Zx.prototype.features;Zx.prototype.file=Zx.prototype.file;Zx.prototype.projection=Zx.prototype.projection;rl.prototype.coordinate=rl.prototype.coordinate;t("ol.interaction.DragBox",sl,OPENLAYERS);sl.prototype.getGeometry=sl.prototype.V;
-t("ol.interaction.DragPan",fl,OPENLAYERS);t("ol.interaction.DragRotateAndZoom",by,OPENLAYERS);t("ol.interaction.DragRotate",jl,OPENLAYERS);t("ol.interaction.DragZoom",Ml,OPENLAYERS);fy.prototype.feature=fy.prototype.feature;t("ol.interaction.Draw",gy,OPENLAYERS);t("ol.interaction.Draw.handleEvent",iy,OPENLAYERS);gy.prototype.removeLastPoint=gy.prototype.Dn;gy.prototype.finishDrawing=gy.prototype.Vc;gy.prototype.extend=gy.prototype.dl;
-t("ol.interaction.Draw.createRegularPolygon",function(a,c){return function(d,e){var f=d[0],g=d[1],h=Math.sqrt(wd(f,g)),k=e?e:Mf(new Ym(f),a);Nf(k,f,h,c?c:Math.atan((g[1]-f[1])/(g[0]-f[0])));return k}},OPENLAYERS);t("ol.interaction.Interaction",Qk,OPENLAYERS);Qk.prototype.getActive=Qk.prototype.c;Qk.prototype.setActive=Qk.prototype.g;t("ol.interaction.defaults",am,OPENLAYERS);t("ol.interaction.KeyboardPan",Nl,OPENLAYERS);t("ol.interaction.KeyboardPan.handleEvent",Ol,OPENLAYERS);
-t("ol.interaction.KeyboardZoom",Pl,OPENLAYERS);t("ol.interaction.KeyboardZoom.handleEvent",Ql,OPENLAYERS);wy.prototype.features=wy.prototype.features;wy.prototype.mapBrowserPointerEvent=wy.prototype.mapBrowserPointerEvent;t("ol.interaction.Modify",xy,OPENLAYERS);t("ol.interaction.Modify.handleEvent",Ay,OPENLAYERS);t("ol.interaction.MouseWheelZoom",Rl,OPENLAYERS);t("ol.interaction.MouseWheelZoom.handleEvent",Sl,OPENLAYERS);Rl.prototype.setMouseAnchor=Rl.prototype.B;
-t("ol.interaction.PinchRotate",Tl,OPENLAYERS);t("ol.interaction.PinchZoom",Xl,OPENLAYERS);t("ol.interaction.Pointer",cl,OPENLAYERS);t("ol.interaction.Pointer.handleEvent",dl,OPENLAYERS);Ky.prototype.selected=Ky.prototype.selected;Ky.prototype.deselected=Ky.prototype.deselected;Ky.prototype.mapBrowserEvent=Ky.prototype.mapBrowserEvent;t("ol.interaction.Select",Ly,OPENLAYERS);Ly.prototype.getFeatures=Ly.prototype.nl;Ly.prototype.getLayer=Ly.prototype.ol;t("ol.interaction.Select.handleEvent",My,OPENLAYERS);
-Ly.prototype.setMap=Ly.prototype.setMap;t("ol.interaction.Snap",Oy,OPENLAYERS);Oy.prototype.addFeature=Oy.prototype.ad;Oy.prototype.removeFeature=Oy.prototype.bd;t("ol.interaction.Translate",Sy,OPENLAYERS);t("ol.geom.Circle",Ym,OPENLAYERS);Ym.prototype.clone=Ym.prototype.clone;Ym.prototype.getCenter=Ym.prototype.$c;Ym.prototype.getRadius=Ym.prototype.df;Ym.prototype.getType=Ym.prototype.W;Ym.prototype.intersectsExtent=Ym.prototype.ua;Ym.prototype.setCenter=Ym.prototype.Wk;
-Ym.prototype.setCenterAndRadius=Ym.prototype.uf;Ym.prototype.setRadius=Ym.prototype.Xk;Ym.prototype.transform=Ym.prototype.Sa;t("ol.geom.Geometry",Ze,OPENLAYERS);Ze.prototype.getClosestPoint=Ze.prototype.Xa;Ze.prototype.getExtent=Ze.prototype.R;Ze.prototype.simplify=Ze.prototype.eb;Ze.prototype.transform=Ze.prototype.Sa;t("ol.geom.GeometryCollection",$m,OPENLAYERS);$m.prototype.clone=$m.prototype.clone;$m.prototype.getGeometries=$m.prototype.Rf;$m.prototype.getType=$m.prototype.W;
-$m.prototype.intersectsExtent=$m.prototype.ua;$m.prototype.setGeometries=$m.prototype.gh;$m.prototype.applyTransform=$m.prototype.Ob;$m.prototype.translate=$m.prototype.wc;t("ol.geom.LinearRing",tf,OPENLAYERS);tf.prototype.clone=tf.prototype.clone;tf.prototype.getArea=tf.prototype.$k;tf.prototype.getCoordinates=tf.prototype.U;tf.prototype.getType=tf.prototype.W;tf.prototype.setCoordinates=tf.prototype.ja;t("ol.geom.LineString",L,OPENLAYERS);L.prototype.appendCoordinate=L.prototype.ji;
-L.prototype.clone=L.prototype.clone;L.prototype.forEachSegment=L.prototype.zi;L.prototype.getCoordinateAtM=L.prototype.Yk;L.prototype.getCoordinates=L.prototype.U;L.prototype.getLength=L.prototype.Zk;L.prototype.getType=L.prototype.W;L.prototype.intersectsExtent=L.prototype.ua;L.prototype.setCoordinates=L.prototype.ja;t("ol.geom.MultiLineString",O,OPENLAYERS);O.prototype.appendLineString=O.prototype.ki;O.prototype.clone=O.prototype.clone;O.prototype.getCoordinateAtM=O.prototype.al;
-O.prototype.getCoordinates=O.prototype.U;O.prototype.getLineString=O.prototype.Wi;O.prototype.getLineStrings=O.prototype.Xc;O.prototype.getType=O.prototype.W;O.prototype.intersectsExtent=O.prototype.ua;O.prototype.setCoordinates=O.prototype.ja;t("ol.geom.MultiPoint",kn,OPENLAYERS);kn.prototype.appendPoint=kn.prototype.mi;kn.prototype.clone=kn.prototype.clone;kn.prototype.getCoordinates=kn.prototype.U;kn.prototype.getPoint=kn.prototype.ej;kn.prototype.getPoints=kn.prototype.Yd;
-kn.prototype.getType=kn.prototype.W;kn.prototype.intersectsExtent=kn.prototype.ua;kn.prototype.setCoordinates=kn.prototype.ja;t("ol.geom.MultiPolygon",P,OPENLAYERS);P.prototype.appendPolygon=P.prototype.ni;P.prototype.clone=P.prototype.clone;P.prototype.getArea=P.prototype.bl;P.prototype.getCoordinates=P.prototype.U;P.prototype.getInteriorPoints=P.prototype.Ti;P.prototype.getPolygon=P.prototype.gj;P.prototype.getPolygons=P.prototype.Fd;P.prototype.getType=P.prototype.W;
-P.prototype.intersectsExtent=P.prototype.ua;P.prototype.setCoordinates=P.prototype.ja;t("ol.geom.Point",D,OPENLAYERS);D.prototype.clone=D.prototype.clone;D.prototype.getCoordinates=D.prototype.U;D.prototype.getType=D.prototype.W;D.prototype.intersectsExtent=D.prototype.ua;D.prototype.setCoordinates=D.prototype.ja;t("ol.geom.Polygon",E,OPENLAYERS);E.prototype.appendLinearRing=E.prototype.li;E.prototype.clone=E.prototype.clone;E.prototype.getArea=E.prototype.cl;E.prototype.getCoordinates=E.prototype.U;
-E.prototype.getInteriorPoint=E.prototype.Si;E.prototype.getLinearRingCount=E.prototype.Xi;E.prototype.getLinearRing=E.prototype.Tf;E.prototype.getLinearRings=E.prototype.Dd;E.prototype.getType=E.prototype.W;E.prototype.intersectsExtent=E.prototype.ua;E.prototype.setCoordinates=E.prototype.ja;t("ol.geom.Polygon.circular",Kf,OPENLAYERS);t("ol.geom.Polygon.fromExtent",Lf,OPENLAYERS);t("ol.geom.Polygon.fromCircle",Mf,OPENLAYERS);t("ol.geom.SimpleGeometry",af,OPENLAYERS);
-af.prototype.getFirstCoordinate=af.prototype.sb;af.prototype.getLastCoordinate=af.prototype.tb;af.prototype.getLayout=af.prototype.ub;af.prototype.applyTransform=af.prototype.Ob;af.prototype.translate=af.prototype.wc;t("ol.format.EsriJSON",Pr,OPENLAYERS);Pr.prototype.readFeature=Pr.prototype.vb;Pr.prototype.readFeatures=Pr.prototype.sa;Pr.prototype.readGeometry=Pr.prototype.Bc;Pr.prototype.readProjection=Pr.prototype.Ha;Pr.prototype.writeGeometry=Pr.prototype.Hc;Pr.prototype.writeGeometryObject=Pr.prototype.we;
-Pr.prototype.writeFeature=Pr.prototype.kd;Pr.prototype.writeFeatureObject=Pr.prototype.Gc;Pr.prototype.writeFeatures=Pr.prototype.xb;Pr.prototype.writeFeaturesObject=Pr.prototype.ue;t("ol.format.Feature",Jr,OPENLAYERS);t("ol.format.GeoJSON",Wr,OPENLAYERS);Wr.prototype.readFeature=Wr.prototype.vb;Wr.prototype.readFeatures=Wr.prototype.sa;Wr.prototype.readGeometry=Wr.prototype.Bc;Wr.prototype.readProjection=Wr.prototype.Ha;Wr.prototype.writeFeature=Wr.prototype.kd;Wr.prototype.writeFeatureObject=Wr.prototype.Gc;
-Wr.prototype.writeFeatures=Wr.prototype.xb;Wr.prototype.writeFeaturesObject=Wr.prototype.ue;Wr.prototype.writeGeometry=Wr.prototype.Hc;Wr.prototype.writeGeometryObject=Wr.prototype.we;t("ol.format.GPX",zs,OPENLAYERS);zs.prototype.readFeature=zs.prototype.vb;zs.prototype.readFeatures=zs.prototype.sa;zs.prototype.readProjection=zs.prototype.Ha;zs.prototype.writeFeatures=zs.prototype.xb;zs.prototype.writeFeaturesNode=zs.prototype.a;t("ol.format.IGC",jt,OPENLAYERS);jt.prototype.readFeature=jt.prototype.vb;
-jt.prototype.readFeatures=jt.prototype.sa;jt.prototype.readProjection=jt.prototype.Ha;t("ol.format.KML",It,OPENLAYERS);It.prototype.readFeature=It.prototype.vb;It.prototype.readFeatures=It.prototype.sa;It.prototype.readName=It.prototype.sn;It.prototype.readNetworkLinks=It.prototype.tn;It.prototype.readProjection=It.prototype.Ha;It.prototype.writeFeatures=It.prototype.xb;It.prototype.writeFeaturesNode=It.prototype.a;t("ol.format.OSMXML",vv,OPENLAYERS);vv.prototype.readFeatures=vv.prototype.sa;
-vv.prototype.readProjection=vv.prototype.Ha;t("ol.format.Polyline",Uv,OPENLAYERS);t("ol.format.Polyline.encodeDeltas",Vv,OPENLAYERS);t("ol.format.Polyline.decodeDeltas",Xv,OPENLAYERS);t("ol.format.Polyline.encodeFloats",Wv,OPENLAYERS);t("ol.format.Polyline.decodeFloats",Yv,OPENLAYERS);Uv.prototype.readFeature=Uv.prototype.vb;Uv.prototype.readFeatures=Uv.prototype.sa;Uv.prototype.readGeometry=Uv.prototype.Bc;Uv.prototype.readProjection=Uv.prototype.Ha;Uv.prototype.writeGeometry=Uv.prototype.Hc;
-t("ol.format.TopoJSON",Zv,OPENLAYERS);Zv.prototype.readFeatures=Zv.prototype.sa;Zv.prototype.readProjection=Zv.prototype.Ha;t("ol.format.WFS",ew,OPENLAYERS);ew.prototype.readFeatures=ew.prototype.sa;ew.prototype.readTransactionResponse=ew.prototype.i;ew.prototype.readFeatureCollectionMetadata=ew.prototype.j;ew.prototype.writeGetFeature=ew.prototype.l;ew.prototype.writeTransaction=ew.prototype.v;ew.prototype.readProjection=ew.prototype.Ha;t("ol.format.WKT",rw,OPENLAYERS);rw.prototype.readFeature=rw.prototype.vb;
-rw.prototype.readFeatures=rw.prototype.sa;rw.prototype.readGeometry=rw.prototype.Bc;rw.prototype.writeFeature=rw.prototype.kd;rw.prototype.writeFeatures=rw.prototype.xb;rw.prototype.writeGeometry=rw.prototype.Hc;t("ol.format.WMSCapabilities",Jw,OPENLAYERS);Jw.prototype.read=Jw.prototype.c;t("ol.format.WMSGetFeatureInfo",fx,OPENLAYERS);fx.prototype.readFeatures=fx.prototype.sa;t("ol.format.WMTSCapabilities",gx,OPENLAYERS);gx.prototype.read=gx.prototype.c;t("ol.format.GML2",ps,OPENLAYERS);
-t("ol.format.GML3",qs,OPENLAYERS);qs.prototype.writeGeometryNode=qs.prototype.C;qs.prototype.writeFeatures=qs.prototype.xb;qs.prototype.writeFeaturesNode=qs.prototype.a;t("ol.format.GML",qs,OPENLAYERS);qs.prototype.writeFeatures=qs.prototype.xb;qs.prototype.writeFeaturesNode=qs.prototype.a;ds.prototype.readFeatures=ds.prototype.sa;t("ol.events.condition.altKeyOnly",function(a){a=a.b;return a.a&&!a.l&&!a.f},OPENLAYERS);t("ol.events.condition.altShiftKeysOnly",Wk,OPENLAYERS);
-t("ol.events.condition.always",re,OPENLAYERS);t("ol.events.condition.click",function(a){return a.type==Sj},OPENLAYERS);t("ol.events.condition.never",qe,OPENLAYERS);t("ol.events.condition.pointerMove",Xk,OPENLAYERS);t("ol.events.condition.singleClick",Yk,OPENLAYERS);t("ol.events.condition.doubleClick",function(a){return a.type==Tj},OPENLAYERS);t("ol.events.condition.noModifierKeys",Zk,OPENLAYERS);t("ol.events.condition.platformModifierKeyOnly",function(a){a=a.b;return!a.a&&a.l&&!a.f},OPENLAYERS);
-t("ol.events.condition.shiftKeyOnly",$k,OPENLAYERS);t("ol.events.condition.targetNotEditable",al,OPENLAYERS);t("ol.events.condition.mouseOnly",bl,OPENLAYERS);t("ol.control.Attribution",Wh,OPENLAYERS);t("ol.control.Attribution.render",Xh,OPENLAYERS);Wh.prototype.getCollapsible=Wh.prototype.Mk;Wh.prototype.setCollapsible=Wh.prototype.Pk;Wh.prototype.setCollapsed=Wh.prototype.Ok;Wh.prototype.getCollapsed=Wh.prototype.Lk;t("ol.control.Control",vh,OPENLAYERS);vh.prototype.getMap=vh.prototype.g;
-vh.prototype.setMap=vh.prototype.setMap;vh.prototype.setTarget=vh.prototype.c;t("ol.control.defaults",bi,OPENLAYERS);t("ol.control.FullScreen",gi,OPENLAYERS);t("ol.control.MousePosition",hi,OPENLAYERS);t("ol.control.MousePosition.render",ii,OPENLAYERS);hi.prototype.getCoordinateFormat=hi.prototype.Qf;hi.prototype.getProjection=hi.prototype.pg;hi.prototype.setCoordinateFormat=hi.prototype.eh;hi.prototype.setProjection=hi.prototype.qg;t("ol.control.OverviewMap",jr,OPENLAYERS);
-t("ol.control.OverviewMap.render",kr,OPENLAYERS);jr.prototype.getCollapsible=jr.prototype.Sk;jr.prototype.setCollapsible=jr.prototype.Vk;jr.prototype.setCollapsed=jr.prototype.Uk;jr.prototype.getCollapsed=jr.prototype.Rk;t("ol.control.Rotate",Zh,OPENLAYERS);t("ol.control.Rotate.render",$h,OPENLAYERS);t("ol.control.ScaleLine",or,OPENLAYERS);or.prototype.getUnits=or.prototype.N;t("ol.control.ScaleLine.render",pr,OPENLAYERS);or.prototype.setUnits=or.prototype.X;t("ol.control.Zoom",ai,OPENLAYERS);
-t("ol.control.ZoomSlider",Cr,OPENLAYERS);t("ol.control.ZoomSlider.render",Er,OPENLAYERS);t("ol.control.ZoomToExtent",Hr,OPENLAYERS);t("ol.color.asArray",yg,OPENLAYERS);t("ol.color.asString",Ag,OPENLAYERS);jd.prototype.changed=jd.prototype.s;jd.prototype.getRevision=jd.prototype.K;jd.prototype.on=jd.prototype.D;jd.prototype.once=jd.prototype.L;jd.prototype.un=jd.prototype.J;jd.prototype.unByKey=jd.prototype.M;tg.prototype.get=tg.prototype.get;tg.prototype.getKeys=tg.prototype.O;
-tg.prototype.getProperties=tg.prototype.P;tg.prototype.set=tg.prototype.set;tg.prototype.setProperties=tg.prototype.H;tg.prototype.unset=tg.prototype.S;tg.prototype.changed=tg.prototype.s;tg.prototype.getRevision=tg.prototype.K;tg.prototype.on=tg.prototype.D;tg.prototype.once=tg.prototype.L;tg.prototype.un=tg.prototype.J;tg.prototype.unByKey=tg.prototype.M;Ir.prototype.get=Ir.prototype.get;Ir.prototype.getKeys=Ir.prototype.O;Ir.prototype.getProperties=Ir.prototype.P;Ir.prototype.set=Ir.prototype.set;
-Ir.prototype.setProperties=Ir.prototype.H;Ir.prototype.unset=Ir.prototype.S;Ir.prototype.changed=Ir.prototype.s;Ir.prototype.getRevision=Ir.prototype.K;Ir.prototype.on=Ir.prototype.D;Ir.prototype.once=Ir.prototype.L;Ir.prototype.un=Ir.prototype.J;Ir.prototype.unByKey=Ir.prototype.M;Q.prototype.get=Q.prototype.get;Q.prototype.getKeys=Q.prototype.O;Q.prototype.getProperties=Q.prototype.P;Q.prototype.set=Q.prototype.set;Q.prototype.setProperties=Q.prototype.H;Q.prototype.unset=Q.prototype.S;
-Q.prototype.changed=Q.prototype.s;Q.prototype.getRevision=Q.prototype.K;Q.prototype.on=Q.prototype.D;Q.prototype.once=Q.prototype.L;Q.prototype.un=Q.prototype.J;Q.prototype.unByKey=Q.prototype.M;tx.prototype.get=tx.prototype.get;tx.prototype.getKeys=tx.prototype.O;tx.prototype.getProperties=tx.prototype.P;tx.prototype.set=tx.prototype.set;tx.prototype.setProperties=tx.prototype.H;tx.prototype.unset=tx.prototype.S;tx.prototype.changed=tx.prototype.s;tx.prototype.getRevision=tx.prototype.K;
-tx.prototype.on=tx.prototype.D;tx.prototype.once=tx.prototype.L;tx.prototype.un=tx.prototype.J;tx.prototype.unByKey=tx.prototype.M;Dx.prototype.getTileCoord=Dx.prototype.j;W.prototype.get=W.prototype.get;W.prototype.getKeys=W.prototype.O;W.prototype.getProperties=W.prototype.P;W.prototype.set=W.prototype.set;W.prototype.setProperties=W.prototype.H;W.prototype.unset=W.prototype.S;W.prototype.changed=W.prototype.s;W.prototype.getRevision=W.prototype.K;W.prototype.on=W.prototype.D;W.prototype.once=W.prototype.L;
-W.prototype.un=W.prototype.J;W.prototype.unByKey=W.prototype.M;Oj.prototype.map=Oj.prototype.map;Oj.prototype.frameState=Oj.prototype.frameState;Pj.prototype.originalEvent=Pj.prototype.originalEvent;Pj.prototype.pixel=Pj.prototype.pixel;Pj.prototype.coordinate=Pj.prototype.coordinate;Pj.prototype.dragging=Pj.prototype.dragging;Pj.prototype.preventDefault=Pj.prototype.preventDefault;Pj.prototype.stopPropagation=Pj.prototype.c;Pj.prototype.map=Pj.prototype.map;Pj.prototype.frameState=Pj.prototype.frameState;
-fr.prototype.get=fr.prototype.get;fr.prototype.getKeys=fr.prototype.O;fr.prototype.getProperties=fr.prototype.P;fr.prototype.set=fr.prototype.set;fr.prototype.setProperties=fr.prototype.H;fr.prototype.unset=fr.prototype.S;fr.prototype.changed=fr.prototype.s;fr.prototype.getRevision=fr.prototype.K;fr.prototype.on=fr.prototype.D;fr.prototype.once=fr.prototype.L;fr.prototype.un=fr.prototype.J;fr.prototype.unByKey=fr.prototype.M;Of.prototype.get=Of.prototype.get;Of.prototype.getKeys=Of.prototype.O;
-Of.prototype.getProperties=Of.prototype.P;Of.prototype.set=Of.prototype.set;Of.prototype.setProperties=Of.prototype.H;Of.prototype.unset=Of.prototype.S;Of.prototype.changed=Of.prototype.s;Of.prototype.getRevision=Of.prototype.K;Of.prototype.on=Of.prototype.D;Of.prototype.once=Of.prototype.L;Of.prototype.un=Of.prototype.J;Of.prototype.unByKey=Of.prototype.M;jA.prototype.getMaxZoom=jA.prototype.Uf;jA.prototype.getMinZoom=jA.prototype.Vf;jA.prototype.getOrigin=jA.prototype.ta;
-jA.prototype.getResolution=jA.prototype.aa;jA.prototype.getResolutions=jA.prototype.Kg;jA.prototype.getTileCoordForCoordAndResolution=jA.prototype.Yc;jA.prototype.getTileCoordForCoordAndZ=jA.prototype.Jd;jA.prototype.getTileSize=jA.prototype.Ka;Fl.prototype.getOpacity=Fl.prototype.de;Fl.prototype.getRotateWithView=Fl.prototype.Gd;Fl.prototype.getRotation=Fl.prototype.ee;Fl.prototype.getScale=Fl.prototype.fe;Fl.prototype.getSnapToPixel=Fl.prototype.Id;Fl.prototype.setOpacity=Fl.prototype.ge;
-Fl.prototype.setRotation=Fl.prototype.he;Fl.prototype.setScale=Fl.prototype.ie;yk.prototype.getOpacity=yk.prototype.de;yk.prototype.getRotateWithView=yk.prototype.Gd;yk.prototype.getRotation=yk.prototype.ee;yk.prototype.getScale=yk.prototype.fe;yk.prototype.getSnapToPixel=yk.prototype.Id;yk.prototype.setOpacity=yk.prototype.ge;yk.prototype.setRotation=yk.prototype.he;yk.prototype.setScale=yk.prototype.ie;tA.prototype.getOpacity=tA.prototype.de;tA.prototype.getRotateWithView=tA.prototype.Gd;
-tA.prototype.getRotation=tA.prototype.ee;tA.prototype.getScale=tA.prototype.fe;tA.prototype.getSnapToPixel=tA.prototype.Id;tA.prototype.setOpacity=tA.prototype.ge;tA.prototype.setRotation=tA.prototype.he;tA.prototype.setScale=tA.prototype.ie;Ch.prototype.get=Ch.prototype.get;Ch.prototype.getKeys=Ch.prototype.O;Ch.prototype.getProperties=Ch.prototype.P;Ch.prototype.set=Ch.prototype.set;Ch.prototype.setProperties=Ch.prototype.H;Ch.prototype.unset=Ch.prototype.S;Ch.prototype.changed=Ch.prototype.s;
-Ch.prototype.getRevision=Ch.prototype.K;Ch.prototype.on=Ch.prototype.D;Ch.prototype.once=Ch.prototype.L;Ch.prototype.un=Ch.prototype.J;Ch.prototype.unByKey=Ch.prototype.M;Rh.prototype.getAttributions=Rh.prototype.na;Rh.prototype.getLogo=Rh.prototype.ma;Rh.prototype.getProjection=Rh.prototype.oa;Rh.prototype.getState=Rh.prototype.pa;Rh.prototype.setAttributions=Rh.prototype.la;Rh.prototype.get=Rh.prototype.get;Rh.prototype.getKeys=Rh.prototype.O;Rh.prototype.getProperties=Rh.prototype.P;
-Rh.prototype.set=Rh.prototype.set;Rh.prototype.setProperties=Rh.prototype.H;Rh.prototype.unset=Rh.prototype.S;Rh.prototype.changed=Rh.prototype.s;Rh.prototype.getRevision=Rh.prototype.K;Rh.prototype.on=Rh.prototype.D;Rh.prototype.once=Rh.prototype.L;Rh.prototype.un=Rh.prototype.J;Rh.prototype.unByKey=Rh.prototype.M;qz.prototype.getTileGrid=qz.prototype.Ca;qz.prototype.getAttributions=qz.prototype.na;qz.prototype.getLogo=qz.prototype.ma;qz.prototype.getProjection=qz.prototype.oa;
-qz.prototype.getState=qz.prototype.pa;qz.prototype.setAttributions=qz.prototype.la;qz.prototype.get=qz.prototype.get;qz.prototype.getKeys=qz.prototype.O;qz.prototype.getProperties=qz.prototype.P;qz.prototype.set=qz.prototype.set;qz.prototype.setProperties=qz.prototype.H;qz.prototype.unset=qz.prototype.S;qz.prototype.changed=qz.prototype.s;qz.prototype.getRevision=qz.prototype.K;qz.prototype.on=qz.prototype.D;qz.prototype.once=qz.prototype.L;qz.prototype.un=qz.prototype.J;qz.prototype.unByKey=qz.prototype.M;
-sz.prototype.getTileLoadFunction=sz.prototype.Ya;sz.prototype.getTileUrlFunction=sz.prototype.Za;sz.prototype.setTileLoadFunction=sz.prototype.cb;sz.prototype.setTileUrlFunction=sz.prototype.Ga;sz.prototype.getTileGrid=sz.prototype.Ca;sz.prototype.getAttributions=sz.prototype.na;sz.prototype.getLogo=sz.prototype.ma;sz.prototype.getProjection=sz.prototype.oa;sz.prototype.getState=sz.prototype.pa;sz.prototype.setAttributions=sz.prototype.la;sz.prototype.get=sz.prototype.get;sz.prototype.getKeys=sz.prototype.O;
-sz.prototype.getProperties=sz.prototype.P;sz.prototype.set=sz.prototype.set;sz.prototype.setProperties=sz.prototype.H;sz.prototype.unset=sz.prototype.S;sz.prototype.changed=sz.prototype.s;sz.prototype.getRevision=sz.prototype.K;sz.prototype.on=sz.prototype.D;sz.prototype.once=sz.prototype.L;sz.prototype.un=sz.prototype.J;sz.prototype.unByKey=sz.prototype.M;V.prototype.getAttributions=V.prototype.na;V.prototype.getLogo=V.prototype.ma;V.prototype.getProjection=V.prototype.oa;V.prototype.getState=V.prototype.pa;
-V.prototype.setAttributions=V.prototype.la;V.prototype.get=V.prototype.get;V.prototype.getKeys=V.prototype.O;V.prototype.getProperties=V.prototype.P;V.prototype.set=V.prototype.set;V.prototype.setProperties=V.prototype.H;V.prototype.unset=V.prototype.S;V.prototype.changed=V.prototype.s;V.prototype.getRevision=V.prototype.K;V.prototype.on=V.prototype.D;V.prototype.once=V.prototype.L;V.prototype.un=V.prototype.J;V.prototype.unByKey=V.prototype.M;Z.prototype.addFeature=Z.prototype.yc;
-Z.prototype.addFeatures=Z.prototype.Nb;Z.prototype.clear=Z.prototype.clear;Z.prototype.forEachFeature=Z.prototype.Le;Z.prototype.forEachFeatureInExtent=Z.prototype.sc;Z.prototype.forEachFeatureIntersectingExtent=Z.prototype.Me;Z.prototype.getFeaturesCollection=Z.prototype.Re;Z.prototype.getFeatures=Z.prototype.zc;Z.prototype.getFeaturesAtCoordinate=Z.prototype.Qe;Z.prototype.getFeaturesInExtent=Z.prototype.Ad;Z.prototype.getClosestFeatureToCoordinate=Z.prototype.Oe;Z.prototype.getExtent=Z.prototype.R;
-Z.prototype.getFeatureById=Z.prototype.Pe;Z.prototype.removeFeature=Z.prototype.dc;Z.prototype.getAttributions=Z.prototype.na;Z.prototype.getLogo=Z.prototype.ma;Z.prototype.getProjection=Z.prototype.oa;Z.prototype.getState=Z.prototype.pa;Z.prototype.setAttributions=Z.prototype.la;Z.prototype.get=Z.prototype.get;Z.prototype.getKeys=Z.prototype.O;Z.prototype.getProperties=Z.prototype.P;Z.prototype.set=Z.prototype.set;Z.prototype.setProperties=Z.prototype.H;Z.prototype.unset=Z.prototype.S;
-Z.prototype.changed=Z.prototype.s;Z.prototype.getRevision=Z.prototype.K;Z.prototype.on=Z.prototype.D;Z.prototype.once=Z.prototype.L;Z.prototype.un=Z.prototype.J;Z.prototype.unByKey=Z.prototype.M;sn.prototype.getAttributions=sn.prototype.na;sn.prototype.getLogo=sn.prototype.ma;sn.prototype.getProjection=sn.prototype.oa;sn.prototype.getState=sn.prototype.pa;sn.prototype.setAttributions=sn.prototype.la;sn.prototype.get=sn.prototype.get;sn.prototype.getKeys=sn.prototype.O;sn.prototype.getProperties=sn.prototype.P;
-sn.prototype.set=sn.prototype.set;sn.prototype.setProperties=sn.prototype.H;sn.prototype.unset=sn.prototype.S;sn.prototype.changed=sn.prototype.s;sn.prototype.getRevision=sn.prototype.K;sn.prototype.on=sn.prototype.D;sn.prototype.once=sn.prototype.L;sn.prototype.un=sn.prototype.J;sn.prototype.unByKey=sn.prototype.M;zn.prototype.getAttributions=zn.prototype.na;zn.prototype.getLogo=zn.prototype.ma;zn.prototype.getProjection=zn.prototype.oa;zn.prototype.getState=zn.prototype.pa;
-zn.prototype.setAttributions=zn.prototype.la;zn.prototype.get=zn.prototype.get;zn.prototype.getKeys=zn.prototype.O;zn.prototype.getProperties=zn.prototype.P;zn.prototype.set=zn.prototype.set;zn.prototype.setProperties=zn.prototype.H;zn.prototype.unset=zn.prototype.S;zn.prototype.changed=zn.prototype.s;zn.prototype.getRevision=zn.prototype.K;zn.prototype.on=zn.prototype.D;zn.prototype.once=zn.prototype.L;zn.prototype.un=zn.prototype.J;zn.prototype.unByKey=zn.prototype.M;
-wz.prototype.getAttributions=wz.prototype.na;wz.prototype.getLogo=wz.prototype.ma;wz.prototype.getProjection=wz.prototype.oa;wz.prototype.getState=wz.prototype.pa;wz.prototype.setAttributions=wz.prototype.la;wz.prototype.get=wz.prototype.get;wz.prototype.getKeys=wz.prototype.O;wz.prototype.getProperties=wz.prototype.P;wz.prototype.set=wz.prototype.set;wz.prototype.setProperties=wz.prototype.H;wz.prototype.unset=wz.prototype.S;wz.prototype.changed=wz.prototype.s;wz.prototype.getRevision=wz.prototype.K;
-wz.prototype.on=wz.prototype.D;wz.prototype.once=wz.prototype.L;wz.prototype.un=wz.prototype.J;wz.prototype.unByKey=wz.prototype.M;xz.prototype.getAttributions=xz.prototype.na;xz.prototype.getLogo=xz.prototype.ma;xz.prototype.getProjection=xz.prototype.oa;xz.prototype.getState=xz.prototype.pa;xz.prototype.setAttributions=xz.prototype.la;xz.prototype.get=xz.prototype.get;xz.prototype.getKeys=xz.prototype.O;xz.prototype.getProperties=xz.prototype.P;xz.prototype.set=xz.prototype.set;
-xz.prototype.setProperties=xz.prototype.H;xz.prototype.unset=xz.prototype.S;xz.prototype.changed=xz.prototype.s;xz.prototype.getRevision=xz.prototype.K;xz.prototype.on=xz.prototype.D;xz.prototype.once=xz.prototype.L;xz.prototype.un=xz.prototype.J;xz.prototype.unByKey=xz.prototype.M;Rp.prototype.getAttributions=Rp.prototype.na;Rp.prototype.getLogo=Rp.prototype.ma;Rp.prototype.getProjection=Rp.prototype.oa;Rp.prototype.getState=Rp.prototype.pa;Rp.prototype.setAttributions=Rp.prototype.la;
-Rp.prototype.get=Rp.prototype.get;Rp.prototype.getKeys=Rp.prototype.O;Rp.prototype.getProperties=Rp.prototype.P;Rp.prototype.set=Rp.prototype.set;Rp.prototype.setProperties=Rp.prototype.H;Rp.prototype.unset=Rp.prototype.S;Rp.prototype.changed=Rp.prototype.s;Rp.prototype.getRevision=Rp.prototype.K;Rp.prototype.on=Rp.prototype.D;Rp.prototype.once=Rp.prototype.L;Rp.prototype.un=Rp.prototype.J;Rp.prototype.unByKey=Rp.prototype.M;yz.prototype.getAttributions=yz.prototype.na;yz.prototype.getLogo=yz.prototype.ma;
-yz.prototype.getProjection=yz.prototype.oa;yz.prototype.getState=yz.prototype.pa;yz.prototype.setAttributions=yz.prototype.la;yz.prototype.get=yz.prototype.get;yz.prototype.getKeys=yz.prototype.O;yz.prototype.getProperties=yz.prototype.P;yz.prototype.set=yz.prototype.set;yz.prototype.setProperties=yz.prototype.H;yz.prototype.unset=yz.prototype.S;yz.prototype.changed=yz.prototype.s;yz.prototype.getRevision=yz.prototype.K;yz.prototype.on=yz.prototype.D;yz.prototype.once=yz.prototype.L;
-yz.prototype.un=yz.prototype.J;yz.prototype.unByKey=yz.prototype.M;Cz.prototype.getTileLoadFunction=Cz.prototype.Ya;Cz.prototype.getTileUrlFunction=Cz.prototype.Za;Cz.prototype.setTileLoadFunction=Cz.prototype.cb;Cz.prototype.setTileUrlFunction=Cz.prototype.Ga;Cz.prototype.getTileGrid=Cz.prototype.Ca;Cz.prototype.getAttributions=Cz.prototype.na;Cz.prototype.getLogo=Cz.prototype.ma;Cz.prototype.getProjection=Cz.prototype.oa;Cz.prototype.getState=Cz.prototype.pa;Cz.prototype.setAttributions=Cz.prototype.la;
-Cz.prototype.get=Cz.prototype.get;Cz.prototype.getKeys=Cz.prototype.O;Cz.prototype.getProperties=Cz.prototype.P;Cz.prototype.set=Cz.prototype.set;Cz.prototype.setProperties=Cz.prototype.H;Cz.prototype.unset=Cz.prototype.S;Cz.prototype.changed=Cz.prototype.s;Cz.prototype.getRevision=Cz.prototype.K;Cz.prototype.on=Cz.prototype.D;Cz.prototype.once=Cz.prototype.L;Cz.prototype.un=Cz.prototype.J;Cz.prototype.unByKey=Cz.prototype.M;Fz.prototype.getUrls=Fz.prototype.g;Fz.prototype.setUrl=Fz.prototype.f;
-Fz.prototype.getTileLoadFunction=Fz.prototype.Ya;Fz.prototype.getTileUrlFunction=Fz.prototype.Za;Fz.prototype.setTileLoadFunction=Fz.prototype.cb;Fz.prototype.setTileUrlFunction=Fz.prototype.Ga;Fz.prototype.getTileGrid=Fz.prototype.Ca;Fz.prototype.getAttributions=Fz.prototype.na;Fz.prototype.getLogo=Fz.prototype.ma;Fz.prototype.getProjection=Fz.prototype.oa;Fz.prototype.getState=Fz.prototype.pa;Fz.prototype.setAttributions=Fz.prototype.la;Fz.prototype.get=Fz.prototype.get;Fz.prototype.getKeys=Fz.prototype.O;
-Fz.prototype.getProperties=Fz.prototype.P;Fz.prototype.set=Fz.prototype.set;Fz.prototype.setProperties=Fz.prototype.H;Fz.prototype.unset=Fz.prototype.S;Fz.prototype.changed=Fz.prototype.s;Fz.prototype.getRevision=Fz.prototype.K;Fz.prototype.on=Fz.prototype.D;Fz.prototype.once=Fz.prototype.L;Fz.prototype.un=Fz.prototype.J;Fz.prototype.unByKey=Fz.prototype.M;Dz.prototype.getUrls=Dz.prototype.g;Dz.prototype.setUrl=Dz.prototype.f;Dz.prototype.getTileLoadFunction=Dz.prototype.Ya;
-Dz.prototype.getTileUrlFunction=Dz.prototype.Za;Dz.prototype.setTileLoadFunction=Dz.prototype.cb;Dz.prototype.setTileUrlFunction=Dz.prototype.Ga;Dz.prototype.getTileGrid=Dz.prototype.Ca;Dz.prototype.getAttributions=Dz.prototype.na;Dz.prototype.getLogo=Dz.prototype.ma;Dz.prototype.getProjection=Dz.prototype.oa;Dz.prototype.getState=Dz.prototype.pa;Dz.prototype.setAttributions=Dz.prototype.la;Dz.prototype.get=Dz.prototype.get;Dz.prototype.getKeys=Dz.prototype.O;Dz.prototype.getProperties=Dz.prototype.P;
-Dz.prototype.set=Dz.prototype.set;Dz.prototype.setProperties=Dz.prototype.H;Dz.prototype.unset=Dz.prototype.S;Dz.prototype.changed=Dz.prototype.s;Dz.prototype.getRevision=Dz.prototype.K;Dz.prototype.on=Dz.prototype.D;Dz.prototype.once=Dz.prototype.L;Dz.prototype.un=Dz.prototype.J;Dz.prototype.unByKey=Dz.prototype.M;Iz.prototype.getAttributions=Iz.prototype.na;Iz.prototype.getLogo=Iz.prototype.ma;Iz.prototype.getProjection=Iz.prototype.oa;Iz.prototype.getState=Iz.prototype.pa;
-Iz.prototype.setAttributions=Iz.prototype.la;Iz.prototype.get=Iz.prototype.get;Iz.prototype.getKeys=Iz.prototype.O;Iz.prototype.getProperties=Iz.prototype.P;Iz.prototype.set=Iz.prototype.set;Iz.prototype.setProperties=Iz.prototype.H;Iz.prototype.unset=Iz.prototype.S;Iz.prototype.changed=Iz.prototype.s;Iz.prototype.getRevision=Iz.prototype.K;Iz.prototype.on=Iz.prototype.D;Iz.prototype.once=Iz.prototype.L;Iz.prototype.un=Iz.prototype.J;Iz.prototype.unByKey=Iz.prototype.M;Sz.prototype.getUrls=Sz.prototype.g;
-Sz.prototype.setUrl=Sz.prototype.f;Sz.prototype.getTileLoadFunction=Sz.prototype.Ya;Sz.prototype.getTileUrlFunction=Sz.prototype.Za;Sz.prototype.setTileLoadFunction=Sz.prototype.cb;Sz.prototype.setTileUrlFunction=Sz.prototype.Ga;Sz.prototype.getTileGrid=Sz.prototype.Ca;Sz.prototype.getAttributions=Sz.prototype.na;Sz.prototype.getLogo=Sz.prototype.ma;Sz.prototype.getProjection=Sz.prototype.oa;Sz.prototype.getState=Sz.prototype.pa;Sz.prototype.setAttributions=Sz.prototype.la;Sz.prototype.get=Sz.prototype.get;
-Sz.prototype.getKeys=Sz.prototype.O;Sz.prototype.getProperties=Sz.prototype.P;Sz.prototype.set=Sz.prototype.set;Sz.prototype.setProperties=Sz.prototype.H;Sz.prototype.unset=Sz.prototype.S;Sz.prototype.changed=Sz.prototype.s;Sz.prototype.getRevision=Sz.prototype.K;Sz.prototype.on=Sz.prototype.D;Sz.prototype.once=Sz.prototype.L;Sz.prototype.un=Sz.prototype.J;Sz.prototype.unByKey=Sz.prototype.M;Uz.prototype.getTileLoadFunction=Uz.prototype.Ya;Uz.prototype.getTileUrlFunction=Uz.prototype.Za;
-Uz.prototype.setTileLoadFunction=Uz.prototype.cb;Uz.prototype.setTileUrlFunction=Uz.prototype.Ga;Uz.prototype.getTileGrid=Uz.prototype.Ca;Uz.prototype.getAttributions=Uz.prototype.na;Uz.prototype.getLogo=Uz.prototype.ma;Uz.prototype.getProjection=Uz.prototype.oa;Uz.prototype.getState=Uz.prototype.pa;Uz.prototype.setAttributions=Uz.prototype.la;Uz.prototype.get=Uz.prototype.get;Uz.prototype.getKeys=Uz.prototype.O;Uz.prototype.getProperties=Uz.prototype.P;Uz.prototype.set=Uz.prototype.set;
-Uz.prototype.setProperties=Uz.prototype.H;Uz.prototype.unset=Uz.prototype.S;Uz.prototype.changed=Uz.prototype.s;Uz.prototype.getRevision=Uz.prototype.K;Uz.prototype.on=Uz.prototype.D;Uz.prototype.once=Uz.prototype.L;Uz.prototype.un=Uz.prototype.J;Uz.prototype.unByKey=Uz.prototype.M;Wz.prototype.getTileGrid=Wz.prototype.Ca;Wz.prototype.getAttributions=Wz.prototype.na;Wz.prototype.getLogo=Wz.prototype.ma;Wz.prototype.getProjection=Wz.prototype.oa;Wz.prototype.getState=Wz.prototype.pa;
-Wz.prototype.setAttributions=Wz.prototype.la;Wz.prototype.get=Wz.prototype.get;Wz.prototype.getKeys=Wz.prototype.O;Wz.prototype.getProperties=Wz.prototype.P;Wz.prototype.set=Wz.prototype.set;Wz.prototype.setProperties=Wz.prototype.H;Wz.prototype.unset=Wz.prototype.S;Wz.prototype.changed=Wz.prototype.s;Wz.prototype.getRevision=Wz.prototype.K;Wz.prototype.on=Wz.prototype.D;Wz.prototype.once=Wz.prototype.L;Wz.prototype.un=Wz.prototype.J;Wz.prototype.unByKey=Wz.prototype.M;
-Xz.prototype.getTileLoadFunction=Xz.prototype.Ya;Xz.prototype.getTileUrlFunction=Xz.prototype.Za;Xz.prototype.setTileLoadFunction=Xz.prototype.cb;Xz.prototype.setTileUrlFunction=Xz.prototype.Ga;Xz.prototype.getTileGrid=Xz.prototype.Ca;Xz.prototype.getAttributions=Xz.prototype.na;Xz.prototype.getLogo=Xz.prototype.ma;Xz.prototype.getProjection=Xz.prototype.oa;Xz.prototype.getState=Xz.prototype.pa;Xz.prototype.setAttributions=Xz.prototype.la;Xz.prototype.get=Xz.prototype.get;Xz.prototype.getKeys=Xz.prototype.O;
-Xz.prototype.getProperties=Xz.prototype.P;Xz.prototype.set=Xz.prototype.set;Xz.prototype.setProperties=Xz.prototype.H;Xz.prototype.unset=Xz.prototype.S;Xz.prototype.changed=Xz.prototype.s;Xz.prototype.getRevision=Xz.prototype.K;Xz.prototype.on=Xz.prototype.D;Xz.prototype.once=Xz.prototype.L;Xz.prototype.un=Xz.prototype.J;Xz.prototype.unByKey=Xz.prototype.M;Yz.prototype.getTileGrid=Yz.prototype.Ca;Yz.prototype.getAttributions=Yz.prototype.na;Yz.prototype.getLogo=Yz.prototype.ma;
-Yz.prototype.getProjection=Yz.prototype.oa;Yz.prototype.getState=Yz.prototype.pa;Yz.prototype.setAttributions=Yz.prototype.la;Yz.prototype.get=Yz.prototype.get;Yz.prototype.getKeys=Yz.prototype.O;Yz.prototype.getProperties=Yz.prototype.P;Yz.prototype.set=Yz.prototype.set;Yz.prototype.setProperties=Yz.prototype.H;Yz.prototype.unset=Yz.prototype.S;Yz.prototype.changed=Yz.prototype.s;Yz.prototype.getRevision=Yz.prototype.K;Yz.prototype.on=Yz.prototype.D;Yz.prototype.once=Yz.prototype.L;
-Yz.prototype.un=Yz.prototype.J;Yz.prototype.unByKey=Yz.prototype.M;cA.prototype.addFeature=cA.prototype.yc;cA.prototype.addFeatures=cA.prototype.Nb;cA.prototype.clear=cA.prototype.clear;cA.prototype.forEachFeature=cA.prototype.Le;cA.prototype.forEachFeatureInExtent=cA.prototype.sc;cA.prototype.forEachFeatureIntersectingExtent=cA.prototype.Me;cA.prototype.getFeaturesCollection=cA.prototype.Re;cA.prototype.getFeatures=cA.prototype.zc;cA.prototype.getFeaturesAtCoordinate=cA.prototype.Qe;
-cA.prototype.getFeaturesInExtent=cA.prototype.Ad;cA.prototype.getClosestFeatureToCoordinate=cA.prototype.Oe;cA.prototype.getExtent=cA.prototype.R;cA.prototype.getFeatureById=cA.prototype.Pe;cA.prototype.removeFeature=cA.prototype.dc;cA.prototype.getAttributions=cA.prototype.na;cA.prototype.getLogo=cA.prototype.ma;cA.prototype.getProjection=cA.prototype.oa;cA.prototype.getState=cA.prototype.pa;cA.prototype.setAttributions=cA.prototype.la;cA.prototype.get=cA.prototype.get;cA.prototype.getKeys=cA.prototype.O;
-cA.prototype.getProperties=cA.prototype.P;cA.prototype.set=cA.prototype.set;cA.prototype.setProperties=cA.prototype.H;cA.prototype.unset=cA.prototype.S;cA.prototype.changed=cA.prototype.s;cA.prototype.getRevision=cA.prototype.K;cA.prototype.on=cA.prototype.D;cA.prototype.once=cA.prototype.L;cA.prototype.un=cA.prototype.J;cA.prototype.unByKey=cA.prototype.M;fA.prototype.getTileLoadFunction=fA.prototype.Ya;fA.prototype.getTileUrlFunction=fA.prototype.Za;fA.prototype.setTileLoadFunction=fA.prototype.cb;
-fA.prototype.setTileUrlFunction=fA.prototype.Ga;fA.prototype.getTileGrid=fA.prototype.Ca;fA.prototype.getAttributions=fA.prototype.na;fA.prototype.getLogo=fA.prototype.ma;fA.prototype.getProjection=fA.prototype.oa;fA.prototype.getState=fA.prototype.pa;fA.prototype.setAttributions=fA.prototype.la;fA.prototype.get=fA.prototype.get;fA.prototype.getKeys=fA.prototype.O;fA.prototype.getProperties=fA.prototype.P;fA.prototype.set=fA.prototype.set;fA.prototype.setProperties=fA.prototype.H;
-fA.prototype.unset=fA.prototype.S;fA.prototype.changed=fA.prototype.s;fA.prototype.getRevision=fA.prototype.K;fA.prototype.on=fA.prototype.D;fA.prototype.once=fA.prototype.L;fA.prototype.un=fA.prototype.J;fA.prototype.unByKey=fA.prototype.M;lA.prototype.getTileLoadFunction=lA.prototype.Ya;lA.prototype.getTileUrlFunction=lA.prototype.Za;lA.prototype.setTileLoadFunction=lA.prototype.cb;lA.prototype.setTileUrlFunction=lA.prototype.Ga;lA.prototype.getTileGrid=lA.prototype.Ca;
-lA.prototype.getAttributions=lA.prototype.na;lA.prototype.getLogo=lA.prototype.ma;lA.prototype.getProjection=lA.prototype.oa;lA.prototype.getState=lA.prototype.pa;lA.prototype.setAttributions=lA.prototype.la;lA.prototype.get=lA.prototype.get;lA.prototype.getKeys=lA.prototype.O;lA.prototype.getProperties=lA.prototype.P;lA.prototype.set=lA.prototype.set;lA.prototype.setProperties=lA.prototype.H;lA.prototype.unset=lA.prototype.S;lA.prototype.changed=lA.prototype.s;lA.prototype.getRevision=lA.prototype.K;
-lA.prototype.on=lA.prototype.D;lA.prototype.once=lA.prototype.L;lA.prototype.un=lA.prototype.J;lA.prototype.unByKey=lA.prototype.M;nA.prototype.getTileLoadFunction=nA.prototype.Ya;nA.prototype.getTileUrlFunction=nA.prototype.Za;nA.prototype.setTileLoadFunction=nA.prototype.cb;nA.prototype.setTileUrlFunction=nA.prototype.Ga;nA.prototype.getTileGrid=nA.prototype.Ca;nA.prototype.getAttributions=nA.prototype.na;nA.prototype.getLogo=nA.prototype.ma;nA.prototype.getProjection=nA.prototype.oa;
-nA.prototype.getState=nA.prototype.pa;nA.prototype.setAttributions=nA.prototype.la;nA.prototype.get=nA.prototype.get;nA.prototype.getKeys=nA.prototype.O;nA.prototype.getProperties=nA.prototype.P;nA.prototype.set=nA.prototype.set;nA.prototype.setProperties=nA.prototype.H;nA.prototype.unset=nA.prototype.S;nA.prototype.changed=nA.prototype.s;nA.prototype.getRevision=nA.prototype.K;nA.prototype.on=nA.prototype.D;nA.prototype.once=nA.prototype.L;nA.prototype.un=nA.prototype.J;nA.prototype.unByKey=nA.prototype.M;
-nk.prototype.changed=nk.prototype.s;nk.prototype.getRevision=nk.prototype.K;nk.prototype.on=nk.prototype.D;nk.prototype.once=nk.prototype.L;nk.prototype.un=nk.prototype.J;nk.prototype.unByKey=nk.prototype.M;Pq.prototype.changed=Pq.prototype.s;Pq.prototype.getRevision=Pq.prototype.K;Pq.prototype.on=Pq.prototype.D;Pq.prototype.once=Pq.prototype.L;Pq.prototype.un=Pq.prototype.J;Pq.prototype.unByKey=Pq.prototype.M;Sq.prototype.changed=Sq.prototype.s;Sq.prototype.getRevision=Sq.prototype.K;
-Sq.prototype.on=Sq.prototype.D;Sq.prototype.once=Sq.prototype.L;Sq.prototype.un=Sq.prototype.J;Sq.prototype.unByKey=Sq.prototype.M;Yq.prototype.changed=Yq.prototype.s;Yq.prototype.getRevision=Yq.prototype.K;Yq.prototype.on=Yq.prototype.D;Yq.prototype.once=Yq.prototype.L;Yq.prototype.un=Yq.prototype.J;Yq.prototype.unByKey=Yq.prototype.M;$q.prototype.changed=$q.prototype.s;$q.prototype.getRevision=$q.prototype.K;$q.prototype.on=$q.prototype.D;$q.prototype.once=$q.prototype.L;$q.prototype.un=$q.prototype.J;
-$q.prototype.unByKey=$q.prototype.M;Yp.prototype.changed=Yp.prototype.s;Yp.prototype.getRevision=Yp.prototype.K;Yp.prototype.on=Yp.prototype.D;Yp.prototype.once=Yp.prototype.L;Yp.prototype.un=Yp.prototype.J;Yp.prototype.unByKey=Yp.prototype.M;Zp.prototype.changed=Zp.prototype.s;Zp.prototype.getRevision=Zp.prototype.K;Zp.prototype.on=Zp.prototype.D;Zp.prototype.once=Zp.prototype.L;Zp.prototype.un=Zp.prototype.J;Zp.prototype.unByKey=Zp.prototype.M;$p.prototype.changed=$p.prototype.s;
-$p.prototype.getRevision=$p.prototype.K;$p.prototype.on=$p.prototype.D;$p.prototype.once=$p.prototype.L;$p.prototype.un=$p.prototype.J;$p.prototype.unByKey=$p.prototype.M;bq.prototype.changed=bq.prototype.s;bq.prototype.getRevision=bq.prototype.K;bq.prototype.on=bq.prototype.D;bq.prototype.once=bq.prototype.L;bq.prototype.un=bq.prototype.J;bq.prototype.unByKey=bq.prototype.M;ym.prototype.changed=ym.prototype.s;ym.prototype.getRevision=ym.prototype.K;ym.prototype.on=ym.prototype.D;
-ym.prototype.once=ym.prototype.L;ym.prototype.un=ym.prototype.J;ym.prototype.unByKey=ym.prototype.M;Tp.prototype.changed=Tp.prototype.s;Tp.prototype.getRevision=Tp.prototype.K;Tp.prototype.on=Tp.prototype.D;Tp.prototype.once=Tp.prototype.L;Tp.prototype.un=Tp.prototype.J;Tp.prototype.unByKey=Tp.prototype.M;Up.prototype.changed=Up.prototype.s;Up.prototype.getRevision=Up.prototype.K;Up.prototype.on=Up.prototype.D;Up.prototype.once=Up.prototype.L;Up.prototype.un=Up.prototype.J;Up.prototype.unByKey=Up.prototype.M;
-Vp.prototype.changed=Vp.prototype.s;Vp.prototype.getRevision=Vp.prototype.K;Vp.prototype.on=Vp.prototype.D;Vp.prototype.once=Vp.prototype.L;Vp.prototype.un=Vp.prototype.J;Vp.prototype.unByKey=Vp.prototype.M;ck.prototype.get=ck.prototype.get;ck.prototype.getKeys=ck.prototype.O;ck.prototype.getProperties=ck.prototype.P;ck.prototype.set=ck.prototype.set;ck.prototype.setProperties=ck.prototype.H;ck.prototype.unset=ck.prototype.S;ck.prototype.changed=ck.prototype.s;ck.prototype.getRevision=ck.prototype.K;
-ck.prototype.on=ck.prototype.D;ck.prototype.once=ck.prototype.L;ck.prototype.un=ck.prototype.J;ck.prototype.unByKey=ck.prototype.M;gk.prototype.getExtent=gk.prototype.R;gk.prototype.getMaxResolution=gk.prototype.Cb;gk.prototype.getMinResolution=gk.prototype.Db;gk.prototype.getOpacity=gk.prototype.Hb;gk.prototype.getVisible=gk.prototype.ib;gk.prototype.getZIndex=gk.prototype.Ib;gk.prototype.setExtent=gk.prototype.Yb;gk.prototype.setMaxResolution=gk.prototype.gc;gk.prototype.setMinResolution=gk.prototype.hc;
-gk.prototype.setOpacity=gk.prototype.Zb;gk.prototype.setVisible=gk.prototype.$b;gk.prototype.setZIndex=gk.prototype.ac;gk.prototype.get=gk.prototype.get;gk.prototype.getKeys=gk.prototype.O;gk.prototype.getProperties=gk.prototype.P;gk.prototype.set=gk.prototype.set;gk.prototype.setProperties=gk.prototype.H;gk.prototype.unset=gk.prototype.S;gk.prototype.changed=gk.prototype.s;gk.prototype.getRevision=gk.prototype.K;gk.prototype.on=gk.prototype.D;gk.prototype.once=gk.prototype.L;gk.prototype.un=gk.prototype.J;
-gk.prototype.unByKey=gk.prototype.M;H.prototype.setMap=H.prototype.setMap;H.prototype.setSource=H.prototype.Ec;H.prototype.getExtent=H.prototype.R;H.prototype.getMaxResolution=H.prototype.Cb;H.prototype.getMinResolution=H.prototype.Db;H.prototype.getOpacity=H.prototype.Hb;H.prototype.getVisible=H.prototype.ib;H.prototype.getZIndex=H.prototype.Ib;H.prototype.setExtent=H.prototype.Yb;H.prototype.setMaxResolution=H.prototype.gc;H.prototype.setMinResolution=H.prototype.hc;H.prototype.setOpacity=H.prototype.Zb;
-H.prototype.setVisible=H.prototype.$b;H.prototype.setZIndex=H.prototype.ac;H.prototype.get=H.prototype.get;H.prototype.getKeys=H.prototype.O;H.prototype.getProperties=H.prototype.P;H.prototype.set=H.prototype.set;H.prototype.setProperties=H.prototype.H;H.prototype.unset=H.prototype.S;H.prototype.changed=H.prototype.s;H.prototype.getRevision=H.prototype.K;H.prototype.on=H.prototype.D;H.prototype.once=H.prototype.L;H.prototype.un=H.prototype.J;H.prototype.unByKey=H.prototype.M;
-Y.prototype.getSource=Y.prototype.ea;Y.prototype.getStyle=Y.prototype.T;Y.prototype.getStyleFunction=Y.prototype.X;Y.prototype.setStyle=Y.prototype.g;Y.prototype.setMap=Y.prototype.setMap;Y.prototype.setSource=Y.prototype.Ec;Y.prototype.getExtent=Y.prototype.R;Y.prototype.getMaxResolution=Y.prototype.Cb;Y.prototype.getMinResolution=Y.prototype.Db;Y.prototype.getOpacity=Y.prototype.Hb;Y.prototype.getVisible=Y.prototype.ib;Y.prototype.getZIndex=Y.prototype.Ib;Y.prototype.setExtent=Y.prototype.Yb;
-Y.prototype.setMaxResolution=Y.prototype.gc;Y.prototype.setMinResolution=Y.prototype.hc;Y.prototype.setOpacity=Y.prototype.Zb;Y.prototype.setVisible=Y.prototype.$b;Y.prototype.setZIndex=Y.prototype.ac;Y.prototype.get=Y.prototype.get;Y.prototype.getKeys=Y.prototype.O;Y.prototype.getProperties=Y.prototype.P;Y.prototype.set=Y.prototype.set;Y.prototype.setProperties=Y.prototype.H;Y.prototype.unset=Y.prototype.S;Y.prototype.changed=Y.prototype.s;Y.prototype.getRevision=Y.prototype.K;Y.prototype.on=Y.prototype.D;
-Y.prototype.once=Y.prototype.L;Y.prototype.un=Y.prototype.J;Y.prototype.unByKey=Y.prototype.M;jm.prototype.setMap=jm.prototype.setMap;jm.prototype.setSource=jm.prototype.Ec;jm.prototype.getExtent=jm.prototype.R;jm.prototype.getMaxResolution=jm.prototype.Cb;jm.prototype.getMinResolution=jm.prototype.Db;jm.prototype.getOpacity=jm.prototype.Hb;jm.prototype.getVisible=jm.prototype.ib;jm.prototype.getZIndex=jm.prototype.Ib;jm.prototype.setExtent=jm.prototype.Yb;jm.prototype.setMaxResolution=jm.prototype.gc;
-jm.prototype.setMinResolution=jm.prototype.hc;jm.prototype.setOpacity=jm.prototype.Zb;jm.prototype.setVisible=jm.prototype.$b;jm.prototype.setZIndex=jm.prototype.ac;jm.prototype.get=jm.prototype.get;jm.prototype.getKeys=jm.prototype.O;jm.prototype.getProperties=jm.prototype.P;jm.prototype.set=jm.prototype.set;jm.prototype.setProperties=jm.prototype.H;jm.prototype.unset=jm.prototype.S;jm.prototype.changed=jm.prototype.s;jm.prototype.getRevision=jm.prototype.K;jm.prototype.on=jm.prototype.D;
-jm.prototype.once=jm.prototype.L;jm.prototype.un=jm.prototype.J;jm.prototype.unByKey=jm.prototype.M;bm.prototype.getExtent=bm.prototype.R;bm.prototype.getMaxResolution=bm.prototype.Cb;bm.prototype.getMinResolution=bm.prototype.Db;bm.prototype.getOpacity=bm.prototype.Hb;bm.prototype.getVisible=bm.prototype.ib;bm.prototype.getZIndex=bm.prototype.Ib;bm.prototype.setExtent=bm.prototype.Yb;bm.prototype.setMaxResolution=bm.prototype.gc;bm.prototype.setMinResolution=bm.prototype.hc;
-bm.prototype.setOpacity=bm.prototype.Zb;bm.prototype.setVisible=bm.prototype.$b;bm.prototype.setZIndex=bm.prototype.ac;bm.prototype.get=bm.prototype.get;bm.prototype.getKeys=bm.prototype.O;bm.prototype.getProperties=bm.prototype.P;bm.prototype.set=bm.prototype.set;bm.prototype.setProperties=bm.prototype.H;bm.prototype.unset=bm.prototype.S;bm.prototype.changed=bm.prototype.s;bm.prototype.getRevision=bm.prototype.K;bm.prototype.on=bm.prototype.D;bm.prototype.once=bm.prototype.L;bm.prototype.un=bm.prototype.J;
-bm.prototype.unByKey=bm.prototype.M;G.prototype.setMap=G.prototype.setMap;G.prototype.setSource=G.prototype.Ec;G.prototype.getExtent=G.prototype.R;G.prototype.getMaxResolution=G.prototype.Cb;G.prototype.getMinResolution=G.prototype.Db;G.prototype.getOpacity=G.prototype.Hb;G.prototype.getVisible=G.prototype.ib;G.prototype.getZIndex=G.prototype.Ib;G.prototype.setExtent=G.prototype.Yb;G.prototype.setMaxResolution=G.prototype.gc;G.prototype.setMinResolution=G.prototype.hc;G.prototype.setOpacity=G.prototype.Zb;
-G.prototype.setVisible=G.prototype.$b;G.prototype.setZIndex=G.prototype.ac;G.prototype.get=G.prototype.get;G.prototype.getKeys=G.prototype.O;G.prototype.getProperties=G.prototype.P;G.prototype.set=G.prototype.set;G.prototype.setProperties=G.prototype.H;G.prototype.unset=G.prototype.S;G.prototype.changed=G.prototype.s;G.prototype.getRevision=G.prototype.K;G.prototype.on=G.prototype.D;G.prototype.once=G.prototype.L;G.prototype.un=G.prototype.J;G.prototype.unByKey=G.prototype.M;Qk.prototype.get=Qk.prototype.get;
-Qk.prototype.getKeys=Qk.prototype.O;Qk.prototype.getProperties=Qk.prototype.P;Qk.prototype.set=Qk.prototype.set;Qk.prototype.setProperties=Qk.prototype.H;Qk.prototype.unset=Qk.prototype.S;Qk.prototype.changed=Qk.prototype.s;Qk.prototype.getRevision=Qk.prototype.K;Qk.prototype.on=Qk.prototype.D;Qk.prototype.once=Qk.prototype.L;Qk.prototype.un=Qk.prototype.J;Qk.prototype.unByKey=Qk.prototype.M;Uk.prototype.getActive=Uk.prototype.c;Uk.prototype.setActive=Uk.prototype.g;Uk.prototype.get=Uk.prototype.get;
-Uk.prototype.getKeys=Uk.prototype.O;Uk.prototype.getProperties=Uk.prototype.P;Uk.prototype.set=Uk.prototype.set;Uk.prototype.setProperties=Uk.prototype.H;Uk.prototype.unset=Uk.prototype.S;Uk.prototype.changed=Uk.prototype.s;Uk.prototype.getRevision=Uk.prototype.K;Uk.prototype.on=Uk.prototype.D;Uk.prototype.once=Uk.prototype.L;Uk.prototype.un=Uk.prototype.J;Uk.prototype.unByKey=Uk.prototype.M;Yx.prototype.getActive=Yx.prototype.c;Yx.prototype.setActive=Yx.prototype.g;Yx.prototype.get=Yx.prototype.get;
-Yx.prototype.getKeys=Yx.prototype.O;Yx.prototype.getProperties=Yx.prototype.P;Yx.prototype.set=Yx.prototype.set;Yx.prototype.setProperties=Yx.prototype.H;Yx.prototype.unset=Yx.prototype.S;Yx.prototype.changed=Yx.prototype.s;Yx.prototype.getRevision=Yx.prototype.K;Yx.prototype.on=Yx.prototype.D;Yx.prototype.once=Yx.prototype.L;Yx.prototype.un=Yx.prototype.J;Yx.prototype.unByKey=Yx.prototype.M;cl.prototype.getActive=cl.prototype.c;cl.prototype.setActive=cl.prototype.g;cl.prototype.get=cl.prototype.get;
-cl.prototype.getKeys=cl.prototype.O;cl.prototype.getProperties=cl.prototype.P;cl.prototype.set=cl.prototype.set;cl.prototype.setProperties=cl.prototype.H;cl.prototype.unset=cl.prototype.S;cl.prototype.changed=cl.prototype.s;cl.prototype.getRevision=cl.prototype.K;cl.prototype.on=cl.prototype.D;cl.prototype.once=cl.prototype.L;cl.prototype.un=cl.prototype.J;cl.prototype.unByKey=cl.prototype.M;sl.prototype.getActive=sl.prototype.c;sl.prototype.setActive=sl.prototype.g;sl.prototype.get=sl.prototype.get;
-sl.prototype.getKeys=sl.prototype.O;sl.prototype.getProperties=sl.prototype.P;sl.prototype.set=sl.prototype.set;sl.prototype.setProperties=sl.prototype.H;sl.prototype.unset=sl.prototype.S;sl.prototype.changed=sl.prototype.s;sl.prototype.getRevision=sl.prototype.K;sl.prototype.on=sl.prototype.D;sl.prototype.once=sl.prototype.L;sl.prototype.un=sl.prototype.J;sl.prototype.unByKey=sl.prototype.M;fl.prototype.getActive=fl.prototype.c;fl.prototype.setActive=fl.prototype.g;fl.prototype.get=fl.prototype.get;
-fl.prototype.getKeys=fl.prototype.O;fl.prototype.getProperties=fl.prototype.P;fl.prototype.set=fl.prototype.set;fl.prototype.setProperties=fl.prototype.H;fl.prototype.unset=fl.prototype.S;fl.prototype.changed=fl.prototype.s;fl.prototype.getRevision=fl.prototype.K;fl.prototype.on=fl.prototype.D;fl.prototype.once=fl.prototype.L;fl.prototype.un=fl.prototype.J;fl.prototype.unByKey=fl.prototype.M;by.prototype.getActive=by.prototype.c;by.prototype.setActive=by.prototype.g;by.prototype.get=by.prototype.get;
-by.prototype.getKeys=by.prototype.O;by.prototype.getProperties=by.prototype.P;by.prototype.set=by.prototype.set;by.prototype.setProperties=by.prototype.H;by.prototype.unset=by.prototype.S;by.prototype.changed=by.prototype.s;by.prototype.getRevision=by.prototype.K;by.prototype.on=by.prototype.D;by.prototype.once=by.prototype.L;by.prototype.un=by.prototype.J;by.prototype.unByKey=by.prototype.M;jl.prototype.getActive=jl.prototype.c;jl.prototype.setActive=jl.prototype.g;jl.prototype.get=jl.prototype.get;
-jl.prototype.getKeys=jl.prototype.O;jl.prototype.getProperties=jl.prototype.P;jl.prototype.set=jl.prototype.set;jl.prototype.setProperties=jl.prototype.H;jl.prototype.unset=jl.prototype.S;jl.prototype.changed=jl.prototype.s;jl.prototype.getRevision=jl.prototype.K;jl.prototype.on=jl.prototype.D;jl.prototype.once=jl.prototype.L;jl.prototype.un=jl.prototype.J;jl.prototype.unByKey=jl.prototype.M;Ml.prototype.getGeometry=Ml.prototype.V;Ml.prototype.getActive=Ml.prototype.c;Ml.prototype.setActive=Ml.prototype.g;
-Ml.prototype.get=Ml.prototype.get;Ml.prototype.getKeys=Ml.prototype.O;Ml.prototype.getProperties=Ml.prototype.P;Ml.prototype.set=Ml.prototype.set;Ml.prototype.setProperties=Ml.prototype.H;Ml.prototype.unset=Ml.prototype.S;Ml.prototype.changed=Ml.prototype.s;Ml.prototype.getRevision=Ml.prototype.K;Ml.prototype.on=Ml.prototype.D;Ml.prototype.once=Ml.prototype.L;Ml.prototype.un=Ml.prototype.J;Ml.prototype.unByKey=Ml.prototype.M;gy.prototype.getActive=gy.prototype.c;gy.prototype.setActive=gy.prototype.g;
-gy.prototype.get=gy.prototype.get;gy.prototype.getKeys=gy.prototype.O;gy.prototype.getProperties=gy.prototype.P;gy.prototype.set=gy.prototype.set;gy.prototype.setProperties=gy.prototype.H;gy.prototype.unset=gy.prototype.S;gy.prototype.changed=gy.prototype.s;gy.prototype.getRevision=gy.prototype.K;gy.prototype.on=gy.prototype.D;gy.prototype.once=gy.prototype.L;gy.prototype.un=gy.prototype.J;gy.prototype.unByKey=gy.prototype.M;Nl.prototype.getActive=Nl.prototype.c;Nl.prototype.setActive=Nl.prototype.g;
-Nl.prototype.get=Nl.prototype.get;Nl.prototype.getKeys=Nl.prototype.O;Nl.prototype.getProperties=Nl.prototype.P;Nl.prototype.set=Nl.prototype.set;Nl.prototype.setProperties=Nl.prototype.H;Nl.prototype.unset=Nl.prototype.S;Nl.prototype.changed=Nl.prototype.s;Nl.prototype.getRevision=Nl.prototype.K;Nl.prototype.on=Nl.prototype.D;Nl.prototype.once=Nl.prototype.L;Nl.prototype.un=Nl.prototype.J;Nl.prototype.unByKey=Nl.prototype.M;Pl.prototype.getActive=Pl.prototype.c;Pl.prototype.setActive=Pl.prototype.g;
-Pl.prototype.get=Pl.prototype.get;Pl.prototype.getKeys=Pl.prototype.O;Pl.prototype.getProperties=Pl.prototype.P;Pl.prototype.set=Pl.prototype.set;Pl.prototype.setProperties=Pl.prototype.H;Pl.prototype.unset=Pl.prototype.S;Pl.prototype.changed=Pl.prototype.s;Pl.prototype.getRevision=Pl.prototype.K;Pl.prototype.on=Pl.prototype.D;Pl.prototype.once=Pl.prototype.L;Pl.prototype.un=Pl.prototype.J;Pl.prototype.unByKey=Pl.prototype.M;xy.prototype.getActive=xy.prototype.c;xy.prototype.setActive=xy.prototype.g;
-xy.prototype.get=xy.prototype.get;xy.prototype.getKeys=xy.prototype.O;xy.prototype.getProperties=xy.prototype.P;xy.prototype.set=xy.prototype.set;xy.prototype.setProperties=xy.prototype.H;xy.prototype.unset=xy.prototype.S;xy.prototype.changed=xy.prototype.s;xy.prototype.getRevision=xy.prototype.K;xy.prototype.on=xy.prototype.D;xy.prototype.once=xy.prototype.L;xy.prototype.un=xy.prototype.J;xy.prototype.unByKey=xy.prototype.M;Rl.prototype.getActive=Rl.prototype.c;Rl.prototype.setActive=Rl.prototype.g;
-Rl.prototype.get=Rl.prototype.get;Rl.prototype.getKeys=Rl.prototype.O;Rl.prototype.getProperties=Rl.prototype.P;Rl.prototype.set=Rl.prototype.set;Rl.prototype.setProperties=Rl.prototype.H;Rl.prototype.unset=Rl.prototype.S;Rl.prototype.changed=Rl.prototype.s;Rl.prototype.getRevision=Rl.prototype.K;Rl.prototype.on=Rl.prototype.D;Rl.prototype.once=Rl.prototype.L;Rl.prototype.un=Rl.prototype.J;Rl.prototype.unByKey=Rl.prototype.M;Tl.prototype.getActive=Tl.prototype.c;Tl.prototype.setActive=Tl.prototype.g;
-Tl.prototype.get=Tl.prototype.get;Tl.prototype.getKeys=Tl.prototype.O;Tl.prototype.getProperties=Tl.prototype.P;Tl.prototype.set=Tl.prototype.set;Tl.prototype.setProperties=Tl.prototype.H;Tl.prototype.unset=Tl.prototype.S;Tl.prototype.changed=Tl.prototype.s;Tl.prototype.getRevision=Tl.prototype.K;Tl.prototype.on=Tl.prototype.D;Tl.prototype.once=Tl.prototype.L;Tl.prototype.un=Tl.prototype.J;Tl.prototype.unByKey=Tl.prototype.M;Xl.prototype.getActive=Xl.prototype.c;Xl.prototype.setActive=Xl.prototype.g;
-Xl.prototype.get=Xl.prototype.get;Xl.prototype.getKeys=Xl.prototype.O;Xl.prototype.getProperties=Xl.prototype.P;Xl.prototype.set=Xl.prototype.set;Xl.prototype.setProperties=Xl.prototype.H;Xl.prototype.unset=Xl.prototype.S;Xl.prototype.changed=Xl.prototype.s;Xl.prototype.getRevision=Xl.prototype.K;Xl.prototype.on=Xl.prototype.D;Xl.prototype.once=Xl.prototype.L;Xl.prototype.un=Xl.prototype.J;Xl.prototype.unByKey=Xl.prototype.M;Ly.prototype.getActive=Ly.prototype.c;Ly.prototype.setActive=Ly.prototype.g;
-Ly.prototype.get=Ly.prototype.get;Ly.prototype.getKeys=Ly.prototype.O;Ly.prototype.getProperties=Ly.prototype.P;Ly.prototype.set=Ly.prototype.set;Ly.prototype.setProperties=Ly.prototype.H;Ly.prototype.unset=Ly.prototype.S;Ly.prototype.changed=Ly.prototype.s;Ly.prototype.getRevision=Ly.prototype.K;Ly.prototype.on=Ly.prototype.D;Ly.prototype.once=Ly.prototype.L;Ly.prototype.un=Ly.prototype.J;Ly.prototype.unByKey=Ly.prototype.M;Oy.prototype.getActive=Oy.prototype.c;Oy.prototype.setActive=Oy.prototype.g;
-Oy.prototype.get=Oy.prototype.get;Oy.prototype.getKeys=Oy.prototype.O;Oy.prototype.getProperties=Oy.prototype.P;Oy.prototype.set=Oy.prototype.set;Oy.prototype.setProperties=Oy.prototype.H;Oy.prototype.unset=Oy.prototype.S;Oy.prototype.changed=Oy.prototype.s;Oy.prototype.getRevision=Oy.prototype.K;Oy.prototype.on=Oy.prototype.D;Oy.prototype.once=Oy.prototype.L;Oy.prototype.un=Oy.prototype.J;Oy.prototype.unByKey=Oy.prototype.M;Sy.prototype.getActive=Sy.prototype.c;Sy.prototype.setActive=Sy.prototype.g;
-Sy.prototype.get=Sy.prototype.get;Sy.prototype.getKeys=Sy.prototype.O;Sy.prototype.getProperties=Sy.prototype.P;Sy.prototype.set=Sy.prototype.set;Sy.prototype.setProperties=Sy.prototype.H;Sy.prototype.unset=Sy.prototype.S;Sy.prototype.changed=Sy.prototype.s;Sy.prototype.getRevision=Sy.prototype.K;Sy.prototype.on=Sy.prototype.D;Sy.prototype.once=Sy.prototype.L;Sy.prototype.un=Sy.prototype.J;Sy.prototype.unByKey=Sy.prototype.M;Ze.prototype.get=Ze.prototype.get;Ze.prototype.getKeys=Ze.prototype.O;
-Ze.prototype.getProperties=Ze.prototype.P;Ze.prototype.set=Ze.prototype.set;Ze.prototype.setProperties=Ze.prototype.H;Ze.prototype.unset=Ze.prototype.S;Ze.prototype.changed=Ze.prototype.s;Ze.prototype.getRevision=Ze.prototype.K;Ze.prototype.on=Ze.prototype.D;Ze.prototype.once=Ze.prototype.L;Ze.prototype.un=Ze.prototype.J;Ze.prototype.unByKey=Ze.prototype.M;af.prototype.getClosestPoint=af.prototype.Xa;af.prototype.getExtent=af.prototype.R;af.prototype.simplify=af.prototype.eb;
-af.prototype.transform=af.prototype.Sa;af.prototype.get=af.prototype.get;af.prototype.getKeys=af.prototype.O;af.prototype.getProperties=af.prototype.P;af.prototype.set=af.prototype.set;af.prototype.setProperties=af.prototype.H;af.prototype.unset=af.prototype.S;af.prototype.changed=af.prototype.s;af.prototype.getRevision=af.prototype.K;af.prototype.on=af.prototype.D;af.prototype.once=af.prototype.L;af.prototype.un=af.prototype.J;af.prototype.unByKey=af.prototype.M;Ym.prototype.getFirstCoordinate=Ym.prototype.sb;
-Ym.prototype.getLastCoordinate=Ym.prototype.tb;Ym.prototype.getLayout=Ym.prototype.ub;Ym.prototype.getClosestPoint=Ym.prototype.Xa;Ym.prototype.getExtent=Ym.prototype.R;Ym.prototype.simplify=Ym.prototype.eb;Ym.prototype.get=Ym.prototype.get;Ym.prototype.getKeys=Ym.prototype.O;Ym.prototype.getProperties=Ym.prototype.P;Ym.prototype.set=Ym.prototype.set;Ym.prototype.setProperties=Ym.prototype.H;Ym.prototype.unset=Ym.prototype.S;Ym.prototype.changed=Ym.prototype.s;Ym.prototype.getRevision=Ym.prototype.K;
-Ym.prototype.on=Ym.prototype.D;Ym.prototype.once=Ym.prototype.L;Ym.prototype.un=Ym.prototype.J;Ym.prototype.unByKey=Ym.prototype.M;$m.prototype.getClosestPoint=$m.prototype.Xa;$m.prototype.getExtent=$m.prototype.R;$m.prototype.simplify=$m.prototype.eb;$m.prototype.transform=$m.prototype.Sa;$m.prototype.get=$m.prototype.get;$m.prototype.getKeys=$m.prototype.O;$m.prototype.getProperties=$m.prototype.P;$m.prototype.set=$m.prototype.set;$m.prototype.setProperties=$m.prototype.H;$m.prototype.unset=$m.prototype.S;
-$m.prototype.changed=$m.prototype.s;$m.prototype.getRevision=$m.prototype.K;$m.prototype.on=$m.prototype.D;$m.prototype.once=$m.prototype.L;$m.prototype.un=$m.prototype.J;$m.prototype.unByKey=$m.prototype.M;tf.prototype.getFirstCoordinate=tf.prototype.sb;tf.prototype.getLastCoordinate=tf.prototype.tb;tf.prototype.getLayout=tf.prototype.ub;tf.prototype.getClosestPoint=tf.prototype.Xa;tf.prototype.getExtent=tf.prototype.R;tf.prototype.simplify=tf.prototype.eb;tf.prototype.transform=tf.prototype.Sa;
-tf.prototype.get=tf.prototype.get;tf.prototype.getKeys=tf.prototype.O;tf.prototype.getProperties=tf.prototype.P;tf.prototype.set=tf.prototype.set;tf.prototype.setProperties=tf.prototype.H;tf.prototype.unset=tf.prototype.S;tf.prototype.changed=tf.prototype.s;tf.prototype.getRevision=tf.prototype.K;tf.prototype.on=tf.prototype.D;tf.prototype.once=tf.prototype.L;tf.prototype.un=tf.prototype.J;tf.prototype.unByKey=tf.prototype.M;L.prototype.getFirstCoordinate=L.prototype.sb;
-L.prototype.getLastCoordinate=L.prototype.tb;L.prototype.getLayout=L.prototype.ub;L.prototype.getClosestPoint=L.prototype.Xa;L.prototype.getExtent=L.prototype.R;L.prototype.simplify=L.prototype.eb;L.prototype.transform=L.prototype.Sa;L.prototype.get=L.prototype.get;L.prototype.getKeys=L.prototype.O;L.prototype.getProperties=L.prototype.P;L.prototype.set=L.prototype.set;L.prototype.setProperties=L.prototype.H;L.prototype.unset=L.prototype.S;L.prototype.changed=L.prototype.s;
-L.prototype.getRevision=L.prototype.K;L.prototype.on=L.prototype.D;L.prototype.once=L.prototype.L;L.prototype.un=L.prototype.J;L.prototype.unByKey=L.prototype.M;O.prototype.getFirstCoordinate=O.prototype.sb;O.prototype.getLastCoordinate=O.prototype.tb;O.prototype.getLayout=O.prototype.ub;O.prototype.getClosestPoint=O.prototype.Xa;O.prototype.getExtent=O.prototype.R;O.prototype.simplify=O.prototype.eb;O.prototype.transform=O.prototype.Sa;O.prototype.get=O.prototype.get;O.prototype.getKeys=O.prototype.O;
-O.prototype.getProperties=O.prototype.P;O.prototype.set=O.prototype.set;O.prototype.setProperties=O.prototype.H;O.prototype.unset=O.prototype.S;O.prototype.changed=O.prototype.s;O.prototype.getRevision=O.prototype.K;O.prototype.on=O.prototype.D;O.prototype.once=O.prototype.L;O.prototype.un=O.prototype.J;O.prototype.unByKey=O.prototype.M;kn.prototype.getFirstCoordinate=kn.prototype.sb;kn.prototype.getLastCoordinate=kn.prototype.tb;kn.prototype.getLayout=kn.prototype.ub;
-kn.prototype.getClosestPoint=kn.prototype.Xa;kn.prototype.getExtent=kn.prototype.R;kn.prototype.simplify=kn.prototype.eb;kn.prototype.transform=kn.prototype.Sa;kn.prototype.get=kn.prototype.get;kn.prototype.getKeys=kn.prototype.O;kn.prototype.getProperties=kn.prototype.P;kn.prototype.set=kn.prototype.set;kn.prototype.setProperties=kn.prototype.H;kn.prototype.unset=kn.prototype.S;kn.prototype.changed=kn.prototype.s;kn.prototype.getRevision=kn.prototype.K;kn.prototype.on=kn.prototype.D;
-kn.prototype.once=kn.prototype.L;kn.prototype.un=kn.prototype.J;kn.prototype.unByKey=kn.prototype.M;P.prototype.getFirstCoordinate=P.prototype.sb;P.prototype.getLastCoordinate=P.prototype.tb;P.prototype.getLayout=P.prototype.ub;P.prototype.getClosestPoint=P.prototype.Xa;P.prototype.getExtent=P.prototype.R;P.prototype.simplify=P.prototype.eb;P.prototype.transform=P.prototype.Sa;P.prototype.get=P.prototype.get;P.prototype.getKeys=P.prototype.O;P.prototype.getProperties=P.prototype.P;
-P.prototype.set=P.prototype.set;P.prototype.setProperties=P.prototype.H;P.prototype.unset=P.prototype.S;P.prototype.changed=P.prototype.s;P.prototype.getRevision=P.prototype.K;P.prototype.on=P.prototype.D;P.prototype.once=P.prototype.L;P.prototype.un=P.prototype.J;P.prototype.unByKey=P.prototype.M;D.prototype.getFirstCoordinate=D.prototype.sb;D.prototype.getLastCoordinate=D.prototype.tb;D.prototype.getLayout=D.prototype.ub;D.prototype.getClosestPoint=D.prototype.Xa;D.prototype.getExtent=D.prototype.R;
-D.prototype.simplify=D.prototype.eb;D.prototype.transform=D.prototype.Sa;D.prototype.get=D.prototype.get;D.prototype.getKeys=D.prototype.O;D.prototype.getProperties=D.prototype.P;D.prototype.set=D.prototype.set;D.prototype.setProperties=D.prototype.H;D.prototype.unset=D.prototype.S;D.prototype.changed=D.prototype.s;D.prototype.getRevision=D.prototype.K;D.prototype.on=D.prototype.D;D.prototype.once=D.prototype.L;D.prototype.un=D.prototype.J;D.prototype.unByKey=D.prototype.M;
-E.prototype.getFirstCoordinate=E.prototype.sb;E.prototype.getLastCoordinate=E.prototype.tb;E.prototype.getLayout=E.prototype.ub;E.prototype.getClosestPoint=E.prototype.Xa;E.prototype.getExtent=E.prototype.R;E.prototype.simplify=E.prototype.eb;E.prototype.transform=E.prototype.Sa;E.prototype.get=E.prototype.get;E.prototype.getKeys=E.prototype.O;E.prototype.getProperties=E.prototype.P;E.prototype.set=E.prototype.set;E.prototype.setProperties=E.prototype.H;E.prototype.unset=E.prototype.S;
-E.prototype.changed=E.prototype.s;E.prototype.getRevision=E.prototype.K;E.prototype.on=E.prototype.D;E.prototype.once=E.prototype.L;E.prototype.un=E.prototype.J;E.prototype.unByKey=E.prototype.M;ps.prototype.readFeatures=ps.prototype.sa;qs.prototype.readFeatures=qs.prototype.sa;qs.prototype.readFeatures=qs.prototype.sa;vh.prototype.get=vh.prototype.get;vh.prototype.getKeys=vh.prototype.O;vh.prototype.getProperties=vh.prototype.P;vh.prototype.set=vh.prototype.set;vh.prototype.setProperties=vh.prototype.H;
-vh.prototype.unset=vh.prototype.S;vh.prototype.changed=vh.prototype.s;vh.prototype.getRevision=vh.prototype.K;vh.prototype.on=vh.prototype.D;vh.prototype.once=vh.prototype.L;vh.prototype.un=vh.prototype.J;vh.prototype.unByKey=vh.prototype.M;Wh.prototype.getMap=Wh.prototype.g;Wh.prototype.setMap=Wh.prototype.setMap;Wh.prototype.setTarget=Wh.prototype.c;Wh.prototype.get=Wh.prototype.get;Wh.prototype.getKeys=Wh.prototype.O;Wh.prototype.getProperties=Wh.prototype.P;Wh.prototype.set=Wh.prototype.set;
-Wh.prototype.setProperties=Wh.prototype.H;Wh.prototype.unset=Wh.prototype.S;Wh.prototype.changed=Wh.prototype.s;Wh.prototype.getRevision=Wh.prototype.K;Wh.prototype.on=Wh.prototype.D;Wh.prototype.once=Wh.prototype.L;Wh.prototype.un=Wh.prototype.J;Wh.prototype.unByKey=Wh.prototype.M;gi.prototype.getMap=gi.prototype.g;gi.prototype.setMap=gi.prototype.setMap;gi.prototype.setTarget=gi.prototype.c;gi.prototype.get=gi.prototype.get;gi.prototype.getKeys=gi.prototype.O;gi.prototype.getProperties=gi.prototype.P;
-gi.prototype.set=gi.prototype.set;gi.prototype.setProperties=gi.prototype.H;gi.prototype.unset=gi.prototype.S;gi.prototype.changed=gi.prototype.s;gi.prototype.getRevision=gi.prototype.K;gi.prototype.on=gi.prototype.D;gi.prototype.once=gi.prototype.L;gi.prototype.un=gi.prototype.J;gi.prototype.unByKey=gi.prototype.M;hi.prototype.getMap=hi.prototype.g;hi.prototype.setMap=hi.prototype.setMap;hi.prototype.setTarget=hi.prototype.c;hi.prototype.get=hi.prototype.get;hi.prototype.getKeys=hi.prototype.O;
-hi.prototype.getProperties=hi.prototype.P;hi.prototype.set=hi.prototype.set;hi.prototype.setProperties=hi.prototype.H;hi.prototype.unset=hi.prototype.S;hi.prototype.changed=hi.prototype.s;hi.prototype.getRevision=hi.prototype.K;hi.prototype.on=hi.prototype.D;hi.prototype.once=hi.prototype.L;hi.prototype.un=hi.prototype.J;hi.prototype.unByKey=hi.prototype.M;jr.prototype.getMap=jr.prototype.g;jr.prototype.setMap=jr.prototype.setMap;jr.prototype.setTarget=jr.prototype.c;jr.prototype.get=jr.prototype.get;
-jr.prototype.getKeys=jr.prototype.O;jr.prototype.getProperties=jr.prototype.P;jr.prototype.set=jr.prototype.set;jr.prototype.setProperties=jr.prototype.H;jr.prototype.unset=jr.prototype.S;jr.prototype.changed=jr.prototype.s;jr.prototype.getRevision=jr.prototype.K;jr.prototype.on=jr.prototype.D;jr.prototype.once=jr.prototype.L;jr.prototype.un=jr.prototype.J;jr.prototype.unByKey=jr.prototype.M;Zh.prototype.getMap=Zh.prototype.g;Zh.prototype.setMap=Zh.prototype.setMap;Zh.prototype.setTarget=Zh.prototype.c;
-Zh.prototype.get=Zh.prototype.get;Zh.prototype.getKeys=Zh.prototype.O;Zh.prototype.getProperties=Zh.prototype.P;Zh.prototype.set=Zh.prototype.set;Zh.prototype.setProperties=Zh.prototype.H;Zh.prototype.unset=Zh.prototype.S;Zh.prototype.changed=Zh.prototype.s;Zh.prototype.getRevision=Zh.prototype.K;Zh.prototype.on=Zh.prototype.D;Zh.prototype.once=Zh.prototype.L;Zh.prototype.un=Zh.prototype.J;Zh.prototype.unByKey=Zh.prototype.M;or.prototype.getMap=or.prototype.g;or.prototype.setMap=or.prototype.setMap;
-or.prototype.setTarget=or.prototype.c;or.prototype.get=or.prototype.get;or.prototype.getKeys=or.prototype.O;or.prototype.getProperties=or.prototype.P;or.prototype.set=or.prototype.set;or.prototype.setProperties=or.prototype.H;or.prototype.unset=or.prototype.S;or.prototype.changed=or.prototype.s;or.prototype.getRevision=or.prototype.K;or.prototype.on=or.prototype.D;or.prototype.once=or.prototype.L;or.prototype.un=or.prototype.J;or.prototype.unByKey=or.prototype.M;ai.prototype.getMap=ai.prototype.g;
-ai.prototype.setMap=ai.prototype.setMap;ai.prototype.setTarget=ai.prototype.c;ai.prototype.get=ai.prototype.get;ai.prototype.getKeys=ai.prototype.O;ai.prototype.getProperties=ai.prototype.P;ai.prototype.set=ai.prototype.set;ai.prototype.setProperties=ai.prototype.H;ai.prototype.unset=ai.prototype.S;ai.prototype.changed=ai.prototype.s;ai.prototype.getRevision=ai.prototype.K;ai.prototype.on=ai.prototype.D;ai.prototype.once=ai.prototype.L;ai.prototype.un=ai.prototype.J;ai.prototype.unByKey=ai.prototype.M;
-Cr.prototype.getMap=Cr.prototype.g;Cr.prototype.setMap=Cr.prototype.setMap;Cr.prototype.setTarget=Cr.prototype.c;Cr.prototype.get=Cr.prototype.get;Cr.prototype.getKeys=Cr.prototype.O;Cr.prototype.getProperties=Cr.prototype.P;Cr.prototype.set=Cr.prototype.set;Cr.prototype.setProperties=Cr.prototype.H;Cr.prototype.unset=Cr.prototype.S;Cr.prototype.changed=Cr.prototype.s;Cr.prototype.getRevision=Cr.prototype.K;Cr.prototype.on=Cr.prototype.D;Cr.prototype.once=Cr.prototype.L;Cr.prototype.un=Cr.prototype.J;
-Cr.prototype.unByKey=Cr.prototype.M;Hr.prototype.getMap=Hr.prototype.g;Hr.prototype.setMap=Hr.prototype.setMap;Hr.prototype.setTarget=Hr.prototype.c;Hr.prototype.get=Hr.prototype.get;Hr.prototype.getKeys=Hr.prototype.O;Hr.prototype.getProperties=Hr.prototype.P;Hr.prototype.set=Hr.prototype.set;Hr.prototype.setProperties=Hr.prototype.H;Hr.prototype.unset=Hr.prototype.S;Hr.prototype.changed=Hr.prototype.s;Hr.prototype.getRevision=Hr.prototype.K;Hr.prototype.on=Hr.prototype.D;Hr.prototype.once=Hr.prototype.L;
-Hr.prototype.un=Hr.prototype.J;Hr.prototype.unByKey=Hr.prototype.M;
+function xb(a){this.radius=a}xb.prototype.a=function(a){for(var b=0,c=a.length,d=a[c-1][0],e=a[c-1][1],f=0;f<c;f++)var g=a[f][0],h=a[f][1],b=b+Ha(g-d)*(2+Math.sin(Ha(e))+Math.sin(Ha(h))),d=g,e=h;return b*this.radius*this.radius/2};xb.prototype.b=function(a,b){var c=Ha(a[1]),d=Ha(b[1]),e=(d-c)/2;a=Ha(b[0]-a[0])/2;c=Math.sin(e)*Math.sin(e)+Math.sin(a)*Math.sin(a)*Math.cos(c)*Math.cos(d);return 2*this.radius*Math.atan2(Math.sqrt(c),Math.sqrt(1-c))};
+xb.prototype.offset=function(a,b,c){var d=Ha(a[1]);b/=this.radius;var e=Math.asin(Math.sin(d)*Math.cos(b)+Math.cos(d)*Math.sin(b)*Math.cos(c));return[180*(Ha(a[0])+Math.atan2(Math.sin(c)*Math.sin(b)*Math.cos(d),Math.cos(b)-Math.sin(d)*Math.sin(e)))/Math.PI,180*e/Math.PI]};var yb=new xb(6370997);var zb={};zb.degrees=2*Math.PI*yb.radius/360;zb.ft=.3048;zb.m=1;zb["us-ft"]=1200/3937;var Ab=null;function Bb(a){this.mb=a.code;this.a=a.units;this.f=void 0!==a.extent?a.extent:null;this.g=void 0!==a.worldExtent?a.worldExtent:null;this.b=void 0!==a.axisOrientation?a.axisOrientation:"enu";this.c=void 0!==a.global?a.global:!1;this.i=!(!this.c||!this.f);this.o=a.getPointResolution;this.j=null;this.l=a.metersPerUnit;var b=a.code,c=Ab||window.proj4;"function"==typeof c&&(b=c.defs(b),void 0!==b&&(void 0!==b.axis&&void 0===a.axisOrientation&&(this.b=b.axis),void 0===a.metersPerUnit&&(this.l=b.to_meter),
+void 0===a.units&&(this.a=b.units)))}k=Bb.prototype;k.Jk=function(){return this.mb};k.G=function(){return this.f};k.Un=function(){return this.a};k.sc=function(){return this.l||zb[this.a]};k.tl=function(){return this.g};k.dm=function(){return this.c};k.$p=function(a){this.c=a;this.i=!(!a||!this.f)};k.Vn=function(a){this.f=a;this.i=!(!this.c||!a)};k.kq=function(a){this.g=a};k.Zp=function(a){this.o=a};function Cb(a){Bb.call(this,{code:a,units:"m",extent:Db,global:!0,worldExtent:Eb,getPointResolution:function(a,c){return a/Da(c[1]/6378137)}})}v(Cb,Bb);var Fb=6378137*Math.PI,Db=[-Fb,-Fb,Fb,Fb],Eb=[-180,-85,180,85],Gb="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(a){return new Cb(a)});
+function Hb(a,b,c){var d=a.length;c=1<c?c:2;void 0===b&&(2<c?b=a.slice():b=Array(d));for(var e=0;e<d;e+=c){b[e]=Fb*a[e]/180;var f=6378137*Math.log(Math.tan(Math.PI*(a[e+1]+90)/360));f>Fb?f=Fb:f<-Fb&&(f=-Fb);b[e+1]=f}return b}function Ib(a,b,c){var d=a.length;c=1<c?c:2;void 0===b&&(2<c?b=a.slice():b=Array(d));for(var e=0;e<d;e+=c)b[e]=180*a[e]/Fb,b[e+1]=360*Math.atan(Math.exp(a[e+1]/6378137))/Math.PI-90;return b};var Jb=new xb(6378137);function Kb(a,b){Bb.call(this,{code:a,units:"degrees",extent:Lb,axisOrientation:b,global:!0,metersPerUnit:Mb,worldExtent:Lb})}v(Kb,Bb);var Lb=[-180,-90,180,90],Mb=Math.PI*Jb.radius/180,Nb=[new Kb("CRS:84"),new Kb("EPSG:4326","neu"),new Kb("urn:ogc:def:crs:EPSG::4326","neu"),new Kb("urn:ogc:def:crs:EPSG:6.6:4326","neu"),new Kb("urn:ogc:def:crs:OGC:1.3:CRS84"),new Kb("urn:ogc:def:crs:OGC:2:84"),new Kb("http://www.opengis.net/gml/srs/epsg.xml#4326","neu"),new Kb("urn:x-ogc:def:crs:EPSG:4326","neu")];var Ob={};var Pb={};function Qb(a,b,c){a=a.mb;b=b.mb;a in Pb||(Pb[a]={});Pb[a][b]=c}function Rb(a,b){var c;a in Pb&&b in Pb[a]&&(c=Pb[a][b]);return c};function Sb(a,b,c){a=Tb(a);var d=a.o;d?b=d(b,c):"degrees"!=a.a&&(d=Vb(a,Tb("EPSG:4326")),b=[c[0]-b/2,c[1],c[0]+b/2,c[1],c[0],c[1]-b/2,c[0],c[1]+b/2],b=d(b,b,2),b=(yb.b(b.slice(0,2),b.slice(2,4))+yb.b(b.slice(4,6),b.slice(6,8)))/2,a=a.sc(),void 0!==a&&(b/=a));return b}function Wb(a){a.forEach(Xb);a.forEach(function(b){a.forEach(function(a){b!==a&&Qb(b,a,Yb)})})}function Zb(){Nb.forEach(function(a){Gb.forEach(function(b){Qb(a,b,Hb);Qb(b,a,Ib)})})}function Xb(a){Ob[a.mb]=a;Qb(a,a,Yb)}
+function $b(a){return a?"string"===typeof a?Tb(a):a:Tb("EPSG:3857")}function ac(a,b,c,d){a=Tb(a);b=Tb(b);Qb(a,b,cc(c));Qb(b,a,cc(d))}function cc(a){return function(b,c,d){var e=b.length;d=void 0!==d?d:2;c=void 0!==c?c:Array(e);var f;for(f=0;f<e;f+=d){var g=a([b[f],b[f+1]]);c[f]=g[0];c[f+1]=g[1];for(g=d-1;2<=g;--g)c[f+g]=b[f+g]}return c}}
+function Tb(a){var b=null;if(a instanceof Bb)b=a;else if("string"===typeof a){var b=Ob[a]||null,c=Ab||window.proj4;b||"function"!=typeof c||void 0===c.defs(a)||(b=new Bb({code:a}),Xb(b))}return b}function dc(a,b){if(a===b)return!0;var c=a.a===b.a;return a.mb===b.mb?c:Vb(a,b)===Yb&&c}function ec(a,b){a=Tb(a);b=Tb(b);return Vb(a,b)}
+function Vb(a,b){var c=a.mb,d=b.mb,e=Rb(c,d);if(!e){var f=Ab||window.proj4;if("function"==typeof f){var g=f.defs(c),h=f.defs(d);void 0!==g&&void 0!==h&&(g===h?Wb([b,a]):(e=f(d,c),ac(b,a,e.forward,e.inverse)),e=Rb(c,d))}}e||(e=fc);return e}function fc(a,b){if(void 0!==b&&a!==b){for(var c=0,d=a.length;c<d;++c)b[c]=a[c];a=b}return a}function Yb(a,b){if(void 0!==b){for(var c=0,d=a.length;c<d;++c)b[c]=a[c];a=b}else a=a.slice();return a}function gc(a,b,c){return ec(b,c)(a,void 0,a.length)}
+function hc(a,b,c){b=ec(b,c);return sb(a,b)}function ic(){Wb(Gb);Wb(Nb);Zb()}ic();function jc(a,b,c,d){return void 0!==d?(d[0]=a,d[1]=b,d[2]=c,d):[a,b,c]}function kc(a){var b=a[0],c=Array(b),d=1<<b-1,e;for(e=0;e<b;++e){var f=48;a[1]&d&&(f+=1);a[2]&d&&(f+=2);c[e]=String.fromCharCode(f);d>>=1}return c.join("")};function lc(a){this.minZoom=void 0!==a.minZoom?a.minZoom:0;this.b=a.resolutions;xa(ta(this.b,function(a,b){return b-a}),17);this.maxZoom=this.b.length-1;this.i=void 0!==a.origin?a.origin:null;this.c=null;void 0!==a.origins&&(this.c=a.origins,xa(this.c.length==this.b.length,20));var b=a.extent;void 0===b||this.i||this.c||(this.i=ib(b));xa(!this.i&&this.c||this.i&&!this.c,18);this.f=null;void 0!==a.tileSizes&&(this.f=a.tileSizes,xa(this.f.length==this.b.length,19));this.g=void 0!==a.tileSize?a.tileSize:
+this.f?null:256;xa(!this.g&&this.f||this.g&&!this.f,22);this.v=void 0!==b?b:null;this.a=null;this.j=[0,0];void 0!==a.sizes?this.a=a.sizes.map(function(a){return new ya(Math.min(0,a[0]),Math.max(a[0]-1,-1),Math.min(0,a[1]),Math.max(a[1]-1,-1))},this):b&&mc(this,b)}var nc=[0,0,0];k=lc.prototype;k.Rf=function(a,b,c){a=oc(this,a,b);for(var d=a.ca,e=a.$;d<=e;++d)for(var f=a.da,g=a.ia;f<=g;++f)c([b,d,f])};
+function pc(a,b,c,d,e){e=a.Aa(b,e);for(b=b[0]-1;b>=a.minZoom;){if(c.call(null,b,oc(a,e,b,d)))return!0;--b}return!1}k.G=function(){return this.v};k.Ti=function(){return this.maxZoom};k.Ui=function(){return this.minZoom};k.Pc=function(a){return this.i?this.i:this.c[a]};k.Da=function(a){return this.b[a]};k.Vi=function(){return this.b};function qc(a,b,c,d){return b[0]<a.maxZoom?(d=a.Aa(b,d),oc(a,d,b[0]+1,c)):null}
+function rc(a,b,c,d){sc(a,b[0],b[1],c,!1,nc);var e=nc[1],f=nc[2];sc(a,b[2],b[3],c,!0,nc);a=nc[1];b=nc[2];void 0!==d?(d.ca=e,d.$=a,d.da=f,d.ia=b):d=new ya(e,a,f,b);return d}function oc(a,b,c,d){return rc(a,b,a.Da(c),d)}function tc(a,b){var c=a.Pc(b[0]),d=a.Da(b[0]);a=Ma(a.gb(b[0]),a.j);return[c[0]+(b[1]+.5)*a[0]*d,c[1]+(b[2]+.5)*a[1]*d]}k.Aa=function(a,b){var c=this.Pc(a[0]),d=this.Da(a[0]),e=Ma(this.gb(a[0]),this.j),f=c[0]+a[1]*e[0]*d;a=c[1]+a[2]*e[1]*d;return Xa(f,a,f+e[0]*d,a+e[1]*d,b)};
+k.Be=function(a,b,c){return sc(this,a[0],a[1],b,!1,c)};function sc(a,b,c,d,e,f){var g=a.tc(d),h=d/a.Da(g),l=a.Pc(g);a=Ma(a.gb(g),a.j);b=h*Math.floor((b-l[0])/d+(e?.5:0))/a[0];c=h*Math.floor((c-l[1])/d+(e?0:.5))/a[1];e?(b=Math.ceil(b)-1,c=Math.ceil(c)-1):(b=Math.floor(b),c=Math.floor(c));return jc(g,b,c,f)}k.bg=function(a,b,c){return sc(this,a[0],a[1],this.Da(b),!1,c)};k.gb=function(a){return this.g?this.g:this.f[a]};k.tc=function(a,b){return Ca(ka(this.b,a,b||0),this.minZoom,this.maxZoom)};
+function mc(a,b){for(var c=a.b.length,d=Array(c),e=a.minZoom;e<c;++e)d[e]=oc(a,b,e);a.a=d};function vc(a){var b=a.j;b||(b=wc(a),a.j=b);return b}function xc(a){var b={};tb(b,a?a:{});void 0===b.extent&&(b.extent=Tb("EPSG:3857").G());b.resolutions=yc(b.extent,b.maxZoom,b.tileSize);delete b.maxZoom;return new lc(b)}function yc(a,b,c){b=void 0!==b?b:42;var d=mb(a);a=lb(a);c=Ma(void 0!==c?c:256);c=Math.max(a/c[0],d/c[1]);b+=1;d=Array(b);for(a=0;a<b;++a)d[a]=c/Math.pow(2,a);return d}function wc(a,b,c){a=zc(a);b=yc(a,b,c);return new lc({extent:a,origin:ib(a),resolutions:b,tileSize:c})}
+function zc(a){a=Tb(a);var b=a.G();b||(a=180*zb.degrees/a.sc(),b=Xa(-a,-a,a,a));return b};function Ac(a){this.b=a.html;this.a=a.tileRanges?a.tileRanges:null}Ac.prototype.i=function(){return this.b};function Bc(a){return function(b){if(b)return[Ca(b[0],a[0],a[2]),Ca(b[1],a[1],a[3])]}}function Cc(a){return a};function Dc(a){function b(b){var c=a.listener,e=a.lh||a.target;a.nh&&Ec(a);return c.call(e,b)}return a.mh=b}function Fc(a,b,c,d){for(var e,f=0,g=a.length;f<g;++f)if(e=a[f],e.listener===b&&e.lh===c)return d&&(e.deleteIndex=f),e}function Gc(a,b){return(a=a.fb)?a[b]:void 0}function Hc(a){var b=a.fb;b||(b=a.fb={});return b}function Ic(a,b){var c=Gc(a,b);if(c){for(var d=0,e=c.length;d<e;++d)a.removeEventListener(b,c[d].mh),ub(c[d]);c.length=0;if(c=a.fb)delete c[b],Object.keys(c).length||delete a.fb}}
+function y(a,b,c,d,e){var f=Hc(a),g=f[b];g||(g=f[b]=[]);(f=Fc(g,c,d,!1))?e||(f.nh=!1):(f={lh:d,nh:!!e,listener:c,target:a,type:b},a.addEventListener(b,Dc(f)),g.push(f));return f}function Jc(a,b,c,d){return y(a,b,c,d,!0)}function Kc(a,b,c,d){(a=Gc(a,b))&&(c=Fc(a,c,d,!0))&&Ec(c)}function Ec(a){if(a&&a.target){a.target.removeEventListener(a.type,a.mh);var b=Gc(a.target,a.type);if(b){var c="deleteIndex"in a?a.deleteIndex:b.indexOf(a);-1!==c&&b.splice(c,1);b.length||Ic(a.target,a.type)}ub(a)}}
+function Lc(a){var b=Hc(a),c;for(c in b)Ic(a,c)};function Mc(){}Mc.prototype.Jb=!1;function Nc(a){a.Jb||(a.Jb=!0,a.ka())}Mc.prototype.ka=ua;function Oc(a){this.type=a;this.target=null}Oc.prototype.preventDefault=Oc.prototype.stopPropagation=function(){this.qp=!0};function Pc(a){a.stopPropagation()};function Qc(){this.Ua={};this.ra={};this.oa={}}v(Qc,Mc);Qc.prototype.addEventListener=function(a,b){var c=this.oa[a];c||(c=this.oa[a]=[]);-1===c.indexOf(b)&&c.push(b)};
+Qc.prototype.b=function(a){var b="string"===typeof a?new Oc(a):a;a=b.type;b.target=this;var c=this.oa[a];if(c){a in this.ra||(this.ra[a]=0,this.Ua[a]=0);++this.ra[a];for(var d=0,e=c.length;d<e;++d)if(!1===c[d].call(this,b)||b.qp){var f=!1;break}--this.ra[a];if(!this.ra[a]){b=this.Ua[a];for(delete this.Ua[a];b--;)this.removeEventListener(a,ua);delete this.ra[a]}return f}};Qc.prototype.ka=function(){Lc(this)};function Rc(a,b){return b?b in a.oa:0<Object.keys(a.oa).length}
+Qc.prototype.removeEventListener=function(a,b){var c=this.oa[a];c&&(b=c.indexOf(b),a in this.Ua?(c[b]=ua,++this.Ua[a]):(c.splice(b,1),c.length||delete this.oa[a]))};function Sc(){Qc.call(this);this.i=0}v(Sc,Qc);k=Sc.prototype;k.s=function(){++this.i;this.b("change")};k.L=function(){return this.i};k.J=function(a,b,c){if(Array.isArray(a)){for(var d=a.length,e=Array(d),f=0;f<d;++f)e[f]=y(this,a[f],b,c);return e}return y(this,a,b,c)};k.once=function(a,b,c){if(Array.isArray(a)){for(var d=a.length,e=Array(d),f=0;f<d;++f)e[f]=Jc(this,a[f],b,c);return e}return Jc(this,a,b,c)};
+k.K=function(a,b,c){if(Array.isArray(a))for(var d=0,e=a.length;d<e;++d)Kc(this,a[d],b,c);else Kc(this,a,b,c)};function Tc(a){Sc.call(this);w(this);this.S={};void 0!==a&&this.H(a)}v(Tc,Sc);var Uc={};function Vc(a){return Uc.hasOwnProperty(a)?Uc[a]:Uc[a]="change:"+a}k=Tc.prototype;k.get=function(a){var b;this.S.hasOwnProperty(a)&&(b=this.S[a]);return b};k.O=function(){return Object.keys(this.S)};k.N=function(){return tb({},this.S)};function Wc(a,b,c){var d=Vc(b);a.b(new Xc(d,b,c));a.b(new Xc("propertychange",b,c))}k.set=function(a,b,c){c?this.S[a]=b:(c=this.S[a],this.S[a]=b,c!==b&&Wc(this,a,c))};
+k.H=function(a,b){for(var c in a)this.set(c,a[c],b)};k.P=function(a,b){if(a in this.S){var c=this.S[a];delete this.S[a];b||Wc(this,a,c)}};function Xc(a,b,c){Oc.call(this,a);this.key=b;this.oldValue=c}v(Xc,Oc);function Yc(a,b){Tc.call(this);this.c=!!(b||{}).unique;this.a=a?a:[];if(this.c)for(a=0,b=this.a.length;a<b;++a)Zc(this,this.a[a],a);$c(this)}v(Yc,Tc);k=Yc.prototype;k.clear=function(){for(;0<this.dc();)this.pop()};k.fg=function(a){var b;var c=0;for(b=a.length;c<b;++c)this.push(a[c]);return this};k.forEach=function(a,b){this.a.forEach(a,b)};k.tm=function(){return this.a};k.item=function(a){return this.a[a]};k.dc=function(){return this.get(ad)};
+k.He=function(a,b){this.c&&Zc(this,b);this.a.splice(a,0,b);$c(this);this.b(new bd("add",b))};k.pop=function(){return this.Hg(this.dc()-1)};k.push=function(a){this.c&&Zc(this,a);var b=this.dc();this.He(b,a);return this.dc()};k.remove=function(a){var b=this.a,c;var d=0;for(c=b.length;d<c;++d)if(b[d]===a)return this.Hg(d)};k.Hg=function(a){var b=this.a[a];this.a.splice(a,1);$c(this);this.b(new bd("remove",b));return b};
+k.Wp=function(a,b){var c=this.dc();if(a<c)this.c&&Zc(this,b,a),c=this.a[a],this.a[a]=b,this.b(new bd("remove",c)),this.b(new bd("add",b));else{for(;c<a;++c)this.He(c,void 0);this.He(a,b)}};function $c(a){a.set(ad,a.a.length)}function Zc(a,b,c){for(var d=0,e=a.a.length;d<e;++d)if(a.a[d]===b&&d!==c)throw new wa(58);}var ad="length";function bd(a,b){Oc.call(this,a);this.element=b}v(bd,Oc);var cd=/^#(?:[0-9a-f]{3}){1,2}$/i,dd=/^([a-z]*)$/i;function ed(a){return Array.isArray(a)?a:fd(a)}function gd(a){if("string"!==typeof a){var b=a[0];b!=(b|0)&&(b=b+.5|0);var c=a[1];c!=(c|0)&&(c=c+.5|0);var d=a[2];d!=(d|0)&&(d=d+.5|0);a="rgba("+b+","+c+","+d+","+(void 0===a[3]?1:a[3])+")"}return a}
+var fd=function(){var a={},b=0;return function(c){if(a.hasOwnProperty(c))var d=a[c];else{if(1024<=b){d=0;for(var e in a)d++&3||(delete a[e],--b)}d=c;dd.exec(d)&&(e=document.createElement("div"),e.style.color=d,document.body.appendChild(e),d=getComputedStyle(e).color,document.body.removeChild(e));if(cd.exec(d)){var f=d.length-1;xa(3==f||6==f,54);var g=3==f?1:2;f=parseInt(d.substr(1+0*g,g),16);e=parseInt(d.substr(1+1*g,g),16);d=parseInt(d.substr(1+2*g,g),16);1==g&&(f=(f<<4)+f,e=(e<<4)+e,d=(d<<4)+d);
+f=[f,e,d,1]}else d.indexOf("rgba(")?d.indexOf("rgb(")?xa(!1,14):(d=d.slice(4,-1).split(",").map(Number),d.push(1),f=hd(d)):(d=d.slice(5,-1).split(",").map(Number),f=hd(d));d=f;a[c]=d;++b}return d}}();function hd(a){var b=[];b[0]=Ca(a[0]+.5|0,0,255);b[1]=Ca(a[1]+.5|0,0,255);b[2]=Ca(a[2]+.5|0,0,255);b[3]=Ca(a[3],0,1);return b};function id(a){return"string"===typeof a||a instanceof CanvasPattern||a instanceof CanvasGradient?a:gd(a)};function jd(a,b){var c=document.createElement("CANVAS");a&&(c.width=a);b&&(c.height=b);return c.getContext("2d")}function kd(a,b){var c=b.parentNode;c&&c.replaceChild(a,b)}function ld(a){a&&a.parentNode&&a.parentNode.removeChild(a)};function md(a){Tc.call(this);this.element=a.element?a.element:null;this.a=this.R=null;this.v=[];this.render=a.render?a.render:ua;a.target&&this.f(a.target)}v(md,Tc);md.prototype.ka=function(){ld(this.element);Tc.prototype.ka.call(this)};md.prototype.g=function(){return this.a};
+md.prototype.setMap=function(a){this.a&&ld(this.element);for(var b=0,c=this.v.length;b<c;++b)Ec(this.v[b]);this.v.length=0;if(this.a=a)(this.R?this.R:a.D).appendChild(this.element),this.render!==ua&&this.v.push(y(a,"postrender",this.render,this)),a.render()};md.prototype.f=function(a){this.R="string"===typeof a?document.getElementById(a):a};function nd(a){a=a?a:{};this.I=document.createElement("UL");this.u=document.createElement("LI");this.I.appendChild(this.u);this.u.style.display="none";this.c=void 0!==a.collapsed?a.collapsed:!0;this.o=void 0!==a.collapsible?a.collapsible:!0;this.o||(this.c=!1);var b=void 0!==a.className?a.className:"ol-attribution",c=void 0!==a.tipLabel?a.tipLabel:"Attributions",d=void 0!==a.collapseLabel?a.collapseLabel:"\u00bb";"string"===typeof d?(this.D=document.createElement("span"),this.D.textContent=d):this.D=
+d;d=void 0!==a.label?a.label:"i";"string"===typeof d?(this.C=document.createElement("span"),this.C.textContent=d):this.C=d;var e=this.o&&!this.c?this.D:this.C,d=document.createElement("button");d.setAttribute("type","button");d.title=c;d.appendChild(e);y(d,"click",this.Vm,this);c=document.createElement("div");c.className=b+" ol-unselectable ol-control"+(this.c&&this.o?" ol-collapsed":"")+(this.o?"":" ol-uncollapsible");c.appendChild(this.I);c.appendChild(d);md.call(this,{element:c,render:a.render?
+a.render:od,target:a.target});this.B=!0;this.l={};this.j={};this.T={}}v(nd,md);
+function od(a){if(a=a.frameState){var b,c,d,e,f,g=a.layerStatesArray,h=tb({},a.attributions),l={},m={},n=a.viewState.projection;var p=0;for(b=g.length;p<b;p++)if(e=g[p].layer.ha()){var q=w(e).toString();if(f=e.j){var r=0;for(c=f.length;r<c;r++){var u=f[r];var x=w(u).toString();if(!(x in h)){if(d=a.usedTiles[q]){var B=e.Ta(n);a:{var E=void 0;var A,L=u,oa=B,ha=n;if(L.a){for(E in d)if(E in L.a){var B=d[E];var ga=0;for(A=L.a[E].length;ga<A;++ga){var z=L.a[E][ga];if(Ba(z,B)){E=!0;break a}var M=oc(oa,zc(ha),
+parseInt(E,10)),ba=M.$-M.ca+1;if(B.ca<M.ca||B.$>M.$)if(Ba(z,new ya(Ia(B.ca,ba),Ia(B.$,ba),B.da,B.ia))||B.$-B.ca+1>ba&&Ba(z,M)){E=!0;break a}}}E=!1}else E=!0}}else E=!1;E?(x in l&&delete l[x],E=u.b,E in m||(m[E]=!0,h[x]=u)):l[x]=u}}}}b=[h,l];p=b[0];b=b[1];for(var da in this.l)da in p?(this.j[da]||(this.l[da].style.display="",this.j[da]=!0),delete p[da]):da in b?(this.j[da]&&(this.l[da].style.display="none",delete this.j[da]),delete b[da]):(ld(this.l[da]),delete this.l[da],delete this.j[da]);for(da in p)r=
+document.createElement("LI"),r.innerHTML=p[da].b,this.I.appendChild(r),this.l[da]=r,this.j[da]=!0;for(da in b)r=document.createElement("LI"),r.innerHTML=b[da].b,r.style.display="none",this.I.appendChild(r),this.l[da]=r;da=!wb(this.j)||!wb(a.logos);this.B!=da&&(this.element.style.display=da?"":"none",this.B=da);da&&wb(this.j)?this.element.classList.add("ol-logo-only"):this.element.classList.remove("ol-logo-only");a=a.logos;da=this.T;for(ca in da)ca in a||(ld(da[ca]),delete da[ca]);for(var fb in a)if(b=
+a[fb],b instanceof HTMLElement&&(this.u.appendChild(b),da[fb]=b),!(fb in da)){var ca=new Image;ca.src=fb;""===b?p=ca:(p=document.createElement("a"),p.href=b,p.appendChild(ca));this.u.appendChild(p);da[fb]=p}this.u.style.display=wb(a)?"none":""}else this.B&&(this.element.style.display="none",this.B=!1)}k=nd.prototype;k.Vm=function(a){a.preventDefault();pd(this)};function pd(a){a.element.classList.toggle("ol-collapsed");a.c?kd(a.D,a.C):kd(a.C,a.D);a.c=!a.c}k.Um=function(){return this.o};
+k.Xm=function(a){this.o!==a&&(this.o=a,this.element.classList.toggle("ol-uncollapsible"),!a&&this.c&&pd(this))};k.Wm=function(a){this.o&&this.c!==a&&pd(this)};k.Tm=function(){return this.c};function qd(a){return Math.pow(a,3)}function rd(a){return 1-qd(1-a)}function sd(a){return 3*a*a-2*a*a*a}function td(a){return a};function ud(a){a=a?a:{};var b=void 0!==a.className?a.className:"ol-rotate",c=void 0!==a.label?a.label:"\u21e7";this.c=null;"string"===typeof c?(this.c=document.createElement("span"),this.c.className="ol-compass",this.c.textContent=c):(this.c=c,this.c.classList.add("ol-compass"));var d=a.tipLabel?a.tipLabel:"Reset rotation",c=document.createElement("button");c.className=b+"-reset";c.setAttribute("type","button");c.title=d;c.appendChild(this.c);y(c,"click",ud.prototype.D,this);d=document.createElement("div");
+d.className=b+" ol-unselectable ol-control";d.appendChild(c);b=a.render?a.render:vd;this.o=a.resetNorth?a.resetNorth:void 0;md.call(this,{element:d,render:b,target:a.target});this.l=void 0!==a.duration?a.duration:250;this.j=void 0!==a.autoHide?a.autoHide:!0;this.u=void 0;this.j&&this.element.classList.add("ol-hidden")}v(ud,md);ud.prototype.D=function(a){a.preventDefault();this.o?this.o():(a=this.a.Z())&&void 0!==a.Qa()&&(0<this.l?a.animate({rotation:0,duration:this.l,easing:rd}):a.Oe(0))};
+function vd(a){if(a=a.frameState){a=a.viewState.rotation;if(a!=this.u){var b="rotate("+a+"rad)";if(this.j){var c=this.element.classList.contains("ol-hidden");c||a?c&&a&&this.element.classList.remove("ol-hidden"):this.element.classList.add("ol-hidden")}this.c.style.msTransform=b;this.c.style.webkitTransform=b;this.c.style.transform=b}this.u=a}};function wd(a){a=a?a:{};var b=void 0!==a.className?a.className:"ol-zoom",c=void 0!==a.delta?a.delta:1,d=void 0!==a.zoomInLabel?a.zoomInLabel:"+",e=void 0!==a.zoomOutLabel?a.zoomOutLabel:"\u2212",f=void 0!==a.zoomInTipLabel?a.zoomInTipLabel:"Zoom in",g=void 0!==a.zoomOutTipLabel?a.zoomOutTipLabel:"Zoom out",h=document.createElement("button");h.className=b+"-in";h.setAttribute("type","button");h.title=f;h.appendChild("string"===typeof d?document.createTextNode(d):d);y(h,"click",wd.prototype.j.bind(this,
+c));d=document.createElement("button");d.className=b+"-out";d.setAttribute("type","button");d.title=g;d.appendChild("string"===typeof e?document.createTextNode(e):e);y(d,"click",wd.prototype.j.bind(this,-c));c=document.createElement("div");c.className=b+" ol-unselectable ol-control";c.appendChild(h);c.appendChild(d);md.call(this,{element:c,target:a.target});this.c=void 0!==a.duration?a.duration:250}v(wd,md);
+wd.prototype.j=function(a,b){b.preventDefault();if(b=this.a.Z()){var c=b.Pa();c&&(a=b.constrainResolution(c,a),0<this.c?(b.Ic()&&b.ed(),b.animate({resolution:a,duration:this.c,easing:rd})):b.Vc(a))}};function xd(a){a=a?a:{};var b=new Yc;(void 0!==a.zoom?a.zoom:1)&&b.push(new wd(a.zoomOptions));(void 0!==a.rotate?a.rotate:1)&&b.push(new ud(a.rotateOptions));(void 0!==a.attribution?a.attribution:1)&&b.push(new nd(a.attributionOptions));return b};function yd(a){a=a?a:{};this.c=void 0!==a.className?a.className:"ol-full-screen";var b=void 0!==a.label?a.label:"\u2922";this.o="string"===typeof b?document.createTextNode(b):b;b=void 0!==a.labelActive?a.labelActive:"\u00d7";this.l="string"===typeof b?document.createTextNode(b):b;var c=a.tipLabel?a.tipLabel:"Toggle full-screen",b=document.createElement("button");b.className=this.c+"-"+zd();b.setAttribute("type","button");b.title=c;b.appendChild(this.o);y(b,"click",this.C,this);c=document.createElement("div");
+c.className=this.c+" ol-unselectable ol-control "+(Ad()?"":"ol-unsupported");c.appendChild(b);md.call(this,{element:c,target:a.target});this.D=void 0!==a.keys?a.keys:!1;this.j=a.source}v(yd,md);
+yd.prototype.C=function(a){a.preventDefault();Ad()&&(a=this.a)&&(zd()?document.exitFullscreen?document.exitFullscreen():document.msExitFullscreen?document.msExitFullscreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.webkitExitFullscreen&&document.webkitExitFullscreen():(a=this.j?"string"===typeof this.j?document.getElementById(this.j):this.j:a.jd(),this.D?a.mozRequestFullScreenWithKeys?a.mozRequestFullScreenWithKeys():a.webkitRequestFullscreen?a.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT):
+Bd(a):Bd(a)))};yd.prototype.u=function(){var a=this.element.firstElementChild,b=this.a;zd()?(a.className=this.c+"-true",kd(this.l,this.o)):(a.className=this.c+"-false",kd(this.o,this.l));b&&b.Ad()};yd.prototype.setMap=function(a){md.prototype.setMap.call(this,a);a&&this.v.push(y(document,Cd(),this.u,this))};
+function Ad(){var a=document.body;return!!(a.webkitRequestFullscreen||a.mozRequestFullScreen&&document.mozFullScreenEnabled||a.msRequestFullscreen&&document.msFullscreenEnabled||a.requestFullscreen&&document.fullscreenEnabled)}function zd(){return!!(document.webkitIsFullScreen||document.mozFullScreen||document.msFullscreenElement||document.fullscreenElement)}
+function Bd(a){a.requestFullscreen?a.requestFullscreen():a.msRequestFullscreen?a.msRequestFullscreen():a.mozRequestFullScreen?a.mozRequestFullScreen():a.webkitRequestFullscreen&&a.webkitRequestFullscreen()}var Cd=function(){var a;return function(){if(!a){var b=document.body;b.webkitRequestFullscreen?a="webkitfullscreenchange":b.mozRequestFullScreen?a="mozfullscreenchange":b.msRequestFullscreen?a="MSFullscreenChange":b.requestFullscreen&&(a="fullscreenchange")}return a}}();function Dd(a){a=a?a:{};var b=document.createElement("DIV");b.className=void 0!==a.className?a.className:"ol-mouse-position";md.call(this,{element:b,render:a.render?a.render:Ed,target:a.target});y(this,Vc(Fd),this.Ym,this);a.coordinateFormat&&this.kj(a.coordinateFormat);a.projection&&this.$h(a.projection);this.u=void 0!==a.undefinedHTML?a.undefinedHTML:"";this.l=b.innerHTML;this.o=this.j=this.c=null}v(Dd,md);
+function Ed(a){a=a.frameState;a?this.c!=a.viewState.projection&&(this.c=a.viewState.projection,this.j=null):this.c=null;Gd(this,this.o)}k=Dd.prototype;k.Ym=function(){this.j=null};k.xh=function(){return this.get(Hd)};k.Zh=function(){return this.get(Fd)};k.Ll=function(a){this.o=this.a.xe(a);Gd(this,this.o)};k.Ml=function(){Gd(this,null);this.o=null};k.setMap=function(a){md.prototype.setMap.call(this,a);a&&(a=a.a,this.v.push(y(a,"mousemove",this.Ll,this),y(a,"mouseout",this.Ml,this)))};
+k.kj=function(a){this.set(Hd,a)};k.$h=function(a){this.set(Fd,Tb(a))};function Gd(a,b){var c=a.u;if(b&&a.c){if(!a.j){var d=a.Zh();a.j=d?Vb(a.c,d):fc}if(b=a.a.Wa(b))a.j(b,b),c=(c=a.xh())?c(b):b.toString()}a.l&&c==a.l||(a.element.innerHTML=c,a.l=c)}var Fd="projection",Hd="coordinateFormat";function Id(a,b,c){Oc.call(this,a);this.map=b;this.frameState=void 0!==c?c:null}v(Id,Oc);function Jd(a,b,c,d,e){Id.call(this,a,b,e);this.originalEvent=c;this.pixel=b.xe(c);this.coordinate=b.Wa(this.pixel);this.dragging=void 0!==d?d:!1}v(Jd,Id);Jd.prototype.preventDefault=function(){Id.prototype.preventDefault.call(this);this.originalEvent.preventDefault()};Jd.prototype.stopPropagation=function(){Id.prototype.stopPropagation.call(this);this.originalEvent.stopPropagation()};var Kd=["experimental-webgl","webgl","webkit-3d","moz-webgl"];function Ld(a,b){var c,d,e=Kd.length;for(d=0;d<e;++d)try{if(c=a.getContext(Kd[d],b))return c}catch(f){}return null};var Md,Nd="undefined"!==typeof navigator?navigator.userAgent.toLowerCase():"",Od=-1!==Nd.indexOf("firefox"),Pd=-1!==Nd.indexOf("safari")&&-1==Nd.indexOf("chrom"),Qd=-1!==Nd.indexOf("webkit")&&-1==Nd.indexOf("edge"),Rd=-1!==Nd.indexOf("macintosh"),Sd=window.devicePixelRatio||1,Td=!1,Ud=function(){if(!("HTMLCanvasElement"in window))return!1;try{var a=document.createElement("CANVAS").getContext("2d");return a?(void 0!==a.setLineDash&&(Td=!0),!0):!1}catch(b){return!1}}(),Vd="DeviceOrientationEvent"in
+window,Wd="geolocation"in navigator,Xd="ontouchstart"in window,Yd="PointerEvent"in window,Zd=!!navigator.msPointerEnabled,$d=!1,ae,be=[];if("WebGLRenderingContext"in window)try{var ce=Ld(document.createElement("CANVAS"),{failIfMajorPerformanceCaveat:!0});ce&&($d=!0,ae=ce.getParameter(ce.MAX_TEXTURE_SIZE),be=ce.getSupportedExtensions())}catch(a){}Md=$d;fa=be;ea=ae;var de={Iq:"singleclick",xq:"click",yq:"dblclick",Bq:"pointerdrag",Eq:"pointermove",Aq:"pointerdown",Hq:"pointerup",Gq:"pointerover",Fq:"pointerout",Cq:"pointerenter",Dq:"pointerleave",zq:"pointercancel"};function ee(a,b,c,d,e){Jd.call(this,a,b,c.b,d,e);this.b=c}v(ee,Jd);function fe(a,b){this.b=a;this.f=b};function ge(a){fe.call(this,a,{mousedown:this.fm,mousemove:this.gm,mouseup:this.jm,mouseover:this.im,mouseout:this.hm});this.a=a.i;this.i=[]}v(ge,fe);function he(a,b){a=a.i;var c=b.clientX;b=b.clientY;for(var d=0,e=a.length,f;d<e&&(f=a[d]);d++){var g=Math.abs(b-f[1]);if(25>=Math.abs(c-f[0])&&25>=g)return!0}return!1}function ie(a){var b=je(a,a),c=b.preventDefault;b.preventDefault=function(){a.preventDefault();c()};b.pointerId=1;b.isPrimary=!0;b.pointerType="mouse";return b}k=ge.prototype;
+k.fm=function(a){if(!he(this,a)){(1).toString()in this.a&&this.cancel(a);var b=ie(a);this.a[(1).toString()]=a;ke(this.b,"pointerdown",b,a)}};k.gm=function(a){if(!he(this,a)){var b=ie(a);ke(this.b,"pointermove",b,a)}};k.jm=function(a){if(!he(this,a)){var b=this.a[(1).toString()];b&&b.button===a.button&&(b=ie(a),ke(this.b,"pointerup",b,a),delete this.a[(1).toString()])}};k.im=function(a){if(!he(this,a)){var b=ie(a);le(this.b,b,a)}};k.hm=function(a){if(!he(this,a)){var b=ie(a);me(this.b,b,a)}};
+k.cancel=function(a){var b=ie(a);this.b.cancel(b,a);delete this.a[(1).toString()]};function ne(a){fe.call(this,a,{MSPointerDown:this.om,MSPointerMove:this.pm,MSPointerUp:this.sm,MSPointerOut:this.qm,MSPointerOver:this.rm,MSPointerCancel:this.nm,MSGotPointerCapture:this.lm,MSLostPointerCapture:this.mm});this.a=a.i;this.i=["","unavailable","touch","pen","mouse"]}v(ne,fe);function oe(a,b){var c=b;"number"===typeof b.pointerType&&(c=je(b,b),c.pointerType=a.i[b.pointerType]);return c}k=ne.prototype;
+k.om=function(a){this.a[a.pointerId.toString()]=a;var b=oe(this,a);ke(this.b,"pointerdown",b,a)};k.pm=function(a){var b=oe(this,a);ke(this.b,"pointermove",b,a)};k.sm=function(a){var b=oe(this,a);ke(this.b,"pointerup",b,a);delete this.a[a.pointerId.toString()]};k.qm=function(a){var b=oe(this,a);me(this.b,b,a)};k.rm=function(a){var b=oe(this,a);le(this.b,b,a)};k.nm=function(a){var b=oe(this,a);this.b.cancel(b,a);delete this.a[a.pointerId.toString()]};
+k.mm=function(a){this.b.b(new pe("lostpointercapture",a,a))};k.lm=function(a){this.b.b(new pe("gotpointercapture",a,a))};function qe(a){fe.call(this,a,{pointerdown:this.ip,pointermove:this.jp,pointerup:this.mp,pointerout:this.kp,pointerover:this.lp,pointercancel:this.hp,gotpointercapture:this.ul,lostpointercapture:this.em})}v(qe,fe);k=qe.prototype;k.ip=function(a){re(this.b,a)};k.jp=function(a){re(this.b,a)};k.mp=function(a){re(this.b,a)};k.kp=function(a){re(this.b,a)};k.lp=function(a){re(this.b,a)};k.hp=function(a){re(this.b,a)};k.em=function(a){re(this.b,a)};k.ul=function(a){re(this.b,a)};function pe(a,b,c){Oc.call(this,a);this.b=b;a=c?c:{};this.buttons=se(a);this.pressure=te(a,this.buttons);this.bubbles="bubbles"in a?a.bubbles:!1;this.cancelable="cancelable"in a?a.cancelable:!1;this.view="view"in a?a.view:null;this.detail="detail"in a?a.detail:null;this.screenX="screenX"in a?a.screenX:0;this.screenY="screenY"in a?a.screenY:0;this.clientX="clientX"in a?a.clientX:0;this.clientY="clientY"in a?a.clientY:0;this.ctrlKey="ctrlKey"in a?a.ctrlKey:!1;this.altKey="altKey"in a?a.altKey:!1;this.shiftKey=
+"shiftKey"in a?a.shiftKey:!1;this.metaKey="metaKey"in a?a.metaKey:!1;this.button="button"in a?a.button:0;this.relatedTarget="relatedTarget"in a?a.relatedTarget:null;this.pointerId="pointerId"in a?a.pointerId:0;this.width="width"in a?a.width:0;this.height="height"in a?a.height:0;this.tiltX="tiltX"in a?a.tiltX:0;this.tiltY="tiltY"in a?a.tiltY:0;this.pointerType="pointerType"in a?a.pointerType:"";this.isPrimary="isPrimary"in a?a.isPrimary:!1;b.preventDefault&&(this.preventDefault=function(){b.preventDefault()})}
+v(pe,Oc);function se(a){if(a.buttons||ue)a=a.buttons;else switch(a.which){case 1:a=1;break;case 2:a=4;break;case 3:a=2;break;default:a=0}return a}function te(a,b){var c=0;a.pressure?c=a.pressure:c=b?.5:0;return c}var ue=!1;try{ue=1===(new MouseEvent("click",{buttons:1})).buttons}catch(a){};function ve(a,b){fe.call(this,a,{touchstart:this.rq,touchmove:this.qq,touchend:this.pq,touchcancel:this.oq});this.a=a.i;this.j=b;this.i=void 0;this.g=0;this.c=void 0}v(ve,fe);k=ve.prototype;k.ij=function(){this.g=0;this.c=void 0};
+function we(a,b,c){b=je(b,c);b.pointerId=c.identifier+2;b.bubbles=!0;b.cancelable=!0;b.detail=a.g;b.button=0;b.buttons=1;b.width=c.webkitRadiusX||c.radiusX||0;b.height=c.webkitRadiusY||c.radiusY||0;b.pressure=c.webkitForce||c.force||.5;b.isPrimary=a.i===c.identifier;b.pointerType="touch";b.clientX=c.clientX;b.clientY=c.clientY;b.screenX=c.screenX;b.screenY=c.screenY;return b}
+function xe(a,b,c){function d(){b.preventDefault()}var e=Array.prototype.slice.call(b.changedTouches),f=e.length,g;for(g=0;g<f;++g){var h=we(a,b,e[g]);h.preventDefault=d;c.call(a,b,h)}}
+k.rq=function(a){var b=a.touches,c=Object.keys(this.a),d=c.length;if(d>=b.length){var e=[],f;for(f=0;f<d;++f){var g=c[f];var h=this.a[g];var l;if(!(l=1==g))a:{for(var m=b.length,n=0;n<m;n++)if(l=b[n],l.identifier===g-2){l=!0;break a}l=!1}l||e.push(h.out)}for(f=0;f<e.length;++f)this.Kf(a,e[f])}b=a.changedTouches[0];c=Object.keys(this.a).length;if(!c||1===c&&(1).toString()in this.a)this.i=b.identifier,void 0!==this.c&&clearTimeout(this.c);ye(this,a);this.g++;xe(this,a,this.cp)};
+k.cp=function(a,b){this.a[b.pointerId]={target:b.target,out:b,Wi:b.target};var c=this.b;b.bubbles=!0;ke(c,"pointerover",b,a);c=this.b;b.bubbles=!1;ke(c,"pointerenter",b,a);ke(this.b,"pointerdown",b,a)};k.qq=function(a){a.preventDefault();xe(this,a,this.km)};
+k.km=function(a,b){var c=this.a[b.pointerId];if(c){var d=c.out,e=c.Wi;ke(this.b,"pointermove",b,a);d&&e!==b.target&&(d.relatedTarget=b.target,b.relatedTarget=e,d.target=e,b.target?(me(this.b,d,a),le(this.b,b,a)):(b.target=e,b.relatedTarget=null,this.Kf(a,b)));c.out=b;c.Wi=b.target}};k.pq=function(a){ye(this,a);xe(this,a,this.sq)};
+k.sq=function(a,b){ke(this.b,"pointerup",b,a);this.b.out(b,a);ze(this.b,b,a);delete this.a[b.pointerId];b.isPrimary&&(this.i=void 0,this.c=setTimeout(this.ij.bind(this),200))};k.oq=function(a){xe(this,a,this.Kf)};k.Kf=function(a,b){this.b.cancel(b,a);this.b.out(b,a);ze(this.b,b,a);delete this.a[b.pointerId];b.isPrimary&&(this.i=void 0,this.c=setTimeout(this.ij.bind(this),200))};
+function ye(a,b){var c=a.j.i;b=b.changedTouches[0];if(a.i===b.identifier){var d=[b.clientX,b.clientY];c.push(d);setTimeout(function(){ma(c,d)},2500)}};function Ae(a){Qc.call(this);this.g=a;this.i={};this.f={};this.a=[];Yd?Be(this,new qe(this)):Zd?Be(this,new ne(this)):(a=new ge(this),Be(this,a),Xd&&Be(this,new ve(this,a)));a=this.a.length;for(var b,c=0;c<a;c++)b=this.a[c],Ce(this,Object.keys(b.f))}v(Ae,Qc);function Be(a,b){var c=Object.keys(b.f);c&&(c.forEach(function(a){var c=b.f[a];c&&(this.f[a]=c.bind(b))},a),a.a.push(b))}Ae.prototype.c=function(a){var b=this.f[a.type];b&&b(a)};
+function Ce(a,b){b.forEach(function(a){y(this.g,a,this.c,this)},a)}function De(a,b){b.forEach(function(a){Kc(this.g,a,this.c,this)},a)}function je(a,b){for(var c={},d,e=0,f=Ee.length;e<f;e++)d=Ee[e][0],c[d]=a[d]||b[d]||Ee[e][1];return c}function ze(a,b,c){b.bubbles=!1;ke(a,"pointerleave",b,c)}Ae.prototype.out=function(a,b){a.bubbles=!0;ke(this,"pointerout",a,b)};Ae.prototype.cancel=function(a,b){ke(this,"pointercancel",a,b)};
+function me(a,b,c){a.out(b,c);var d=b.target,e=b.relatedTarget;d&&e&&d.contains(e)||ze(a,b,c)}function le(a,b,c){b.bubbles=!0;ke(a,"pointerover",b,c);var d=b.target,e=b.relatedTarget;d&&e&&d.contains(e)||(b.bubbles=!1,ke(a,"pointerenter",b,c))}function ke(a,b,c,d){a.b(new pe(b,d,c))}function re(a,b){a.b(new pe(b.type,b,b))}Ae.prototype.ka=function(){for(var a=this.a.length,b,c=0;c<a;c++)b=this.a[c],De(this,Object.keys(b.f));Qc.prototype.ka.call(this)};
+var Ee=[["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 Fe(a,b){Qc.call(this);this.i=a;this.j=0;this.o=!1;this.f=[];this.D=b?b*Sd:Sd;this.c=null;a=this.i.a;this.S=0;this.u={};this.g=new Ae(a);this.a=null;this.l=y(this.g,"pointerdown",this.Ol,this);this.v=y(this.g,"pointermove",this.Lp,this)}v(Fe,Qc);function Ge(a,b){var c=new ee("click",a.i,b);a.b(c);a.j?(clearTimeout(a.j),a.j=0,c=new ee("dblclick",a.i,b),a.b(c)):a.j=setTimeout(function(){this.j=0;var a=new ee("singleclick",this.i,b);this.b(a)}.bind(a),250)}
+function He(a,b){"pointerup"==b.type||"pointercancel"==b.type?delete a.u[b.pointerId]:"pointerdown"==b.type&&(a.u[b.pointerId]=!0);a.S=Object.keys(a.u).length}k=Fe.prototype;k.Jh=function(a){He(this,a);var b=new ee("pointerup",this.i,a);this.b(b);this.o||a.button||Ge(this,this.c);this.S||(this.f.forEach(Ec),this.f.length=0,this.o=!1,this.c=null,Nc(this.a),this.a=null)};
+k.Ol=function(a){He(this,a);var b=new ee("pointerdown",this.i,a);this.b(b);this.c=a;this.f.length||(this.a=new Ae(document),this.f.push(y(this.a,"pointermove",this.Hm,this),y(this.a,"pointerup",this.Jh,this),y(this.g,"pointercancel",this.Jh,this)))};k.Hm=function(a){if(Ie(this,a)){this.o=!0;var b=new ee("pointerdrag",this.i,a,this.o);this.b(b)}a.preventDefault()};k.Lp=function(a){this.b(new ee(a.type,this.i,a,!(!this.c||!Ie(this,a))))};
+function Ie(a,b){return Math.abs(b.clientX-a.c.clientX)>a.D||Math.abs(b.clientY-a.c.clientY)>a.D}k.ka=function(){this.v&&(Ec(this.v),this.v=null);this.l&&(Ec(this.l),this.l=null);this.f.forEach(Ec);this.f.length=0;this.a&&(Nc(this.a),this.a=null);this.g&&(Nc(this.g),this.g=null);Qc.prototype.ka.call(this)};function Ke(a,b){this.l=a;this.c=b;this.b=[];this.i=[];this.a={}}Ke.prototype.clear=function(){this.b.length=0;this.i.length=0;ub(this.a)};function Le(a){var b=a.b,c=a.i,d=b[0];1==b.length?(b.length=0,c.length=0):(b[0]=b.pop(),c[0]=c.pop(),Me(a,0));b=a.c(d);delete a.a[b];return d}Ke.prototype.f=function(a){xa(!(this.c(a)in this.a),31);var b=this.l(a);return Infinity!=b?(this.b.push(a),this.i.push(b),this.a[this.c(a)]=!0,Ne(this,0,this.b.length-1),!0):!1};
+function Me(a,b){for(var c=a.b,d=a.i,e=c.length,f=c[b],g=d[b],h=b;b<e>>1;){var l=2*b+1,m=2*b+2,l=m<e&&d[m]<d[l]?m:l;c[b]=c[l];d[b]=d[l];b=l}c[b]=f;d[b]=g;Ne(a,h,b)}function Ne(a,b,c){var d=a.b;a=a.i;for(var e=d[c],f=a[c];c>b;){var g=c-1>>1;if(a[g]>f)d[c]=d[g],a[c]=a[g],c=g;else break}d[c]=e;a[c]=f}
+function Oe(a){var b=a.l,c=a.b,d=a.i,e=0,f=c.length,g;for(g=0;g<f;++g){var h=c[g];var l=b(h);Infinity==l?delete a.a[a.c(h)]:(d[e]=l,c[e++]=h)}c.length=e;d.length=e;for(b=(a.b.length>>1)-1;0<=b;b--)Me(a,b)};function Pe(a,b){Ke.call(this,function(b){return a.apply(null,b)},function(a){return a[0].bb()});this.v=b;this.j=0;this.g={}}v(Pe,Ke);Pe.prototype.f=function(a){var b=Ke.prototype.f.call(this,a);b&&y(a[0],"change",this.o,this);return b};Pe.prototype.o=function(a){a=a.target;var b=a.getState();if(2===b||3===b||4===b||5===b)Kc(a,"change",this.o,this),a=a.bb(),a in this.g&&(delete this.g[a],--this.j),this.v()};
+function Qe(a,b,c){for(var d=0,e,f;a.j<b&&d<c&&0<a.b.length;)e=Le(a)[0],f=e.bb(),0!==e.getState()||f in a.g||(a.g[f]=!0,++a.j,++d,e.load())};function Re(a){return function(b,c,d){if(void 0!==b)return b=ka(a,b,d),b=Ca(b+c,0,a.length-1),c=Math.floor(b),b!=c&&c<a.length-1?a[c]/Math.pow(a[c]/a[c+1],b-c):a[c]}}function Se(a,b,c){return function(d,e,f){if(void 0!==d)return d=Math.max(Math.floor(Math.log(b/d)/Math.log(a)+(-f/2+.5))+e,0),void 0!==c&&(d=Math.min(d,c)),b/Math.pow(a,d)}};function Te(a){if(void 0!==a)return 0}function Ue(a,b){if(void 0!==a)return a+b}function Ve(a){var b=2*Math.PI/a;return function(a,d){if(void 0!==a)return a=Math.floor((a+d)/b+.5)*b}}function We(){var a=Ha(5);return function(b,c){if(void 0!==b)return Math.abs(b+c)<=a?0:b+c}};function Xe(a,b){a=void 0!==b?a.toFixed(b):""+a;b=a.indexOf(".");b=-1===b?a.length:b;return 2<b?a:Array(3-b).join("0")+a}function Ye(a){a=(""+a).split(".");for(var b=["1","3"],c=0;c<Math.max(a.length,b.length);c++){var d=parseInt(a[c]||"0",10),e=parseInt(b[c]||"0",10);if(d>e)return 1;if(e>d)return-1}return 0};function Ze(a,b){a[0]+=b[0];a[1]+=b[1];return a}function $e(a,b){var c=b.pd(),d=b.wa();b=d[0];var d=d[1],e=a[0]-b;a=a[1]-d;e||a||(e=1);var f=Math.sqrt(e*e+a*a);return[b+c*e/f,d+c*a/f]}function af(a,b){var c=a[0];a=a[1];var d=b[0],e=b[1];b=d[0];var d=d[1],f=e[0],e=e[1],g=f-b,h=e-d,c=g||h?(g*(c-b)+h*(a-d))/(g*g+h*h||0):0;0>=c?(a=b,c=d):1<=c?(a=f,c=e):(a=b+c*g,c=d+c*h);return[a,c]}
+function bf(a,b,c){b=Ia(b+180,360)-180;var d=Math.abs(3600*b);c=c||0;var e=Math.pow(10,c),f=Math.floor(d/3600),g=Math.floor((d-3600*f)/60),d=Math.ceil((d-3600*f-60*g)*e)/e;60<=d&&(d=0,g+=1);60<=g&&(g=0,f+=1);return f+"\u00b0 "+Xe(g)+"\u2032 "+Xe(d,c)+"\u2033"+(b?" "+a.charAt(0>b?1:0):"")}function cf(a,b,c){return a?b.replace("{x}",a[0].toFixed(c)).replace("{y}",a[1].toFixed(c)):""}function df(a,b){for(var c=!0,d=a.length-1;0<=d;--d)if(a[d]!=b[d]){c=!1;break}return c}
+function ef(a,b){var c=Math.cos(b);b=Math.sin(b);var d=a[1]*c+a[0]*b;a[0]=a[0]*c-a[1]*b;a[1]=d;return a}function gf(a,b){a[0]*=b;a[1]*=b}function hf(a,b){var c=a[0]-b[0];a=a[1]-b[1];return c*c+a*a}function jf(a,b){return Math.sqrt(hf(a,b))}function kf(a,b){return hf(a,af(a,b))}function lf(a,b){return cf(a,"{x}, {y}",b)};function mf(){return!0}function nf(){return!1};function of(){Tc.call(this);this.l=Oa();this.v=-1;this.f={};this.o=this.g=0}v(of,Tc);k=of.prototype;k.Ab=function(a,b){b=b?b:[NaN,NaN];this.Kb(a[0],a[1],b,Infinity);return b};k.sb=function(a){return this.Mc(a[0],a[1])};k.Mc=nf;k.G=function(a){this.v!=this.i&&(this.l=this.se(this.l),this.v=this.i);var b=this.l;a?(a[0]=b[0],a[1]=b[1],a[2]=b[2],a[3]=b[3]):a=b;return a};k.Rb=function(a){return this.Vd(a*a)};k.tb=function(a,b){this.Dc(ec(a,b));return this};function pf(a,b,c,d,e,f){for(var g=f?f:[],h=0;b<c;b+=d){var l=a[b],m=a[b+1];g[h++]=e[0]*l+e[2]*m+e[4];g[h++]=e[1]*l+e[3]*m+e[5]}f&&g.length!=h&&(g.length=h);return g}function qf(a,b,c,d,e,f,g){for(var h=g?g:[],l=0,m;b<c;b+=d)for(h[l++]=a[b]+e,h[l++]=a[b+1]+f,m=b+2;m<b+d;++m)h[l++]=a[m];g&&h.length!=l&&(h.length=l);return h};function rf(){of.call(this);this.ja="XY";this.a=2;this.A=null}v(rf,of);function sf(a){var b;"XY"==a?b=2:"XYZ"==a||"XYM"==a?b=3:"XYZM"==a&&(b=4);return b}k=rf.prototype;k.Mc=nf;k.se=function(a){return $a(this.A,0,this.A.length,this.a,a)};k.ac=function(){return this.A.slice(0,this.a)};k.ga=function(){return this.A};k.bc=function(){return this.A.slice(this.A.length-this.a)};k.cc=function(){return this.ja};
+k.Vd=function(a){this.o!=this.i&&(ub(this.f),this.g=0,this.o=this.i);if(0>a||this.g&&a<=this.g)return this;var b=a.toString();if(this.f.hasOwnProperty(b))return this.f[b];var c=this.hd(a);if(c.ga().length<this.A.length)return this.f[b]=c;this.g=a;return this};k.hd=function(){return this};k.qa=function(){return this.a};function tf(a,b,c){a.a=sf(b);a.ja=b;a.A=c}
+function uf(a,b,c,d){if(b)c=sf(b);else{for(b=0;b<d;++b)if(c.length)c=c[0];else{a.ja="XY";a.a=2;return}c=c.length;var e;2==c?e="XY":3==c?e="XYZ":4==c&&(e="XYZM");b=e}a.ja=b;a.a=c}k.Dc=function(a){this.A&&(a(this.A,this.A,this.a),this.s())};
+k.rotate=function(a,b){var c=this.ga();if(c){var d=c.length,e=this.qa(),f=c?c:[],g=Math.cos(a);a=Math.sin(a);var h=b[0];b=b[1];for(var l=0,m=0;m<d;m+=e){var n=c[m]-h,p=c[m+1]-b;f[l++]=h+n*g-p*a;f[l++]=b+n*a+p*g;for(n=m+2;n<m+e;++n)f[l++]=c[n]}c&&f.length!=l&&(f.length=l);this.s()}};
+k.scale=function(a,b,c){var d=b;void 0===d&&(d=a);var e=c;e||(e=nb(this.G()));if(c=this.ga()){b=c.length;for(var f=this.qa(),g=c?c:[],h=e[0],e=e[1],l=0,m=0;m<b;m+=f){var n=c[m]-h,p=c[m+1]-e;g[l++]=h+a*n;g[l++]=e+d*p;for(n=m+2;n<m+f;++n)g[l++]=c[n]}c&&g.length!=l&&(g.length=l);this.s()}};k.translate=function(a,b){var c=this.ga();c&&(qf(c,0,c.length,this.qa(),a,b,c),this.s())};function vf(a,b,c,d){for(var e=0,f=a[c-d],g=a[c-d+1];b<c;b+=d)var h=a[b],l=a[b+1],e=e+(g*h-f*l),f=h,g=l;return e/2}function wf(a,b,c,d){var e=0,f;var g=0;for(f=c.length;g<f;++g){var h=c[g],e=e+vf(a,b,h,d);b=h}return e};function xf(a,b,c,d,e,f,g){var h=a[b],l=a[b+1],m=a[c]-h,n=a[c+1]-l;if(m||n)if(f=((e-h)*m+(f-l)*n)/(m*m+n*n),1<f)b=c;else if(0<f){for(e=0;e<d;++e)g[e]=Ja(a[b+e],a[c+e],f);g.length=d;return}for(e=0;e<d;++e)g[e]=a[b+e];g.length=d}function yf(a,b,c,d,e){var f=a[b],g=a[b+1];for(b+=d;b<c;b+=d){var h=a[b],l=a[b+1],f=Ga(f,g,h,l);f>e&&(e=f);f=h;g=l}return e}function zf(a,b,c,d,e){var f;var g=0;for(f=c.length;g<f;++g){var h=c[g];e=yf(a,b,h,d,e);b=h}return e}
+function Af(a,b,c,d,e,f,g,h,l,m,n){if(b==c)return m;if(!e){var p=Ga(g,h,a[b],a[b+1]);if(p<m){for(n=0;n<d;++n)l[n]=a[b+n];l.length=d;return p}return m}for(var q=n?n:[NaN,NaN],r=b+d;r<c;)if(xf(a,r-d,r,d,g,h,q),p=Ga(g,h,q[0],q[1]),p<m){m=p;for(n=0;n<d;++n)l[n]=q[n];l.length=d;r+=d}else r+=d*Math.max((Math.sqrt(p)-Math.sqrt(m))/e|0,1);if(f&&(xf(a,c-d,b,d,g,h,q),p=Ga(g,h,q[0],q[1]),p<m)){m=p;for(n=0;n<d;++n)l[n]=q[n];l.length=d}return m}
+function Bf(a,b,c,d,e,f,g,h,l,m,n){n=n?n:[NaN,NaN];var p;var q=0;for(p=c.length;q<p;++q){var r=c[q];m=Af(a,b,r,d,e,f,g,h,l,m,n);b=r}return m};function Cf(a,b){var c=0,d;var e=0;for(d=b.length;e<d;++e)a[c++]=b[e];return c}function Df(a,b,c,d){var e;var f=0;for(e=c.length;f<e;++f){var g=c[f],h;for(h=0;h<d;++h)a[b++]=g[h]}return b}function Ef(a,b,c,d,e){e=e?e:[];var f=0,g;var h=0;for(g=c.length;h<g;++h)b=Df(a,b,c[h],d),e[f++]=b;e.length=f;return e};function Ff(a,b,c,d,e){e=void 0!==e?e:[];for(var f=0;b<c;b+=d)e[f++]=a.slice(b,b+d);e.length=f;return e}function Gf(a,b,c,d,e){e=void 0!==e?e:[];var f=0,g;var h=0;for(g=c.length;h<g;++h){var l=c[h];e[f++]=Ff(a,b,l,d,e[f]);b=l}e.length=f;return e};function Hf(a,b,c,d,e,f,g){var h=(c-b)/d;if(3>h){for(;b<c;b+=d)f[g++]=a[b],f[g++]=a[b+1];return g}var l=Array(h);l[0]=1;l[h-1]=1;c=[b,c-d];for(var m=0,n;0<c.length;){var p=c.pop(),q=c.pop(),r=0,u=a[q],x=a[q+1],B=a[p],E=a[p+1];for(n=q+d;n<p;n+=d){var A=Fa(a[n],a[n+1],u,x,B,E);A>r&&(m=n,r=A)}r>e&&(l[(m-b)/d]=1,q+d<m&&c.push(q,m),m+d<p&&c.push(m,p))}for(n=0;n<h;++n)l[n]&&(f[g++]=a[b+n*d],f[g++]=a[b+n*d+1]);return g}
+function If(a,b,c,d,e,f,g,h){var l;var m=0;for(l=c.length;m<l;++m){var n=c[m];a:{var p=a,q=n,r=d,u=e,x=f,B=g;if(b!=q){var E=u*Math.round(p[b]/u),A=u*Math.round(p[b+1]/u);b+=r;x[B++]=E;x[B++]=A;do{var L=u*Math.round(p[b]/u);g=u*Math.round(p[b+1]/u);b+=r;if(b==q){x[B++]=L;x[B++]=g;g=B;break a}}while(L==E&&g==A);for(;b<q;){var oa=u*Math.round(p[b]/u);var ha=u*Math.round(p[b+1]/u);b+=r;if(oa!=L||ha!=g){var ga=L-E,z=g-A,M=oa-E,ba=ha-A;ga*ba==z*M&&(0>ga&&M<ga||ga==M||0<ga&&M>ga)&&(0>z&&ba<z||z==ba||0<z&&
+ba>z)||(x[B++]=L,x[B++]=g,E=L,A=g);L=oa;g=ha}}x[B++]=L;x[B++]=g}g=B}h.push(g);b=n}return g};function Jf(a,b){rf.call(this);this.c=this.j=-1;this.ma(a,b)}v(Jf,rf);k=Jf.prototype;k.clone=function(){var a=new Jf(null);Kf(a,this.ja,this.A.slice());return a};k.Kb=function(a,b,c,d){if(d<Sa(this.G(),a,b))return d;this.c!=this.i&&(this.j=Math.sqrt(yf(this.A,0,this.A.length,this.a,0)),this.c=this.i);return Af(this.A,0,this.A.length,this.a,this.j,!0,a,b,c,d)};k.qn=function(){return vf(this.A,0,this.A.length,this.a)};k.X=function(){return Ff(this.A,0,this.A.length,this.a)};
+k.hd=function(a){var b=[];b.length=Hf(this.A,0,this.A.length,this.a,a,b,0);a=new Jf(null);Kf(a,"XY",b);return a};k.U=function(){return"LinearRing"};k.Xa=function(){};k.ma=function(a,b){a?(uf(this,b,a,1),this.A||(this.A=[]),this.A.length=Df(this.A,0,a,this.a),this.s()):Kf(this,"XY",null)};function Kf(a,b,c){tf(a,b,c);a.s()};function C(a,b){rf.call(this);this.ma(a,b)}v(C,rf);k=C.prototype;k.clone=function(){var a=new C(null);a.ba(this.ja,this.A.slice());return a};k.Kb=function(a,b,c,d){var e=this.A;a=Ga(a,b,e[0],e[1]);if(a<d){d=this.a;for(b=0;b<d;++b)c[b]=e[b];c.length=d;return a}return d};k.X=function(){return this.A?this.A.slice():[]};k.se=function(a){return Za(this.A,a)};k.U=function(){return"Point"};k.Xa=function(a){return Ua(a,this.A[0],this.A[1])};
+k.ma=function(a,b){a?(uf(this,b,a,0),this.A||(this.A=[]),this.A.length=Cf(this.A,a),this.s()):this.ba("XY",null)};k.ba=function(a,b){tf(this,a,b);this.s()};function Lf(a,b,c,d,e){return!db(e,function(e){return!Mf(a,b,c,d,e[0],e[1])})}function Mf(a,b,c,d,e,f){for(var g=0,h=a[c-d],l=a[c-d+1];b<c;b+=d){var m=a[b],n=a[b+1];l<=f?n>f&&0<(m-h)*(f-l)-(e-h)*(n-l)&&g++:n<=f&&0>(m-h)*(f-l)-(e-h)*(n-l)&&g--;h=m;l=n}return!!g}function Nf(a,b,c,d,e,f){if(!c.length||!Mf(a,b,c[0],d,e,f))return!1;var g;b=1;for(g=c.length;b<g;++b)if(Mf(a,c[b-1],c[b],d,e,f))return!1;return!0};function Of(a,b,c,d,e,f,g){var h,l=e[f+1],m=[],n=c[0];var p=a[n-d];var q=a[n-d+1];for(h=b;h<n;h+=d){var r=a[h];var u=a[h+1];if(l<=q&&u<=l||q<=l&&l<=u)p=(l-q)/(u-q)*(r-p)+p,m.push(p);p=r;q=u}n=NaN;q=-Infinity;m.sort(ia);p=m[0];h=1;for(u=m.length;h<u;++h){r=m[h];var x=Math.abs(r-p);x>q&&(p=(p+r)/2,Nf(a,b,c,d,p,l)&&(n=p,q=x));p=r}isNaN(n)&&(n=e[f]);return g?(g.push(n,l),g):[n,l]};function Pf(a,b,c,d,e,f){for(var g=[a[b],a[b+1]],h=[],l;b+d<c;b+=d){h[0]=a[b+d];h[1]=a[b+d+1];if(l=e.call(f,g,h))return l;g[0]=h[0];g[1]=h[1]}return!1};function Qf(a,b,c,d,e){var f=ab(Oa(),a,b,c,d);return qb(e,f)?Va(e,f)||f[0]>=e[0]&&f[2]<=e[2]||f[1]>=e[1]&&f[3]<=e[3]?!0:Pf(a,b,c,d,function(a,b){var c=!1,d=Wa(e,a),f=Wa(e,b);if(1===d||1===f)c=!0;else{var g=e[0],h=e[1],r=e[2],u=e[3],x=b[0];b=b[1];a=(b-a[1])/(x-a[0]);f&2&&!(d&2)&&(c=x-(b-u)/a,c=c>=g&&c<=r);c||!(f&4)||d&4||(c=b-(x-r)*a,c=c>=h&&c<=u);c||!(f&8)||d&8||(c=x-(b-h)/a,c=c>=g&&c<=r);c||!(f&16)||d&16||(c=b-(x-g)*a,c=c>=h&&c<=u)}return c}):!1}
+function Rf(a,b,c,d,e){var f=c[0];if(!(Qf(a,b,f,d,e)||Mf(a,b,f,d,e[0],e[1])||Mf(a,b,f,d,e[0],e[3])||Mf(a,b,f,d,e[2],e[1])||Mf(a,b,f,d,e[2],e[3])))return!1;if(1===c.length)return!0;b=1;for(f=c.length;b<f;++b)if(Lf(a,c[b-1],c[b],d,e))return!1;return!0};function Sf(a,b,c,d){for(var e=0,f=a[c-d],g=a[c-d+1];b<c;b+=d)var h=a[b],l=a[b+1],e=e+(h-f)*(l+g),f=h,g=l;return 0<e}function Tf(a,b,c,d){var e=0;d=void 0!==d?d:!1;var f;var g=0;for(f=b.length;g<f;++g){var h=b[g],e=Sf(a,e,h,c);if(!g){if(d&&e||!d&&!e)return!1}else if(d&&!e||!d&&e)return!1;e=h}return!0}
+function Uf(a,b,c,d,e){e=void 0!==e?e:!1;var f;var g=0;for(f=c.length;g<f;++g){var h=c[g],l=Sf(a,b,h,d);if(g?e&&!l||!e&&l:e&&l||!e&&!l)for(var l=a,m=h,n=d;b<m-n;){var p;for(p=0;p<n;++p){var q=l[b+p];l[b+p]=l[m-n+p];l[m-n+p]=q}b+=n;m-=n}b=h}return b}function Vf(a,b,c,d){var e=0,f;var g=0;for(f=b.length;g<f;++g)e=Uf(a,e,b[g],c,d);return e};function D(a,b){rf.call(this);this.c=[];this.u=-1;this.D=null;this.I=this.C=this.B=-1;this.j=null;this.ma(a,b)}v(D,rf);k=D.prototype;k.pk=function(a){this.A?la(this.A,a.ga()):this.A=a.ga().slice();this.c.push(this.A.length);this.s()};k.clone=function(){var a=new D(null);a.ba(this.ja,this.A.slice(),this.c.slice());return a};
+k.Kb=function(a,b,c,d){if(d<Sa(this.G(),a,b))return d;this.C!=this.i&&(this.B=Math.sqrt(zf(this.A,0,this.c,this.a,0)),this.C=this.i);return Bf(this.A,0,this.c,this.a,this.B,!0,a,b,c,d)};k.Mc=function(a,b){return Nf(this.ec(),0,this.c,this.a,a,b)};k.tn=function(){return wf(this.ec(),0,this.c,this.a)};k.X=function(a){if(void 0!==a){var b=this.ec().slice();Uf(b,0,this.c,this.a,a)}else b=this.A;return Gf(b,0,this.c,this.a)};k.Bb=function(){return this.c};
+function Wf(a){if(a.u!=a.i){var b=nb(a.G());a.D=Of(a.ec(),0,a.c,a.a,b,0);a.u=a.i}return a.D}k.Tk=function(){return new C(Wf(this))};k.Zk=function(){return this.c.length};k.Ch=function(a){if(0>a||this.c.length<=a)return null;var b=new Jf(null);Kf(b,this.ja,this.A.slice(a?this.c[a-1]:0,this.c[a]));return b};k.Sd=function(){var a=this.ja,b=this.A,c=this.c,d=[],e=0,f;var g=0;for(f=c.length;g<f;++g){var h=c[g],l=new Jf(null);Kf(l,a,b.slice(e,h));d.push(l);e=h}return d};
+k.ec=function(){if(this.I!=this.i){var a=this.A;Tf(a,this.c,this.a)?this.j=a:(this.j=a.slice(),this.j.length=Uf(this.j,0,this.c,this.a));this.I=this.i}return this.j};k.hd=function(a){var b=[],c=[];b.length=If(this.A,0,this.c,this.a,Math.sqrt(a),b,0,c);a=new D(null);a.ba("XY",b,c);return a};k.U=function(){return"Polygon"};k.Xa=function(a){return Rf(this.ec(),0,this.c,this.a,a)};
+k.ma=function(a,b){a?(uf(this,b,a,2),this.A||(this.A=[]),a=Ef(this.A,0,a,this.a,this.c),this.A.length=a.length?a[a.length-1]:0,this.s()):this.ba("XY",null,this.c)};k.ba=function(a,b,c){tf(this,a,b);this.c=c;this.s()};function Xf(a,b,c,d){var e=d?d:32;d=[];var f;for(f=0;f<e;++f)la(d,a.offset(b,c,2*Math.PI*f/e));d.push(d[0],d[1]);a=new D(null);a.ba("XY",d,[d.length]);return a}function Yf(a){var b=a[0],c=a[1],d=a[2];a=a[3];b=[b,c,b,a,d,a,d,c,b,c];c=new D(null);c.ba("XY",b,[b.length]);return c}
+function Zf(a,b,c){var d=b?b:32,e=a.qa();b=a.ja;for(var f=new D(null,b),d=e*(d+1),e=Array(d),g=0;g<d;g++)e[g]=0;f.ba(b,e,[e.length]);$f(f,a.wa(),a.pd(),c);return f}function $f(a,b,c,d){var e=a.ga(),f=a.ja,g=a.qa(),h=a.Bb(),l=e.length/g-1;d=d?d:0;for(var m,n,p=0;p<=l;++p)n=p*g,m=d+2*Ia(p,l)*Math.PI/l,e[n]=b[0]+c*Math.cos(m),e[n+1]=b[1]+c*Math.sin(m);a.ba(f,e,h)};function F(a){Tc.call(this);a=tb({},a);this.o=[0,0];this.c=[];this.wf=this.wf.bind(this);this.v=$b(a.projection);ag(this,a)}v(F,Tc);
+function ag(a,b){var c={};c.center=void 0!==b.center?b.center:null;var d=void 0!==b.minZoom?b.minZoom:0;var e=void 0!==b.maxZoom?b.maxZoom:28;var f=void 0!==b.zoomFactor?b.zoomFactor:2;if(void 0!==b.resolutions){e=b.resolutions;var g=e[0];var h=e[e.length-1];e=Re(e)}else{g=$b(b.projection);h=g.G();var l=(h?Math.max(lb(h),mb(h)):360*zb.degrees/g.sc())/256/Math.pow(2,0),m=l/Math.pow(2,28);g=b.maxResolution;void 0!==g?d=0:g=l/Math.pow(f,d);h=b.minResolution;void 0===h&&(h=void 0!==b.maxZoom?void 0!==
+b.maxResolution?g/Math.pow(f,e):l/Math.pow(f,e):m);e=d+Math.floor(Math.log(g/h)/Math.log(f));h=g/Math.pow(f,e-d);e=Se(f,g,e-d)}a.a=g;a.f=h;a.C=f;a.j=b.resolutions;a.l=d;(void 0!==b.enableRotation?b.enableRotation:1)?(d=b.constrainRotation,d=void 0===d||!0===d?We():!1===d?Ue:"number"===typeof d?Ve(d):Ue):d=Te;a.g={center:void 0!==b.extent?Bc(b.extent):Cc,resolution:e,rotation:d};void 0!==b.resolution?c.resolution=b.resolution:void 0!==b.zoom&&(c.resolution=a.constrainResolution(a.a,b.zoom-a.l));c.rotation=
+void 0!==b.rotation?b.rotation:0;a.H(c);a.D=b}function bg(a,b){var c=tb({},a.D);void 0!==c.resolution?c.resolution=a.Pa():c.zoom=a.Hh();c.center=a.wa();c.rotation=a.Qa();return tb({},c,b)}k=F.prototype;
+k.animate=function(a){var b=Date.now(),c=this.wa().slice(),d=this.Pa(),e=this.Qa(),f=arguments.length;if(1<f&&"function"===typeof arguments[f-1]){var g=arguments[f-1];--f}for(var h=[],l=0;l<f;++l){var m=arguments[l],n={start:b,complete:!1,anchor:m.anchor,duration:void 0!==m.duration?m.duration:1E3,easing:m.easing||sd};m.center&&(n.Rg=c,n.Tg=m.center,c=n.Tg);void 0!==m.zoom?(n.tf=d,n.zd=this.constrainResolution(this.a,m.zoom-this.l,0),d=n.zd):m.resolution&&(n.tf=d,n.zd=m.resolution,d=n.zd);void 0!==
+m.rotation&&(n.Sg=e,n.uf=m.rotation,e=n.uf);n.callback=g;b+=n.duration;h.push(n)}this.c.push(h);cg(this,0,1);this.wf()};k.Ic=function(){return 0<dg(this)[0]};k.Rk=function(){return 0<dg(this)[1]};k.ed=function(){cg(this,0,-dg(this)[0]);for(var a=0,b=this.c.length;a<b;++a){var c=this.c[a];c[0].callback&&c[0].callback(!1)}this.c.length=0};
+k.wf=function(){void 0!==this.u&&(cancelAnimationFrame(this.u),this.u=void 0);if(this.Ic()){for(var a=Date.now(),b=!1,c=this.c.length-1;0<=c;--c){for(var d=this.c[c],e=!0,f=0,g=d.length;f<g;++f){var h=d[f];if(!h.complete){b=a-h.start;b=0<h.duration?b/h.duration:1;1<=b?(h.complete=!0,b=1):e=!1;b=h.easing(b);if(h.Rg){var l=h.Rg[0],m=h.Rg[1];this.set("center",[l+b*(h.Tg[0]-l),m+b*(h.Tg[1]-m)])}h.tf&&h.zd&&(l=1===b?h.zd:h.tf+b*(h.zd-h.tf),h.anchor&&this.set("center",eg(this,l,h.anchor)),this.set("resolution",
+l));void 0!==h.Sg&&void 0!==h.uf&&(b=1===b?h.uf:h.Sg+b*(h.uf-h.Sg),h.anchor&&this.set("center",fg(this,b,h.anchor)),this.set("rotation",b));b=!0;if(!h.complete)break}}e&&(this.c[c]=null,cg(this,0,-1),(d=d[0].callback)&&d(!0))}this.c=this.c.filter(Boolean);b&&void 0===this.u&&(this.u=requestAnimationFrame(this.wf))}};function fg(a,b,c){var d=a.wa();if(void 0!==d){var e=[d[0]-c[0],d[1]-c[1]];ef(e,b-a.Qa());Ze(e,c)}return e}
+function eg(a,b,c){var d,e=a.wa();a=a.Pa();void 0!==e&&void 0!==a&&(d=[c[0]-b*(c[0]-e[0])/a,c[1]-b*(c[1]-e[1])/a]);return d}function gg(a){var b=[100,100];a='.ol-viewport[data-view="'+w(a)+'"]';if(a=document.querySelector(a))a=getComputedStyle(a),b[0]=parseInt(a.width,10),b[1]=parseInt(a.height,10);return b}k.Ec=function(a){return this.g.center(a)};k.constrainResolution=function(a,b,c){return this.g.resolution(a,b||0,c||0)};k.constrainRotation=function(a,b){return this.g.rotation(a,b||0)};k.wa=function(){return this.get("center")};
+function dg(a,b){return void 0!==b?(b[0]=a.o[0],b[1]=a.o[1],b):a.o.slice()}k.dd=function(a){a=a||gg(this);var b=this.wa();xa(b,1);var c=this.Pa();xa(void 0!==c,2);var d=this.Qa();xa(void 0!==d,3);return ob(b,c,d,a)};k.Nm=function(){return this.a};k.Pm=function(){return this.f};k.Om=function(){return this.Ce(this.f)};k.eq=function(a){ag(this,bg(this,{maxZoom:a}))};k.Qm=function(){return this.Ce(this.a)};k.fq=function(a){ag(this,bg(this,{minZoom:a}))};k.Rm=function(){return this.v};k.Pa=function(){return this.get("resolution")};
+k.Sm=function(){return this.j};k.ze=function(a,b){b=b||gg(this);return Math.max(lb(a)/b[0],mb(a)/b[1])};function hg(a){var b=a.a,c=Math.log(b/a.f)/Math.log(2);return function(a){return b/Math.pow(2,a*c)}}k.Qa=function(){return this.get("rotation")};function ig(a){var b=a.a,c=Math.log(b/a.f)/Math.log(2);return function(a){return Math.log(b/a)/Math.log(2)/c}}k.getState=function(){var a=this.wa(),b=this.v,c=this.Pa(),d=this.Qa();return{center:a.slice(),projection:void 0!==b?b:null,resolution:c,rotation:d}};
+k.Hh=function(){var a,b=this.Pa();void 0!==b&&(a=this.Ce(b));return a};k.Ce=function(a){if(a>=this.f&&a<=this.a){var b=this.l||0;if(this.j){var c=ka(this.j,a,1);b+=c;if(c==this.j.length-1)return b;var d=this.j[c];c=d/this.j[c+1]}else d=this.a,c=this.C;b+=Math.log(d/a)/Math.log(c)}return b};
+k.Qf=function(a,b){b=b||{};var c=b.size;c||(c=gg(this));if(a instanceof rf)if("Circle"===a.U()){a=a.G();var d=Yf(a);d.rotate(this.Qa(),nb(a))}else d=a;else xa(Array.isArray(a),24),xa(!kb(a),25),d=Yf(a);var e=b.padding?b.padding:[0,0,0,0],f=void 0!==b.constrainResolution?b.constrainResolution:!0,g=void 0!==b.nearest?b.nearest:!1,h;void 0!==b.minResolution?h=b.minResolution:void 0!==b.maxZoom?h=this.constrainResolution(this.a,b.maxZoom-this.l,0):h=0;var l=d.ga(),m=this.Qa();a=Math.cos(-m);var m=Math.sin(-m),
+n=Infinity,p=Infinity,q=-Infinity,r=-Infinity;d=d.qa();for(var u=0,x=l.length;u<x;u+=d)var B=l[u]*a-l[u+1]*m,E=l[u]*m+l[u+1]*a,n=Math.min(n,B),p=Math.min(p,E),q=Math.max(q,B),r=Math.max(r,E);c=this.ze([n,p,q,r],[c[0]-e[1]-e[3],c[1]-e[0]-e[2]]);c=isNaN(c)?h:Math.max(c,h);f&&(h=this.constrainResolution(c,0,0),!g&&h<c&&(h=this.constrainResolution(h,-1,0)),c=h);m=-m;h=(n+q)/2+(e[1]-e[3])/2*c;e=(p+r)/2+(e[0]-e[2])/2*c;a=[h*a-e*m,e*a+h*m];e=b.callback?b.callback:ua;void 0!==b.duration?this.animate({resolution:c,
+center:a,duration:b.duration,easing:b.easing},e):(this.Vc(c),this.ob(a),setTimeout(e.bind(void 0,!0),0))};k.uk=function(a,b,c){var d=this.Qa(),e=Math.cos(-d),d=Math.sin(-d),f=a[0]*e-a[1]*d;a=a[1]*e+a[0]*d;var g=this.Pa(),f=f+(b[0]/2-c[0])*g;a+=(c[1]-b[1]/2)*g;d=-d;this.ob([f*e-a*d,a*e+f*d])};function jg(a){return!!a.wa()&&void 0!==a.Pa()}k.rotate=function(a,b){void 0!==b&&(b=fg(this,a,b),this.ob(b));this.Oe(a)};k.ob=function(a){this.set("center",a);this.Ic()&&this.ed()};
+function cg(a,b,c){a.o[b]+=c;a.s()}k.Vc=function(a){this.set("resolution",a);this.Ic()&&this.ed()};k.Oe=function(a){this.set("rotation",a);this.Ic()&&this.ed()};k.lq=function(a){a=this.constrainResolution(this.a,a-this.l,0);this.Vc(a)};function kg(a,b,c){this.f=a;this.c=b;this.g=c;this.b=[];this.a=this.i=0}function lg(a){a.b.length=0;a.i=0;a.a=0}function mg(a){if(6>a.b.length)return!1;var b=Date.now()-a.g,c=a.b.length-3;if(a.b[c+2]<b)return!1;for(var d=c-3;0<d&&a.b[d+2]>b;)d-=3;b=a.b[c+2]-a.b[d+2];if(b<1E3/60)return!1;var e=a.b[c]-a.b[d],c=a.b[c+1]-a.b[d+1];a.i=Math.atan2(c,e);a.a=Math.sqrt(e*e+c*c)/b;return a.a>a.c};function ng(a){Tc.call(this);this.v=null;this.Ha(!0);this.handleEvent=a.handleEvent}v(ng,Tc);ng.prototype.c=function(){return this.get("active")};ng.prototype.f=function(){return this.v};ng.prototype.Ha=function(a){this.set("active",a)};ng.prototype.setMap=function(a){this.v=a};function og(a,b,c,d){if(void 0!==b){var e=a.Qa(),f=a.wa();void 0!==e&&f&&0<d?a.animate({rotation:b,anchor:c,duration:d,easing:rd}):a.rotate(b,c)}}
+function pg(a,b,c,d){var e=a.Pa();b=a.constrainResolution(e,b,0);if(c&&void 0!==b&&b!==e){var f=a.wa();c=eg(a,b,c);c=a.Ec(c);c=[(b*f[0]-e*c[0])/(b-e),(b*f[1]-e*c[1])/(b-e)]}qg(a,b,c,d)}function qg(a,b,c,d){if(b){var e=a.Pa(),f=a.wa();void 0!==e&&f&&b!==e&&d?a.animate({resolution:b,anchor:c,duration:d,easing:rd}):(c&&(c=eg(a,b,c),a.ob(c)),a.Vc(b))}};function rg(a){a=a?a:{};this.a=a.delta?a.delta:1;ng.call(this,{handleEvent:sg});this.g=void 0!==a.duration?a.duration:250}v(rg,ng);function sg(a){var b=!1,c=a.originalEvent;if("dblclick"==a.type){var b=a.coordinate,c=c.shiftKey?-this.a:this.a,d=a.map.Z();pg(d,c,b,this.g);a.preventDefault();b=!0}return!b};function tg(a){a=a.originalEvent;return a.altKey&&!(a.metaKey||a.ctrlKey)&&a.shiftKey}function ug(a){a=a.originalEvent;return!a.button&&!(Qd&&Rd&&a.ctrlKey)}function vg(a){return"pointermove"==a.type}function wg(a){return"singleclick"==a.type}function xg(a){a=a.originalEvent;return!a.altKey&&!(a.metaKey||a.ctrlKey)&&!a.shiftKey}function yg(a){a=a.originalEvent;return!a.altKey&&!(a.metaKey||a.ctrlKey)&&a.shiftKey}
+function Ag(a){a=a.originalEvent.target.tagName;return"INPUT"!==a&&"SELECT"!==a&&"TEXTAREA"!==a}function Bg(a){xa(a.b,56);return"mouse"==a.b.pointerType}function Cg(a){a=a.b;return a.isPrimary&&0===a.button};function Dg(a){a=a?a:{};ng.call(this,{handleEvent:a.handleEvent?a.handleEvent:Eg});this.yf=a.handleDownEvent?a.handleDownEvent:nf;this.If=a.handleDragEvent?a.handleDragEvent:ua;this.Jf=a.handleMoveEvent?a.handleMoveEvent:ua;this.sk=a.handleUpEvent?a.handleUpEvent:nf;this.D=!1;this.na={};this.o=[]}v(Dg,ng);function Fg(a){for(var b=a.length,c=0,d=0,e=0;e<b;e++)c+=a[e].clientX,d+=a[e].clientY;return[c/b,d/b]}
+function Eg(a){if(!(a instanceof ee))return!0;var b=!1,c=a.type;if("pointerdown"===c||"pointerdrag"===c||"pointerup"===c)c=a.b,"pointerup"==a.type?delete this.na[c.pointerId]:"pointerdown"==a.type?this.na[c.pointerId]=c:c.pointerId in this.na&&(this.na[c.pointerId]=c),this.o=vb(this.na);this.D?"pointerdrag"==a.type?this.If(a):"pointerup"==a.type&&(this.D=this.sk(a)&&0<this.o.length):"pointerdown"==a.type?(this.D=a=this.yf(a),b=this.Xc(a)):"pointermove"==a.type&&this.Jf(a);return!b}
+Dg.prototype.Xc=function(a){return a};function Gg(a){Dg.call(this,{handleDownEvent:Hg,handleDragEvent:Ig,handleUpEvent:Jg});a=a?a:{};this.a=a.kinetic;this.g=null;this.u=a.condition?a.condition:xg;this.j=!1}v(Gg,Dg);function Ig(a){var b=this.o,c=Fg(b);if(b.length==this.l){if(this.a&&this.a.b.push(c[0],c[1],Date.now()),this.g){var d=this.g[0]-c[0],e=c[1]-this.g[1];a=a.map.Z();var f=a.getState(),d=[d,e];gf(d,f.resolution);ef(d,f.rotation);Ze(d,f.center);d=a.Ec(d);a.ob(d)}}else this.a&&lg(this.a);this.g=c;this.l=b.length}
+function Jg(a){var b=a.map;a=b.Z();if(this.o.length)return this.a&&lg(this.a),this.g=null,!0;if(!this.j&&this.a&&mg(this.a)){var c=this.a;c=(c.c-c.a)/c.f;var d=this.a.i,e=a.wa(),e=b.Ja(e),b=b.Wa([e[0]-c*Math.cos(d),e[1]-c*Math.sin(d)]);a.animate({center:a.Ec(b),duration:500,easing:rd})}cg(a,1,-1);return!1}
+function Hg(a){if(0<this.o.length&&this.u(a)){var b=a.map.Z();this.g=null;this.D||cg(b,1,1);dg(b)[0]&&b.ob(a.frameState.viewState.center);this.a&&lg(this.a);this.j=1<this.o.length;return!0}return!1}Gg.prototype.Xc=nf;function Kg(a){a=a?a:{};Dg.call(this,{handleDownEvent:Lg,handleDragEvent:Mg,handleUpEvent:Ng});this.g=a.condition?a.condition:tg;this.a=void 0;this.j=void 0!==a.duration?a.duration:250}v(Kg,Dg);function Mg(a){if(Bg(a)){var b=a.map,c=b.Z();if(c.g.rotation!==Te){b=b.Ob();a=a.pixel;a=Math.atan2(b[1]/2-a[1],a[0]-b[0]/2);if(void 0!==this.a){var b=a-this.a,d=c.Qa();og(c,d-b)}this.a=a}}}
+function Ng(a){if(!Bg(a))return!0;a=a.map.Z();cg(a,1,-1);var b=a.Qa(),c=this.j,b=a.constrainRotation(b,0);og(a,b,void 0,c);return!1}function Lg(a){return Bg(a)&&ug(a)&&this.g(a)?(cg(a.map.Z(),1,1),this.a=void 0,!0):!1}Kg.prototype.Xc=nf;function Og(a){this.Gc=null;this.a=document.createElement("div");this.a.style.position="absolute";this.a.className="ol-box "+a;this.i=this.c=this.b=null}v(Og,Mc);Og.prototype.ka=function(){this.setMap(null)};function Pg(a){var b=a.c,c=a.i;a=a.a.style;a.left=Math.min(b[0],c[0])+"px";a.top=Math.min(b[1],c[1])+"px";a.width=Math.abs(c[0]-b[0])+"px";a.height=Math.abs(c[1]-b[1])+"px"}
+Og.prototype.setMap=function(a){if(this.b){this.b.C.removeChild(this.a);var b=this.a.style;b.left=b.top=b.width=b.height="inherit"}(this.b=a)&&this.b.C.appendChild(this.a)};function Qg(a){var b=a.c,c=a.i,b=[b,[b[0],c[1]],c,[c[0],b[1]]].map(a.b.Wa,a.b);b[4]=b[0].slice();a.Gc?a.Gc.ma([b]):a.Gc=new D([b])}Og.prototype.V=function(){return this.Gc};function Rg(a){Dg.call(this,{handleDownEvent:Sg,handleDragEvent:Tg,handleUpEvent:Ug});a=a?a:{};this.a=new Og(a.className||"ol-dragbox");this.u=void 0!==a.minArea?a.minArea:64;this.g=null;this.C=a.condition?a.condition:mf;this.l=a.boxEndCondition?a.boxEndCondition:Vg}v(Rg,Dg);function Vg(a,b,c){a=c[0]-b[0];b=c[1]-b[1];return a*a+b*b>=this.u}function Tg(a){if(Bg(a)){var b=this.a,c=a.pixel;b.c=this.g;b.i=c;Qg(b);Pg(b);this.b(new Wg(Xg,a.coordinate,a))}}Rg.prototype.V=function(){return this.a.V()};
+Rg.prototype.j=ua;function Ug(a){if(!Bg(a))return!0;this.a.setMap(null);this.l(a,this.g,a.pixel)&&(this.j(a),this.b(new Wg(Yg,a.coordinate,a)));return!1}function Sg(a){if(Bg(a)&&ug(a)&&this.C(a)){this.g=a.pixel;this.a.setMap(a.map);var b=this.a,c=this.g;b.c=this.g;b.i=c;Qg(b);Pg(b);this.b(new Wg(Zg,a.coordinate,a));return!0}return!1}var Zg="boxstart",Xg="boxdrag",Yg="boxend";function Wg(a,b,c){Oc.call(this,a);this.coordinate=b;this.mapBrowserEvent=c}v(Wg,Oc);function $g(a){a=a?a:{};var b=a.condition?a.condition:yg;this.B=void 0!==a.duration?a.duration:200;this.I=void 0!==a.out?a.out:!1;Rg.call(this,{condition:b,className:a.className||"ol-dragzoom"})}v($g,Rg);
+$g.prototype.j=function(){var a=this.v,b=a.Z(),c=a.Ob(),d=this.V().G();if(this.I){var e=b.dd(c),d=[a.Ja(eb(d)),a.Ja(hb(d))],a=Ya(void 0),f;var g=0;for(f=d.length;g<f;++g)Pa(a,d[g]);d=b.ze(a,c);rb(e,1/d);d=e}c=b.constrainResolution(b.ze(d,c));e=nb(d);e=b.Ec(e);b.animate({resolution:c,center:e,duration:this.B,easing:rd})};function ah(a){ng.call(this,{handleEvent:bh});a=a||{};this.a=function(a){return xg(a)&&Ag(a)};this.g=a.condition?a.condition:this.a;this.j=void 0!==a.duration?a.duration:100;this.o=void 0!==a.pixelDelta?a.pixelDelta:128}v(ah,ng);
+function bh(a){var b=!1;if("keydown"==a.type){var c=a.originalEvent.keyCode;if(this.g(a)&&(40==c||37==c||39==c||38==c)){var b=a.map.Z(),d=b.Pa()*this.o,e=0,f=0;40==c?f=-d:37==c?e=-d:39==c?e=d:f=d;d=[e,f];ef(d,b.Qa());c=this.j;if(e=b.wa())d=b.Ec([e[0]+d[0],e[1]+d[1]]),c?b.animate({duration:c,easing:td,center:d}):b.ob(d);a.preventDefault();b=!0}}return!b};function ch(a){ng.call(this,{handleEvent:dh});a=a?a:{};this.g=a.condition?a.condition:Ag;this.a=a.delta?a.delta:1;this.j=void 0!==a.duration?a.duration:100}v(ch,ng);function dh(a){var b=!1;if("keydown"==a.type||"keypress"==a.type){var c=a.originalEvent.charCode;!this.g(a)||43!=c&&45!=c||(b=43==c?this.a:-this.a,c=a.map.Z(),pg(c,b,void 0,this.j),a.preventDefault(),b=!0)}return!b};function eh(a){ng.call(this,{handleEvent:fh});a=a||{};this.j=0;this.D=void 0!==a.duration?a.duration:250;this.na=void 0!==a.timeout?a.timeout:80;this.C=void 0!==a.useAnchor?a.useAnchor:!0;this.R=a.constrainResolution||!1;this.a=null;this.l=this.o=this.u=this.g=void 0}v(eh,ng);
+function fh(a){var b=a.type;if("wheel"!==b&&"mousewheel"!==b)return!0;a.preventDefault();var b=a.map,c=a.originalEvent;this.C&&(this.a=a.coordinate);if("wheel"==a.type){var d=c.deltaY;Od&&c.deltaMode===WheelEvent.DOM_DELTA_PIXEL&&(d/=Sd);c.deltaMode===WheelEvent.DOM_DELTA_LINE&&(d*=40)}else"mousewheel"==a.type&&(d=-c.wheelDeltaY,Pd&&(d/=3));if(0===d)return!1;a=Date.now();void 0===this.g&&(this.g=a);if(!this.o||400<a-this.g)this.o=4>Math.abs(d)?gh:hh;if(this.o===gh){b=b.Z();this.l?clearTimeout(this.l):
+cg(b,1,1);this.l=setTimeout(this.B.bind(this),400);var c=b.Pa()*Math.pow(2,d/300),e=b.f,f=b.a,g=0;c<e?(c=Math.max(c,e/1.5),g=1):c>f&&(c=Math.min(c,1.5*f),g=-1);if(this.a){var h=eg(b,c,this.a);b.ob(b.Ec(h))}b.Vc(c);!g&&this.R&&b.animate({resolution:b.constrainResolution(c,0<d?-1:1),easing:rd,anchor:this.a,duration:this.D});0<g?b.animate({resolution:e,easing:rd,anchor:this.a,duration:500}):0>g&&b.animate({resolution:f,easing:rd,anchor:this.a,duration:500});this.g=a;return!1}this.j+=d;d=Math.max(this.na-
+(a-this.g),0);clearTimeout(this.u);this.u=setTimeout(this.I.bind(this,b),d);return!1}eh.prototype.B=function(){this.l=void 0;cg(this.v.Z(),1,-1)};eh.prototype.I=function(a){a=a.Z();a.Ic()&&a.ed();pg(a,-Ca(this.j,-1,1),this.a,this.D);this.o=void 0;this.j=0;this.a=null;this.u=this.g=void 0};eh.prototype.T=function(a){this.C=a;a||(this.a=null)};var gh="trackpad",hh="wheel";function ih(a){Dg.call(this,{handleDownEvent:jh,handleDragEvent:kh,handleUpEvent:lh});a=a||{};this.g=null;this.j=void 0;this.a=!1;this.l=0;this.C=void 0!==a.threshold?a.threshold:.3;this.u=void 0!==a.duration?a.duration:250}v(ih,Dg);
+function kh(a){var b=0,c=this.o[0],d=this.o[1],c=Math.atan2(d.clientY-c.clientY,d.clientX-c.clientX);void 0!==this.j&&(b=c-this.j,this.l+=b,!this.a&&Math.abs(this.l)>this.C&&(this.a=!0));this.j=c;a=a.map;c=a.Z();if(c.g.rotation!==Te){var d=a.a.getBoundingClientRect(),e=Fg(this.o);e[0]-=d.left;e[1]-=d.top;this.g=a.Wa(e);this.a&&(d=c.Qa(),a.render(),og(c,d+b,this.g))}}
+function lh(a){if(2>this.o.length){a=a.map.Z();cg(a,1,-1);if(this.a){var b=a.Qa(),c=this.g,d=this.u,b=a.constrainRotation(b,0);og(a,b,c,d)}return!1}return!0}function jh(a){return 2<=this.o.length?(a=a.map,this.g=null,this.j=void 0,this.a=!1,this.l=0,this.D||cg(a.Z(),1,1),!0):!1}ih.prototype.Xc=nf;function mh(a){Dg.call(this,{handleDownEvent:nh,handleDragEvent:oh,handleUpEvent:ph});a=a?a:{};this.l=a.constrainResolution||!1;this.g=null;this.u=void 0!==a.duration?a.duration:400;this.a=void 0;this.j=1}v(mh,Dg);
+function oh(a){var b=1,c=this.o[0],d=this.o[1],e=c.clientX-d.clientX,c=c.clientY-d.clientY,e=Math.sqrt(e*e+c*c);void 0!==this.a&&(b=this.a/e);this.a=e;a=a.map;var e=a.Z(),d=e.Pa(),f=e.a,g=e.f,c=d*b;c>f?(b=f/d,c=f):c<g&&(b=g/d,c=g);1!=b&&(this.j=b);b=a.a.getBoundingClientRect();d=Fg(this.o);d[0]-=b.left;d[1]-=b.top;this.g=a.Wa(d);a.render();qg(e,c,this.g)}
+function ph(a){if(2>this.o.length){a=a.map.Z();cg(a,1,-1);var b=a.Pa();if(this.l||b<a.f||b>a.a){var c=this.g,d=this.u,b=a.constrainResolution(b,0,this.j-1);qg(a,b,c,d)}return!1}return!0}function nh(a){return 2<=this.o.length?(a=a.map,this.g=null,this.a=void 0,this.j=1,this.D||cg(a.Z(),1,1),!0):!1}mh.prototype.Xc=nf;function qh(a){a=a?a:{};var b=new Yc,c=new kg(-.005,.05,100);(void 0!==a.altShiftDragRotate?a.altShiftDragRotate:1)&&b.push(new Kg);(void 0!==a.doubleClickZoom?a.doubleClickZoom:1)&&b.push(new rg({delta:a.zoomDelta,duration:a.zoomDuration}));(void 0!==a.dragPan?a.dragPan:1)&&b.push(new Gg({kinetic:c}));(void 0!==a.pinchRotate?a.pinchRotate:1)&&b.push(new ih);(void 0!==a.pinchZoom?a.pinchZoom:1)&&b.push(new mh({constrainResolution:a.constrainResolution,duration:a.zoomDuration}));if(void 0!==a.keyboard?
+a.keyboard:1)b.push(new ah),b.push(new ch({delta:a.zoomDelta,duration:a.zoomDuration}));(void 0!==a.mouseWheelZoom?a.mouseWheelZoom:1)&&b.push(new eh({constrainResolution:a.constrainResolution,duration:a.zoomDuration}));(void 0!==a.shiftDragZoom?a.shiftDragZoom:1)&&b.push(new $g({duration:a.zoomDuration}));return b};function sh(a){Tc.call(this);var b=tb({},a);b.opacity=void 0!==a.opacity?a.opacity:1;b.visible=void 0!==a.visible?a.visible:!0;b.zIndex=void 0!==a.zIndex?a.zIndex:0;b.maxResolution=void 0!==a.maxResolution?a.maxResolution:Infinity;b.minResolution=void 0!==a.minResolution?a.minResolution:0;this.H(b);this.a={layer:this,Je:!0}}v(sh,Tc);
+function th(a){a.a.opacity=Ca(a.hc(),0,1);a.a.yj=a.$f();a.a.visible=a.Mb();a.a.extent=a.G();a.a.zIndex=a.Ba();a.a.maxResolution=a.fc();a.a.minResolution=Math.max(a.gc(),0);return a.a}k=sh.prototype;k.G=function(){return this.get("extent")};k.fc=function(){return this.get("maxResolution")};k.gc=function(){return this.get("minResolution")};k.hc=function(){return this.get("opacity")};k.Mb=function(){return this.get("visible")};k.Ba=function(){return this.get("zIndex")};
+k.vc=function(a){this.set("extent",a)};k.Ac=function(a){this.set("maxResolution",a)};k.Bc=function(a){this.set("minResolution",a)};k.wc=function(a){this.set("opacity",a)};k.xc=function(a){this.set("visible",a)};k.Vb=function(a){this.set("zIndex",a)};function uh(a){var b=a||{};a=tb({},b);delete a.layers;b=b.layers;sh.call(this,a);this.f=[];this.c={};y(this,Vc(vh),this.Hl,this);b?Array.isArray(b)?b=new Yc(b.slice(),{unique:!0}):xa(b instanceof Yc,43):b=new Yc(void 0,{unique:!0});this.xi(b)}v(uh,sh);k=uh.prototype;k.Fd=function(){};k.Fe=function(){this.Mb()&&this.s()};
+k.Hl=function(){this.f.forEach(Ec);this.f.length=0;var a=this.qd();this.f.push(y(a,"add",this.Gl,this),y(a,"remove",this.Il,this));for(var b in this.c)this.c[b].forEach(Ec);ub(this.c);var a=a.a,c;b=0;for(c=a.length;b<c;b++){var d=a[b];this.c[w(d).toString()]=[y(d,"propertychange",this.Fe,this),y(d,"change",this.Fe,this)]}this.s()};k.Gl=function(a){a=a.element;var b=w(a).toString();this.c[b]=[y(a,"propertychange",this.Fe,this),y(a,"change",this.Fe,this)];this.s()};
+k.Il=function(a){a=w(a.element).toString();this.c[a].forEach(Ec);delete this.c[a];this.s()};k.qd=function(){return this.get(vh)};k.xi=function(a){this.set(vh,a)};
+k.Yf=function(a){var b=void 0!==a?a:[],c=b.length;this.qd().forEach(function(a){a.Yf(b)});a=th(this);var d;for(d=b.length;c<d;c++){var e=b[c];e.opacity*=a.opacity;e.visible=e.visible&&a.visible;e.maxResolution=Math.min(e.maxResolution,a.maxResolution);e.minResolution=Math.max(e.minResolution,a.minResolution);void 0!==a.extent&&(e.extent=void 0!==e.extent?pb(e.extent,a.extent):a.extent)}return b};k.$f=function(){return"ready"};var vh="layers";function wh(a){var b=tb({},a);delete b.source;sh.call(this,b);this.v=this.l=this.o=null;a.map&&this.setMap(a.map);y(this,Vc("source"),this.Ul,this);this.Wc(a.source?a.source:null)}v(wh,sh);function xh(a,b){return a.visible&&b>=a.minResolution&&b<a.maxResolution}k=wh.prototype;k.Yf=function(a){a=a?a:[];a.push(th(this));return a};k.ha=function(){return this.get("source")||null};k.$f=function(){var a=this.ha();return a?a.getState():"undefined"};k.Tn=function(){this.s()};
+k.Ul=function(){this.v&&(Ec(this.v),this.v=null);var a=this.ha();a&&(this.v=y(a,"change",this.Tn,this));this.s()};k.setMap=function(a){this.o&&(Ec(this.o),this.o=null);a||this.s();this.l&&(Ec(this.l),this.l=null);a&&(this.o=y(a,"precompose",function(a){var b=th(this);b.Je=!1;b.zIndex=Infinity;a.frameState.layerStatesArray.push(b);a.frameState.layerStates[w(this)]=b},this),this.l=y(this,"change",a.render,a),this.s())};k.Wc=function(a){this.set("source",a)};function yh(){this.b={};this.a=0}yh.prototype.clear=function(){this.b={};this.a=0};yh.prototype.get=function(a,b,c){a=b+":"+a+":"+(c?gd(c):"null");return a in this.b?this.b[a]:null};yh.prototype.set=function(a,b,c,d){this.b[b+":"+a+":"+(c?gd(c):"null")]=d;++this.a};var zh=new yh;var Ah=Array(6);function Bh(){return[1,0,0,1,0,0]}function Ch(a){return Dh(a,1,0,0,1,0,0)}function Eh(a,b){var c=a[0],d=a[1],e=a[2],f=a[3],g=a[4],h=a[5],l=b[0],m=b[1],n=b[2],p=b[3],q=b[4];b=b[5];a[0]=c*l+e*m;a[1]=d*l+f*m;a[2]=c*n+e*p;a[3]=d*n+f*p;a[4]=c*q+e*b+g;a[5]=d*q+f*b+h;return a}function Dh(a,b,c,d,e,f,g){a[0]=b;a[1]=c;a[2]=d;a[3]=e;a[4]=f;a[5]=g;return a}function Fh(a,b){a[0]=b[0];a[1]=b[1];a[2]=b[2];a[3]=b[3];a[4]=b[4];a[5]=b[5];return a}
+function Gh(a,b){var c=b[0],d=b[1];b[0]=a[0]*c+a[2]*d+a[4];b[1]=a[1]*c+a[3]*d+a[5];return b}function Hh(a,b){var c=Math.cos(b);b=Math.sin(b);Eh(a,Dh(Ah,c,b,-b,c,0,0))}function Ih(a,b,c){return Eh(a,Dh(Ah,b,0,0,c,0,0))}function Jh(a,b,c){Eh(a,Dh(Ah,1,0,0,1,b,c))}function Kh(a,b,c,d,e,f,g,h){var l=Math.sin(f);f=Math.cos(f);a[0]=d*f;a[1]=e*l;a[2]=-d*l;a[3]=e*f;a[4]=g*d*f-h*d*l+b;a[5]=g*e*l+h*e*f+c;return a}
+function Lh(a){var b=a[0]*a[3]-a[1]*a[2];xa(!!b,32);var c=a[0],d=a[1],e=a[2],f=a[3],g=a[4],h=a[5];a[0]=f/b;a[1]=-d/b;a[2]=-e/b;a[3]=c/b;a[4]=(e*h-f*g)/b;a[5]=-(c*h-d*g)/b;return a};function Mh(a,b){this.o=b;this.c={};this.v={}}v(Mh,Mc);function Nh(a){var b=a.viewState,c=a.coordinateToPixelTransform,d=a.pixelToCoordinateTransform;Kh(c,a.size[0]/2,a.size[1]/2,1/b.resolution,-1/b.resolution,-b.rotation,-b.center[0],-b.center[1]);Lh(Fh(d,c))}k=Mh.prototype;k.ka=function(){for(var a in this.c)Nc(this.c[a])};function Oh(){if(32<zh.a){var a=0,b;for(b in zh.b){var c=zh.b[b];a++&3||Rc(c)||(delete zh.b[b],--zh.a)}}}
+k.Ea=function(a,b,c,d,e,f,g){function h(a,c){var f=w(a).toString(),g=b.layerStates[w(c)].Je;if(!(f in b.skippedFeatureUids)||g)return d.call(e,a,g?c:null)}var l,m=b.viewState,n=m.resolution,p=m.projection,m=a;if(p.i){var p=p.G(),q=lb(p),r=a[0];if(r<p[0]||r>p[2])m=[r+q*Math.ceil((p[0]-r)/q),a[1]]}p=b.layerStatesArray;for(q=p.length-1;0<=q;--q){var u=p[q],r=u.layer;if(xh(u,n)&&f.call(g,r)&&(u=Ph(this,r),r.ha()&&(l=u.Ea(r.ha().u?m:a,b,c,h,e)),l))return l}};
+k.Ei=function(a,b,c,d,e){return void 0!==this.Ea(a,b,c,mf,this,d,e)};function Ph(a,b){var c=w(b).toString();if(c in a.c)return a.c[c];b=b.Fd(a);a.c[c]=b;a.v[c]=y(b,"change",a.Fl,a);return b}k.Fl=function(){this.o.render()};k.Jg=ua;k.Rp=function(a,b){for(var c in this.c)if(!(b&&c in b.layerStates)){a=c;var d=this.c[a];delete this.c[a];Ec(this.v[a]);delete this.v[a];Nc(d)}};function Qh(a,b){for(var c in a.c)if(!(c in b.layerStates)){b.postRenderFunctions.push(a.Rp.bind(a));break}}
+function ra(a,b){return a.zIndex-b.zIndex};function Rh(a,b,c,d,e){Oc.call(this,a);this.vectorContext=b;this.frameState=c;this.context=d;this.glContext=e}v(Rh,Oc);var Sh=[0,0,0,1],Th=[],Uh=[0,0,0,1];function Vh(a,b,c,d){b&&(a.translate(c,d),a.rotate(b),a.translate(-c,-d))};function Wh(){}k=Wh.prototype;k.zb=function(){};k.rd=function(){};k.Zb=function(){};k.te=function(){};k.ue=function(){};k.mc=function(){};k.nc=function(){};k.oc=function(){};k.pc=function(){};k.qc=function(){};k.rc=function(){};k.yc=function(){};k.Ma=function(){};k.Ub=function(){};k.Cb=function(){};function Xh(a,b,c,d,e){this.i=a;this.u=b;this.c=c;this.S=d;this.Yb=e;this.M=this.b=this.a=this.Ua=this.R=this.I=null;this.na=this.T=this.l=this.B=this.C=this.D=0;this.fa=!1;this.f=this.fb=0;this.pa=!1;this.oa=0;this.Ia="";this.va=this.Jb=0;this.Sa=!1;this.j=this.$a=0;this.ra=this.o=this.g=null;this.v=[];this.xb=Bh()}v(Xh,Wh);
+function Yh(a,b,c){if(a.M){b=pf(b,0,c,2,a.S,a.v);c=a.i;var d=a.xb,e=c.globalAlpha;1!=a.l&&(c.globalAlpha=e*a.l);var f=a.fb;a.fa&&(f+=a.Yb);var g;var h=0;for(g=b.length;h<g;h+=2){var l=b[h]-a.D,m=b[h+1]-a.C;a.pa&&(l=Math.round(l),m=Math.round(m));if(f||1!=a.f){var n=l+a.D,p=m+a.C;Kh(d,n,p,a.f,a.f,f,-n,-p);c.setTransform.apply(c,d)}c.drawImage(a.M,a.T,a.na,a.oa,a.B,l,m,a.oa,a.B)}(f||1!=a.f)&&c.setTransform(1,0,0,1,0,0);1!=a.l&&(c.globalAlpha=e)}}
+function Zh(a,b,c,d){var e=0;if(a.ra&&""!==a.Ia){a.g&&$h(a,a.g);a.o&&ai(a,a.o);var f=a.ra,g=a.i,h=a.Ua;h?(h.font!=f.font&&(h.font=g.font=f.font),h.textAlign!=f.textAlign&&(h.textAlign=g.textAlign=f.textAlign),h.textBaseline!=f.textBaseline&&(h.textBaseline=g.textBaseline=f.textBaseline)):(g.font=f.font,g.textAlign=f.textAlign,g.textBaseline=f.textBaseline,a.Ua={font:f.font,textAlign:f.textAlign,textBaseline:f.textBaseline});b=pf(b,e,c,d,a.S,a.v);f=a.i;g=a.$a;for(a.Sa&&(g+=a.Yb);e<c;e+=d){var h=b[e]+
+a.Jb,l=b[e+1]+a.va;if(g||1!=a.j){var m=Kh(a.xb,h,l,a.j,a.j,g,-h,-l);f.setTransform.apply(f,m)}a.o&&f.strokeText(a.Ia,h,l);a.g&&f.fillText(a.Ia,h,l)}(g||1!=a.j)&&f.setTransform(1,0,0,1,0,0)}}function bi(a,b,c,d,e,f){var g=a.i;a=pf(b,c,d,e,a.S,a.v);g.moveTo(a[0],a[1]);b=a.length;f&&(b-=2);for(c=2;c<b;c+=2)g.lineTo(a[c],a[c+1]);f&&g.closePath();return d}function ci(a,b,c,d,e){var f;var g=0;for(f=d.length;g<f;++g)c=bi(a,b,c,d[g],e,!0);return c}k=Xh.prototype;
+k.Zb=function(a){if(qb(this.c,a.G())){if(this.a||this.b){this.a&&$h(this,this.a);this.b&&ai(this,this.b);var b=this.S;var c=this.v,d=a.ga();b=d?pf(d,0,d.length,a.qa(),b,c):null;c=b[2]-b[0];d=b[3]-b[1];c=Math.sqrt(c*c+d*d);d=this.i;d.beginPath();d.arc(b[0],b[1],c,0,2*Math.PI);this.a&&d.fill();this.b&&d.stroke()}""!==this.Ia&&Zh(this,a.wa(),2,2)}};k.rd=function(a){this.Ma(a.Fa(),a.Ga());this.Ub(a.Y());this.Cb(a.Na())};
+k.zb=function(a){switch(a.U()){case "Point":this.qc(a);break;case "LineString":this.mc(a);break;case "Polygon":this.rc(a);break;case "MultiPoint":this.oc(a);break;case "MultiLineString":this.nc(a);break;case "MultiPolygon":this.pc(a);break;case "GeometryCollection":this.ue(a);break;case "Circle":this.Zb(a)}};k.te=function(a,b){(a=(0,b.Za)(a))&&qb(this.c,a.G())&&(this.rd(b),this.zb(a))};k.ue=function(a){a=a.a;var b;var c=0;for(b=a.length;c<b;++c)this.zb(a[c])};
+k.qc=function(a){var b=a.ga();a=a.qa();this.M&&Yh(this,b,b.length);""!==this.Ia&&Zh(this,b,b.length,a)};k.oc=function(a){var b=a.ga();a=a.qa();this.M&&Yh(this,b,b.length);""!==this.Ia&&Zh(this,b,b.length,a)};k.mc=function(a){if(qb(this.c,a.G())){if(this.b){ai(this,this.b);var b=this.i,c=a.ga();b.beginPath();bi(this,c,0,c.length,a.qa(),!1);b.stroke()}""!==this.Ia&&(a=di(a),Zh(this,a,2,2))}};
+k.nc=function(a){var b=a.G();if(qb(this.c,b)){if(this.b){ai(this,this.b);var b=this.i,c=a.ga(),d=0,e=a.Bb(),f=a.qa();b.beginPath();var g;var h=0;for(g=e.length;h<g;++h)d=bi(this,c,d,e[h],f,!1);b.stroke()}""!==this.Ia&&(a=ei(a),Zh(this,a,a.length,2))}};k.rc=function(a){if(qb(this.c,a.G())){if(this.b||this.a){this.a&&$h(this,this.a);this.b&&ai(this,this.b);var b=this.i;b.beginPath();ci(this,a.ec(),0,a.Bb(),a.qa());this.a&&b.fill();this.b&&b.stroke()}""!==this.Ia&&(a=Wf(a),Zh(this,a,2,2))}};
+k.pc=function(a){if(qb(this.c,a.G())){if(this.b||this.a){this.a&&$h(this,this.a);this.b&&ai(this,this.b);var b=this.i,c=fi(a),d=0,e=a.c,f=a.qa(),g;b.beginPath();var h=0;for(g=e.length;h<g;++h)d=ci(this,c,d,e[h],f);this.a&&b.fill();this.b&&b.stroke()}""!==this.Ia&&(a=gi(a),Zh(this,a,a.length,2))}};function $h(a,b){var c=a.i,d=a.I;d?d.fillStyle!=b.fillStyle&&(d.fillStyle=c.fillStyle=b.fillStyle):(c.fillStyle=b.fillStyle,a.I={fillStyle:b.fillStyle})}
+function ai(a,b){var c=a.i,d=a.R;d?(d.lineCap!=b.lineCap&&(d.lineCap=c.lineCap=b.lineCap),Td&&!pa(d.lineDash,b.lineDash)&&c.setLineDash(d.lineDash=b.lineDash),d.lineJoin!=b.lineJoin&&(d.lineJoin=c.lineJoin=b.lineJoin),d.lineWidth!=b.lineWidth&&(d.lineWidth=c.lineWidth=b.lineWidth),d.miterLimit!=b.miterLimit&&(d.miterLimit=c.miterLimit=b.miterLimit),d.strokeStyle!=b.strokeStyle&&(d.strokeStyle=c.strokeStyle=b.strokeStyle)):(c.lineCap=b.lineCap,Td&&c.setLineDash(b.lineDash),c.lineJoin=b.lineJoin,c.lineWidth=
+b.lineWidth,c.miterLimit=b.miterLimit,c.strokeStyle=b.strokeStyle,a.R={lineCap:b.lineCap,lineDash:b.lineDash,lineJoin:b.lineJoin,lineWidth:b.lineWidth,miterLimit:b.miterLimit,strokeStyle:b.strokeStyle})}
+k.Ma=function(a,b){a?(a=a.b,this.a={fillStyle:id(a?a:Sh)}):this.a=null;if(b){a=b.a;var c=b.f,d=b.i,e=b.g,f=b.j,g=b.c;b=b.o;this.b={lineCap:void 0!==c?c:"round",lineDash:d?d:Th,lineDashOffset:e?e:0,lineJoin:void 0!==f?f:"round",lineWidth:this.u*(void 0!==g?g:1),miterLimit:void 0!==b?b:10,strokeStyle:id(a?a:Uh)}}else this.b=null};
+k.Ub=function(a){if(a){var b=a.Hc(),c=a.Y(1),d=a.Oc(),e=a.ic();this.D=b[0];this.C=b[1];this.B=e[1];this.M=c;this.l=a.f;this.T=d[0];this.na=d[1];this.fa=a.l;this.fb=a.g;this.f=a.a;this.pa=a.v;this.oa=e[0]}else this.M=null};
+k.Cb=function(a){if(a){var b=a.Fa();b?(b=b.b,this.g={fillStyle:id(b?b:Sh)}):this.g=null;var c=a.Ga();if(c){var b=c.a,d=c.f,e=c.i,f=c.g,g=c.j,h=c.c,c=c.o;this.o={lineCap:void 0!==d?d:"round",lineDash:e?e:Th,lineDashOffset:f?f:0,lineJoin:void 0!==g?g:"round",lineWidth:void 0!==h?h:1,miterLimit:void 0!==c?c:10,strokeStyle:id(b?b:Uh)}}else this.o=null;var b=a.a,d=a.i,e=a.c,f=a.o,g=a.f,h=a.b,c=a.Na(),l=a.g;a=a.j;this.ra={font:void 0!==b?b:"10px sans-serif",textAlign:void 0!==l?l:"center",textBaseline:void 0!==
+a?a:"middle"};this.Ia=void 0!==c?c:"";this.Jb=void 0!==d?this.u*d:0;this.va=void 0!==e?this.u*e:0;this.Sa=void 0!==f?f:!1;this.$a=void 0!==g?g:0;this.j=this.u*(void 0!==h?h:1)}else this.Ia=""};function hi(a,b){Mh.call(this,0,b);this.i=jd();this.b=this.i.canvas;this.b.style.width="100%";this.b.style.height="100%";this.b.style.display="block";this.b.className="ol-unselectable";a.insertBefore(this.b,a.childNodes[0]||null);this.a=!0;this.f=Bh()}v(hi,Mh);
+function ii(a,b,c){var d=a.o,e=a.i;if(Rc(d,b)){var f=c.extent,g=c.pixelRatio,h=c.viewState.rotation,l=c.viewState,m=c.pixelRatio/l.resolution;a=Kh(a.f,a.b.width/2,a.b.height/2,m,-m,-l.rotation,-l.center[0],-l.center[1]);d.b(new Rh(b,new Xh(e,g,f,a,h),c,e,null))}}hi.prototype.U=function(){return"canvas"};
+hi.prototype.Jg=function(a){if(a){var b=this.i,c=a.pixelRatio,d=Math.round(a.size[0]*c),e=Math.round(a.size[1]*c);this.b.width!=d||this.b.height!=e?(this.b.width=d,this.b.height=e):b.clearRect(0,0,d,e);c=a.viewState.rotation;Nh(a);ii(this,"precompose",a);var f=a.layerStatesArray;qa(f);c&&(b.save(),Vh(b,c,d/2,e/2));var d=a.viewState.resolution,g,e=0;for(g=f.length;e<g;++e){var h=f[e];var l=h.layer;l=Ph(this,l);xh(h,d)&&"ready"==h.yj&&l.sd(a,h)&&l.S(a,h,b)}c&&b.restore();ii(this,"postcompose",a);this.a||
+(this.b.style.display="",this.a=!0);Qh(this,a);a.postRenderFunctions.push(Oh)}else this.a&&(this.b.style.display="none",this.a=!1)};hi.prototype.Di=function(a,b,c,d,e,f){var g=b.viewState.resolution,h=b.layerStatesArray,l=h.length;a=Gh(b.pixelToCoordinateTransform,a.slice());for(--l;0<=l;--l){var m=h[l];var n=m.layer;if(xh(m,g)&&e.call(f,n)&&(m=Ph(this,n).u(a,b,c,d)))return m}};var ji=["Polygon","Circle","LineString","Image","Text"];function ki(){};function li(a){this.b=a};function mi(a){this.b=a}v(mi,li);mi.prototype.U=function(){return 35632};function ni(a){this.b=a}v(ni,li);ni.prototype.U=function(){return 35633};function oi(){this.b="precision mediump float;varying vec2 a;varying vec2 b;varying float c;varying float d;uniform float m;uniform vec4 n;uniform vec4 o;uniform vec2 p;void main(void){vec2 windowCenter=vec2((a.x+1.0)/2.0*p.x*d,(a.y+1.0)/2.0*p.y*d);vec2 windowOffset=vec2((b.x+1.0)/2.0*p.x*d,(b.y+1.0)/2.0*p.y*d);float radius=length(windowCenter-windowOffset);float dist=length(windowCenter-gl_FragCoord.xy);if(dist>radius+c){if(o.a==0.0){gl_FragColor=n;}else{gl_FragColor=o;}gl_FragColor.a=gl_FragColor.a-(dist-(radius+c));}else if(n.a==0.0){gl_FragColor=o;if(dist<radius-c){gl_FragColor.a=gl_FragColor.a-(radius-c-dist);}} else{gl_FragColor=n;float strokeDist=radius-c;float antialias=2.0*d;if(dist>strokeDist){gl_FragColor=o;}else if(dist>=strokeDist-antialias){float step=smoothstep(strokeDist-antialias,strokeDist,dist);gl_FragColor=mix(n,o,step);}} gl_FragColor.a=gl_FragColor.a*m;if(gl_FragColor.a<=0.0){discard;}}"}
+v(oi,mi);var pi=new oi;
+function qi(){this.b="varying vec2 a;varying vec2 b;varying float c;varying float d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;void main(void){mat4 offsetMatrix=i*j;a=vec4(h*vec4(e,0.0,1.0)).xy;d=l;float lineWidth=k*l;c=lineWidth/2.0;if(lineWidth==0.0){lineWidth=2.0*l;}vec2 offset;float radius=g+3.0*l;if(f==0.0){offset=vec2(-1.0,1.0);}else if(f==1.0){offset=vec2(-1.0,-1.0);}else if(f==2.0){offset=vec2(1.0,-1.0);}else{offset=vec2(1.0,1.0);}gl_Position=h*vec4(e+offset*radius,0.0,1.0)+offsetMatrix*vec4(offset*lineWidth,0.0,0.0);b=vec4(h*vec4(e.x+g,e.y,0.0,1.0)).xy;if(distance(a,b)>20000.0){gl_Position=vec4(a,0.0,1.0);}}"}
+v(qi,ni);var ri=new qi;function si(a,b){this.B=a.getUniformLocation(b,"n");this.oa=a.getUniformLocation(b,"k");this.c=a.getUniformLocation(b,"j");this.f=a.getUniformLocation(b,"i");this.a=a.getUniformLocation(b,"m");this.ra=a.getUniformLocation(b,"l");this.i=a.getUniformLocation(b,"h");this.I=a.getUniformLocation(b,"p");this.R=a.getUniformLocation(b,"o");this.j=a.getAttribLocation(b,"f");this.b=a.getAttribLocation(b,"e");this.S=a.getAttribLocation(b,"g")};function ti(){return[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]}function ui(a,b){a[0]=b[0];a[1]=b[1];a[4]=b[2];a[5]=b[3];a[12]=b[4];a[13]=b[5];return a};function vi(a,b){this.origin=nb(b);this.xb=Bh();this.Sa=Bh();this.$a=Bh();this.Jb=ti();this.b=[];this.o=null;this.i=[];this.f=[];this.a=[];this.l=null;this.g=void 0}v(vi,Wh);
+vi.prototype.La=function(a,b,c,d,e,f,g,h,l,m,n){var p=a.b;if(this.g){var q=p.isEnabled(p.STENCIL_TEST);var r=p.getParameter(p.STENCIL_FUNC);var u=p.getParameter(p.STENCIL_VALUE_MASK);var x=p.getParameter(p.STENCIL_REF);var B=p.getParameter(p.STENCIL_WRITEMASK);var E=p.getParameter(p.STENCIL_FAIL);var A=p.getParameter(p.STENCIL_PASS_DEPTH_PASS);var L=p.getParameter(p.STENCIL_PASS_DEPTH_FAIL);p.enable(p.STENCIL_TEST);p.clear(p.STENCIL_BUFFER_BIT);p.stencilMask(255);p.stencilFunc(p.ALWAYS,1,255);p.stencilOp(p.KEEP,
+p.KEEP,p.REPLACE);this.g.La(a,b,c,d,e,f,g,h,l,m,n);p.stencilMask(0);p.stencilFunc(p.NOTEQUAL,1,255)}wi(a,34962,this.l);wi(a,34963,this.o);f=this.rf(p,a,e,f);var oa=Ch(this.xb);Ih(oa,2/(c*e[0]),2/(c*e[1]));Hh(oa,-d);Jh(oa,-(b[0]-this.origin[0]),-(b[1]-this.origin[1]));b=Ch(this.$a);Ih(b,2/e[0],2/e[1]);e=Ch(this.Sa);d&&Hh(e,-d);p.uniformMatrix4fv(f.i,!1,ui(this.Jb,oa));p.uniformMatrix4fv(f.f,!1,ui(this.Jb,b));p.uniformMatrix4fv(f.c,!1,ui(this.Jb,e));p.uniform1f(f.a,g);if(l){m?a=this.ve(p,a,h,l,n):(p.clear(p.COLOR_BUFFER_BIT|
+p.DEPTH_BUFFER_BIT),this.Od(p,a,h,!0),a=(a=l(null))?a:void 0);var ha=a}else this.Od(p,a,h,!1);this.sf(p,f);this.g&&(q||p.disable(p.STENCIL_TEST),p.clear(p.STENCIL_BUFFER_BIT),p.stencilFunc(r,x,u),p.stencilMask(B),p.stencilOp(E,L,A));return ha};function xi(a,b,c,d){a.drawElements(4,d-c,b.g?5125:5123,c*(b.g?4:2))};var yi=[0,0,0,1],zi=[],Ai=[0,0,0,1];function Bi(a,b,c,d,e,f){a=(c-a)*(f-b)-(e-a)*(d-b);return a<=Ci&&a>=-Ci?void 0:0<a}var Ci=Number.EPSILON||2.220446049250313E-16;function Di(a){this.b=void 0!==a?a:[];this.a=Ei}var Ei=35044;function Fi(a,b){vi.call(this,0,b);this.v=null;this.j=[];this.u=[];this.S=0;this.c={fillColor:null,strokeColor:null,lineDash:null,lineDashOffset:void 0,lineWidth:void 0,s:!1}}v(Fi,vi);k=Fi.prototype;
+k.Zb=function(a,b){var c=a.pd(),d=a.qa();if(c){this.i.push(this.b.length);this.f.push(b);this.c.s&&(this.u.push(this.b.length),this.c.s=!1);this.S=c;a=a.ga();a=qf(a,0,2,d,-this.origin[0],-this.origin[1]);b=this.a.length;var c=this.b.length,e=b/4,f;for(f=0;2>f;f+=d)this.a[b++]=a[f],this.a[b++]=a[f+1],this.a[b++]=0,this.a[b++]=this.S,this.a[b++]=a[f],this.a[b++]=a[f+1],this.a[b++]=1,this.a[b++]=this.S,this.a[b++]=a[f],this.a[b++]=a[f+1],this.a[b++]=2,this.a[b++]=this.S,this.a[b++]=a[f],this.a[b++]=
+a[f+1],this.a[b++]=3,this.a[b++]=this.S,this.b[c++]=e,this.b[c++]=e+1,this.b[c++]=e+2,this.b[c++]=e+2,this.b[c++]=e+3,this.b[c++]=e,e+=4}else this.c.s&&(this.j.pop(),this.j.length&&(d=this.j[this.j.length-1],this.c.fillColor=d[0],this.c.strokeColor=d[1],this.c.lineWidth=d[2],this.c.s=!1))};k.Db=function(){this.l=new Di(this.a);this.o=new Di(this.b);this.i.push(this.b.length);!this.u.length&&0<this.j.length&&(this.j=[]);this.b=this.a=null};
+k.Eb=function(a){var b=this.l,c=this.o;return function(){Gi(a,b);Gi(a,c)}};k.rf=function(a,b,c,d){var e=Hi(b,pi,ri);if(this.v)var f=this.v;else this.v=f=new si(a,e);b.Qc(e);a.enableVertexAttribArray(f.b);a.vertexAttribPointer(f.b,2,5126,!1,16,0);a.enableVertexAttribArray(f.j);a.vertexAttribPointer(f.j,1,5126,!1,16,8);a.enableVertexAttribArray(f.S);a.vertexAttribPointer(f.S,1,5126,!1,16,12);a.uniform2fv(f.I,c);a.uniform1f(f.ra,d);return f};
+k.sf=function(a,b){a.disableVertexAttribArray(b.b);a.disableVertexAttribArray(b.j);a.disableVertexAttribArray(b.S)};
+k.Od=function(a,b,c){if(wb(c)){var d=this.i[this.i.length-1];for(c=this.u.length-1;0<=c;--c){var e=this.u[c];var f=this.j[c];a.uniform4fv(this.v.B,f[0]);Ii(this,a,f[1],f[2]);xi(a,b,e,d);d=e}}else{var g=this.i.length-2;f=d=this.i[g+1];for(e=this.u.length-1;0<=e;--e){var h=this.j[e];a.uniform4fv(this.v.B,h[0]);Ii(this,a,h[1],h[2]);for(h=this.u[e];0<=g&&this.i[g]>=h;){var l=this.i[g];var m=this.f[g];m=w(m).toString();c[m]&&(d!==f&&xi(a,b,d,f),f=l);g--;d=l}d!==f&&xi(a,b,d,f);d=f=h}}};
+k.ve=function(a,b,c,d,e){var f,g;var h=this.i.length-2;var l=this.i[h+1];for(f=this.u.length-1;0<=f;--f){var m=this.j[f];a.uniform4fv(this.v.B,m[0]);Ii(this,a,m[1],m[2]);for(g=this.u[f];0<=h&&this.i[h]>=g;){m=this.i[h];var n=this.f[h];var p=w(n).toString();if(void 0===c[p]&&n.V()&&(void 0===e||qb(e,n.V().G()))&&(a.clear(a.COLOR_BUFFER_BIT|a.DEPTH_BUFFER_BIT),xi(a,b,m,l),l=d(n)))return l;h--;l=m}}};function Ii(a,b,c,d){b.uniform4fv(a.v.R,c);b.uniform1f(a.v.oa,d)}
+k.Ma=function(a,b){if(b){var c=b.i;this.c.lineDash=c?c:zi;c=b.g;this.c.lineDashOffset=c?c:0;c=b.a;c instanceof CanvasGradient||c instanceof CanvasPattern?c=Ai:c=ed(c).map(function(a,b){return 3!=b?a/255:a})||Ai;b=b.c;b=void 0!==b?b:1}else c=[0,0,0,0],b=0;a=a?a.b:[0,0,0,0];a instanceof CanvasGradient||a instanceof CanvasPattern?a=yi:a=ed(a).map(function(a,b){return 3!=b?a/255:a})||yi;this.c.strokeColor&&pa(this.c.strokeColor,c)&&this.c.fillColor&&pa(this.c.fillColor,a)&&this.c.lineWidth===b||(this.c.s=
+!0,this.c.fillColor=a,this.c.strokeColor=c,this.c.lineWidth=b,this.j.push([a,c,b]))};function Ji(){this.b="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;}"}v(Ji,mi);var Ki=new Ji;
+function Li(){this.b="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,0.0);gl_Position=h*vec4(c,0.0,1.0)+offsets;a=d;b=f;}"}v(Li,ni);var Mi=new Li;
+function Ni(a,b){this.c=a.getUniformLocation(b,"j");this.f=a.getUniformLocation(b,"i");this.a=a.getUniformLocation(b,"k");this.i=a.getUniformLocation(b,"h");this.v=a.getAttribLocation(b,"e");this.u=a.getAttribLocation(b,"f");this.b=a.getAttribLocation(b,"c");this.D=a.getAttribLocation(b,"g");this.C=a.getAttribLocation(b,"d")};function Oi(a,b){this.j=a;this.b=b;this.a={};this.c={};this.i={};this.l=this.v=this.f=this.o=null;(this.g=ja(fa,"OES_element_index_uint"))&&b.getExtension("OES_element_index_uint");y(this.j,"webglcontextlost",this.Xo,this);y(this.j,"webglcontextrestored",this.Yo,this)}v(Oi,Mc);
+function wi(a,b,c){var d=a.b,e=c.b,f=String(w(c));if(f in a.a)d.bindBuffer(b,a.a[f].buffer);else{var g=d.createBuffer();d.bindBuffer(b,g);var h;34962==b?h=new Float32Array(e):34963==b&&(h=a.g?new Uint32Array(e):new Uint16Array(e));d.bufferData(b,h,c.a);a.a[f]={lc:c,buffer:g}}}function Gi(a,b){var c=a.b;b=String(w(b));var d=a.a[b];c.isContextLost()||c.deleteBuffer(d.buffer);delete a.a[b]}k=Oi.prototype;
+k.ka=function(){Lc(this.j);var a=this.b;if(!a.isContextLost()){for(var b in this.a)a.deleteBuffer(this.a[b].buffer);for(b in this.i)a.deleteProgram(this.i[b]);for(b in this.c)a.deleteShader(this.c[b]);a.deleteFramebuffer(this.f);a.deleteRenderbuffer(this.l);a.deleteTexture(this.v)}};k.Wo=function(){return this.b};
+function Pi(a){if(!a.f){var b=a.b,c=b.createFramebuffer();b.bindFramebuffer(b.FRAMEBUFFER,c);var d=Qi(b,1,1),e=b.createRenderbuffer();b.bindRenderbuffer(b.RENDERBUFFER,e);b.renderbufferStorage(b.RENDERBUFFER,b.DEPTH_COMPONENT16,1,1);b.framebufferTexture2D(b.FRAMEBUFFER,b.COLOR_ATTACHMENT0,b.TEXTURE_2D,d,0);b.framebufferRenderbuffer(b.FRAMEBUFFER,b.DEPTH_ATTACHMENT,b.RENDERBUFFER,e);b.bindTexture(b.TEXTURE_2D,null);b.bindRenderbuffer(b.RENDERBUFFER,null);b.bindFramebuffer(b.FRAMEBUFFER,null);a.f=c;
+a.v=d;a.l=e}return a.f}function Ri(a,b){var c=String(w(b));if(c in a.c)return a.c[c];var d=a.b,e=d.createShader(b.U());d.shaderSource(e,b.b);d.compileShader(e);return a.c[c]=e}function Hi(a,b,c){var d=w(b)+"/"+w(c);if(d in a.i)return a.i[d];var e=a.b,f=e.createProgram();e.attachShader(f,Ri(a,b));e.attachShader(f,Ri(a,c));e.linkProgram(f);return a.i[d]=f}k.Xo=function(){ub(this.a);ub(this.c);ub(this.i);this.l=this.v=this.f=this.o=null};k.Yo=function(){};
+k.Qc=function(a){if(a==this.o)return!1;this.b.useProgram(a);this.o=a;return!0};function Si(a,b,c){var d=a.createTexture();a.bindTexture(a.TEXTURE_2D,d);a.texParameteri(a.TEXTURE_2D,a.TEXTURE_MAG_FILTER,a.LINEAR);a.texParameteri(a.TEXTURE_2D,a.TEXTURE_MIN_FILTER,a.LINEAR);void 0!==b&&a.texParameteri(3553,10242,b);void 0!==c&&a.texParameteri(3553,10243,c);return d}function Qi(a,b,c){var d=Si(a,void 0,void 0);a.texImage2D(a.TEXTURE_2D,0,a.RGBA,b,c,0,a.RGBA,a.UNSIGNED_BYTE,null);return d}
+function Ti(a,b){var c=Si(a,33071,33071);a.texImage2D(a.TEXTURE_2D,0,a.RGBA,a.RGBA,a.UNSIGNED_BYTE,b);return c};function Ui(a,b){vi.call(this,0,b);this.C=this.D=void 0;this.S=[];this.v=[];this.oa=void 0;this.j=[];this.c=[];this.I=this.ra=void 0;this.B=null;this.fb=this.fa=this.na=this.T=this.Ua=this.R=void 0;this.va=[];this.u=[];this.pa=void 0}v(Ui,vi);k=Ui.prototype;k.Eb=function(a){var b=this.l,c=this.o,d=this.va,e=this.u,f=a.b;return function(){if(!f.isContextLost()){var g;var h=0;for(g=d.length;h<g;++h)f.deleteTexture(d[h]);h=0;for(g=e.length;h<g;++h)f.deleteTexture(e[h])}Gi(a,b);Gi(a,c)}};
+function Vi(a,b,c,d){var e=a.D,f=a.C,g=a.oa,h=a.ra,l=a.I,m=a.R,n=a.Ua,p=a.T,q=a.na?1:0,r=-a.fa,u=a.fb,x=a.pa,B=Math.cos(r),r=Math.sin(r),E=a.b.length,A=a.a.length,L;for(L=0;L<c;L+=d){var oa=b[L]-a.origin[0];var ha=b[L+1]-a.origin[1];var ga=A/8;var z=-u*e;var M=-u*(g-f);a.a[A++]=oa;a.a[A++]=ha;a.a[A++]=z*B-M*r;a.a[A++]=z*r+M*B;a.a[A++]=n/l;a.a[A++]=(p+g)/h;a.a[A++]=m;a.a[A++]=q;z=u*(x-e);M=-u*(g-f);a.a[A++]=oa;a.a[A++]=ha;a.a[A++]=z*B-M*r;a.a[A++]=z*r+M*B;a.a[A++]=(n+x)/l;a.a[A++]=(p+g)/h;a.a[A++]=
+m;a.a[A++]=q;z=u*(x-e);M=u*f;a.a[A++]=oa;a.a[A++]=ha;a.a[A++]=z*B-M*r;a.a[A++]=z*r+M*B;a.a[A++]=(n+x)/l;a.a[A++]=p/h;a.a[A++]=m;a.a[A++]=q;z=-u*e;M=u*f;a.a[A++]=oa;a.a[A++]=ha;a.a[A++]=z*B-M*r;a.a[A++]=z*r+M*B;a.a[A++]=n/l;a.a[A++]=p/h;a.a[A++]=m;a.a[A++]=q;a.b[E++]=ga;a.b[E++]=ga+1;a.b[E++]=ga+2;a.b[E++]=ga;a.b[E++]=ga+2;a.b[E++]=ga+3}}k.oc=function(a,b){this.i.push(this.b.length);this.f.push(b);b=a.ga();Vi(this,b,b.length,a.qa())};
+k.qc=function(a,b){this.i.push(this.b.length);this.f.push(b);b=a.ga();Vi(this,b,b.length,a.qa())};k.Db=function(a){a=a.b;this.S.push(this.b.length);this.v.push(this.b.length);this.l=new Di(this.a);this.o=new Di(this.b);var b={};Wi(this.va,this.j,b,a);Wi(this.u,this.c,b,a);this.oa=this.C=this.D=void 0;this.c=this.j=null;this.I=this.ra=void 0;this.b=null;this.fb=this.fa=this.na=this.T=this.Ua=this.R=void 0;this.a=null;this.pa=void 0};
+function Wi(a,b,c,d){var e,f=b.length;for(e=0;e<f;++e){var g=b[e];var h=w(g).toString();h in c?g=c[h]:(g=Ti(d,g),c[h]=g);a[e]=g}}
+k.rf=function(a,b){var c=Hi(b,Ki,Mi);if(this.B)var d=this.B;else this.B=d=new Ni(a,c);b.Qc(c);a.enableVertexAttribArray(d.b);a.vertexAttribPointer(d.b,2,5126,!1,32,0);a.enableVertexAttribArray(d.v);a.vertexAttribPointer(d.v,2,5126,!1,32,8);a.enableVertexAttribArray(d.C);a.vertexAttribPointer(d.C,2,5126,!1,32,16);a.enableVertexAttribArray(d.u);a.vertexAttribPointer(d.u,1,5126,!1,32,24);a.enableVertexAttribArray(d.D);a.vertexAttribPointer(d.D,1,5126,!1,32,28);return d};
+k.sf=function(a,b){a.disableVertexAttribArray(b.b);a.disableVertexAttribArray(b.v);a.disableVertexAttribArray(b.C);a.disableVertexAttribArray(b.u);a.disableVertexAttribArray(b.D)};
+k.Od=function(a,b,c,d){var e=d?this.u:this.va;d=d?this.v:this.S;if(wb(c)){var f;c=0;var g=e.length;for(f=0;c<g;++c){a.bindTexture(3553,e[c]);var h=d[c];xi(a,b,f,h);f=h}}else for(f=g=0,h=e.length;f<h;++f){a.bindTexture(3553,e[f]);for(var l=0<f?d[f-1]:0,m=d[f],n=l;g<this.i.length&&this.i[g]<=m;){var p=w(this.f[g]).toString();void 0!==c[p]?(n!==l&&xi(a,b,n,l),l=n=g===this.i.length-1?m:this.i[g+1]):l=g===this.i.length-1?m:this.i[g+1];g++}n!==l&&xi(a,b,n,l)}};
+k.ve=function(a,b,c,d,e){var f,g,h=this.i.length-1;for(f=this.u.length-1;0<=f;--f){a.bindTexture(3553,this.u[f]);var l=0<f?this.v[f-1]:0;for(g=this.v[f];0<=h&&this.i[h]>=l;){var m=this.i[h];var n=this.f[h];var p=w(n).toString();if(void 0===c[p]&&n.V()&&(void 0===e||qb(e,n.V().G()))&&(a.clear(a.COLOR_BUFFER_BIT|a.DEPTH_BUFFER_BIT),xi(a,b,m,g),g=d(n)))return g;g=m;h--}}};
+k.Ub=function(a){var b=a.Hc(),c=a.Y(1),d=a.ye(),e=a.qg(1),f=a.f,g=a.Oc(),h=a.l,l=a.g,m=a.ic();a=a.a;if(this.j.length){var n=this.j[this.j.length-1];w(n)!=w(c)&&(this.S.push(this.b.length),this.j.push(c))}else this.j.push(c);this.c.length?(n=this.c[this.c.length-1],w(n)!=w(e)&&(this.v.push(this.b.length),this.c.push(e))):this.c.push(e);this.D=b[0];this.C=b[1];this.oa=m[1];this.ra=d[1];this.I=d[0];this.R=f;this.Ua=g[0];this.T=g[1];this.fa=l;this.na=h;this.fb=a;this.pa=m[0]};function Xi(a,b,c){var d=b-c;return a[0]===a[d]&&a[1]===a[d+1]&&3<(b-0)/c?!!vf(a,0,b,c):!1};function Yi(){this.b="precision mediump float;varying float a;varying vec2 b;varying float c;uniform float m;uniform vec4 n;uniform vec2 o;uniform float p;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*o.x*p,(b.y+1.0)/2.0*o.y*p);if(length(windowCoords-gl_FragCoord.xy)>c*p){discard;}} gl_FragColor=n;float alpha=n.a*m;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}"}v(Yi,mi);var Zi=new Yi;
+function $i(){this.b="varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;bool nearlyEquals(in float value,in float ref){float epsilon=0.000000000001;return value>=ref-epsilon&&value<=ref+epsilon;}void alongNormal(out vec2 offset,in vec2 nextP,in float turnDir,in float direction){vec2 dirVect=nextP-e;vec2 normal=normalize(vec2(-turnDir*dirVect.y,turnDir*dirVect.x));offset=k/2.0*normal*direction;}void miterUp(out vec2 offset,out float round,in bool isRound,in float direction){float halfWidth=k/2.0;vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));float miterLength=abs(halfWidth/dot(normal,tmpNormal));offset=normal*direction*miterLength;round=0.0;if(isRound){round=1.0;}else if(miterLength>l+k){offset=halfWidth*tmpNormal*direction;}} bool miterDown(out vec2 offset,in vec4 projPos,in mat4 offsetMatrix,in float direction){bool degenerate=false;vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);vec2 dirVect=d-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;float halfWidth=k/2.0;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*halfWidth;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*halfWidth;longVertex=f;shortProjVertex=h*vec4(d,0.0,1.0);}else{shortOffset=tmpNormal*direction*halfWidth;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*halfWidth;longVertex=d;shortProjVertex=h*vec4(f,0.0,1.0);}vec4 p1=h*vec4(longVertex,0.0,1.0)+offsetMatrix*vec4(longOffset,0.0,0.0);vec4 p2=projPos+offsetMatrix*vec4(longOffset,0.0,0.0);vec4 p3=shortProjVertex+offsetMatrix*vec4(-shortOffset,0.0,0.0);vec4 p4=shortProjVertex+offsetMatrix*vec4(shortOffset,0.0,0.0);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;float epsilon=0.000000000001;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){shortProjVertex.x=p1.x+firstU*(p2.x-p1.x);shortProjVertex.y=p1.y+firstU*(p2.y-p1.y);offset=shortProjVertex.xy;degenerate=true;}else{float miterLength=abs(halfWidth/dot(normal,tmpNormal));offset=normal*direction*miterLength;}return degenerate;}void squareCap(out vec2 offset,out float round,in bool isRound,in vec2 nextP,in float turnDir,in float direction){round=0.0;vec2 dirVect=e-nextP;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(turnDir*firstNormal.y*direction,-turnDir*firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);vec2 normal=vec2(turnDir*hypotenuse.y*direction,-turnDir*hypotenuse.x*direction);float length=sqrt(c*c*2.0);offset=normal*length;if(isRound){round=1.0;}} void main(void){bool degenerate=false;float direction=float(sign(g));mat4 offsetMatrix=i*j;vec2 offset;vec4 projPos=h*vec4(e,0.0,1.0);bool round=nearlyEquals(mod(g,2.0),0.0);a=0.0;c=k/2.0;b=projPos.xy;if(nearlyEquals(mod(g,3.0),0.0)||nearlyEquals(mod(g,17.0),0.0)){alongNormal(offset,f,1.0,direction);}else if(nearlyEquals(mod(g,5.0),0.0)||nearlyEquals(mod(g,13.0),0.0)){alongNormal(offset,d,-1.0,direction);}else if(nearlyEquals(mod(g,23.0),0.0)){miterUp(offset,a,round,direction);}else if(nearlyEquals(mod(g,19.0),0.0)){degenerate=miterDown(offset,projPos,offsetMatrix,direction);}else if(nearlyEquals(mod(g,7.0),0.0)){squareCap(offset,a,round,f,1.0,direction);}else if(nearlyEquals(mod(g,11.0),0.0)){squareCap(offset,a,round,d,-1.0,direction);}if(!degenerate){vec4 offsets=offsetMatrix*vec4(offset,0.0,0.0);gl_Position=projPos+offsets;}else{gl_Position=vec4(offset,0.0,1.0);}}"}
+v($i,ni);var aj=new $i;function bj(a,b){this.B=a.getUniformLocation(b,"n");this.oa=a.getUniformLocation(b,"k");this.R=a.getUniformLocation(b,"l");this.c=a.getUniformLocation(b,"j");this.f=a.getUniformLocation(b,"i");this.a=a.getUniformLocation(b,"m");this.ra=a.getUniformLocation(b,"p");this.i=a.getUniformLocation(b,"h");this.I=a.getUniformLocation(b,"o");this.g=a.getAttribLocation(b,"g");this.o=a.getAttribLocation(b,"d");this.l=a.getAttribLocation(b,"f");this.b=a.getAttribLocation(b,"e")};function cj(a,b){vi.call(this,0,b);this.v=null;this.u=[];this.j=[];this.c={strokeColor:null,lineCap:void 0,lineDash:null,lineDashOffset:void 0,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0,s:!1}}v(cj,vi);
+function dj(a,b,c,d){var e,f=a.a.length,g=a.b.length,h="bevel"===a.c.lineJoin?0:"miter"===a.c.lineJoin?1:2,l="butt"===a.c.lineCap?0:"square"===a.c.lineCap?1:2,m=Xi(b,c,d),n=g,p=1;for(e=0;e<c;e+=d){var q=f/7;var r=u;var u=x||[b[e],b[e+1]];if(e)if(e===c-d){if(m)var x=B;else r=r||[0,0],f=ej(a,r,u,[0,0],p*fj*(l||1),f),f=ej(a,r,u,[0,0],-p*fj*(l||1),f),a.b[g++]=q,a.b[g++]=n-1,a.b[g++]=n,a.b[g++]=n,a.b[g++]=q+1,a.b[g++]=q,l&&(f=ej(a,r,u,[0,0],p*gj*l,f),f=ej(a,r,u,[0,0],-p*gj*l,f),a.b[g++]=q+2,a.b[g++]=q,
+a.b[g++]=q+1,a.b[g++]=q+1,a.b[g++]=q+3,a.b[g++]=q+2);break}else x=[b[e+d],b[e+d+1]];else{x=[b[e+d],b[e+d+1]];if(c-0===2*d&&pa(u,x))break;if(m){r=[b[c-2*d],b[c-2*d+1]];var B=x}else{l&&(f=ej(a,[0,0],u,x,p*hj*l,f),f=ej(a,[0,0],u,x,-p*hj*l,f),a.b[g++]=q+2,a.b[g++]=q,a.b[g++]=q+1,a.b[g++]=q+1,a.b[g++]=q+3,a.b[g++]=q+2);f=ej(a,[0,0],u,x,p*ij*(l||1),f);f=ej(a,[0,0],u,x,-p*ij*(l||1),f);n=f/7-1;continue}}var E=Bi(r[0],r[1],u[0],u[1],x[0],x[1])?-1:1;f=ej(a,r,u,x,E*jj*(h||1),f);f=ej(a,r,u,x,E*kj*(h||1),f);f=
+ej(a,r,u,x,-E*lj*(h||1),f);0<e&&(a.b[g++]=q,a.b[g++]=n-1,a.b[g++]=n,a.b[g++]=q+2,a.b[g++]=q,a.b[g++]=0<p*E?n:n-1);a.b[g++]=q;a.b[g++]=q+2;a.b[g++]=q+1;n=q+2;p=E;h&&(f=ej(a,r,u,x,E*mj*h,f),a.b[g++]=q+1,a.b[g++]=q+3,a.b[g++]=q)}m&&(q=q||f/7,E=Sf([r[0],r[1],u[0],u[1],x[0],x[1]],0,6,2)?1:-1,f=ej(a,r,u,x,E*jj*(h||1),f),ej(a,r,u,x,-E*lj*(h||1),f),a.b[g++]=q,a.b[g++]=n-1,a.b[g++]=n,a.b[g++]=q+1,a.b[g++]=q,a.b[g++]=0<p*E?n:n-1)}
+function ej(a,b,c,d,e,f){a.a[f++]=b[0];a.a[f++]=b[1];a.a[f++]=c[0];a.a[f++]=c[1];a.a[f++]=d[0];a.a[f++]=d[1];a.a[f++]=e;return f}function nj(a,b,c,d){c-=b;return c<2*d?!1:c===2*d?!pa([a[b],a[b+1]],[a[b+d],a[b+d+1]]):!0}k=cj.prototype;k.mc=function(a,b){var c=a.ga();a=a.qa();nj(c,0,c.length,a)&&(c=qf(c,0,c.length,a,-this.origin[0],-this.origin[1]),this.c.s&&(this.j.push(this.b.length),this.c.s=!1),this.i.push(this.b.length),this.f.push(b),dj(this,c,c.length,a))};
+k.nc=function(a,b){var c=this.b.length,d=a.Bb();d.unshift(0);var e=a.ga();a=a.qa();var f;if(1<d.length){var g=1;for(f=d.length;g<f;++g)if(nj(e,d[g-1],d[g],a)){var h=qf(e,d[g-1],d[g],a,-this.origin[0],-this.origin[1]);dj(this,h,h.length,a)}}this.b.length>c&&(this.i.push(c),this.f.push(b),this.c.s&&(this.j.push(c),this.c.s=!1))};
+function oj(a,b,c,d){Xi(b,b.length,d)||(b.push(b[0]),b.push(b[1]));dj(a,b,b.length,d);if(c.length){var e;b=0;for(e=c.length;b<e;++b)Xi(c[b],c[b].length,d)||(c[b].push(c[b][0]),c[b].push(c[b][1])),dj(a,c[b],c[b].length,d)}}function pj(a,b,c){c=void 0===c?a.b.length:c;a.i.push(c);a.f.push(b);a.c.s&&(a.j.push(c),a.c.s=!1)}k.Db=function(){this.l=new Di(this.a);this.o=new Di(this.b);this.i.push(this.b.length);!this.j.length&&0<this.u.length&&(this.u=[]);this.b=this.a=null};
+k.Eb=function(a){var b=this.l,c=this.o;return function(){Gi(a,b);Gi(a,c)}};
+k.rf=function(a,b,c,d){var e=Hi(b,Zi,aj);if(this.v)var f=this.v;else this.v=f=new bj(a,e);b.Qc(e);a.enableVertexAttribArray(f.o);a.vertexAttribPointer(f.o,2,5126,!1,28,0);a.enableVertexAttribArray(f.b);a.vertexAttribPointer(f.b,2,5126,!1,28,8);a.enableVertexAttribArray(f.l);a.vertexAttribPointer(f.l,2,5126,!1,28,16);a.enableVertexAttribArray(f.g);a.vertexAttribPointer(f.g,1,5126,!1,28,24);a.uniform2fv(f.I,c);a.uniform1f(f.ra,d);return f};
+k.sf=function(a,b){a.disableVertexAttribArray(b.o);a.disableVertexAttribArray(b.b);a.disableVertexAttribArray(b.l);a.disableVertexAttribArray(b.g)};
+k.Od=function(a,b,c,d){var e=a.getParameter(a.DEPTH_FUNC),f=a.getParameter(a.DEPTH_WRITEMASK);d||(a.enable(a.DEPTH_TEST),a.depthMask(!0),a.depthFunc(a.NOTEQUAL));if(wb(c)){var g=this.i[this.i.length-1];for(c=this.j.length-1;0<=c;--c){var h=this.j[c];var l=this.u[c];qj(this,a,l[0],l[1],l[2]);xi(a,b,h,g);a.clear(a.DEPTH_BUFFER_BIT);g=h}}else{var m=this.i.length-2;l=g=this.i[m+1];for(h=this.j.length-1;0<=h;--h){var n=this.u[h];qj(this,a,n[0],n[1],n[2]);for(n=this.j[h];0<=m&&this.i[m]>=n;){var p=this.i[m];
+var q=this.f[m];q=w(q).toString();c[q]&&(g!==l&&(xi(a,b,g,l),a.clear(a.DEPTH_BUFFER_BIT)),l=p);m--;g=p}g!==l&&(xi(a,b,g,l),a.clear(a.DEPTH_BUFFER_BIT));g=l=n}}d||(a.disable(a.DEPTH_TEST),a.clear(a.DEPTH_BUFFER_BIT),a.depthMask(f),a.depthFunc(e))};
+k.ve=function(a,b,c,d,e){var f,g;var h=this.i.length-2;var l=this.i[h+1];for(f=this.j.length-1;0<=f;--f){var m=this.u[f];qj(this,a,m[0],m[1],m[2]);for(g=this.j[f];0<=h&&this.i[h]>=g;){m=this.i[h];var n=this.f[h];var p=w(n).toString();if(void 0===c[p]&&n.V()&&(void 0===e||qb(e,n.V().G()))&&(a.clear(a.COLOR_BUFFER_BIT|a.DEPTH_BUFFER_BIT),xi(a,b,m,l),l=d(n)))return l;h--;l=m}}};function qj(a,b,c,d,e){b.uniform4fv(a.v.B,c);b.uniform1f(a.v.oa,d);b.uniform1f(a.v.R,e)}
+k.Ma=function(a,b){a=b.f;this.c.lineCap=void 0!==a?a:"round";a=b.i;this.c.lineDash=a?a:zi;a=b.g;this.c.lineDashOffset=a?a:0;a=b.j;this.c.lineJoin=void 0!==a?a:"round";a=b.a;a instanceof CanvasGradient||a instanceof CanvasPattern?a=Ai:a=ed(a).map(function(a,b){return 3!=b?a/255:a})||Ai;var c=b.c,c=void 0!==c?c:1;b=b.o;b=void 0!==b?b:10;this.c.strokeColor&&pa(this.c.strokeColor,a)&&this.c.lineWidth===c&&this.c.miterLimit===b||(this.c.s=!0,this.c.strokeColor=a,this.c.lineWidth=c,this.c.miterLimit=b,
+this.u.push([a,c,b]))};var ij=3,fj=5,hj=7,gj=11,jj=13,kj=17,lj=19,mj=23;function rj(){this.b="precision mediump float;uniform vec4 e;uniform float f;void main(void){gl_FragColor=e;float alpha=e.a*f;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}"}v(rj,mi);var sj=new rj;function tj(){this.b="attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){gl_Position=b*vec4(a,0.0,1.0);}"}v(tj,ni);var uj=new tj;
+function vj(a,b){this.B=a.getUniformLocation(b,"e");this.c=a.getUniformLocation(b,"d");this.f=a.getUniformLocation(b,"c");this.a=a.getUniformLocation(b,"f");this.i=a.getUniformLocation(b,"b");this.b=a.getAttribLocation(b,"a")};function wj(a){a=a||{};this.a=void 0!==a.color?a.color:null;this.f=a.lineCap;this.i=void 0!==a.lineDash?a.lineDash:null;this.g=a.lineDashOffset;this.j=a.lineJoin;this.o=a.miterLimit;this.c=a.width;this.b=void 0}k=wj.prototype;k.clone=function(){var a=this.a;return new wj({color:a&&a.slice?a.slice():a||void 0,lineCap:this.f,lineDash:this.i?this.i.slice():void 0,lineDashOffset:this.g,lineJoin:this.j,miterLimit:this.o,width:this.c})};k.No=function(){return this.a};k.Vk=function(){return this.f};
+k.Oo=function(){return this.i};k.Wk=function(){return this.g};k.Xk=function(){return this.j};k.bl=function(){return this.o};k.Po=function(){return this.c};k.Qo=function(a){this.a=a;this.b=void 0};k.aq=function(a){this.f=a;this.b=void 0};k.setLineDash=function(a){this.i=a;this.b=void 0};k.bq=function(a){this.g=a;this.b=void 0};k.cq=function(a){this.j=a;this.b=void 0};k.gq=function(a){this.o=a;this.b=void 0};k.jq=function(a){this.c=a;this.b=void 0};function xj(a){this.b=this.a=this.i=void 0;this.f=void 0===a?!0:a;this.c=0}function yj(a){var b=a.b;if(b){var c=b.next,d=b.ub;c&&(c.ub=d);d&&(d.next=c);a.b=c||d;a.i===a.a?(a.b=void 0,a.i=void 0,a.a=void 0):a.i===b?a.i=a.b:a.a===b&&(a.a=d?a.b.ub:a.b);a.c--}}function zj(a){a.b=a.i;if(a.b)return a.b.data}function Aj(a){if(a.b&&a.b.next)return a.b=a.b.next,a.b.data}function Bj(a){if(a.b&&a.b.next)return a.b.next.data}function Cj(a){if(a.b&&a.b.ub)return a.b=a.b.ub,a.b.data}
+function Dj(a){if(a.b&&a.b.ub)return a.b.ub.data}function Ej(a){if(a.b)return a.b.data}xj.prototype.concat=function(a){if(a.b){if(this.b){var b=this.b.next;this.b.next=a.i;a.i.ub=this.b;b.ub=a.a;a.a.next=b;this.c+=a.c}else this.b=a.b,this.i=a.i,this.a=a.a,this.c=a.c;a.b=void 0;a.i=void 0;a.a=void 0;a.c=0}};var Fj={$d:function(){}};
+(function(a){function b(a,e,f,g,h){f=f||0;g=g||a.length-1;for(h=h||d;g>f;){if(600<g-f){var l=g-f+1,m=e-f+1,n=Math.log(l),p=.5*Math.exp(2*n/3),n=.5*Math.sqrt(n*p*(l-p)/l)*(0>m-l/2?-1:1);b(a,e,Math.max(f,Math.floor(e-m*p/l+n)),Math.min(g,Math.floor(e+(l-m)*p/l+n)),h)}l=a[e];m=f;p=g;c(a,f,e);for(0<h(a[g],l)&&c(a,f,g);m<p;){c(a,m,p);m++;for(p--;0>h(a[m],l);)m++;for(;0<h(a[p],l);)p--}0===h(a[f],l)?c(a,f,p):(p++,c(a,p,g));p<=e&&(f=p+1);e<=p&&(g=p-1)}}function c(a,b,c){var d=a[b];a[b]=a[c];a[c]=d}function d(a,
+b){return a<b?-1:a>b?1:0}function e(a,b){if(!(this instanceof e))return new e(a,b);this.Hf=Math.max(4,a||9);this.fh=Math.max(2,Math.ceil(.4*this.Hf));b&&this.ek(b);this.clear()}function f(a,b){g(a,0,a.children.length,b,a)}function g(a,b,c,d,e){e||(e=u(null));e.ca=Infinity;e.da=Infinity;e.$=-Infinity;e.ia=-Infinity;for(var f;b<c;b++)f=a.children[b],h(e,a.ib?d(f):f);return e}function h(a,b){a.ca=Math.min(a.ca,b.ca);a.da=Math.min(a.da,b.da);a.$=Math.max(a.$,b.$);a.ia=Math.max(a.ia,b.ia)}function l(a,
+b){return a.ca-b.ca}function m(a,b){return a.da-b.da}function n(a){return(a.$-a.ca)*(a.ia-a.da)}function p(a){return a.$-a.ca+(a.ia-a.da)}function q(a,b){return a.ca<=b.ca&&a.da<=b.da&&b.$<=a.$&&b.ia<=a.ia}function r(a,b){return b.ca<=a.$&&b.da<=a.ia&&b.$>=a.ca&&b.ia>=a.da}function u(a){return{children:a,height:1,ib:!0,ca:Infinity,da:Infinity,$:-Infinity,ia:-Infinity}}function x(a,b,c,d,e){for(var f=[b,c],g;f.length;)c=f.pop(),b=f.pop(),c-b<=d||(g=b+Math.ceil((c-b)/d/2)*d,B(a,g,b,c,e),f.push(b,g,
+g,c))}var B=b;e.prototype={all:function(){return this.$g(this.data,[])},search:function(a){var b=this.data,c=[],d=this.wb;if(!r(a,b))return c;for(var e=[],f,g,h,l;b;){f=0;for(g=b.children.length;f<g;f++)h=b.children[f],l=b.ib?d(h):h,r(a,l)&&(b.ib?c.push(h):q(a,l)?this.$g(h,c):e.push(h));b=e.pop()}return c},load:function(a){if(!a||!a.length)return this;if(a.length<this.fh){for(var b=0,c=a.length;b<c;b++)this.Ca(a[b]);return this}a=this.bh(a.slice(),0,a.length-1,0);this.data.children.length?this.data.height===
+a.height?this.hh(this.data,a):(this.data.height<a.height&&(b=this.data,this.data=a,a=b),this.eh(a,this.data.height-a.height-1,!0)):this.data=a;return this},Ca:function(a){a&&this.eh(a,this.data.height-1);return this},clear:function(){this.data=u([]);return this},remove:function(a,b){if(!a)return this;for(var c=this.data,d=this.wb(a),e=[],f=[],g,h,l,m;c||e.length;){c||(c=e.pop(),h=e[e.length-1],g=f.pop(),m=!0);if(c.ib){a:{l=a;var n=c.children,p=b;if(p){for(var r=0;r<n.length;r++)if(p(l,n[r])){l=r;
+break a}l=-1}else l=n.indexOf(l)}if(-1!==l){c.children.splice(l,1);e.push(c);this.ck(e);break}}m||c.ib||!q(c,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},wb:function(a){return a},Lf:l,Mf:m,toJSON:function(){return this.data},$g:function(a,b){for(var c=[];a;)a.ib?b.push.apply(b,a.children):c.push.apply(c,a.children),a=c.pop();return b},bh:function(a,b,c,d){var e=c-b+1,g=this.Hf;if(e<=g){var h=u(a.slice(b,c+1));f(h,this.wb);return h}d||(d=Math.ceil(Math.log(e)/
+Math.log(g)),g=Math.ceil(e/Math.pow(g,d-1)));h=u([]);h.ib=!1;h.height=d;var e=Math.ceil(e/g),g=e*Math.ceil(Math.sqrt(g)),l;for(x(a,b,c,g,this.Lf);b<=c;b+=g){var m=Math.min(b+g-1,c);x(a,b,m,e,this.Mf);for(l=b;l<=m;l+=e){var n=Math.min(l+e-1,m);h.children.push(this.bh(a,l,n,d-1))}}f(h,this.wb);return h},bk:function(a,b,c,d){for(var e,f,g,h,l,m,p,q;;){d.push(b);if(b.ib||d.length-1===c)break;p=q=Infinity;e=0;for(f=b.children.length;e<f;e++)g=b.children[e],l=n(g),m=(Math.max(g.$,a.$)-Math.min(g.ca,a.ca))*
+(Math.max(g.ia,a.ia)-Math.min(g.da,a.da))-l,m<q?(q=m,p=l<p?l:p,h=g):m===q&&l<p&&(p=l,h=g);b=h||b.children[0]}return b},eh:function(a,b,c){var d=this.wb;c=c?a:d(a);var d=[],e=this.bk(c,this.data,b,d);e.children.push(a);for(h(e,c);0<=b;)if(d[b].children.length>this.Hf)this.jk(d,b),b--;else break;this.Zj(c,d,b)},jk:function(a,b){var c=a[b],d=c.children.length,e=this.fh;this.$j(c,e,d);d=this.ak(c,e,d);d=u(c.children.splice(d,c.children.length-d));d.height=c.height;d.ib=c.ib;f(c,this.wb);f(d,this.wb);
+b?a[b-1].children.push(d):this.hh(c,d)},hh:function(a,b){this.data=u([a,b]);this.data.height=a.height+1;this.data.ib=!1;f(this.data,this.wb)},ak:function(a,b,c){var d,e;var f=e=Infinity;for(d=b;d<=c-b;d++){var h=g(a,0,d,this.wb);var l=g(a,d,c,this.wb);var m=Math.max(0,Math.min(h.$,l.$)-Math.max(h.ca,l.ca))*Math.max(0,Math.min(h.ia,l.ia)-Math.max(h.da,l.da));h=n(h)+n(l);if(m<f){f=m;var p=d;e=h<e?h:e}else m===f&&h<e&&(e=h,p=d)}return p},$j:function(a,b,c){var d=a.ib?this.Lf:l,e=a.ib?this.Mf:m,f=this.ah(a,
+b,c,d);b=this.ah(a,b,c,e);f<b&&a.children.sort(d)},ah:function(a,b,c,d){a.children.sort(d);d=this.wb;var e=g(a,0,b,d),f=g(a,c-b,c,d),l=p(e)+p(f),m;for(m=b;m<c-b;m++){var n=a.children[m];h(e,a.ib?d(n):n);l+=p(e)}for(m=c-b-1;m>=b;m--)n=a.children[m],h(f,a.ib?d(n):n),l+=p(f);return l},Zj:function(a,b,c){for(;0<=c;c--)h(b[c],a)},ck:function(a){for(var b=a.length-1,c;0<=b;b--)0===a[b].children.length?0<b?(c=a[b-1].children,c.splice(c.indexOf(a[b]),1)):this.clear():f(a[b],this.wb)},ek:function(a){var b=
+["return a"," - b",";"];this.Lf=new Function("a","b",b.join(a[0]));this.Mf=new Function("a","b",b.join(a[1]));this.wb=new Function("a","return {minX: a"+a[0]+", minY: a"+a[1]+", maxX: a"+a[2]+", maxY: a"+a[3]+"};")}};a["default"]=e})(Fj.$d=Fj.$d||{});Fj.$d=Fj.$d.default;function Gj(a){this.a=Fj.$d(a);this.b={}}k=Gj.prototype;k.Ca=function(a,b){a={ca:a[0],da:a[1],$:a[2],ia:a[3],value:b};this.a.Ca(a);this.b[w(b)]=a};k.load=function(a,b){for(var c=Array(b.length),d=0,e=b.length;d<e;d++){var f=a[d],g=b[d],f={ca:f[0],da:f[1],$:f[2],ia:f[3],value:g};c[d]=f;this.b[w(g)]=f}this.a.load(c)};k.remove=function(a){a=w(a);var b=this.b[a];delete this.b[a];return null!==this.a.remove(b)};function Hj(a,b,c){var d=a.b[w(c)];bb([d.ca,d.da,d.$,d.ia],b)||(a.remove(c),a.Ca(b,c))}
+function Ij(a){return a.a.all().map(function(a){return a.value})}function Jj(a,b){return a.a.search({ca:b[0],da:b[1],$:b[2],ia:b[3]}).map(function(a){return a.value})}k.forEach=function(a,b){return Kj(Ij(this),a,b)};function Lj(a,b,c,d){return Kj(Jj(a,b),c,d)}function Kj(a,b,c){for(var d,e=0,f=a.length;e<f&&!(d=b.call(c,a[e]));e++);return d}k.clear=function(){this.a.clear();this.b={}};k.G=function(a){var b=this.a.data;return Xa(b.ca,b.da,b.$,b.ia,a)};
+k.concat=function(a){this.a.load(a.a.all());for(var b in a.b)this.b[b|0]=a.b[b|0]};function Mj(a,b){vi.call(this,0,b);this.g=new cj(0,b);this.v=null;this.u=[];this.c=[];this.j={fillColor:null,s:!1}}v(Mj,vi);
+function Nj(a,b,c,d){var e=new xj,f=new Gj;b=Oj(a,b,d,e,f,!0);if(c.length){var g,h=[];var l=0;for(g=c.length;l<g;++l){var m={list:new xj,$:void 0,Mg:new Gj};h.push(m);m.$=Oj(a,c[l],d,m.list,m.Mg,!1)}h.sort(function(a,b){return b.$[0]===a.$[0]?a.$[1]-b.$[1]:b.$[0]-a.$[0]});for(l=0;l<h.length;++l){c=h[l].list;g=d=zj(c);do{if(Pj(g,f).length){var n=!0;break}g=Aj(c)}while(d!==g);n||(Qj(c,h[l].Mg,!0),Rj(c,h[l].$[0],e,b[0],f)&&(f.concat(h[l].Mg),Qj(e,f,!1)))}}else Qj(e,f,!1);Sj(a,e,f)}
+function Oj(a,b,c,d,e,f){var g,h=a.a.length/2,l,m=[],n=[];if(f===Sf(b,0,b.length,c)){var p=l=Tj(a,b[0],b[1],h++);f=b[0];var q=b[1];var r=c;for(g=b.length;r<g;r+=c){var u=Tj(a,b[r],b[r+1],h++);n.push(Uj(p,u,d));m.push([Math.min(p.x,u.x),Math.min(p.y,u.y),Math.max(p.x,u.x),Math.max(p.y,u.y)]);b[r]>f&&(f=b[r],q=b[r+1]);p=u}}else for(r=b.length-c,p=l=Tj(a,b[r],b[r+1],h++),f=b[r],q=b[r+1],r-=c,g=0;r>=g;r-=c)u=Tj(a,b[r],b[r+1],h++),n.push(Uj(p,u,d)),m.push([Math.min(p.x,u.x),Math.min(p.y,u.y),Math.max(p.x,
+u.x),Math.max(p.y,u.y)]),b[r]>f&&(f=b[r],q=b[r+1]),p=u;n.push(Uj(u,l,d));m.push([Math.min(p.x,u.x),Math.min(p.y,u.y),Math.max(p.x,u.x),Math.max(p.y,u.y)]);e.load(m,n);return[f,q]}function Qj(a,b,c){var d=zj(a),e=d,f=Aj(a),g=!1;do{var h=c?Bi(f.W.x,f.W.y,e.W.x,e.W.y,e.aa.x,e.aa.y):Bi(e.aa.x,e.aa.y,e.W.x,e.W.y,f.W.x,f.W.y);void 0===h?(Vj(e,f,a,b),g=!0,f===d&&(d=Bj(a)),f=e,Cj(a)):e.W.Fb!==h&&(e.W.Fb=h,g=!0);e=f;f=Aj(a)}while(e!==d);return g}
+function Rj(a,b,c,d,e){for(var f=zj(a);f.W.x!==b;)f=Aj(a);b=f.W;d={x:d,y:b.y,hb:-1};var g=Infinity,h;var l=Pj({aa:b,W:d},e,!0);var m=0;for(h=l.length;m<h;++m){var n=l[m],p=Wj(b,d,n.aa,n.W,!0),q=Math.abs(b.x-p[0]);if(q<g&&void 0!==Bi(b.x,b.y,n.aa.x,n.aa.y,n.W.x,n.W.y)){g=q;var r={x:p[0],y:p[1],hb:-1};f=n}}if(Infinity===g)return!1;l=f.W;if(0<g&&(f=Xj(b,r,f.W,e),f.length))for(r=Infinity,m=0,h=f.length;m<h;++m)if(g=f[m],n=Math.atan2(b.y-g.y,d.x-g.x),n<r||n===r&&g.x<l.x)r=n,l=g;for(f=zj(c);f.W.x!==l.x||
+f.W.y!==l.y;)f=Aj(c);d={x:b.x,y:b.y,hb:b.hb,Fb:void 0};m={x:f.W.x,y:f.W.y,hb:f.W.hb,Fb:void 0};Bj(a).aa=d;Uj(b,f.W,a,e);Uj(m,d,a,e);f.W=m;a.f&&a.b&&(a.i=a.b,a.a=a.b.ub);c.concat(a);return!0}
+function Sj(a,b,c){for(var d=!1,e=Yj(b,c);3<b.c;)if(e){if(!Zj(a,b,c,e,d)&&!Qj(b,c,d)&&!ak(a,b,c,!0))break}else if(!Zj(a,b,c,e,d)&&!Qj(b,c,d)&&!ak(a,b,c))if(e=Yj(b,c)){var d=b,f=2*d.c,g=Array(f),h=zj(d),l=h,m=0;do g[m++]=l.aa.x,g[m++]=l.aa.y,l=Aj(d);while(l!==h);d=!Sf(g,0,f,2);Qj(b,c,d)}else{e=a;d=b;f=g=zj(d);do{h=Pj(f,c);if(h.length){g=h[0];h=Wj(f.aa,f.W,g.aa,g.W);h=Tj(e,h[0],h[1],e.a.length/2);l=new xj;m=new Gj;Uj(h,f.W,l,m);f.W=h;Hj(c,[Math.min(f.aa.x,h.x),Math.min(f.aa.y,h.y),Math.max(f.aa.x,h.x),
+Math.max(f.aa.y,h.y)],f);for(f=Aj(d);f!==g;)Uj(f.aa,f.W,l,m),c.remove(f),yj(d),f=Ej(d);Uj(g.aa,h,l,m);g.aa=h;Hj(c,[Math.min(g.W.x,h.x),Math.min(g.W.y,h.y),Math.max(g.W.x,h.x),Math.max(g.W.y,h.y)],g);Qj(d,c,!1);Sj(e,d,c);Qj(l,m,!1);Sj(e,l,m);break}f=Aj(d)}while(f!==g);break}3===b.c&&(e=a.b.length,a.b[e++]=Dj(b).aa.hb,a.b[e++]=Ej(b).aa.hb,a.b[e++]=Bj(b).aa.hb)}
+function Zj(a,b,c,d,e){var f=a.b.length,g=zj(b),h=Dj(b),l=g,m=Aj(b),n=Bj(b),p=!1;do{var q=l.aa;var r=l.W;var u=m.W;if(!1===r.Fb){var x=e?bk(n.W,u,r,q,h.aa):bk(h.aa,q,r,u,n.W);!d&&Pj({aa:q,W:u},c).length||!x||Xj(q,r,u,c,!0).length||!d&&!1!==q.Fb&&!1!==u.Fb&&Sf([h.aa.x,h.aa.y,q.x,q.y,r.x,r.y,u.x,u.y,n.W.x,n.W.y],0,10,2)!==!e||(a.b[f++]=q.hb,a.b[f++]=r.hb,a.b[f++]=u.hb,Vj(l,m,b,c),m===g&&(g=n),p=!0)}h=Dj(b);l=Ej(b);m=Aj(b);n=Bj(b)}while(l!==g&&3<b.c);return p}
+function ak(a,b,c,d){var e=zj(b);Aj(b);var f=e,g=Aj(b),h=!1;do{var l=Wj(f.aa,f.W,g.aa,g.W,d);if(l){var h=a.b.length,m=a.a.length/2,n=Cj(b);yj(b);c.remove(n);var p=n===e;d?(l[0]===f.aa.x&&l[1]===f.aa.y?(Cj(b),l=f.aa,g.aa=l,c.remove(f),p=p||f===e):(l=g.W,f.W=l,c.remove(g),p=p||g===e),yj(b)):(l=Tj(a,l[0],l[1],m),f.W=l,g.aa=l,Hj(c,[Math.min(f.aa.x,f.W.x),Math.min(f.aa.y,f.W.y),Math.max(f.aa.x,f.W.x),Math.max(f.aa.y,f.W.y)],f),Hj(c,[Math.min(g.aa.x,g.W.x),Math.min(g.aa.y,g.W.y),Math.max(g.aa.x,g.W.x),
+Math.max(g.aa.y,g.W.y)],g));a.b[h++]=n.aa.hb;a.b[h++]=n.W.hb;a.b[h++]=l.hb;h=!0;if(p)break}f=Dj(b);g=Aj(b)}while(f!==e);return h}function Yj(a,b){var c=zj(a),d=c;do{if(Pj(d,b).length)return!1;d=Aj(a)}while(d!==c);return!0}function Tj(a,b,c,d){var e=a.a.length;a.a[e++]=b;a.a[e++]=c;return{x:b,y:c,hb:d,Fb:void 0}}
+function Uj(a,b,c,d){var e={aa:a,W:b},f={ub:void 0,next:void 0,data:e},g=c.b;if(g){var h=g.next;f.ub=g;f.next=h;g.next=f;h&&(h.ub=f);g===c.a&&(c.a=f)}else c.i=f,c.a=f,c.f&&(f.next=f,f.ub=f);c.b=f;c.c++;d&&d.Ca([Math.min(a.x,b.x),Math.min(a.y,b.y),Math.max(a.x,b.x),Math.max(a.y,b.y)],e);return e}function Vj(a,b,c,d){Ej(c)===b&&(yj(c),a.W=b.W,d.remove(b),Hj(d,[Math.min(a.aa.x,a.W.x),Math.min(a.aa.y,a.W.y),Math.max(a.aa.x,a.W.x),Math.max(a.aa.y,a.W.y)],a))}
+function Xj(a,b,c,d,e){var f,g,h=[],l=Jj(d,[Math.min(a.x,b.x,c.x),Math.min(a.y,b.y,c.y),Math.max(a.x,b.x,c.x),Math.max(a.y,b.y,c.y)]);d=0;for(f=l.length;d<f;++d)for(g in l[d]){var m=l[d][g];"object"!==typeof m||e&&!m.Fb||m.x===a.x&&m.y===a.y||m.x===b.x&&m.y===b.y||m.x===c.x&&m.y===c.y||-1!==h.indexOf(m)||!Mf([a.x,a.y,b.x,b.y,c.x,c.y],0,6,2,m.x,m.y)||h.push(m)}return h}
+function Pj(a,b,c){var d=a.aa,e=a.W;b=Jj(b,[Math.min(d.x,e.x),Math.min(d.y,e.y),Math.max(d.x,e.x),Math.max(d.y,e.y)]);var f=[],g;var h=0;for(g=b.length;h<g;++h){var l=b[h];a!==l&&(c||l.aa!==e||l.W!==d)&&Wj(d,e,l.aa,l.W,c)&&f.push(l)}return f}
+function Wj(a,b,c,d,e){var f=(d.y-c.y)*(b.x-a.x)-(d.x-c.x)*(b.y-a.y);if(f&&(d=((d.x-c.x)*(a.y-c.y)-(d.y-c.y)*(a.x-c.x))/f,c=((b.x-a.x)*(a.y-c.y)-(b.y-a.y)*(a.x-c.x))/f,!e&&d>Ci&&d<1-Ci&&c>Ci&&c<1-Ci||e&&0<=d&&1>=d&&0<=c&&1>=c))return[a.x+d*(b.x-a.x),a.y+d*(b.y-a.y)]}
+function bk(a,b,c,d,e){if(void 0===b.Fb||void 0===d.Fb)return!1;var f=(c.x-d.x)*(b.y-d.y)>(c.y-d.y)*(b.x-d.x);e=(e.x-d.x)*(b.y-d.y)<(e.y-d.y)*(b.x-d.x);a=(a.x-b.x)*(d.y-b.y)>(a.y-b.y)*(d.x-b.x);c=(c.x-b.x)*(d.y-b.y)<(c.y-b.y)*(d.x-b.x);b=b.Fb?c||a:c&&a;return(d.Fb?e||f:e&&f)&&b}k=Mj.prototype;
+k.pc=function(a,b){var c=a.c,d=a.qa(),e=this.b.length,f=this.g.b.length;a=a.ga();var g,h,l;var m=h=0;for(g=c.length;m<g;++m){var n=c[m];if(0<n.length){var p=qf(a,h,n[0],d,-this.origin[0],-this.origin[1]);if(p.length){var q=[];h=1;for(l=n.length;h<l;++h)if(n[h]!==n[h-1]){var r=qf(a,n[h-1],n[h],d,-this.origin[0],-this.origin[1]);q.push(r)}oj(this.g,p,q,d);Nj(this,p,q,d)}}h=n[n.length-1]}this.b.length>e&&(this.i.push(e),this.f.push(b),this.j.s&&(this.c.push(e),this.j.s=!1));this.g.b.length>f&&pj(this.g,
+b,f)};k.rc=function(a,b){var c=a.Bb(),d=a.qa();if(0<c.length){a=a.ga().map(Number);var e=qf(a,0,c[0],d,-this.origin[0],-this.origin[1]);if(e.length){var f=[],g;var h=1;for(g=c.length;h<g;++h)if(c[h]!==c[h-1]){var l=qf(a,c[h-1],c[h],d,-this.origin[0],-this.origin[1]);f.push(l)}this.i.push(this.b.length);this.f.push(b);this.j.s&&(this.c.push(this.b.length),this.j.s=!1);pj(this.g,b);oj(this.g,e,f,d);Nj(this,e,f,d)}}};
+k.Db=function(a){this.l=new Di(this.a);this.o=new Di(this.b);this.i.push(this.b.length);this.g.Db(a);!this.c.length&&0<this.u.length&&(this.u=[]);this.b=this.a=null};k.Eb=function(a){var b=this.l,c=this.o,d=this.g.Eb(a);return function(){Gi(a,b);Gi(a,c);d()}};k.rf=function(a,b){var c=Hi(b,sj,uj);if(this.v)var d=this.v;else this.v=d=new vj(a,c);b.Qc(c);a.enableVertexAttribArray(d.b);a.vertexAttribPointer(d.b,2,5126,!1,8,0);return d};k.sf=function(a,b){a.disableVertexAttribArray(b.b)};
+k.Od=function(a,b,c,d){var e=a.getParameter(a.DEPTH_FUNC),f=a.getParameter(a.DEPTH_WRITEMASK);d||(a.enable(a.DEPTH_TEST),a.depthMask(!0),a.depthFunc(a.NOTEQUAL));if(wb(c)){var g=this.i[this.i.length-1];for(c=this.c.length-1;0<=c;--c){var h=this.c[c];var l=this.u[c];a.uniform4fv(this.v.B,l);xi(a,b,h,g);g=h}}else{var m=this.i.length-2;l=g=this.i[m+1];for(h=this.c.length-1;0<=h;--h){var n=this.u[h];a.uniform4fv(this.v.B,n);for(n=this.c[h];0<=m&&this.i[m]>=n;){var p=this.i[m];var q=this.f[m];q=w(q).toString();
+c[q]&&(g!==l&&(xi(a,b,g,l),a.clear(a.DEPTH_BUFFER_BIT)),l=p);m--;g=p}g!==l&&(xi(a,b,g,l),a.clear(a.DEPTH_BUFFER_BIT));g=l=n}}d||(a.disable(a.DEPTH_TEST),a.clear(a.DEPTH_BUFFER_BIT),a.depthMask(f),a.depthFunc(e))};
+k.ve=function(a,b,c,d,e){var f,g;var h=this.i.length-2;var l=this.i[h+1];for(f=this.c.length-1;0<=f;--f){var m=this.u[f];a.uniform4fv(this.v.B,m);for(g=this.c[f];0<=h&&this.i[h]>=g;){m=this.i[h];var n=this.f[h];var p=w(n).toString();if(void 0===c[p]&&n.V()&&(void 0===e||qb(e,n.V().G()))&&(a.clear(a.COLOR_BUFFER_BIT|a.DEPTH_BUFFER_BIT),xi(a,b,m,l),l=d(n)))return l;h--;l=m}}};
+k.Ma=function(a,b){a=a?a.b:[0,0,0,0];a instanceof CanvasGradient||a instanceof CanvasPattern?a=yi:a=ed(a).map(function(a,b){return 3!=b?a/255:a})||yi;this.j.fillColor&&pa(a,this.j.fillColor)||(this.j.fillColor=a,this.j.s=!0,this.u.push(a));b?this.g.Ma(null,b):this.g.Ma(null,new wj({color:[0,0,0,0],lineWidth:0}))};function ck(){}ck.prototype.La=function(){};function dk(a,b,c){this.f=b;this.g=a;this.c=c;this.a={}}v(dk,ki);function ek(a,b){var c=[],d;for(d in a.a){var e=a.a[d],f;for(f in e)c.push(e[f].Eb(b))}return function(){for(var a=c.length,b,d=0;d<a;d++)b=c[d].apply(this,arguments);return b}}function fk(a,b){for(var c in a.a){var d=a.a[c],e;for(e in d)d[e].Db(b)}}dk.prototype.b=function(a,b){var c=void 0!==a?a.toString():"0";a=this.a[c];void 0===a&&(a={},this.a[c]=a);c=a[b];void 0===c&&(c=new gk[b](this.g,this.f),a[b]=c);return c};
+dk.prototype.i=function(){return wb(this.a)};dk.prototype.La=function(a,b,c,d,e,f,g,h){var l=Object.keys(this.a).map(Number);l.sort(ia);var m,n;var p=0;for(m=l.length;p<m;++p){var q=this.a[l[p].toString()];var r=0;for(n=ji.length;r<n;++r){var u=q[ji[r]];void 0!==u&&u.La(a,b,c,d,e,f,g,h,void 0,!1)}}};
+function hk(a,b,c,d,e,f,g,h,l,m,n){var p=ik,q=Object.keys(a.a).map(Number);q.sort(function(a,b){return b-a});var r,u;var x=0;for(r=q.length;x<r;++x){var B=a.a[q[x].toString()];for(u=ji.length-1;0<=u;--u){var E=B[ji[u]];if(void 0!==E&&(E=E.La(b,c,d,e,p,f,g,h,l,m,n)))return E}}}
+dk.prototype.Ea=function(a,b,c,d,e,f,g,h,l,m){var n=b.b;n.bindFramebuffer(n.FRAMEBUFFER,Pi(b));var p;void 0!==this.c&&(p=Qa(Za(a),d*this.c));return hk(this,b,a,d,e,g,h,l,function(a){var b=new Uint8Array(4);n.readPixels(0,0,1,1,n.RGBA,n.UNSIGNED_BYTE,b);if(0<b[3]&&(a=m(a)))return a},!0,p)};
+function jk(a,b,c,d,e,f,g,h){var l=c.b;l.bindFramebuffer(l.FRAMEBUFFER,Pi(c));return void 0!==hk(a,c,b,d,e,f,g,h,function(){var a=new Uint8Array(4);l.readPixels(0,0,1,1,l.RGBA,l.UNSIGNED_BYTE,a);return 0<a[3]},!1)}var ik=[1,1],gk={Circle:Fi,Image:Ui,LineString:cj,Polygon:Mj,Text:ck};function kk(a,b,c,d,e,f,g){this.b=a;this.i=b;this.a=f;this.c=g;this.j=e;this.g=d;this.f=c;this.o=this.l=this.v=null}v(kk,Wh);k=kk.prototype;k.rd=function(a){this.Ma(a.Fa(),a.Ga());this.Ub(a.Y())};
+k.zb=function(a){switch(a.U()){case "Point":this.qc(a,null);break;case "LineString":this.mc(a,null);break;case "Polygon":this.rc(a,null);break;case "MultiPoint":this.oc(a,null);break;case "MultiLineString":this.nc(a,null);break;case "MultiPolygon":this.pc(a,null);break;case "GeometryCollection":this.ue(a,null);break;case "Circle":this.Zb(a,null)}};k.te=function(a,b){(a=(0,b.Za)(a))&&qb(this.a,a.G())&&(this.rd(b),this.zb(a))};k.ue=function(a){a=a.a;var b;var c=0;for(b=a.length;c<b;++c)this.zb(a[c])};
+k.qc=function(a,b){var c=this.b,d=(new dk(1,this.a)).b(0,"Image");d.Ub(this.v);d.qc(a,b);d.Db(c);d.La(this.b,this.i,this.f,this.g,this.j,this.c,1,{},void 0,!1);d.Eb(c)()};k.oc=function(a,b){var c=this.b,d=(new dk(1,this.a)).b(0,"Image");d.Ub(this.v);d.oc(a,b);d.Db(c);d.La(this.b,this.i,this.f,this.g,this.j,this.c,1,{},void 0,!1);d.Eb(c)()};
+k.mc=function(a,b){var c=this.b,d=(new dk(1,this.a)).b(0,"LineString");d.Ma(null,this.o);d.mc(a,b);d.Db(c);d.La(this.b,this.i,this.f,this.g,this.j,this.c,1,{},void 0,!1);d.Eb(c)()};k.nc=function(a,b){var c=this.b,d=(new dk(1,this.a)).b(0,"LineString");d.Ma(null,this.o);d.nc(a,b);d.Db(c);d.La(this.b,this.i,this.f,this.g,this.j,this.c,1,{},void 0,!1);d.Eb(c)()};
+k.rc=function(a,b){var c=this.b,d=(new dk(1,this.a)).b(0,"Polygon");d.Ma(this.l,this.o);d.rc(a,b);d.Db(c);d.La(this.b,this.i,this.f,this.g,this.j,this.c,1,{},void 0,!1);d.Eb(c)()};k.pc=function(a,b){var c=this.b,d=(new dk(1,this.a)).b(0,"Polygon");d.Ma(this.l,this.o);d.pc(a,b);d.Db(c);d.La(this.b,this.i,this.f,this.g,this.j,this.c,1,{},void 0,!1);d.Eb(c)()};
+k.Zb=function(a,b){var c=this.b,d=(new dk(1,this.a)).b(0,"Circle");d.Ma(this.l,this.o);d.Zb(a,b);d.Db(c);d.La(this.b,this.i,this.f,this.g,this.j,this.c,1,{},void 0,!1);d.Eb(c)()};k.Ub=function(a){this.v=a};k.Ma=function(a,b){this.l=a;this.o=b};function lk(){this.c=0;this.b={};this.i=this.a=null}k=lk.prototype;k.clear=function(){this.c=0;this.b={};this.i=this.a=null};k.forEach=function(a,b){for(var c=this.a;c;)a.call(b,c.Yc,c.uc,this),c=c.Nb};k.get=function(a){a=this.b[a];xa(!!a,15);if(a===this.i)return a.Yc;a===this.a?(this.a=this.a.Nb,this.a.vd=null):(a.Nb.vd=a.vd,a.vd.Nb=a.Nb);a.Nb=null;a.vd=this.i;this.i=this.i.Nb=a;return a.Yc};
+k.pop=function(){var a=this.a;delete this.b[a.uc];a.Nb&&(a.Nb.vd=null);this.a=a.Nb;this.a||(this.i=null);--this.c;return a.Yc};k.replace=function(a,b){this.get(a);this.b[a].Yc=b};k.set=function(a,b){xa(!(a in this.b),16);b={uc:a,Nb:null,vd:this.i,Yc:b};this.i?this.i.Nb=b:this.a=b;this.i=b;this.b[a]=b;++this.c};function mk(a,b){Mh.call(this,0,b);this.b=document.createElement("CANVAS");this.b.style.width="100%";this.b.style.height="100%";this.b.style.display="block";this.b.className="ol-unselectable";a.insertBefore(this.b,a.childNodes[0]||null);this.S=this.D=0;this.C=jd();this.l=!0;this.i=Ld(this.b,{antialias:!0,depth:!0,failIfMajorPerformanceCaveat:!0,preserveDrawingBuffer:!1,stencil:!0});this.f=new Oi(this.b,this.i);y(this.b,"webglcontextlost",this.Yn,this);y(this.b,"webglcontextrestored",this.Zn,this);
+this.a=new lk;this.u=null;this.j=new Ke(function(a){var b=a[1];a=a[2];var c=b[0]-this.u[0],b=b[1]-this.u[1];return 65536*Math.log(a)+Math.sqrt(c*c+b*b)/a}.bind(this),function(a){return a[0].bb()});this.B=function(){if(this.j.b.length){Oe(this.j);var a=Le(this.j);nk(this,a[0],a[3],a[4])}return!1}.bind(this);this.g=0;ok(this)}v(mk,Mh);
+function nk(a,b,c,d){var e=a.i,f=b.bb();if(a.a.b.hasOwnProperty(f))a=a.a.get(f),e.bindTexture(3553,a.Ib),9729!=a.Ph&&(e.texParameteri(3553,10240,9729),a.Ph=9729),9729!=a.Rh&&(e.texParameteri(3553,10241,9729),a.Rh=9729);else{var g=e.createTexture();e.bindTexture(3553,g);if(0<d){var h=a.C.canvas,l=a.C;a.D!==c[0]||a.S!==c[1]?(h.width=c[0],h.height=c[1],a.D=c[0],a.S=c[1]):l.clearRect(0,0,c[0],c[1]);l.drawImage(b.Y(),d,d,c[0],c[1],0,0,c[0],c[1]);e.texImage2D(3553,0,6408,6408,5121,h)}else e.texImage2D(3553,
+0,6408,6408,5121,b.Y());e.texParameteri(3553,10240,9729);e.texParameteri(3553,10241,9729);e.texParameteri(3553,10242,33071);e.texParameteri(3553,10243,33071);a.a.set(f,{Ib:g,Ph:9729,Rh:9729})}}function pk(a,b,c){var d=a.o;if(Rc(d,b)){a=a.f;var e=c.viewState;d.b(new Rh(b,new kk(a,e.center,e.resolution,e.rotation,c.size,c.extent,c.pixelRatio),c,null,a))}}k=mk.prototype;k.ka=function(){var a=this.i;a.isContextLost()||this.a.forEach(function(b){b&&a.deleteTexture(b.Ib)});Nc(this.f);Mh.prototype.ka.call(this)};
+k.xk=function(a,b){a=this.i;for(var c;1024<this.a.c-this.g;){if(c=this.a.a.Yc)a.deleteTexture(c.Ib);else if(+this.a.a.uc==b.index)break;else--this.g;this.a.pop()}};k.U=function(){return"webgl"};k.Yn=function(a){a.preventDefault();this.a.clear();this.g=0;a=this.c;for(var b in a)a[b].mg()};k.Zn=function(){ok(this);this.o.render()};function ok(a){a=a.i;a.activeTexture(33984);a.blendFuncSeparate(770,771,1,771);a.disable(2884);a.disable(2929);a.disable(3089);a.disable(2960)}
+k.Jg=function(a){var b=this.f,c=this.i;if(c.isContextLost())return!1;if(!a)return this.l&&(this.b.style.display="none",this.l=!1),!1;this.u=a.focus;this.a.set((-a.index).toString(),null);++this.g;pk(this,"precompose",a);var d=[],e=a.layerStatesArray;qa(e);var f=a.viewState.resolution,g;var h=0;for(g=e.length;h<g;++h){var l=e[h];if(xh(l,f)&&"ready"==l.yj){var m=Ph(this,l.layer);m.ng(a,l,b)&&d.push(l)}}e=a.size[0]*a.pixelRatio;f=a.size[1]*a.pixelRatio;if(this.b.width!=e||this.b.height!=f)this.b.width=
+e,this.b.height=f;c.bindFramebuffer(36160,null);c.clearColor(0,0,0,0);c.clear(16384);c.enable(3042);c.viewport(0,0,this.b.width,this.b.height);h=0;for(g=d.length;h<g;++h)l=d[h],m=Ph(this,l.layer),m.Gi(a,l,b);this.l||(this.b.style.display="",this.l=!0);Nh(a);1024<this.a.c-this.g&&a.postRenderFunctions.push(this.xk.bind(this));this.j.b.length&&(a.postRenderFunctions.push(this.B),a.animate=!0);pk(this,"postcompose",a);Qh(this,a);a.postRenderFunctions.push(Oh)};
+k.Ea=function(a,b,c,d,e,f,g){if(this.i.isContextLost())return!1;var h=b.viewState,l=b.layerStatesArray,m;for(m=l.length-1;0<=m;--m){var n=l[m];var p=n.layer;if(xh(n,h.resolution)&&f.call(g,p)&&(n=Ph(this,p).Ea(a,b,c,d,e)))return n}};k.Ei=function(a,b,c,d,e){c=!1;if(this.i.isContextLost())return!1;var f=b.viewState,g=b.layerStatesArray,h;for(h=g.length-1;0<=h;--h){var l=g[h],m=l.layer;if(xh(l,f.resolution)&&d.call(e,m)&&(c=Ph(this,m).Ue(a,b)))return!0}return c};
+k.Di=function(a,b,c,d,e){if(this.i.isContextLost())return!1;var f=b.viewState,g=b.layerStatesArray,h;for(h=g.length-1;0<=h;--h){var l=g[h];var m=l.layer;if(xh(l,f.resolution)&&e.call(d,m)&&(l=Ph(this,m).lg(a,b,c,d)))return l}};var qk=["canvas","webgl"];
+function G(a){Tc.call(this);var b=rk(a);this.Cf=void 0!==a.loadTilesWhileAnimating?a.loadTilesWhileAnimating:!1;this.Df=void 0!==a.loadTilesWhileInteracting?a.loadTilesWhileInteracting:!1;this.If=void 0!==a.pixelRatio?a.pixelRatio:Sd;this.yf=b.logos;this.pa=function(){this.j=void 0;this.Sp.call(this,Date.now())}.bind(this);this.Yb=Bh();this.Jf=Bh();this.ad=0;this.I=this.R=this.T=this.g=this.c=null;this.a=document.createElement("DIV");this.a.className="ol-viewport"+(Xd?" ol-touch":"");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";this.C=document.createElement("DIV");this.C.className="ol-overlaycontainer";this.a.appendChild(this.C);this.D=document.createElement("DIV");this.D.className="ol-overlaycontainer-stopevent";for(var c="click dblclick mousedown touchstart MSPointerDown pointerdown mousewheel wheel".split(" "),d=0,e=c.length;d<e;++d)y(this.D,c[d],Pc);this.a.appendChild(this.D);
+this.Sa=new Fe(this,a.moveTolerance);for(var f in de)y(this.Sa,de[f],this.Ih,this);this.va=b.keyboardEventTarget;this.u=null;y(this.a,"wheel",this.ld,this);y(this.a,"mousewheel",this.ld,this);this.l=b.controls;this.o=b.interactions;this.v=b.overlays;this.rg={};this.B=new b.Up(this.a,this);this.na=null;this.xb=[];this.$a=new Pe(this.ql.bind(this),this.Wl.bind(this));this.fa={};y(this,Vc("layergroup"),this.El,this);y(this,Vc("view"),this.Xl,this);y(this,Vc("size"),this.Tl,this);y(this,Vc("target"),
+this.Vl,this);this.H(b.values);this.l.forEach(function(a){a.setMap(this)},this);y(this.l,"add",function(a){a.element.setMap(this)},this);y(this.l,"remove",function(a){a.element.setMap(null)},this);this.o.forEach(function(a){a.setMap(this)},this);y(this.o,"add",function(a){a.element.setMap(this)},this);y(this.o,"remove",function(a){a.element.setMap(null)},this);this.v.forEach(this.kh,this);y(this.v,"add",function(a){this.kh(a.element)},this);y(this.v,"remove",function(a){var b=a.element.g;void 0!==
+b&&delete this.rg[b.toString()];a.element.setMap(null)},this)}v(G,Tc);k=G.prototype;k.kk=function(a){this.l.push(a)};k.lk=function(a){this.o.push(a)};k.ih=function(a){this.Kc().qd().push(a)};k.jh=function(a){this.v.push(a)};k.kh=function(a){var b=a.g;void 0!==b&&(this.rg[b.toString()]=a);a.setMap(this)};
+k.ka=function(){Nc(this.Sa);Nc(this.B);Kc(this.a,"wheel",this.ld,this);Kc(this.a,"mousewheel",this.ld,this);this.f&&(window.removeEventListener("resize",this.f,!1),this.f=void 0);this.j&&(cancelAnimationFrame(this.j),this.j=void 0);this.Le(null);Tc.prototype.ka.call(this)};k.we=function(a,b,c){if(this.c)return a=this.Wa(a),c=c?c:{},this.B.Ea(a,this.c,void 0!==c.hitTolerance?c.hitTolerance*this.c.pixelRatio:0,b,null,c.layerFilter?c.layerFilter:mf,null)};
+k.Im=function(a,b,c,d,e){if(this.c)return this.B.Di(a,this.c,b,void 0!==c?c:null,d?d:mf,void 0!==e?e:null)};k.Yl=function(a,b){if(!this.c)return!1;a=this.Wa(a);b=b?b:{};return this.B.Ei(a,this.c,void 0!==b.hitTolerance?b.hitTolerance*this.c.pixelRatio:0,b.layerFilter?b.layerFilter:mf,null)};k.Tf=function(a){return this.Wa(this.xe(a))};k.xe=function(a){var b=this.a.getBoundingClientRect();a=a.changedTouches?a.changedTouches[0]:a;return[a.clientX-b.left,a.clientY-b.top]};k.ag=function(){return this.get("target")};
+k.jd=function(){var a=this.ag();return void 0!==a?"string"===typeof a?document.getElementById(a):a:null};k.Wa=function(a){var b=this.c;return b?Gh(b.pixelToCoordinateTransform,a.slice()):null};k.Lk=function(){return this.l};k.fl=function(){return this.v};k.el=function(a){a=this.rg[a.toString()];return void 0!==a?a:null};k.Sk=function(){return this.o};k.Kc=function(){return this.get("layergroup")};k.Xh=function(){return this.Kc().qd()};
+k.Ja=function(a){var b=this.c;return b?Gh(b.coordinateToPixelTransform,a.slice(0,2)):null};k.Ob=function(){return this.get("size")};k.Z=function(){return this.get("view")};k.sl=function(){return this.a};k.ql=function(a,b,c,d){var e=this.c;if(!(e&&b in e.wantedTiles&&e.wantedTiles[b][a.bb()]))return Infinity;a=c[0]-e.focus[0];c=c[1]-e.focus[1];return 65536*Math.log(d)+Math.sqrt(a*a+c*c)/d};k.ld=function(a,b){a=new Jd(b||a.type,this,a);this.Ih(a)};
+k.Ih=function(a){if(this.c){this.na=a.coordinate;a.frameState=this.c;var b=this.o.a,c;if(!1!==this.b(a))for(c=b.length-1;0<=c;c--){var d=b[c];if(d.c()&&!d.handleEvent(a))break}}};k.Rl=function(){var a=this.c,b=this.$a;if(b.b.length){var c=16,d=c;if(a){var e=a.viewHints;e[0]&&(c=this.Cf?8:0,d=2);e[1]&&(c=this.Df?8:0,d=2)}b.j<c&&(Oe(b),Qe(b,c,d))}b=this.xb;c=0;for(d=b.length;c<d;++c)b[c](this,a);b.length=0};k.Tl=function(){this.render()};
+k.Vl=function(){var a;this.ag()&&(a=this.jd());if(this.u){for(var b=0,c=this.u.length;b<c;++b)Ec(this.u[b]);this.u=null}a?(a.appendChild(this.a),a=this.va?this.va:a,this.u=[y(a,"keydown",this.ld,this),y(a,"keypress",this.ld,this)],this.f||(this.f=this.Ad.bind(this),window.addEventListener("resize",this.f,!1))):(ld(this.a),this.f&&(window.removeEventListener("resize",this.f,!1),this.f=void 0));this.Ad()};k.Wl=function(){this.render()};k.Lh=function(){this.render()};
+k.Xl=function(){this.T&&(Ec(this.T),this.T=null);this.R&&(Ec(this.R),this.R=null);var a=this.Z();a&&(this.a.setAttribute("data-view",w(a)),this.T=y(a,"propertychange",this.Lh,this),this.R=y(a,"change",this.Lh,this));this.render()};k.El=function(){this.I&&(this.I.forEach(Ec),this.I=null);var a=this.Kc();a&&(this.I=[y(a,"propertychange",this.render,this),y(a,"change",this.render,this)]);this.render()};k.Tp=function(){this.j&&cancelAnimationFrame(this.j);this.pa()};
+k.render=function(){void 0===this.j&&(this.j=requestAnimationFrame(this.pa))};k.Mp=function(a){return this.l.remove(a)};k.Np=function(a){return this.o.remove(a)};k.Pp=function(a){return this.Kc().qd().remove(a)};k.Qp=function(a){return this.v.remove(a)};
+k.Sp=function(a){var b,c=this.Ob(),d=this.Z(),e=Oa(),f=this.c,g=null;if(void 0!==c&&0<c[0]&&0<c[1]&&d&&jg(d)){var g=dg(d,this.c?this.c.viewHints:void 0),h=this.Kc().Yf(),l={};var m=0;for(b=h.length;m<b;++m)l[w(h[m].layer)]=h[m];m=d.getState();g={animate:!1,attributions:{},coordinateToPixelTransform:this.Yb,extent:e,focus:this.na?this.na:m.center,index:this.ad++,layerStates:l,layerStatesArray:h,logos:tb({},this.yf),pixelRatio:this.If,pixelToCoordinateTransform:this.Jf,postRenderFunctions:[],size:c,
+skippedFeatureUids:this.fa,tileQueue:this.$a,time:a,usedTiles:{},viewState:m,viewHints:g,wantedTiles:{}}}g&&(g.extent=ob(m.center,m.resolution,m.rotation,g.size,e));this.c=g;this.B.Jg(g);g&&(g.animate&&this.render(),Array.prototype.push.apply(this.xb,g.postRenderFunctions),!f||this.g&&(kb(this.g)||bb(g.extent,this.g))||(this.b(new Id("movestart",this,f)),this.g=Ya(this.g)),!this.g||g.viewHints[0]||g.viewHints[1]||bb(g.extent,this.g)||(this.b(new Id("moveend",this,g)),Ra(g.extent,this.g)));this.b(new Id("postrender",
+this,g));setTimeout(this.Rl.bind(this),0)};k.qj=function(a){this.set("layergroup",a)};k.Qg=function(a){this.set("size",a)};k.Le=function(a){this.set("target",a)};k.iq=function(a){this.set("view",a)};k.xj=function(a){a=w(a).toString();this.fa[a]=!0;this.render()};
+k.Ad=function(){var a=this.jd();if(a){var b=getComputedStyle(a);this.Qg([a.offsetWidth-parseFloat(b.borderLeftWidth)-parseFloat(b.paddingLeft)-parseFloat(b.paddingRight)-parseFloat(b.borderRightWidth),a.offsetHeight-parseFloat(b.borderTopWidth)-parseFloat(b.paddingTop)-parseFloat(b.paddingBottom)-parseFloat(b.borderBottomWidth)])}else this.Qg(void 0)};k.Cj=function(a){a=w(a).toString();delete this.fa[a];this.render()};
+function rk(a){var b=null;void 0!==a.keyboardEventTarget&&(b="string"===typeof a.keyboardEventTarget?document.getElementById(a.keyboardEventTarget):a.keyboardEventTarget);var c={},d={};if(void 0===a.logo||"boolean"===typeof a.logo&&a.logo)d["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"]=
+"https://openlayers.org/";else{var e=a.logo;"string"===typeof e?d[e]="":e instanceof HTMLElement?d[w(e).toString()]=e:e&&(xa("string"==typeof e.href,44),xa("string"==typeof e.src,45),d[e.src]=e.href)}e=a.layers instanceof uh?a.layers:new uh({layers:a.layers});c.layergroup=e;c.target=a.target;c.view=void 0!==a.view?a.view:new F;var e=Mh,f;void 0!==a.renderer?(Array.isArray(a.renderer)?f=a.renderer:"string"===typeof a.renderer?f=[a.renderer]:xa(!1,46),0<=f.indexOf("dom")&&(f=f.concat(qk))):f=qk;var g;
+var h=0;for(g=f.length;h<g;++h){var l=f[h];if("canvas"==l){if(Ud){e=hi;break}}else if("webgl"==l&&Md){e=mk;break}}void 0!==a.controls?Array.isArray(a.controls)?f=new Yc(a.controls.slice()):(xa(a.controls instanceof Yc,47),f=a.controls):f=xd();void 0!==a.interactions?Array.isArray(a.interactions)?h=new Yc(a.interactions.slice()):(xa(a.interactions instanceof Yc,48),h=a.interactions):h=qh();void 0!==a.overlays?Array.isArray(a.overlays)?a=new Yc(a.overlays.slice()):(xa(a.overlays instanceof Yc,49),a=
+a.overlays):a=new Yc;return{controls:f,interactions:h,keyboardEventTarget:b,logos:d,overlays:a,Up:e,values:c}};function sk(a){Tc.call(this);this.g=a.id;this.l=void 0!==a.insertFirst?a.insertFirst:!0;this.v=void 0!==a.stopEvent?a.stopEvent:!0;this.c=document.createElement("DIV");this.c.className="ol-overlay-container ol-selectable";this.c.style.position="absolute";this.autoPan=void 0!==a.autoPan?a.autoPan:!1;this.j=a.autoPanAnimation||{};this.o=void 0!==a.autoPanMargin?a.autoPanMargin:20;this.a={re:"",Ie:"",nf:"",vf:"",visible:!0};this.f=null;y(this,Vc(tk),this.zl,this);y(this,Vc(uk),this.Jl,this);y(this,Vc(vk),
+this.Nl,this);y(this,Vc(wk),this.Pl,this);y(this,Vc(xk),this.Ql,this);void 0!==a.element&&this.lj(a.element);this.rj(void 0!==a.offset?a.offset:[0,0]);this.uj(void 0!==a.positioning?a.positioning:"top-left");void 0!==a.position&&this.Ne(a.position)}v(sk,Tc);k=sk.prototype;k.Rd=function(){return this.get(tk)};k.Jm=function(){return this.g};k.Me=function(){return this.get(uk)};k.Dh=function(){return this.get(vk)};k.Yh=function(){return this.get(wk)};k.Eh=function(){return this.get(xk)};
+k.zl=function(){for(var a=this.c;a.lastChild;)a.removeChild(a.lastChild);(a=this.Rd())&&this.c.appendChild(a)};k.Jl=function(){this.f&&(ld(this.c),Ec(this.f),this.f=null);var a=this.Me();a&&(this.f=y(a,"postrender",this.render,this),yk(this),a=this.v?a.D:a.C,this.l?a.insertBefore(this.c,a.childNodes[0]||null):a.appendChild(this.c))};k.render=function(){yk(this)};k.Nl=function(){yk(this)};
+k.Pl=function(){yk(this);if(this.get(wk)&&this.autoPan){var a=this.Me();if(a&&a.jd()){var b=zk(a.jd(),a.Ob()),c=this.Rd(),d=c.offsetWidth,e=getComputedStyle(c),d=d+(parseInt(e.marginLeft,10)+parseInt(e.marginRight,10)),e=c.offsetHeight,f=getComputedStyle(c),e=e+(parseInt(f.marginTop,10)+parseInt(f.marginBottom,10)),g=zk(c,[d,e]),c=this.o;Va(b,g)||(d=g[0]-b[0],e=b[2]-g[2],f=g[1]-b[1],g=b[3]-g[3],b=[0,0],0>d?b[0]=d-c:0>e&&(b[0]=Math.abs(e)+c),0>f?b[1]=f-c:0>g&&(b[1]=Math.abs(g)+c),0===b[0]&&0===b[1])||
+(c=a.Z().wa(),c=a.Ja(c),b=[c[0]+b[0],c[1]+b[1]],a.Z().animate({center:a.Wa(b),duration:this.j.duration,easing:this.j.easing}))}}};k.Ql=function(){yk(this)};k.lj=function(a){this.set(tk,a)};k.setMap=function(a){this.set(uk,a)};k.rj=function(a){this.set(vk,a)};k.Ne=function(a){this.set(wk,a)};function zk(a,b){var c=a.getBoundingClientRect();a=c.left+window.pageXOffset;c=c.top+window.pageYOffset;return[a,c,a+b[0],c+b[1]]}k.uj=function(a){this.set(xk,a)};
+function Ak(a,b){a.a.visible!==b&&(a.c.style.display=b?"":"none",a.a.visible=b)}
+function yk(a){var b=a.Me(),c=a.Yh();if(b&&b.c&&c){var c=b.Ja(c),d=b.Ob(),b=a.c.style,e=a.Dh(),f=a.Eh();Ak(a,!0);var g=e[0],e=e[1];if("bottom-right"==f||"center-right"==f||"top-right"==f)""!==a.a.Ie&&(a.a.Ie=b.left=""),g=Math.round(d[0]-c[0]-g)+"px",a.a.nf!=g&&(a.a.nf=b.right=g);else{""!==a.a.nf&&(a.a.nf=b.right="");if("bottom-center"==f||"center-center"==f||"top-center"==f)g-=a.c.offsetWidth/2;g=Math.round(c[0]+g)+"px";a.a.Ie!=g&&(a.a.Ie=b.left=g)}if("bottom-left"==f||"bottom-center"==f||"bottom-right"==
+f)""!==a.a.vf&&(a.a.vf=b.top=""),c=Math.round(d[1]-c[1]-e)+"px",a.a.re!=c&&(a.a.re=b.bottom=c);else{""!==a.a.re&&(a.a.re=b.bottom="");if("center-left"==f||"center-center"==f||"center-right"==f)e-=a.c.offsetHeight/2;c=Math.round(c[1]+e)+"px";a.a.vf!=c&&(a.a.vf=b.top=c)}}else Ak(a,!1)}var tk="element",uk="map",vk="offset",wk="position",xk="positioning";function Bk(a){function b(a){a=h.Tf(a);l.a.Z().ob(a);window.removeEventListener("mousemove",c);window.removeEventListener("mouseup",b)}function c(a){a=h.Tf({clientX:a.clientX-n.offsetWidth/2,clientY:a.clientY+n.offsetHeight/2});m.Ne(a)}a=a?a:{};this.j=void 0!==a.collapsed?a.collapsed:!0;this.o=void 0!==a.collapsible?a.collapsible:!0;this.o||(this.j=!1);var d=void 0!==a.className?a.className:"ol-overviewmap",e=void 0!==a.tipLabel?a.tipLabel:"Overview map",f=void 0!==a.collapseLabel?a.collapseLabel:
+"\u00ab";"string"===typeof f?(this.u=document.createElement("span"),this.u.textContent=f):this.u=f;f=void 0!==a.label?a.label:"\u00bb";"string"===typeof f?(this.D=document.createElement("span"),this.D.textContent=f):this.D=f;var g=this.o&&!this.j?this.u:this.D,f=document.createElement("button");f.setAttribute("type","button");f.title=e;f.appendChild(g);y(f,"click",this.an,this);this.C=document.createElement("DIV");this.C.className="ol-overviewmap-map";var h=this.c=new G({controls:new Yc,interactions:new Yc,
+view:a.view});a.layers&&a.layers.forEach(function(a){h.ih(a)},this);e=document.createElement("DIV");e.className="ol-overviewmap-box";e.style.boxSizing="border-box";this.l=new sk({position:[0,0],positioning:"bottom-left",element:e});this.c.jh(this.l);e=document.createElement("div");e.className=d+" ol-unselectable ol-control"+(this.j&&this.o?" ol-collapsed":"")+(this.o?"":" ol-uncollapsible");e.appendChild(this.C);e.appendChild(f);md.call(this,{element:e,render:a.render?a.render:Ck,target:a.target});
+var l=this,m=this.l,n=this.l.Rd();n.addEventListener("mousedown",function(){window.addEventListener("mousemove",c);window.addEventListener("mouseup",b)})}v(Bk,md);k=Bk.prototype;k.setMap=function(a){var b=this.a;a!==b&&(b&&((b=b.Z())&&Kc(b,Vc("rotation"),this.Ge,this),this.c.Le(null)),md.prototype.setMap.call(this,a),a&&(this.c.Le(this.C),this.v.push(y(a,"propertychange",this.Kl,this)),this.c.Xh().dc()||this.c.qj(a.Kc()),a=a.Z()))&&(y(a,Vc("rotation"),this.Ge,this),jg(a)&&(this.c.Ad(),Dk(this)))};
+k.Kl=function(a){"view"===a.key&&((a=a.oldValue)&&Kc(a,Vc("rotation"),this.Ge,this),a=this.a.Z(),y(a,Vc("rotation"),this.Ge,this))};k.Ge=function(){this.c.Z().Oe(this.a.Z().Qa())};function Ck(){var a=this.a,b=this.c;if(a.c&&b.c){var c=a.Ob(),a=a.Z().dd(c),d=b.Ob(),c=b.Z().dd(d),e=b.Ja(ib(a)),f=b.Ja(gb(a)),b=Math.abs(e[0]-f[0]),e=Math.abs(e[1]-f[1]),f=d[0],d=d[1];b<.1*f||e<.1*d||b>.75*f||e>.75*d?Dk(this):Va(c,a)||(a=this.c,c=this.a.Z(),a.Z().ob(c.wa()))}Ek(this)}
+function Dk(a){var b=a.a;a=a.c;var c=b.Ob(),b=b.Z().dd(c);a=a.Z();rb(b,1/(.1*Math.pow(2,Math.log(7.5)/Math.LN2/2)));a.Qf(b)}function Ek(a){var b=a.a,c=a.c;if(b.c&&c.c){var d=b.Ob(),e=b.Z(),f=c.Z(),c=e.Qa(),b=a.l,g=a.l.Rd(),h=e.dd(d),d=f.Pa(),e=eb(h),f=hb(h);if(a=a.a.Z().wa()){var l=[e[0]-a[0],e[1]-a[1]];ef(l,c);Ze(l,a)}b.Ne(l);g&&(g.style.width=Math.abs((e[0]-f[0])/d)+"px",g.style.height=Math.abs((f[1]-e[1])/d)+"px")}}k.an=function(a){a.preventDefault();Fk(this)};
+function Fk(a){a.element.classList.toggle("ol-collapsed");a.j?kd(a.u,a.D):kd(a.D,a.u);a.j=!a.j;var b=a.c;a.j||b.c||(b.Ad(),Dk(a),Jc(b,"postrender",function(){Ek(this)},a))}k.$m=function(){return this.o};k.cn=function(a){this.o!==a&&(this.o=a,this.element.classList.toggle("ol-uncollapsible"),!a&&this.j&&Fk(this))};k.bn=function(a){this.o&&this.j!==a&&Fk(this)};k.Zm=function(){return this.j};k.gl=function(){return this.c};function Gk(a){a=a?a:{};var b=void 0!==a.className?a.className:"ol-scale-line";this.o=document.createElement("DIV");this.o.className=b+"-inner";this.c=document.createElement("DIV");this.c.className=b+" ol-unselectable";this.c.appendChild(this.o);this.u=null;this.l=void 0!==a.minWidth?a.minWidth:64;this.j=!1;this.B=void 0;this.D="";md.call(this,{element:this.c,render:a.render?a.render:Hk,target:a.target});y(this,Vc(Ik),this.T,this);this.I(a.units||"metric")}v(Gk,md);var Jk=[1,2,5];Gk.prototype.C=function(){return this.get(Ik)};
+function Hk(a){(a=a.frameState)?this.u=a.viewState:this.u=null;Kk(this)}Gk.prototype.T=function(){Kk(this)};Gk.prototype.I=function(a){this.set(Ik,a)};
+function Kk(a){var b=a.u;if(b){var c=b.projection,d=c.sc(),b=Sb(c,b.resolution,b.center)*d,d=a.l*b,c="",e=a.C();"degrees"==e?(c=zb.degrees,b/=c,d<c/60?(c="\u2033",b*=3600):d<c?(c="\u2032",b*=60):c="\u00b0"):"imperial"==e?.9144>d?(c="in",b/=.0254):1609.344>d?(c="ft",b/=.3048):(c="mi",b/=1609.344):"nautical"==e?(b/=1852,c="nm"):"metric"==e?.001>d?(c="\u03bcm",b*=1E6):1>d?(c="mm",b*=1E3):1E3>d?c="m":(c="km",b/=1E3):"us"==e?.9144>d?(c="in",b*=39.37):1609.344>d?(c="ft",b/=.30480061):(c="mi",b/=1609.3472):
+xa(!1,33);for(var e=3*Math.floor(Math.log(a.l*b)/Math.log(10)),f;;){f=Jk[(e%3+3)%3]*Math.pow(10,Math.floor(e/3));d=Math.round(f/b);if(isNaN(d)){a.c.style.display="none";a.j=!1;return}if(d>=a.l)break;++e}b=f+" "+c;a.D!=b&&(a.o.innerHTML=b,a.D=b);a.B!=d&&(a.o.style.width=d+"px",a.B=d);a.j||(a.c.style.display="",a.j=!0)}else a.j&&(a.c.style.display="none",a.j=!1)}var Ik="units";function Lk(a){a=a?a:{};this.c=void 0;this.j=Mk;this.D=this.l=0;this.I=null;this.na=!1;this.T=void 0!==a.duration?a.duration:200;var b=void 0!==a.className?a.className:"ol-zoomslider",c=document.createElement("button");c.setAttribute("type","button");c.className=b+"-thumb ol-unselectable";var d=document.createElement("div");d.className=b+" ol-unselectable ol-control";d.appendChild(c);this.o=new Ae(d);y(this.o,"pointerdown",this.yl,this);y(this.o,"pointermove",this.wl,this);y(this.o,"pointerup",this.xl,
+this);y(d,"click",this.vl,this);y(c,"click",Pc);md.call(this,{element:d,render:a.render?a.render:Nk})}v(Lk,md);Lk.prototype.ka=function(){Nc(this.o);md.prototype.ka.call(this)};var Mk=0;k=Lk.prototype;k.setMap=function(a){md.prototype.setMap.call(this,a);a&&a.render()};
+function Nk(a){if(a.frameState){if(!this.na){var b=this.element,c=b.offsetWidth,d=b.offsetHeight,e=b.firstElementChild,f=getComputedStyle(e),b=e.offsetWidth+parseFloat(f.marginRight)+parseFloat(f.marginLeft),e=e.offsetHeight+parseFloat(f.marginTop)+parseFloat(f.marginBottom);this.I=[b,e];c>d?(this.j=1,this.D=c-b):(this.j=Mk,this.l=d-e);this.na=!0}a=a.frameState.viewState.resolution;a!==this.c&&(this.c=a,Ok(this,a))}}
+k.vl=function(a){var b=this.a.Z();a=Pk(this,Ca(1===this.j?(a.offsetX-this.I[0]/2)/this.D:(a.offsetY-this.I[1]/2)/this.l,0,1));b.animate({resolution:b.constrainResolution(a),duration:this.T,easing:rd})};k.yl=function(a){this.u||a.b.target!==this.element.firstElementChild||(cg(this.a.Z(),1,1),this.C=a.clientX,this.B=a.clientY,this.u=!0)};
+k.wl=function(a){if(this.u){var b=this.element.firstElementChild;this.c=Pk(this,Ca(1===this.j?(a.clientX-this.C+parseInt(b.style.left,10))/this.D:(a.clientY-this.B+parseInt(b.style.top,10))/this.l,0,1));this.a.Z().Vc(this.c);Ok(this,this.c);this.C=a.clientX;this.B=a.clientY}};k.xl=function(){if(this.u){var a=this.a.Z();cg(a,1,-1);a.animate({resolution:a.constrainResolution(this.c),duration:this.T,easing:rd});this.u=!1;this.B=this.C=void 0}};
+function Ok(a,b){b=1-ig(a.a.Z())(b);var c=a.element.firstElementChild;1==a.j?c.style.left=a.D*b+"px":c.style.top=a.l*b+"px"}function Pk(a,b){return hg(a.a.Z())(1-b)};function Qk(a){a=a?a:{};this.c=a.extent?a.extent:null;var b=void 0!==a.className?a.className:"ol-zoom-extent",c=void 0!==a.label?a.label:"E",d=void 0!==a.tipLabel?a.tipLabel:"Fit to extent",e=document.createElement("button");e.setAttribute("type","button");e.title=d;e.appendChild("string"===typeof c?document.createTextNode(c):c);y(e,"click",this.j,this);c=document.createElement("div");c.className=b+" ol-unselectable ol-control";c.appendChild(e);md.call(this,{element:c,target:a.target})}v(Qk,md);
+Qk.prototype.j=function(a){a.preventDefault();a=this.a.Z();var b=this.c?this.c:a.v.G();a.Qf(b)};function Rk(a){Tc.call(this);a=a?a:{};this.a=null;y(this,Vc(Sk),this.vm,this);this.gg(void 0!==a.tracking?a.tracking:!1)}v(Rk,Tc);k=Rk.prototype;k.ka=function(){this.gg(!1);Tc.prototype.ka.call(this)};
+k.ap=function(a){if(null!==a.alpha){var b=Ha(a.alpha);this.set(Tk,b);"boolean"===typeof a.absolute&&a.absolute?this.set(Uk,b):"number"===typeof a.webkitCompassHeading&&-1!=a.webkitCompassAccuracy&&this.set(Uk,Ha(a.webkitCompassHeading))}null!==a.beta&&this.set(Vk,Ha(a.beta));null!==a.gamma&&this.set(Wk,Ha(a.gamma));this.s()};k.Fk=function(){return this.get(Tk)};k.Ik=function(){return this.get(Vk)};k.Ok=function(){return this.get(Wk)};k.um=function(){return this.get(Uk)};k.Th=function(){return this.get(Sk)};
+k.vm=function(){if(Vd){var a=this.Th();a&&!this.a?this.a=y(window,"deviceorientation",this.ap,this):a||null===this.a||(Ec(this.a),this.a=null)}};k.gg=function(a){this.set(Sk,a)};var Tk="alpha",Vk="beta",Wk="gamma",Uk="heading",Sk="tracking";function Xk(a){this.f=a.opacity;this.l=a.rotateWithView;this.g=a.rotation;this.a=a.scale;this.v=a.snapToPixel}k=Xk.prototype;k.Ze=function(){return this.f};k.$e=function(){return this.l};k.af=function(){return this.g};k.bf=function(){return this.a};k.Ae=function(){return this.v};k.td=function(a){this.f=a};k.cf=function(a){this.g=a};k.ud=function(a){this.a=a};function Yk(a){this.D=this.u=this.c=null;this.Va=void 0!==a.fill?a.fill:null;this.oa=[0,0];this.o=a.points;this.b=void 0!==a.radius?a.radius:a.radius1;this.i=a.radius2;this.j=void 0!==a.angle?a.angle:0;this.Ya=void 0!==a.stroke?a.stroke:null;this.B=this.ra=this.C=null;this.S=a.atlasManager;Zk(this,this.S);Xk.call(this,{opacity:1,rotateWithView:void 0!==a.rotateWithView?a.rotateWithView:!1,rotation:void 0!==a.rotation?a.rotation:0,scale:1,snapToPixel:void 0!==a.snapToPixel?a.snapToPixel:!0})}
+v(Yk,Xk);k=Yk.prototype;k.clone=function(){var a=new Yk({fill:this.Fa()?this.Fa().clone():void 0,points:this.o,radius:this.b,radius2:this.i,angle:this.j,snapToPixel:this.v,stroke:this.Ga()?this.Ga().clone():void 0,rotation:this.g,rotateWithView:this.l,atlasManager:this.S});a.td(this.f);a.ud(this.a);return a};k.Hc=function(){return this.C};k.Pi=function(){return this.j};k.Fa=function(){return this.Va};k.qg=function(){return this.D};k.Y=function(){return this.u};k.ye=function(){return this.B};
+k.Ye=function(){return 2};k.Oc=function(){return this.oa};k.Qi=function(){return this.o};k.Ri=function(){return this.b};k.Fh=function(){return this.i};k.ic=function(){return this.ra};k.Ga=function(){return this.Ya};k.Nh=function(){};k.load=function(){};k.Bj=function(){};
+function Zk(a,b){var c="",d="",e=0,f=null,g=0;if(a.Ya){var h=a.Ya.a;null===h&&(h=Uh);h=id(h);g=a.Ya.c;void 0===g&&(g=1);f=a.Ya.i;Td||(f=null);d=a.Ya.j;void 0===d&&(d="round");c=a.Ya.f;void 0===c&&(c="round");e=a.Ya.o;void 0===e&&(e=10)}var l=2*(a.b+g)+1,c={strokeStyle:h,zj:g,size:l,lineCap:c,lineDash:f,lineJoin:d,miterLimit:e};if(void 0===b){var m=jd(l,l);a.u=m.canvas;b=l=a.u.width;a.rh(c,m,0,0);a.Va?a.D=a.u:(m=jd(c.size,c.size),a.D=m.canvas,a.qh(c,m,0,0))}else l=Math.round(l),(d=!a.Va)&&(m=a.qh.bind(a,
+c)),a.Ya?(e=a.Ya,void 0===e.b&&(e.b="s",e.b=e.a?"string"===typeof e.a?e.b+e.a:e.b+w(e.a).toString():e.b+"-",e.b+=","+(void 0!==e.f?e.f.toString():"-")+","+(e.i?e.i.toString():"-")+","+(void 0!==e.g?e.g:"-")+","+(void 0!==e.j?e.j:"-")+","+(void 0!==e.o?e.o.toString():"-")+","+(void 0!==e.c?e.c.toString():"-")),e=e.b):e="-",a.Va?(f=a.Va,void 0===f.a&&(f.a=f.b instanceof CanvasPattern||f.b instanceof CanvasGradient?w(f.b).toString():"f"+(f.b?gd(f.b):"-")),f=f.a):f="-",a.c&&e==a.c[1]&&f==a.c[2]&&a.b==
+a.c[3]&&a.i==a.c[4]&&a.j==a.c[5]&&a.o==a.c[6]||(a.c=["r"+e+f+(void 0!==a.b?a.b.toString():"-")+(void 0!==a.i?a.i.toString():"-")+(void 0!==a.j?a.j.toString():"-")+(void 0!==a.o?a.o.toString():"-"),e,f,a.b,a.i,a.j,a.o]),m=b.add(a.c[0],l,l,a.rh.bind(a,c),m),a.u=m.image,a.oa=[m.offsetX,m.offsetY],b=m.image.width,a.D=d?m.Zl:a.u;a.C=[l/2,l/2];a.ra=[l,l];a.B=[b,b]}
+k.rh=function(a,b,c,d){b.setTransform(1,0,0,1,0,0);b.translate(c,d);b.beginPath();var e=this.o;if(Infinity===e)b.arc(a.size/2,a.size/2,this.b,0,2*Math.PI,!0);else{var f=void 0!==this.i?this.i:this.b;f!==this.b&&(e*=2);for(c=0;c<=e;c++){d=2*c*Math.PI/e-Math.PI/2+this.j;var g=c%2?f:this.b;b.lineTo(a.size/2+g*Math.cos(d),a.size/2+g*Math.sin(d))}}this.Va&&(c=this.Va.b,null===c&&(c=Sh),b.fillStyle=id(c),b.fill());this.Ya&&(b.strokeStyle=a.strokeStyle,b.lineWidth=a.zj,a.lineDash&&b.setLineDash(a.lineDash),
+b.lineCap=a.lineCap,b.lineJoin=a.lineJoin,b.miterLimit=a.miterLimit,b.stroke());b.closePath()};
+k.qh=function(a,b,c,d){b.setTransform(1,0,0,1,0,0);b.translate(c,d);b.beginPath();c=this.o;if(Infinity===c)b.arc(a.size/2,a.size/2,this.b,0,2*Math.PI,!0);else{d=void 0!==this.i?this.i:this.b;d!==this.b&&(c*=2);var e;for(e=0;e<=c;e++){var f=2*e*Math.PI/c-Math.PI/2+this.j;var g=e%2?d:this.b;b.lineTo(a.size/2+g*Math.cos(f),a.size/2+g*Math.sin(f))}}b.fillStyle=Sh;b.fill();this.Ya&&(b.strokeStyle=a.strokeStyle,b.lineWidth=a.zj,a.lineDash&&b.setLineDash(a.lineDash),b.stroke());b.closePath()};function $k(a){a=a||{};Yk.call(this,{points:Infinity,fill:a.fill,radius:a.radius,snapToPixel:a.snapToPixel,stroke:a.stroke,atlasManager:a.atlasManager})}v($k,Yk);$k.prototype.clone=function(){var a=new $k({fill:this.Fa()?this.Fa().clone():void 0,stroke:this.Ga()?this.Ga().clone():void 0,radius:this.b,snapToPixel:this.v,atlasManager:this.S});a.td(this.f);a.ud(this.a);return a};$k.prototype.Uc=function(a){this.b=a;Zk(this,this.S)};function al(a){a=a||{};this.b=void 0!==a.color?a.color:null;this.a=void 0}al.prototype.clone=function(){var a=this.b;return new al({color:a&&a.slice?a.slice():a||void 0})};al.prototype.i=function(){return this.b};al.prototype.c=function(a){this.b=a;this.a=void 0};function bl(a){a=a||{};this.Gc=null;this.Za=cl;void 0!==a.geometry&&this.Ra(a.geometry);this.Va=void 0!==a.fill?a.fill:null;this.M=void 0!==a.image?a.image:null;this.Ya=void 0!==a.stroke?a.stroke:null;this.Ia=void 0!==a.text?a.text:null;this.Fj=a.zIndex}k=bl.prototype;
+k.clone=function(){var a=this.V();a&&a.clone&&(a=a.clone());return new bl({geometry:a,fill:this.Fa()?this.Fa().clone():void 0,image:this.Y()?this.Y().clone():void 0,stroke:this.Ga()?this.Ga().clone():void 0,text:this.Na()?this.Na().clone():void 0,zIndex:this.Ba()})};k.V=function(){return this.Gc};k.Pk=function(){return this.Za};k.Fa=function(){return this.Va};k.pf=function(a){this.Va=a};k.Y=function(){return this.M};k.Og=function(a){this.M=a};k.Ga=function(){return this.Ya};
+k.qf=function(a){this.Ya=a};k.Na=function(){return this.Ia};k.xd=function(a){this.Ia=a};k.Ba=function(){return this.Fj};k.Ra=function(a){"function"===typeof a?this.Za=a:"string"===typeof a?this.Za=function(b){return b.get(a)}:a?a&&(this.Za=function(){return a}):this.Za=cl;this.Gc=a};k.Vb=function(a){this.Fj=a};function dl(a){if("function"!==typeof a){if(Array.isArray(a))var b=a;else xa(a instanceof bl,41),b=[a];a=function(){return b}}return a}var el=null;
+function fl(){if(!el){var a=new al({color:"rgba(255,255,255,0.4)"}),b=new wj({color:"#3399CC",width:1.25});el=[new bl({image:new $k({fill:a,stroke:b,radius:5}),fill:a,stroke:b})]}return el}
+function gl(){var a={},b=[255,255,255,1],c=[0,153,255,1];a.Polygon=[new bl({fill:new al({color:[255,255,255,.5]})})];a.MultiPolygon=a.Polygon;a.LineString=[new bl({stroke:new wj({color:b,width:5})}),new bl({stroke:new wj({color:c,width:3})})];a.MultiLineString=a.LineString;a.Circle=a.Polygon.concat(a.LineString);a.Point=[new bl({image:new $k({radius:6,fill:new al({color:c}),stroke:new wj({color:b,width:1.5})}),zIndex:Infinity})];a.MultiPoint=a.Point;a.GeometryCollection=a.Polygon.concat(a.LineString,
+a.Point);return a}function cl(a){return a.V()};function H(a){Tc.call(this);this.a=void 0;this.c="geometry";this.g=null;this.j=void 0;this.f=null;y(this,Vc(this.c),this.Ee,this);void 0!==a&&(a instanceof of||!a?this.Ra(a):this.H(a))}v(H,Tc);k=H.prototype;k.clone=function(){var a=new H(this.N());a.Tc(this.c);var b=this.V();b&&a.Ra(b.clone());(b=this.g)&&a.hg(b);return a};k.V=function(){return this.get(this.c)};k.wm=function(){return this.a};k.Qk=function(){return this.c};k.xm=function(){return this.g};k.Lc=function(){return this.j};k.Al=function(){this.s()};
+k.Ee=function(){this.f&&(Ec(this.f),this.f=null);var a=this.V();a&&(this.f=y(a,"change",this.Al,this));this.s()};k.Ra=function(a){this.set(this.c,a)};k.hg=function(a){this.j=(this.g=a)?hl(a):void 0;this.s()};k.jc=function(a){this.a=a;this.s()};k.Tc=function(a){Kc(this,Vc(this.c),this.Ee,this);this.c=a;y(this,Vc(this.c),this.Ee,this);this.Ee()};
+function hl(a){var b;if("function"===typeof a)2==a.length?b=function(b){return a(this,b)}:b=a;else{if(Array.isArray(a))var c=a;else xa(a instanceof bl,41),c=[a];b=function(){return c}}return b};var il=document.implementation.createDocument("","",null);function jl(a,b){return il.createElementNS(a,b)}function kl(a,b){return ll(a,b,[]).join("")}function ll(a,b,c){if(a.nodeType==Node.CDATA_SECTION_NODE||a.nodeType==Node.TEXT_NODE)b?c.push(String(a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):c.push(a.nodeValue);else for(a=a.firstChild;a;a=a.nextSibling)ll(a,b,c);return c}function ml(a){return a instanceof Document}function nl(a){return a instanceof Node}
+function pl(a){return(new DOMParser).parseFromString(a,"application/xml")}function ql(a,b){return function(c,d){c=a.call(b,c,d);void 0!==c&&la(d[d.length-1],c)}}function rl(a,b){return function(c,d){c=a.call(void 0!==b?b:this,c,d);void 0!==c&&d[d.length-1].push(c)}}function sl(a,b){return function(c,d){c=a.call(void 0!==b?b:this,c,d);void 0!==c&&(d[d.length-1]=c)}}
+function tl(a){return function(b,c){var d=a.call(this,b,c);if(void 0!==d){c=c[c.length-1];b=b.localName;var e;b in c?e=c[b]:e=c[b]=[];e.push(d)}}}function I(a,b){return function(c,d){var e=a.call(this,c,d);void 0!==e&&(d[d.length-1][void 0!==b?b:c.localName]=e)}}function J(a,b){return function(c,d,e){a.call(void 0!==b?b:this,c,d,e);e[e.length-1].node.appendChild(c)}}
+function vl(a){var b,c;return function(d,e,f){if(!b){b={};var g={};g[d.localName]=a;b[d.namespaceURI]=g;c=wl(d.localName)}xl(b,c,e,f)}}function wl(a,b){return function(c,d,e){c=d[d.length-1].node;d=a;void 0===d&&(d=e);e=b;void 0===b&&(e=c.namespaceURI);return jl(e,d)}}var yl=wl();function zl(a,b){for(var c=b.length,d=Array(c),e=0;e<c;++e)d[e]=a[b[e]];return d}function K(a,b,c){c=void 0!==c?c:{};var d;var e=0;for(d=a.length;e<d;++e)c[a[e]]=b;return c}
+function Al(a,b,c,d){for(b=b.firstElementChild;b;b=b.nextElementSibling){var e=a[b.namespaceURI];void 0!==e&&(e=e[b.localName])&&e.call(d,b,c)}}function N(a,b,c,d,e){d.push(a);Al(b,c,d,e);return d.pop()}function xl(a,b,c,d,e,f){for(var g=(void 0!==e?e:c).length,h,l,m=0;m<g;++m)h=c[m],void 0!==h&&(l=b.call(f,h,d,void 0!==e?e[m]:void 0),void 0!==l&&a[l.namespaceURI][l.localName].call(f,l,h,d))}function Bl(a,b,c,d,e,f,g){e.push(a);xl(b,c,d,e,f,g);e.pop()};function Cl(a,b,c,d){return function(e,f,g){var h=new XMLHttpRequest;h.open("GET","function"===typeof a?a(e,f,g):a,!0);"arraybuffer"==b.U()&&(h.responseType="arraybuffer");h.onload=function(){if(!h.status||200<=h.status&&300>h.status){var a=b.U();if("json"==a||"text"==a)var e=h.responseText;else"xml"==a?(e=h.responseXML)||(e=pl(h.responseText)):"arraybuffer"==a&&(e=h.response);e?c.call(this,b.Oa(e,{featureProjection:g}),b.kb(e)):d.call(this)}else d.call(this)}.bind(this);h.onerror=function(){d.call(this)}.bind(this);
+h.send()}}function Dl(a,b){return Cl(a,b,function(a){this.cd(a)},ua)};function El(){this.f=this.defaultDataProjection=null}function Fl(a,b,c){var d;c&&(d={dataProjection:c.dataProjection?c.dataProjection:a.kb(b),featureProjection:c.featureProjection});return Gl(a,d)}function Gl(a,b){return tb({dataProjection:a.defaultDataProjection,featureProjection:a.f},b)}
+function Hl(a,b,c){var d=c?Tb(c.featureProjection):null,e=c?Tb(c.dataProjection):null,f;d&&e&&!dc(d,e)?a instanceof of?f=(b?a.clone():a).tb(b?d:e,b?e:d):f=hc(a,e,d):f=a;if(b&&c&&void 0!==c.decimals){var g=Math.pow(10,c.decimals);f===a&&(f=f.clone());f.Dc(function(a){for(var b=0,c=a.length;b<c;++b)a[b]=Math.round(a[b]*g)/g;return a})}return f};function Il(){El.call(this)}v(Il,El);function Jl(a){return"string"===typeof a?(a=JSON.parse(a))?a:null:null!==a?a:null}k=Il.prototype;k.U=function(){return"json"};k.Tb=function(a,b){return this.Rc(Jl(a),Fl(this,a,b))};k.Oa=function(a,b){return this.yg(Jl(a),Fl(this,a,b))};k.Sc=function(a,b){return this.Cg(Jl(a),Fl(this,a,b))};k.kb=function(a){return this.Fg(Jl(a))};k.Bd=function(a,b){return JSON.stringify(this.Zc(a,b))};k.Wb=function(a,b){return JSON.stringify(this.he(a,b))};
+k.$c=function(a,b){return JSON.stringify(this.je(a,b))};function Kl(a,b,c,d,e,f){var g=NaN,h=NaN,l=(c-b)/d;if(1===l)g=a[b],h=a[b+1];else if(2==l)g=(1-e)*a[b]+e*a[b+d],h=(1-e)*a[b+1]+e*a[b+d+1];else if(l){var h=a[b],l=a[b+1],m=0,g=[0],n;for(n=b+d;n<c;n+=d){var p=a[n],q=a[n+1],m=m+Math.sqrt((p-h)*(p-h)+(q-l)*(q-l));g.push(m);h=p;l=q}c=e*m;l=0;m=g.length;for(n=!1;l<m;)e=l+(m-l>>1),h=+ia(g[e],c),0>h?l=e+1:(m=e,n=!h);e=n?l:~l;0>e?(c=(c-g[-e-2])/(g[-e-1]-g[-e-2]),b+=(-e-2)*d,g=Ja(a[b],a[b+d],c),h=Ja(a[b+1],a[b+d+1],c)):(g=a[b+e*d],h=a[b+e*d+1])}return f?(f[0]=
+g,f[1]=h,f):[g,h]}function Ll(a,b,c,d,e,f){if(c==b)return null;if(e<a[b+d-1])return f?(c=a.slice(b,b+d),c[d-1]=e,c):null;if(a[c-1]<e)return f?(c=a.slice(c-d,c),c[d-1]=e,c):null;if(e==a[b+d-1])return a.slice(b,b+d);b/=d;for(c/=d;b<c;)f=b+c>>1,e<a[(f+1)*d-1]?c=f:b=f+1;c=a[b*d-1];if(e==c)return a.slice((b-1)*d,(b-1)*d+d);f=(e-c)/(a[(b+1)*d-1]-c);c=[];var g;for(g=0;g<d-1;++g)c.push(Ja(a[(b-1)*d+g],a[b*d+g],f));c.push(e);return c}
+function Ml(a,b,c,d,e,f){var g=0;if(f)return Ll(a,g,b[b.length-1],c,d,e);if(d<a[c-1])return e?(a=a.slice(0,c),a[c-1]=d,a):null;if(a[a.length-1]<d)return e?(a=a.slice(a.length-c),a[c-1]=d,a):null;e=0;for(f=b.length;e<f;++e){var h=b[e];if(g!=h){if(d<a[g+c-1])break;else if(d<=a[h-1])return Ll(a,g,h,c,d,!1);g=h}}return null};function O(a,b){rf.call(this);this.c=null;this.u=this.D=this.j=-1;this.ma(a,b)}v(O,rf);k=O.prototype;k.mk=function(a){this.A?la(this.A,a):this.A=a.slice();this.s()};k.clone=function(){var a=new O(null);a.ba(this.ja,this.A.slice());return a};k.Kb=function(a,b,c,d){if(d<Sa(this.G(),a,b))return d;this.u!=this.i&&(this.D=Math.sqrt(yf(this.A,0,this.A.length,this.a,0)),this.u=this.i);return Af(this.A,0,this.A.length,this.a,this.D,!1,a,b,c,d)};
+k.Ck=function(a,b){return Pf(this.A,0,this.A.length,this.a,a,b)};k.nn=function(a,b){return"XYM"!=this.ja&&"XYZM"!=this.ja?null:Ll(this.A,0,this.A.length,this.a,a,void 0!==b?b:!1)};k.X=function(){return Ff(this.A,0,this.A.length,this.a)};k.wh=function(a,b){return Kl(this.A,0,this.A.length,this.a,a,b)};k.pn=function(){var a=this.A,b=this.a,c=a[0],d=a[1],e=0,f;for(f=0+b;f<this.A.length;f+=b)var g=a[f],h=a[f+1],e=e+Math.sqrt((g-c)*(g-c)+(h-d)*(h-d)),c=g,d=h;return e};
+function di(a){a.j!=a.i&&(a.c=a.wh(.5,a.c),a.j=a.i);return a.c}k.hd=function(a){var b=[];b.length=Hf(this.A,0,this.A.length,this.a,a,b,0);a=new O(null);a.ba("XY",b);return a};k.U=function(){return"LineString"};k.Xa=function(a){return Qf(this.A,0,this.A.length,this.a,a)};k.ma=function(a,b){a?(uf(this,b,a,1),this.A||(this.A=[]),this.A.length=Df(this.A,0,a,this.a),this.s()):this.ba("XY",null)};k.ba=function(a,b){tf(this,a,b);this.s()};function P(a,b){rf.call(this);this.c=[];this.j=this.u=-1;this.ma(a,b)}v(P,rf);k=P.prototype;k.nk=function(a){this.A?la(this.A,a.ga().slice()):this.A=a.ga().slice();this.c.push(this.A.length);this.s()};k.clone=function(){var a=new P(null);a.ba(this.ja,this.A.slice(),this.c.slice());return a};k.Kb=function(a,b,c,d){if(d<Sa(this.G(),a,b))return d;this.j!=this.i&&(this.u=Math.sqrt(zf(this.A,0,this.c,this.a,0)),this.j=this.i);return Bf(this.A,0,this.c,this.a,this.u,!1,a,b,c,d)};
+k.rn=function(a,b,c){return"XYM"!=this.ja&&"XYZM"!=this.ja||!this.A.length?null:Ml(this.A,this.c,this.a,a,void 0!==b?b:!1,void 0!==c?c:!1)};k.X=function(){return Gf(this.A,0,this.c,this.a)};k.Bb=function(){return this.c};k.Yk=function(a){if(0>a||this.c.length<=a)return null;var b=new O(null);b.ba(this.ja,this.A.slice(a?this.c[a-1]:0,this.c[a]));return b};
+k.gd=function(){var a=this.A,b=this.c,c=this.ja,d=[],e=0,f;var g=0;for(f=b.length;g<f;++g){var h=b[g],l=new O(null);l.ba(c,a.slice(e,h));d.push(l);e=h}return d};function ei(a){var b=[],c=a.A,d=0,e=a.c;a=a.a;var f;var g=0;for(f=e.length;g<f;++g){var h=e[g],d=Kl(c,d,h,a,.5);la(b,d);d=h}return b}k.hd=function(a){var b=[],c=[],d=this.A,e=this.c,f=this.a,g=0,h=0,l;var m=0;for(l=e.length;m<l;++m){var n=e[m],h=Hf(d,g,n,f,a,b,h);c.push(h);g=n}b.length=h;a=new P(null);a.ba("XY",b,c);return a};k.U=function(){return"MultiLineString"};
+k.Xa=function(a){a:{var b=this.A,c=this.c,d=this.a,e=0,f;var g=0;for(f=c.length;g<f;++g){if(Qf(b,e,c[g],d,a)){a=!0;break a}e=c[g]}a=!1}return a};k.ma=function(a,b){a?(uf(this,b,a,2),this.A||(this.A=[]),a=Ef(this.A,0,a,this.a,this.c),this.A.length=a.length?a[a.length-1]:0,this.s()):this.ba("XY",null,this.c)};k.ba=function(a,b,c){tf(this,a,b);this.c=c;this.s()};function Nl(a,b){var c=a.ja,d=[],e=[],f;var g=0;for(f=b.length;g<f;++g){var h=b[g];g||(c=h.ja);la(d,h.ga());e.push(d.length)}a.ba(c,d,e)};function Q(a,b){rf.call(this);this.ma(a,b)}v(Q,rf);k=Q.prototype;k.qk=function(a){this.A?la(this.A,a.ga()):this.A=a.ga().slice();this.s()};k.clone=function(){var a=new Q(null);a.ba(this.ja,this.A.slice());return a};k.Kb=function(a,b,c,d){if(d<Sa(this.G(),a,b))return d;var e=this.A,f=this.a,g;var h=0;for(g=e.length;h<g;h+=f){var l=Ga(a,b,e[h],e[h+1]);if(l<d){d=l;for(l=0;l<f;++l)c[l]=e[h+l];c.length=f}}return d};k.X=function(){return Ff(this.A,0,this.A.length,this.a)};
+k.il=function(a){var b=this.A?this.A.length/this.a:0;if(0>a||b<=a)return null;b=new C(null);b.ba(this.ja,this.A.slice(a*this.a,(a+1)*this.a));return b};k.Zd=function(){var a=this.A,b=this.ja,c=this.a,d=[],e;var f=0;for(e=a.length;f<e;f+=c){var g=new C(null);g.ba(b,a.slice(f,f+c));d.push(g)}return d};k.U=function(){return"MultiPoint"};k.Xa=function(a){var b=this.A,c=this.a,d;var e=0;for(d=b.length;e<d;e+=c){var f=b[e];var g=b[e+1];if(Ua(a,f,g))return!0}return!1};
+k.ma=function(a,b){a?(uf(this,b,a,1),this.A||(this.A=[]),this.A.length=Df(this.A,0,a,this.a),this.s()):this.ba("XY",null)};k.ba=function(a,b){tf(this,a,b);this.s()};function R(a,b){rf.call(this);this.c=[];this.u=-1;this.D=null;this.I=this.C=this.B=-1;this.j=null;this.ma(a,b)}v(R,rf);k=R.prototype;k.rk=function(a){if(this.A){var b=this.A.length;la(this.A,a.ga());a=a.Bb().slice();var c;var d=0;for(c=a.length;d<c;++d)a[d]+=b}else this.A=a.ga().slice(),a=a.Bb().slice(),this.c.push();this.c.push(a);this.s()};k.clone=function(){for(var a=new R(null),b=this.c.length,c=Array(b),d=0;d<b;++d)c[d]=this.c[d].slice();Ol(a,this.ja,this.A.slice(),c);return a};
+k.Kb=function(a,b,c,d){if(d<Sa(this.G(),a,b))return d;if(this.C!=this.i){var e=this.c,f=0,g=0,h;var l=0;for(h=e.length;l<h;++l)var m=e[l],g=zf(this.A,f,m,this.a,g),f=m[m.length-1];this.B=Math.sqrt(g);this.C=this.i}e=fi(this);f=this.c;g=this.a;l=this.B;h=0;var m=[NaN,NaN],n;var p=0;for(n=f.length;p<n;++p){var q=f[p];d=Bf(e,h,q,g,l,!0,a,b,c,d,m);h=q[q.length-1]}return d};
+k.Mc=function(a,b){a:{var c=fi(this),d=this.c,e=0;if(d.length){var f;var g=0;for(f=d.length;g<f;++g){var h=d[g];if(Nf(c,e,h,this.a,a,b)){a=!0;break a}e=h[h.length-1]}}a=!1}return a};k.sn=function(){var a=fi(this),b=this.c,c=0,d=0,e;var f=0;for(e=b.length;f<e;++f)var g=b[f],d=d+wf(a,c,g,this.a),c=g[g.length-1];return d};
+k.X=function(a){if(void 0!==a){var b=fi(this).slice();Vf(b,this.c,this.a,a)}else b=this.A;a=b;b=this.c;var c=this.a,d=0,e=[],f=0,g;var h=0;for(g=b.length;h<g;++h){var l=b[h];e[f++]=Gf(a,d,l,c,e[f]);d=l[l.length-1]}e.length=f;return e};
+function gi(a){if(a.u!=a.i){var b=a.A,c=a.c,d=a.a,e=0,f=[],g;var h=0;for(g=c.length;h<g;++h){var l=c[h],e=$a(b,e,l[0],d);f.push((e[0]+e[2])/2,(e[1]+e[3])/2);e=l[l.length-1]}b=fi(a);c=a.c;d=a.a;h=0;g=[];l=0;for(e=c.length;l<e;++l){var m=c[l];g=Of(b,h,m,d,f,2*l,g);h=m[m.length-1]}a.D=g;a.u=a.i}return a.D}k.Uk=function(){var a=new Q(null);a.ba("XY",gi(this).slice());return a};
+function fi(a){if(a.I!=a.i){var b=a.A;a:{var c=a.c;var d;var e=0;for(d=c.length;e<d;++e)if(!Tf(b,c[e],a.a,void 0)){c=!1;break a}c=!0}c?a.j=b:(a.j=b.slice(),a.j.length=Vf(a.j,a.c,a.a));a.I=a.i}return a.j}k.hd=function(a){var b=[],c=[],d=this.A,e=this.c,f=this.a;a=Math.sqrt(a);var g=0,h=0,l;var m=0;for(l=e.length;m<l;++m){var n=e[m],p=[],h=If(d,g,n,f,a,b,h,p);c.push(p);g=n[n.length-1]}b.length=h;d=new R(null);Ol(d,"XY",b,c);return d};
+k.jl=function(a){if(0>a||this.c.length<=a)return null;if(a){var b=this.c[a-1];b=b[b.length-1]}else b=0;a=this.c[a].slice();var c=a[a.length-1];if(b){var d;var e=0;for(d=a.length;e<d;++e)a[e]-=b}e=new D(null);e.ba(this.ja,this.A.slice(b,c),a);return e};k.Td=function(){var a=this.ja,b=this.A,c=this.c,d=[],e=0,f,g;var h=0;for(f=c.length;h<f;++h){var l=c[h].slice(),m=l[l.length-1];if(e){var n=0;for(g=l.length;n<g;++n)l[n]-=e}n=new D(null);n.ba(a,b.slice(e,m),l);d.push(n);e=m}return d};k.U=function(){return"MultiPolygon"};
+k.Xa=function(a){a:{var b=fi(this),c=this.c,d=this.a,e=0,f;var g=0;for(f=c.length;g<f;++g){var h=c[g];if(Rf(b,e,h,d,a)){a=!0;break a}e=h[h.length-1]}a=!1}return a};k.ma=function(a,b){if(a){uf(this,b,a,3);this.A||(this.A=[]);b=this.A;var c=this.a,d=this.c,e=0,d=d?d:[],f=0,g;var h=0;for(g=a.length;h<g;++h)e=Ef(b,e,a[h],c,d[f]),d[f++]=e,e=e[e.length-1];d.length=f;d.length?(a=d[d.length-1],this.A.length=a.length?a[a.length-1]:0):this.A.length=0;this.s()}else Ol(this,"XY",null,this.c)};
+function Ol(a,b,c,d){tf(a,b,c);a.c=d;a.s()}function Pl(a,b){var c=a.ja,d=[],e=[],f;var g=0;for(f=b.length;g<f;++g){var h=b[g];g||(c=h.ja);var l=d.length;var m=h.Bb();var n;var p=0;for(n=m.length;p<n;++p)m[p]+=l;la(d,h.ga());e.push(m)}Ol(a,c,d,e)};function Ql(a){a=a?a:{};El.call(this);this.b=a.geometryName}v(Ql,Il);
+function Rl(a,b){if(!a)return null;if("number"===typeof a.x&&"number"===typeof a.y)var c="Point";else if(a.points)c="MultiPoint";else if(a.paths)c=1===a.paths.length?"LineString":"MultiLineString";else if(a.rings){var d=a.rings,e=Sl(a),f=[],g=[];c=[];var h;var l=0;for(h=d.length;l<h;++l)f.length=0,Df(f,0,d[l],e.length),Sf(f,0,f.length,e.length)?g.push([d[l]]):c.push(d[l]);for(;c.length;){d=c.shift();e=!1;for(l=g.length-1;0<=l;l--)if(Va((new Jf(g[l][0])).G(),(new Jf(d)).G())){g[l].push(d);e=!0;break}e||
+g.push([d.reverse()])}a=tb({},a);1===g.length?(c="Polygon",a.rings=g[0]):(c="MultiPolygon",a.rings=g)}return Hl((0,Tl[c])(a),!1,b)}function Sl(a){var b="XY";!0===a.hasZ&&!0===a.hasM?b="XYZM":!0===a.hasZ?b="XYZ":!0===a.hasM&&(b="XYM");return b}function Ul(a){a=a.ja;return{hasZ:"XYZ"===a||"XYZM"===a,hasM:"XYM"===a||"XYZM"===a}}
+var Tl={Point:function(a){return void 0!==a.m&&void 0!==a.z?new C([a.x,a.y,a.z,a.m],"XYZM"):void 0!==a.z?new C([a.x,a.y,a.z],"XYZ"):void 0!==a.m?new C([a.x,a.y,a.m],"XYM"):new C([a.x,a.y])},LineString:function(a){return new O(a.paths[0],Sl(a))},Polygon:function(a){return new D(a.rings,Sl(a))},MultiPoint:function(a){return new Q(a.points,Sl(a))},MultiLineString:function(a){return new P(a.paths,Sl(a))},MultiPolygon:function(a){return new R(a.rings,Sl(a))}},Vl={Point:function(a){var b=a.X(),c;a=a.ja;
+"XYZ"===a?c={x:b[0],y:b[1],z:b[2]}:"XYM"===a?c={x:b[0],y:b[1],m:b[2]}:"XYZM"===a?c={x:b[0],y:b[1],z:b[2],m:b[3]}:"XY"===a?c={x:b[0],y:b[1]}:xa(!1,34);return c},LineString:function(a){var b=Ul(a);return{hasZ:b.hasZ,hasM:b.hasM,paths:[a.X()]}},Polygon:function(a){var b=Ul(a);return{hasZ:b.hasZ,hasM:b.hasM,rings:a.X(!1)}},MultiPoint:function(a){var b=Ul(a);return{hasZ:b.hasZ,hasM:b.hasM,points:a.X()}},MultiLineString:function(a){var b=Ul(a);return{hasZ:b.hasZ,hasM:b.hasM,paths:a.X()}},MultiPolygon:function(a){var b=
+Ul(a);a=a.X(!1);for(var c=[],d=0;d<a.length;d++)for(var e=a[d].length-1;0<=e;e--)c.push(a[d][e]);return{hasZ:b.hasZ,hasM:b.hasM,rings:c}}};k=Ql.prototype;k.Rc=function(a,b){var c=Rl(a.geometry,b),d=new H;this.b&&d.Tc(this.b);d.Ra(c);b&&b.dg&&a.attributes[b.dg]&&d.jc(a.attributes[b.dg]);a.attributes&&d.H(a.attributes);return d};k.yg=function(a,b){b=b?b:{};if(a.features){var c=[],d=a.features,e;b.dg=a.objectIdFieldName;a=0;for(e=d.length;a<e;++a)c.push(this.Rc(d[a],b));return c}return[this.Rc(a,b)]};
+k.Cg=function(a,b){return Rl(a,b)};k.Fg=function(a){return a.spatialReference&&a.spatialReference.wkid?Tb("EPSG:"+a.spatialReference.wkid):null};function Wl(a,b){return(0,Vl[a.U()])(Hl(a,!0,b),b)}k.je=function(a,b){return Wl(a,Gl(this,b))};k.Zc=function(a,b){b=Gl(this,b);var c={},d=a.V();d&&(c.geometry=Wl(d,b));d=a.N();delete d[a.c];c.attributes=wb(d)?{}:d;b&&b.featureProjection&&(c.spatialReference={wkid:Tb(b.featureProjection).mb.split(":").pop()});return c};
+k.he=function(a,b){b=Gl(this,b);var c=[],d;var e=0;for(d=a.length;e<d;++e)c.push(this.Zc(a[e],b));return{features:c}};function Xl(a){this.kc=a};function Yl(a,b){this.kc=a;this.b=Array.prototype.slice.call(arguments,1);xa(2<=this.b.length,57)}v(Yl,Xl);function Zl(a){var b=["And"].concat(Array.prototype.slice.call(arguments));Yl.apply(this,b)}v(Zl,Yl);function $l(a,b,c){this.kc="BBOX";this.geometryName=a;this.extent=b;this.srsName=c}v($l,Xl);function am(a,b){this.kc=a;this.b=b}v(am,Xl);function bm(a,b,c){am.call(this,"During",a);this.a=b;this.i=c}v(bm,am);function cm(a,b,c,d){am.call(this,a,b);this.i=c;this.a=d}v(cm,am);function dm(a,b,c){cm.call(this,"PropertyIsEqualTo",a,b,c)}v(dm,cm);function em(a,b){cm.call(this,"PropertyIsGreaterThan",a,b)}v(em,cm);function fm(a,b){cm.call(this,"PropertyIsGreaterThanOrEqualTo",a,b)}v(fm,cm);function gm(a,b,c,d){this.kc=a;this.geometryName=b||"the_geom";this.geometry=c;this.srsName=d}v(gm,Xl);function hm(a,b,c){gm.call(this,"Intersects",a,b,c)}v(hm,gm);function im(a,b,c){am.call(this,"PropertyIsBetween",a);this.a=b;this.i=c}v(im,am);function jm(a,b,c,d,e,f){am.call(this,"PropertyIsLike",a);this.c=b;this.g=void 0!==c?c:"*";this.f=void 0!==d?d:".";this.i=void 0!==e?e:"!";this.a=f}v(jm,am);function km(a){am.call(this,"PropertyIsNull",a)}v(km,am);function lm(a,b){cm.call(this,"PropertyIsLessThan",a,b)}v(lm,cm);function mm(a,b){cm.call(this,"PropertyIsLessThanOrEqualTo",a,b)}v(mm,cm);function nm(a){this.kc="Not";this.condition=a}v(nm,Xl);function om(a,b,c){cm.call(this,"PropertyIsNotEqualTo",a,b,c)}v(om,cm);function pm(a){var b=["Or"].concat(Array.prototype.slice.call(arguments));Yl.apply(this,b)}v(pm,Yl);function qm(a,b,c){gm.call(this,"Within",a,b,c)}v(qm,gm);function rm(a){var b=[null].concat(Array.prototype.slice.call(arguments));return new (Function.prototype.bind.apply(Zl,b))}function sm(a,b,c){return new $l(a,b,c)};function tm(a){of.call(this);this.a=a?a:null;um(this)}v(tm,of);function vm(a){var b=[],c;var d=0;for(c=a.length;d<c;++d)b.push(a[d].clone());return b}function wm(a){var b;if(a.a){var c=0;for(b=a.a.length;c<b;++c)Kc(a.a[c],"change",a.s,a)}}function um(a){var b;if(a.a){var c=0;for(b=a.a.length;c<b;++c)y(a.a[c],"change",a.s,a)}}k=tm.prototype;k.clone=function(){var a=new tm(null);a.oj(this.a);return a};
+k.Kb=function(a,b,c,d){if(d<Sa(this.G(),a,b))return d;var e=this.a,f;var g=0;for(f=e.length;g<f;++g)d=e[g].Kb(a,b,c,d);return d};k.Mc=function(a,b){var c=this.a,d;var e=0;for(d=c.length;e<d;++e)if(c[e].Mc(a,b))return!0;return!1};k.se=function(a){Ya(a);for(var b=this.a,c=0,d=b.length;c<d;++c)cb(a,b[c].G());return a};k.Vf=function(){return vm(this.a)};
+k.Vd=function(a){this.o!=this.i&&(ub(this.f),this.g=0,this.o=this.i);if(0>a||this.g&&a<this.g)return this;var b=a.toString();if(this.f.hasOwnProperty(b))return this.f[b];var c=[],d=this.a,e=!1,f;var g=0;for(f=d.length;g<f;++g){var h=d[g],l=h.Vd(a);c.push(l);l!==h&&(e=!0)}if(e)return a=new tm(null),wm(a),a.a=c,um(a),a.s(),this.f[b]=a;this.g=a;return this};k.U=function(){return"GeometryCollection"};k.Xa=function(a){var b=this.a,c;var d=0;for(c=b.length;d<c;++d)if(b[d].Xa(a))return!0;return!1};
+k.rotate=function(a,b){for(var c=this.a,d=0,e=c.length;d<e;++d)c[d].rotate(a,b);this.s()};k.scale=function(a,b,c){c||(c=nb(this.G()));for(var d=this.a,e=0,f=d.length;e<f;++e)d[e].scale(a,b,c);this.s()};k.oj=function(a){a=vm(a);wm(this);this.a=a;um(this);this.s()};k.Dc=function(a){var b=this.a,c;var d=0;for(c=b.length;d<c;++d)b[d].Dc(a);this.s()};k.translate=function(a,b){var c=this.a,d;var e=0;for(d=c.length;e<d;++e)c[e].translate(a,b);this.s()};k.ka=function(){wm(this);of.prototype.ka.call(this)};function xm(a){a=a?a:{};El.call(this);this.defaultDataProjection=Tb(a.defaultDataProjection?a.defaultDataProjection:"EPSG:4326");a.featureProjection&&(this.f=Tb(a.featureProjection));this.b=a.geometryName}v(xm,Il);function ym(a,b){return a?Hl((0,zm[a.type])(a),!1,b):null}function Am(a,b){return(0,Bm[a.U()])(Hl(a,!0,b),b)}
+var zm={Point:function(a){return new C(a.coordinates)},LineString:function(a){return new O(a.coordinates)},Polygon:function(a){return new D(a.coordinates)},MultiPoint:function(a){return new Q(a.coordinates)},MultiLineString:function(a){return new P(a.coordinates)},MultiPolygon:function(a){return new R(a.coordinates)},GeometryCollection:function(a,b){a=a.geometries.map(function(a){return ym(a,b)});return new tm(a)}},Bm={Point:function(a){return{type:"Point",coordinates:a.X()}},LineString:function(a){return{type:"LineString",
+coordinates:a.X()}},Polygon:function(a,b){if(b)var c=b.rightHanded;return{type:"Polygon",coordinates:a.X(c)}},MultiPoint:function(a){return{type:"MultiPoint",coordinates:a.X()}},MultiLineString:function(a){return{type:"MultiLineString",coordinates:a.X()}},MultiPolygon:function(a,b){if(b)var c=b.rightHanded;return{type:"MultiPolygon",coordinates:a.X(c)}},GeometryCollection:function(a,b){return{type:"GeometryCollection",geometries:a.a.map(function(a){var c=tb({},b);delete c.featureProjection;return Am(a,
+c)})}},Circle:function(){return{type:"GeometryCollection",geometries:[]}}};k=xm.prototype;k.Rc=function(a,b){a="Feature"===a.type?a:{type:"Feature",geometry:a};b=ym(a.geometry,b);var c=new H;this.b&&c.Tc(this.b);c.Ra(b);void 0!==a.id&&c.jc(a.id);a.properties&&c.H(a.properties);return c};k.yg=function(a,b){if("FeatureCollection"===a.type){var c=[];a=a.features;var d;var e=0;for(d=a.length;e<d;++e)c.push(this.Rc(a[e],b))}else c=[this.Rc(a,b)];return c};k.Cg=function(a,b){return ym(a,b)};
+k.Fg=function(a){a=a.crs;var b;a?"name"==a.type?b=Tb(a.properties.name):"EPSG"==a.type?b=Tb("EPSG:"+a.properties.code):xa(!1,36):b=this.defaultDataProjection;return b};k.Zc=function(a,b){b=Gl(this,b);var c={type:"Feature"},d=a.a;void 0!==d&&(c.id=d);(d=a.V())?c.geometry=Am(d,b):c.geometry=null;b=a.N();delete b[a.c];wb(b)?c.properties=null:c.properties=b;return c};k.he=function(a,b){b=Gl(this,b);var c=[],d;var e=0;for(d=a.length;e<d;++e)c.push(this.Zc(a[e],b));return{type:"FeatureCollection",features:c}};
+k.je=function(a,b){return Am(a,Gl(this,b))};function Cm(){this.i=new XMLSerializer;El.call(this)}v(Cm,El);k=Cm.prototype;k.U=function(){return"xml"};k.Tb=function(a,b){return ml(a)?Dm(this,a,b):nl(a)?this.xg(a,b):"string"===typeof a?(a=pl(a),Dm(this,a,b)):null};function Dm(a,b,c){a=Em(a,b,c);return 0<a.length?a[0]:null}k.xg=function(){return null};k.Oa=function(a,b){return ml(a)?Em(this,a,b):nl(a)?this.zc(a,b):"string"===typeof a?(a=pl(a),Em(this,a,b)):[]};
+function Em(a,b,c){var d=[];for(b=b.firstChild;b;b=b.nextSibling)b.nodeType==Node.ELEMENT_NODE&&la(d,a.zc(b,c));return d}k.Sc=function(a,b){if(ml(a))return null;if(nl(a))return this.aj(a,b);"string"===typeof a&&pl(a);return null};k.aj=function(){return null};k.kb=function(a){return ml(a)?this.Eg(a):nl(a)?this.kf(a):"string"===typeof a?(a=pl(a),this.Eg(a)):null};k.Eg=function(){return this.defaultDataProjection};k.kf=function(){return this.defaultDataProjection};
+k.Bd=function(a,b){return this.i.serializeToString(this.Vg(a,b))};k.Vg=function(){return null};k.Wb=function(a,b){a=this.Xb(a,b);return this.i.serializeToString(a)};k.Xb=function(){return null};k.$c=function(a,b){a=this.ie(a,b);return this.i.serializeToString(a)};k.ie=function(){return null};function Fm(a){a=a?a:{};this.featureType=a.featureType;this.featureNS=a.featureNS;this.srsName=a.srsName;this.schemaLocation="";this.b={};this.b["http://www.opengis.net/gml"]={featureMember:sl(Fm.prototype.be),featureMembers:sl(Fm.prototype.be)};Cm.call(this)}v(Fm,Cm);var Gm=/^[\s\xa0]*$/;k=Fm.prototype;
+k.be=function(a,b){var c=a.localName,d=null;if("FeatureCollection"==c)"http://www.opengis.net/wfs"===a.namespaceURI?d=N([],this.b,a,b,this):d=N(null,this.b,a,b,this);else if("featureMembers"==c||"featureMember"==c){var e=b[0],f=e.featureType,g=e.featureNS,h;if(!f&&a.childNodes){f=[];g={};var l=0;for(h=a.childNodes.length;l<h;++l){var m=a.childNodes[l];if(1===m.nodeType){var n=m.nodeName.split(":").pop();if(-1===f.indexOf(n)){var p="",q=0,m=m.namespaceURI,r;for(r in g){if(g[r]===m){p=r;break}++q}p||
+(p="p"+q,g[p]=m);f.push(p+":"+n)}}}"featureMember"!=c&&(e.featureType=f,e.featureNS=g)}"string"===typeof g&&(l=g,g={},g.p0=l);var e={},f=Array.isArray(f)?f:[f],u;for(u in g){n={};l=0;for(h=f.length;l<h;++l)(-1===f[l].indexOf(":")?"p0":f[l].split(":")[0])===u&&(n[f[l].split(":").pop()]="featureMembers"==c?rl(this.wg,this):sl(this.wg,this));e[g[u]]=n}"featureMember"==c?d=N(void 0,e,a,b):d=N([],e,a,b)}null===d&&(d=[]);return d};
+k.gf=function(a,b){var c=b[0];c.srsName=a.firstElementChild.getAttribute("srsName");if(a=N(null,this.Zg,a,b,this))return Hl(a,!1,c)};
+k.wg=function(a,b){var c;(c=a.getAttribute("fid"))||(c=a.getAttributeNS("http://www.opengis.net/gml","id")||"");var d={},e;for(a=a.firstElementChild;a;a=a.nextElementSibling){var f=a.localName;if(0===a.childNodes.length||1===a.childNodes.length&&(3===a.firstChild.nodeType||4===a.firstChild.nodeType)){var g=kl(a,!1);Gm.test(g)&&(g=void 0);d[f]=g}else"boundedBy"!==f&&(e=f),d[f]=this.gf(a,b)}b=new H(d);e&&b.Tc(e);c&&b.jc(c);return b};
+k.fj=function(a,b){if(a=this.ff(a,b))return b=new C(null),b.ba("XYZ",a),b};k.dj=function(a,b){if(a=N([],this.Nj,a,b,this))return new Q(a)};k.cj=function(a,b){if(a=N([],this.Mj,a,b,this))return b=new P(null),Nl(b,a),b};k.ej=function(a,b){if(a=N([],this.Oj,a,b,this))return b=new R(null),Pl(b,a),b};k.Xi=function(a,b){Al(this.Rj,a,b,this)};k.Mh=function(a,b){Al(this.Kj,a,b,this)};k.Yi=function(a,b){Al(this.Sj,a,b,this)};k.hf=function(a,b){if(a=this.ff(a,b))return b=new O(null),b.ba("XYZ",a),b};
+k.wp=function(a,b){if(a=N(null,this.ke,a,b,this))return a};k.bj=function(a,b){if(a=this.ff(a,b))return b=new Jf(null),Kf(b,"XYZ",a),b};k.jf=function(a,b){if((a=N([null],this.zf,a,b,this))&&a[0]){b=new D(null);var c=a[0],d=[c.length],e;var f=1;for(e=a.length;f<e;++f)la(c,a[f]),d.push(c.length);b.ba("XYZ",c,d);return b}};k.ff=function(a,b){return N(null,this.ke,a,b,this)};k.Nj={"http://www.opengis.net/gml":{pointMember:rl(Fm.prototype.Xi),pointMembers:rl(Fm.prototype.Xi)}};
+k.Mj={"http://www.opengis.net/gml":{lineStringMember:rl(Fm.prototype.Mh),lineStringMembers:rl(Fm.prototype.Mh)}};k.Oj={"http://www.opengis.net/gml":{polygonMember:rl(Fm.prototype.Yi),polygonMembers:rl(Fm.prototype.Yi)}};k.Rj={"http://www.opengis.net/gml":{Point:rl(Fm.prototype.ff)}};k.Kj={"http://www.opengis.net/gml":{LineString:rl(Fm.prototype.hf)}};k.Sj={"http://www.opengis.net/gml":{Polygon:rl(Fm.prototype.jf)}};k.le={"http://www.opengis.net/gml":{LinearRing:sl(Fm.prototype.wp)}};
+k.aj=function(a,b){return(a=this.gf(a,[Fl(this,a,b?b:{})]))?a:null};k.zc=function(a,b){var c={featureType:this.featureType,featureNS:this.featureNS};b&&tb(c,Fl(this,a,b));return this.be(a,[c])||[]};k.kf=function(a){return Tb(this.srsName?this.srsName:a.firstElementChild.getAttribute("srsName"))};function Hm(a){a=kl(a,!1);return Im(a)}function Im(a){if(a=/^\s*(true|1)|(false|0)\s*$/.exec(a))return void 0!==a[1]||!1}function Jm(a){a=kl(a,!1);a=Date.parse(a);return isNaN(a)?void 0:a/1E3}function Km(a){a=kl(a,!1);return Lm(a)}function Lm(a){if(a=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(a))return parseFloat(a[1])}function Mm(a){a=kl(a,!1);return Nm(a)}function Nm(a){if(a=/^\s*(\d+)\s*$/.exec(a))return parseInt(a[1],10)}function S(a){return kl(a,!1).trim()}
+function Om(a,b){Pm(a,b?"1":"0")}function Qm(a,b){a.appendChild(il.createTextNode(b.toPrecision()))}function Rm(a,b){a.appendChild(il.createTextNode(b.toString()))}function Pm(a,b){a.appendChild(il.createTextNode(b))};function Sm(a){a=a?a:{};Fm.call(this,a);this.l=void 0!==a.surface?a.surface:!1;this.c=void 0!==a.curve?a.curve:!1;this.g=void 0!==a.multiCurve?a.multiCurve:!0;this.j=void 0!==a.multiSurface?a.multiSurface:!0;this.schemaLocation=a.schemaLocation?a.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd"}v(Sm,Fm);k=Sm.prototype;k.Ap=function(a,b){if(a=N([],this.Lj,a,b,this))return b=new P(null),Nl(b,a),b};
+k.Bp=function(a,b){if(a=N([],this.Pj,a,b,this))return b=new R(null),Pl(b,a),b};k.ph=function(a,b){Al(this.Hj,a,b,this)};k.Aj=function(a,b){Al(this.Uj,a,b,this)};k.Ep=function(a,b){return N([null],this.Qj,a,b,this)};k.Hp=function(a,b){return N([null],this.Tj,a,b,this)};k.Fp=function(a,b){return N([null],this.zf,a,b,this)};k.zp=function(a,b){return N([null],this.ke,a,b,this)};k.cm=function(a,b){(a=N(void 0,this.le,a,b,this))&&b[b.length-1].push(a)};
+k.yk=function(a,b){(a=N(void 0,this.le,a,b,this))&&(b[b.length-1][0]=a)};k.gj=function(a,b){if((a=N([null],this.Vj,a,b,this))&&a[0]){b=new D(null);var c=a[0],d=[c.length],e;var f=1;for(e=a.length;f<e;++f)la(c,a[f]),d.push(c.length);b.ba("XYZ",c,d);return b}};k.Zi=function(a,b){if(a=N([null],this.Ij,a,b,this))return b=new O(null),b.ba("XYZ",a),b};k.vp=function(a,b){a=N([null],this.Jj,a,b,this);return Xa(a[1][0],a[1][1],a[2][0],a[2][1])};
+k.xp=function(a,b){var c=kl(a,!1),d=/^\s*([+\-]?\d*\.?\d+(?:[eE][+\-]?\d+)?)\s*/;a=[];for(var e;e=d.exec(c);)a.push(parseFloat(e[1])),c=c.substr(e[0].length);if(""===c){b=b[0].srsName;c="enu";b&&(c=Tb(b).b);if("neu"===c)for(b=0,c=a.length;b<c;b+=3)d=a[b],a[b]=a[b+1],a[b+1]=d;b=a.length;2==b&&a.push(0);if(b)return a}};
+k.Bg=function(a,b){var c=kl(a,!1).replace(/^\s*|\s*$/g,""),d=b[0].srsName,e=a.parentNode.getAttribute("srsDimension");b="enu";d&&(b=Tb(d).b);c=c.split(/\s+/);d=2;a.getAttribute("srsDimension")?d=Nm(a.getAttribute("srsDimension")):a.getAttribute("dimension")?d=Nm(a.getAttribute("dimension")):e&&(d=Nm(e));for(var f,g=[],h=0,l=c.length;h<l;h+=d)a=parseFloat(c[h]),e=parseFloat(c[h+1]),f=3===d?parseFloat(c[h+2]):0,"en"===b.substr(0,2)?g.push(a,e,f):g.push(e,a,f);return g};
+k.ke={"http://www.opengis.net/gml":{pos:sl(Sm.prototype.xp),posList:sl(Sm.prototype.Bg)}};k.zf={"http://www.opengis.net/gml":{interior:Sm.prototype.cm,exterior:Sm.prototype.yk}};
+k.Zg={"http://www.opengis.net/gml":{Point:sl(Fm.prototype.fj),MultiPoint:sl(Fm.prototype.dj),LineString:sl(Fm.prototype.hf),MultiLineString:sl(Fm.prototype.cj),LinearRing:sl(Fm.prototype.bj),Polygon:sl(Fm.prototype.jf),MultiPolygon:sl(Fm.prototype.ej),Surface:sl(Sm.prototype.gj),MultiSurface:sl(Sm.prototype.Bp),Curve:sl(Sm.prototype.Zi),MultiCurve:sl(Sm.prototype.Ap),Envelope:sl(Sm.prototype.vp)}};k.Lj={"http://www.opengis.net/gml":{curveMember:rl(Sm.prototype.ph),curveMembers:rl(Sm.prototype.ph)}};
+k.Pj={"http://www.opengis.net/gml":{surfaceMember:rl(Sm.prototype.Aj),surfaceMembers:rl(Sm.prototype.Aj)}};k.Hj={"http://www.opengis.net/gml":{LineString:rl(Fm.prototype.hf),Curve:rl(Sm.prototype.Zi)}};k.Uj={"http://www.opengis.net/gml":{Polygon:rl(Fm.prototype.jf),Surface:rl(Sm.prototype.gj)}};k.Vj={"http://www.opengis.net/gml":{patches:sl(Sm.prototype.Ep)}};k.Ij={"http://www.opengis.net/gml":{segments:sl(Sm.prototype.Hp)}};k.Jj={"http://www.opengis.net/gml":{lowerCorner:rl(Sm.prototype.Bg),upperCorner:rl(Sm.prototype.Bg)}};
+k.Qj={"http://www.opengis.net/gml":{PolygonPatch:sl(Sm.prototype.Fp)}};k.Tj={"http://www.opengis.net/gml":{LineStringSegment:sl(Sm.prototype.zp)}};function Tm(a,b,c){var d=c[c.length-1];c=d.hasZ;d=d.srsName;b=b.X();for(var e=b.length,f=Array(e),g,h=0;h<e;++h){g=b[h];var l=h,m=c,n="enu";d&&(n=Tb(d).b);n="en"===n.substr(0,2)?g[0]+" "+g[1]:g[1]+" "+g[0];m&&(n+=" "+(g[2]||0));f[l]=n}Pm(a,f.join(" "))}
+k.ni=function(a,b,c){var d=c[c.length-1].srsName;d&&a.setAttribute("srsName",d);d=jl(a.namespaceURI,"pos");a.appendChild(d);c=c[c.length-1];a=c.hasZ;var e=c.srsName;c="enu";e&&(c=Tb(e).b);b=b.X();c="en"===c.substr(0,2)?b[0]+" "+b[1]:b[1]+" "+b[0];a&&(c+=" "+(b[2]||0));Pm(d,c)};var Um={"http://www.opengis.net/gml":{lowerCorner:J(Pm),upperCorner:J(Pm)}};k=Sm.prototype;
+k.jn=function(a,b,c){var d=c[c.length-1].srsName;d&&a.setAttribute("srsName",d);Bl({node:a},Um,yl,[b[0]+" "+b[1],b[2]+" "+b[3]],c,["lowerCorner","upperCorner"],this)};k.ki=function(a,b,c){var d=c[c.length-1].srsName;d&&a.setAttribute("srsName",d);d=jl(a.namespaceURI,"posList");a.appendChild(d);Tm(d,b,c)};k.hn=function(a,b){a=b[b.length-1];b=a.node;var c=a.exteriorWritten;void 0===c&&(a.exteriorWritten=!0);return jl(b.namespaceURI,void 0!==c?"interior":"exterior")};
+k.Se=function(a,b,c){var d=c[c.length-1],e=d.hasZ,d=d.srsName;"PolygonPatch"!==a.nodeName&&d&&a.setAttribute("srsName",d);"Polygon"===a.nodeName||"PolygonPatch"===a.nodeName?(b=b.Sd(),Bl({node:a,hasZ:e,srsName:d},Vm,this.hn,b,c,void 0,this)):"Surface"===a.nodeName&&(e=jl(a.namespaceURI,"patches"),a.appendChild(e),a=jl(e.namespaceURI,"PolygonPatch"),e.appendChild(a),this.Se(a,b,c))};
+k.Re=function(a,b,c){var d=c[c.length-1].srsName;"LineStringSegment"!==a.nodeName&&d&&a.setAttribute("srsName",d);"LineString"===a.nodeName||"LineStringSegment"===a.nodeName?(d=jl(a.namespaceURI,"posList"),a.appendChild(d),Tm(d,b,c)):"Curve"===a.nodeName&&(d=jl(a.namespaceURI,"segments"),a.appendChild(d),a=jl(d.namespaceURI,"LineStringSegment"),d.appendChild(a),this.Re(a,b,c))};
+k.mi=function(a,b,c){var d=c[c.length-1],e=d.hasZ,f=d.srsName,d=d.surface;f&&a.setAttribute("srsName",f);b=b.Td();Bl({node:a,hasZ:e,srsName:f,surface:d},Wm,this.o,b,c,void 0,this)};k.kn=function(a,b,c){var d=c[c.length-1],e=d.srsName,d=d.hasZ;e&&a.setAttribute("srsName",e);b=b.Zd();Bl({node:a,hasZ:d,srsName:e},Xm,wl("pointMember"),b,c,void 0,this)};
+k.li=function(a,b,c){var d=c[c.length-1],e=d.hasZ,f=d.srsName,d=d.curve;f&&a.setAttribute("srsName",f);b=b.gd();Bl({node:a,hasZ:e,srsName:f,curve:d},Ym,this.o,b,c,void 0,this)};k.oi=function(a,b,c){var d=jl(a.namespaceURI,"LinearRing");a.appendChild(d);this.ki(d,b,c)};k.pi=function(a,b,c){var d=this.a(b,c);d&&(a.appendChild(d),this.Se(d,b,c))};k.ln=function(a,b,c){var d=jl(a.namespaceURI,"Point");a.appendChild(d);this.ni(d,b,c)};
+k.ji=function(a,b,c){var d=this.a(b,c);d&&(a.appendChild(d),this.Re(d,b,c))};k.od=function(a,b,c){var d=c[c.length-1],e=tb({},d);e.node=a;var f;Array.isArray(b)?d.dataProjection?f=hc(b,d.featureProjection,d.dataProjection):f=b:f=Hl(b,!0,d);Bl(e,Zm,this.a,[f],c,void 0,this)};
+k.ii=function(a,b,c){var d=b.a;d&&a.setAttribute("fid",d);var d=c[c.length-1],e=d.featureNS,f=b.c;d.lb||(d.lb={},d.lb[e]={});var g=b.N();b=[];var h=[];for(m in g){var l=g[m];null!==l&&(b.push(m),h.push(l),m==f||l instanceof of?m in d.lb[e]||(d.lb[e][m]=J(this.od,this)):m in d.lb[e]||(d.lb[e][m]=J(Pm)))}var m=tb({},d);m.node=a;Bl(m,d.lb,wl(void 0,e),h,c,b)};
+var Wm={"http://www.opengis.net/gml":{surfaceMember:J(Sm.prototype.pi),polygonMember:J(Sm.prototype.pi)}},Xm={"http://www.opengis.net/gml":{pointMember:J(Sm.prototype.ln)}},Ym={"http://www.opengis.net/gml":{lineStringMember:J(Sm.prototype.ji),curveMember:J(Sm.prototype.ji)}},Vm={"http://www.opengis.net/gml":{exterior:J(Sm.prototype.oi),interior:J(Sm.prototype.oi)}},Zm={"http://www.opengis.net/gml":{Curve:J(Sm.prototype.Re),MultiCurve:J(Sm.prototype.li),Point:J(Sm.prototype.ni),MultiPoint:J(Sm.prototype.kn),
+LineString:J(Sm.prototype.Re),MultiLineString:J(Sm.prototype.li),LinearRing:J(Sm.prototype.ki),Polygon:J(Sm.prototype.Se),MultiPolygon:J(Sm.prototype.mi),Surface:J(Sm.prototype.Se),MultiSurface:J(Sm.prototype.mi),Envelope:J(Sm.prototype.jn)}},$m={MultiLineString:"lineStringMember",MultiCurve:"curveMember",MultiPolygon:"polygonMember",MultiSurface:"surfaceMember"};Sm.prototype.o=function(a,b){return jl("http://www.opengis.net/gml",$m[b[b.length-1].node.nodeName])};
+Sm.prototype.a=function(a,b){var c=b[b.length-1];b=c.multiSurface;var d=c.surface,e=c.curve,c=c.multiCurve;Array.isArray(a)?a="Envelope":(a=a.U(),"MultiPolygon"===a&&!0===b?a="MultiSurface":"Polygon"===a&&!0===d?a="Surface":"LineString"===a&&!0===e?a="Curve":"MultiLineString"===a&&!0===c&&(a="MultiCurve"));return jl("http://www.opengis.net/gml",a)};
+Sm.prototype.ie=function(a,b){b=Gl(this,b);var c=jl("http://www.opengis.net/gml","geom"),d={node:c,hasZ:this.hasZ,srsName:this.srsName,curve:this.c,surface:this.l,multiSurface:this.j,multiCurve:this.g};b&&tb(d,b);this.od(c,a,[d]);return c};
+Sm.prototype.Xb=function(a,b){b=Gl(this,b);var c=jl("http://www.opengis.net/gml","featureMembers");c.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.schemaLocation);var d={srsName:this.srsName,hasZ:this.hasZ,curve:this.c,surface:this.l,multiSurface:this.j,multiCurve:this.g,featureNS:this.featureNS,featureType:this.featureType};b&&tb(d,b);b=[d];var e=b[b.length-1],d=e.featureType,f=e.featureNS,g={};g[f]={};g[f][d]=J(this.ii,this);e=tb({},e);e.node=c;Bl(e,g,wl(d,
+f),a,b);return c};function an(a){a=a?a:{};Fm.call(this,a);this.b["http://www.opengis.net/gml"].featureMember=rl(Fm.prototype.be);this.schemaLocation=a.schemaLocation?a.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd"}v(an,Fm);k=an.prototype;
+k.$i=function(a,b){a=kl(a,!1).replace(/^\s*|\s*$/g,"");var c=b[0].srsName;b="enu";c&&(c=Tb(c))&&(b=c.b);a=a.trim().split(/\s+/);for(var d,e,f=[],g=0,h=a.length;g<h;g++)e=a[g].split(/,+/),c=parseFloat(e[0]),d=parseFloat(e[1]),e=3===e.length?parseFloat(e[2]):0,"en"===b.substr(0,2)?f.push(c,d,e):f.push(d,c,e);return f};k.tp=function(a,b){a=N([null],this.Gj,a,b,this);return Xa(a[1][0],a[1][1],a[1][3],a[1][4])};k.am=function(a,b){(a=N(void 0,this.le,a,b,this))&&b[b.length-1].push(a)};
+k.bp=function(a,b){(a=N(void 0,this.le,a,b,this))&&(b[b.length-1][0]=a)};k.ke={"http://www.opengis.net/gml":{coordinates:sl(an.prototype.$i)}};k.zf={"http://www.opengis.net/gml":{innerBoundaryIs:an.prototype.am,outerBoundaryIs:an.prototype.bp}};k.Gj={"http://www.opengis.net/gml":{coordinates:rl(an.prototype.$i)}};
+k.Zg={"http://www.opengis.net/gml":{Point:sl(Fm.prototype.fj),MultiPoint:sl(Fm.prototype.dj),LineString:sl(Fm.prototype.hf),MultiLineString:sl(Fm.prototype.cj),LinearRing:sl(Fm.prototype.bj),Polygon:sl(Fm.prototype.jf),MultiPolygon:sl(Fm.prototype.ej),Box:sl(an.prototype.tp)}};
+k.jg=function(a,b){var c=b[b.length-1];b=c.multiSurface;var d=c.surface,c=c.multiCurve;Array.isArray(a)?a="Envelope":(a=a.U(),"MultiPolygon"===a&&!0===b?a="MultiSurface":"Polygon"===a&&!0===d?a="Surface":"MultiLineString"===a&&!0===c&&(a="MultiCurve"));return jl("http://www.opengis.net/gml",a)};k.ai=function(a,b,c){var d=c[c.length-1],e=tb({},d);e.node=a;var f;Array.isArray(b)?d.dataProjection?f=hc(b,d.featureProjection,d.dataProjection):f=b:f=Hl(b,!0,d);Bl(e,bn,this.jg,[f],c,void 0,this)};
+k.Pe=function(a,b,c){var d=c[c.length-1].srsName;"LineStringSegment"!==a.nodeName&&d&&a.setAttribute("srsName",d);"LineString"===a.nodeName||"LineStringSegment"===a.nodeName?(d=cn(a.namespaceURI),a.appendChild(d),dn(d,b,c)):"Curve"===a.nodeName&&(d=jl(a.namespaceURI,"segments"),a.appendChild(d),a=jl(d.namespaceURI,"LineStringSegment"),d.appendChild(a),this.Pe(a,b,c))};function cn(a){a=jl(a,"coordinates");a.setAttribute("decimal",".");a.setAttribute("cs",",");a.setAttribute("ts"," ");return a}
+function dn(a,b,c){var d=c[c.length-1];c=d.hasZ;d=d.srsName;b=b.X();for(var e=b.length,f=Array(e),g,h=0;h<e;++h)g=b[h],f[h]=en(g,d,c);Pm(a,f.join(" "))}
+k.Qe=function(a,b,c){var d=c[c.length-1],e=d.hasZ,d=d.srsName;"PolygonPatch"!==a.nodeName&&d&&a.setAttribute("srsName",d);"Polygon"===a.nodeName||"PolygonPatch"===a.nodeName?(b=b.Sd(),Bl({node:a,hasZ:e,srsName:d},fn,this.dn,b,c,void 0,this)):"Surface"===a.nodeName&&(e=jl(a.namespaceURI,"patches"),a.appendChild(e),a=jl(e.namespaceURI,"PolygonPatch"),e.appendChild(a),this.Qe(a,b,c))};
+k.dn=function(a,b){a=b[b.length-1];b=a.node;var c=a.exteriorWritten;void 0===c&&(a.exteriorWritten=!0);return jl(b.namespaceURI,void 0!==c?"innerBoundaryIs":"outerBoundaryIs")};k.gi=function(a,b,c){var d=jl(a.namespaceURI,"LinearRing");a.appendChild(d);this.ci(d,b,c)};function en(a,b,c){var d="enu";b&&(d=Tb(b).b);b="en"===d.substr(0,2)?a[0]+","+a[1]:a[1]+","+a[0];c&&(b+=","+(a[2]||0));return b}
+k.di=function(a,b,c){var d=c[c.length-1],e=d.hasZ,f=d.srsName,d=d.curve;f&&a.setAttribute("srsName",f);b=b.gd();Bl({node:a,hasZ:e,srsName:f,curve:d},gn,this.a,b,c,void 0,this)};k.fi=function(a,b,c){var d=c[c.length-1];c=d.hasZ;var e=d.srsName;e&&a.setAttribute("srsName",e);d=cn(a.namespaceURI);a.appendChild(d);a=b.X();a=en(a,e,c);Pm(d,a)};
+k.fn=function(a,b,c){var d=c[c.length-1],e=d.hasZ;(d=d.srsName)&&a.setAttribute("srsName",d);b=b.Zd();Bl({node:a,hasZ:e,srsName:d},hn,wl("pointMember"),b,c,void 0,this)};k.gn=function(a,b,c){var d=jl(a.namespaceURI,"Point");a.appendChild(d);this.fi(d,b,c)};k.bi=function(a,b,c){var d=this.jg(b,c);d&&(a.appendChild(d),this.Pe(d,b,c))};k.ci=function(a,b,c){var d=c[c.length-1].srsName;d&&a.setAttribute("srsName",d);d=cn(a.namespaceURI);a.appendChild(d);dn(d,b,c)};
+k.ei=function(a,b,c){var d=c[c.length-1],e=d.hasZ,f=d.srsName,d=d.surface;f&&a.setAttribute("srsName",f);b=b.Td();Bl({node:a,hasZ:e,srsName:f,surface:d},jn,this.a,b,c,void 0,this)};k.hi=function(a,b,c){var d=this.jg(b,c);d&&(a.appendChild(d),this.Qe(d,b,c))};k.en=function(a,b,c){var d=c[c.length-1].srsName;d&&a.setAttribute("srsName",d);Bl({node:a},kn,yl,[b[0]+" "+b[1],b[2]+" "+b[3]],c,["lowerCorner","upperCorner"],this)};
+var bn={"http://www.opengis.net/gml":{Curve:J(an.prototype.Pe),MultiCurve:J(an.prototype.di),Point:J(an.prototype.fi),MultiPoint:J(an.prototype.fn),LineString:J(an.prototype.Pe),MultiLineString:J(an.prototype.di),LinearRing:J(an.prototype.ci),Polygon:J(an.prototype.Qe),MultiPolygon:J(an.prototype.ei),Surface:J(an.prototype.Qe),MultiSurface:J(an.prototype.ei),Envelope:J(an.prototype.en)}},fn={"http://www.opengis.net/gml":{outerBoundaryIs:J(an.prototype.gi),innerBoundaryIs:J(an.prototype.gi)}},hn={"http://www.opengis.net/gml":{pointMember:J(an.prototype.gn)}},
+gn={"http://www.opengis.net/gml":{lineStringMember:J(an.prototype.bi),curveMember:J(an.prototype.bi)}};an.prototype.a=function(a,b){return jl("http://www.opengis.net/gml",ln[b[b.length-1].node.nodeName])};var ln={MultiLineString:"lineStringMember",MultiCurve:"curveMember",MultiPolygon:"polygonMember",MultiSurface:"surfaceMember"},jn={"http://www.opengis.net/gml":{surfaceMember:J(an.prototype.hi),polygonMember:J(an.prototype.hi)}},kn={"http://www.opengis.net/gml":{lowerCorner:J(Pm),upperCorner:J(Pm)}};function mn(a){a=a?a:{};Cm.call(this);this.defaultDataProjection=Tb("EPSG:4326");this.b=a.readExtensions}v(mn,Cm);var nn=[null,"http://www.topografix.com/GPX/1/0","http://www.topografix.com/GPX/1/1"];function on(a,b,c,d){a.push(parseFloat(c.getAttribute("lon")),parseFloat(c.getAttribute("lat")));"ele"in d?(a.push(d.ele),delete d.ele,b.hasZ=!0):a.push(0);"time"in d?(a.push(d.time),delete d.time,b.hasM=!0):a.push(0);return a}
+function pn(a,b,c){var d="XY",e=2;a.hasZ&&a.hasM?(d="XYZM",e=4):a.hasZ?(d="XYZ",e=3):a.hasM&&(d="XYM",e=3);if(4!==e){var f;var g=0;for(f=b.length/4;g<f;g++)b[g*e]=b[4*g],b[g*e+1]=b[4*g+1],a.hasZ&&(b[g*e+2]=b[4*g+2]),a.hasM&&(b[g*e+2]=b[4*g+3]);b.length=b.length/4*e;if(c)for(g=0,f=c.length;g<f;g++)c[g]=c[g]/4*e}return d}function qn(a,b){var c=b[b.length-1],d=a.getAttribute("href");null!==d&&(c.link=d);Al(rn,a,b)}function sn(a,b){b[b.length-1].extensionsNode_=a}
+function tn(a,b){var c=b[0];if(a=N({flatCoordinates:[],layoutOptions:{}},un,a,b)){b=a.flatCoordinates;delete a.flatCoordinates;var d=a.layoutOptions;delete a.layoutOptions;var d=pn(d,b),e=new O(null);e.ba(d,b);Hl(e,!1,c);c=new H(e);c.H(a);return c}}
+function vn(a,b){var c=b[0];if(a=N({flatCoordinates:[],ends:[],layoutOptions:{}},wn,a,b)){b=a.flatCoordinates;delete a.flatCoordinates;var d=a.ends;delete a.ends;var e=a.layoutOptions;delete a.layoutOptions;var e=pn(e,b,d),f=new P(null);f.ba(e,b,d);Hl(f,!1,c);c=new H(f);c.H(a);return c}}function xn(a,b){var c=b[0];if(b=N({},yn,a,b)){var d={};a=on([],d,a,b);d=pn(d,a);a=new C(a,d);Hl(a,!1,c);c=new H(a);c.H(b);return c}}
+var zn={rte:tn,trk:vn,wpt:xn},An=K(nn,{rte:rl(tn),trk:rl(vn),wpt:rl(xn)}),rn=K(nn,{text:I(S,"linkText"),type:I(S,"linkType")}),un=K(nn,{name:I(S),cmt:I(S),desc:I(S),src:I(S),link:qn,number:I(Mm),extensions:sn,type:I(S),rtept:function(a,b){var c=N({},Bn,a,b);c&&(b=b[b.length-1],on(b.flatCoordinates,b.layoutOptions,a,c))}}),Bn=K(nn,{ele:I(Km),time:I(Jm)}),wn=K(nn,{name:I(S),cmt:I(S),desc:I(S),src:I(S),link:qn,number:I(Mm),type:I(S),extensions:sn,trkseg:function(a,b){var c=b[b.length-1];Al(Cn,a,b);c.ends.push(c.flatCoordinates.length)}}),
+Cn=K(nn,{trkpt:function(a,b){var c=N({},Dn,a,b);c&&(b=b[b.length-1],on(b.flatCoordinates,b.layoutOptions,a,c))}}),Dn=K(nn,{ele:I(Km),time:I(Jm)}),yn=K(nn,{ele:I(Km),time:I(Jm),magvar:I(Km),geoidheight:I(Km),name:I(S),cmt:I(S),desc:I(S),src:I(S),link:qn,sym:I(S),type:I(S),fix:I(S),sat:I(Mm),hdop:I(Km),vdop:I(Km),pdop:I(Km),ageofdgpsdata:I(Km),dgpsid:I(Mm),extensions:sn});
+function En(a,b){b||(b=[]);for(var c=0,d=b.length;c<d;++c){var e=b[c];if(a.b){var f=e.get("extensionsNode_")||null;a.b(e,f)}e.set("extensionsNode_",void 0)}}mn.prototype.xg=function(a,b){if(!ja(nn,a.namespaceURI))return null;var c=zn[a.localName];if(!c)return null;a=c(a,[Fl(this,a,b)]);if(!a)return null;En(this,[a]);return a};mn.prototype.zc=function(a,b){return ja(nn,a.namespaceURI)?"gpx"==a.localName&&(a=N([],An,a,[Fl(this,a,b)]))?(En(this,a),a):[]:[]};
+function Fn(a,b,c){a.setAttribute("href",b);b=c[c.length-1].properties;Bl({node:a},Gn,yl,[b.linkText,b.linkType],c,Hn)}function In(a,b,c){var d=c[c.length-1],e=d.node.namespaceURI,f=d.properties;a.setAttributeNS(null,"lat",b[1]);a.setAttributeNS(null,"lon",b[0]);switch(d.geometryLayout){case "XYZM":b[3]&&(f.time=b[3]);case "XYZ":b[2]&&(f.ele=b[2]);break;case "XYM":b[2]&&(f.time=b[2])}b="rtept"==a.nodeName?Jn[e]:Kn[e];d=zl(f,b);Bl({node:a,properties:f},Ln,yl,d,c,b)}
+var Hn=["text","type"],Gn=K(nn,{text:J(Pm),type:J(Pm)}),Mn=K(nn,"name cmt desc src link number type rtept".split(" ")),Nn=K(nn,{name:J(Pm),cmt:J(Pm),desc:J(Pm),src:J(Pm),link:J(Fn),number:J(Rm),type:J(Pm),rtept:vl(J(In))}),Jn=K(nn,["ele","time"]),On=K(nn,"name cmt desc src link number type trkseg".split(" ")),Rn=K(nn,{name:J(Pm),cmt:J(Pm),desc:J(Pm),src:J(Pm),link:J(Fn),number:J(Rm),type:J(Pm),trkseg:vl(J(function(a,b,c){Bl({node:a,geometryLayout:b.ja,properties:{}},Pn,Qn,b.X(),c)}))}),Qn=wl("trkpt"),
+Pn=K(nn,{trkpt:J(In)}),Kn=K(nn,"ele time magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid".split(" ")),Ln=K(nn,{ele:J(Qm),time:J(function(a,b){b=new Date(1E3*b);a.appendChild(il.createTextNode(b.getUTCFullYear()+"-"+Xe(b.getUTCMonth()+1)+"-"+Xe(b.getUTCDate())+"T"+Xe(b.getUTCHours())+":"+Xe(b.getUTCMinutes())+":"+Xe(b.getUTCSeconds())+"Z"))}),magvar:J(Qm),geoidheight:J(Qm),name:J(Pm),cmt:J(Pm),desc:J(Pm),src:J(Pm),link:J(Fn),sym:J(Pm),type:J(Pm),fix:J(Pm),
+sat:J(Rm),hdop:J(Qm),vdop:J(Qm),pdop:J(Qm),ageofdgpsdata:J(Qm),dgpsid:J(Rm)}),Sn={Point:"wpt",LineString:"rte",MultiLineString:"trk"};function Tn(a,b){if(a=a.V())if(a=Sn[a.U()])return jl(b[b.length-1].node.namespaceURI,a)}
+var Un=K(nn,{rte:J(function(a,b,c){var d=c[0],e=b.N();a={node:a,properties:e};if(b=b.V())b=Hl(b,!0,d),a.geometryLayout=b.ja,e.rtept=b.X();d=Mn[c[c.length-1].node.namespaceURI];e=zl(e,d);Bl(a,Nn,yl,e,c,d)}),trk:J(function(a,b,c){var d=c[0],e=b.N();a={node:a,properties:e};if(b=b.V())b=Hl(b,!0,d),e.trkseg=b.gd();d=On[c[c.length-1].node.namespaceURI];e=zl(e,d);Bl(a,Rn,yl,e,c,d)}),wpt:J(function(a,b,c){var d=c[0],e=c[c.length-1];e.properties=b.N();if(b=b.V())b=Hl(b,!0,d),e.geometryLayout=b.ja,In(a,b.X(),
+c)})});mn.prototype.Xb=function(a,b){b=Gl(this,b);var c=jl("http://www.topografix.com/GPX/1/1","gpx");c.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");c.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation","http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd");c.setAttribute("version","1.1");c.setAttribute("creator","OpenLayers");Bl({node:c},Un,Tn,a,[b]);return c};function Vn(){El.call(this)}v(Vn,El);function Wn(a){return"string"===typeof a?a:""}k=Vn.prototype;k.U=function(){return"text"};k.Tb=function(a,b){return this.ae(Wn(a),Gl(this,b))};k.Oa=function(a,b){return this.zg(Wn(a),Gl(this,b))};k.Sc=function(a,b){return this.wd(Wn(a),Gl(this,b))};k.kb=function(){return this.defaultDataProjection};k.Bd=function(a,b){return this.ge(a,Gl(this,b))};k.Wb=function(a,b){return this.Wg(a,Gl(this,b))};k.$c=function(a,b){return this.Cd(a,Gl(this,b))};function Xn(a){a=a?a:{};El.call(this);this.defaultDataProjection=Tb("EPSG:4326");this.b=a.altitudeMode?a.altitudeMode:"none"}v(Xn,Vn);var Yn=/^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/,Zn=/^H.([A-Z]{3}).*?:(.*)/,$n=/^HFDTE(\d{2})(\d{2})(\d{2})/,ao=/\r\n|\r|\n/;k=Xn.prototype;
+k.ae=function(a,b){var c=this.b,d=a.split(ao);a={};var e=[],f=2E3,g=0,h=1,l=-1,m;var n=0;for(m=d.length;n<m;++n){var p=d[n],q;if("B"==p.charAt(0)){if(q=Yn.exec(p)){var p=parseInt(q[1],10),r=parseInt(q[2],10),u=parseInt(q[3],10),x=parseInt(q[4],10)+parseInt(q[5],10)/6E4;"S"==q[6]&&(x=-x);var B=parseInt(q[7],10)+parseInt(q[8],10)/6E4;"W"==q[9]&&(B=-B);e.push(B,x);"none"!=c&&e.push("gps"==c?parseInt(q[11],10):"barometric"==c?parseInt(q[12],10):0);q=Date.UTC(f,g,h,p,r,u);q<l&&(q=Date.UTC(f,g,h+1,p,r,
+u));e.push(q/1E3);l=q}}else"H"==p.charAt(0)&&((q=$n.exec(p))?(h=parseInt(q[1],10),g=parseInt(q[2],10)-1,f=2E3+parseInt(q[3],10)):(q=Zn.exec(p))&&(a[q[1]]=q[2].trim()))}if(!e.length)return null;d=new O(null);d.ba("none"==c?"XYM":"XYZM",e);b=new H(Hl(d,!1,b));b.H(a);return b};k.zg=function(a,b){return(a=this.ae(a,b))?[a]:[]};k.ge=function(){};k.Wg=function(){};k.Cd=function(){};k.wd=function(){};function bo(a,b,c,d,e,f){Qc.call(this);this.j=null;this.M=a?a:new Image;null!==d&&(this.M.crossOrigin=d);this.c=f?document.createElement("CANVAS"):null;this.g=f;this.f=null;this.i=e;this.a=c;this.o=b;this.l=!1;2==this.i&&co(this)}v(bo,Qc);function co(a){var b=jd(1,1);try{b.drawImage(a.M,0,0),b.getImageData(0,0,1,1)}catch(c){a.l=!0}}bo.prototype.v=function(){this.i=3;this.f.forEach(Ec);this.f=null;this.b("change")};
+bo.prototype.u=function(){this.i=2;this.a&&(this.M.width=this.a[0],this.M.height=this.a[1]);this.a=[this.M.width,this.M.height];this.f.forEach(Ec);this.f=null;co(this);if(!this.l&&null!==this.g){this.c.width=this.M.width;this.c.height=this.M.height;var a=this.c.getContext("2d");a.drawImage(this.M,0,0);for(var b=a.getImageData(0,0,this.M.width,this.M.height),c=b.data,d=this.g[0]/255,e=this.g[1]/255,f=this.g[2]/255,g=0,h=c.length;g<h;g+=4)c[g]*=d,c[g+1]*=e,c[g+2]*=f;a.putImageData(b,0,0)}this.b("change")};
+bo.prototype.Y=function(){return this.c?this.c:this.M};bo.prototype.load=function(){if(0==this.i){this.i=1;this.f=[Jc(this.M,"error",this.v,this),Jc(this.M,"load",this.u,this)];try{this.M.src=this.o}catch(a){this.v()}}};function eo(a){a=a||{};this.o=void 0!==a.anchor?a.anchor:[.5,.5];this.u=null;this.i=void 0!==a.anchorOrigin?a.anchorOrigin:"top-left";this.C=void 0!==a.anchorXUnits?a.anchorXUnits:"fraction";this.B=void 0!==a.anchorYUnits?a.anchorYUnits:"fraction";this.ra=void 0!==a.crossOrigin?a.crossOrigin:null;var b=void 0!==a.img?a.img:null,c=void 0!==a.imgSize?a.imgSize:null,d=a.src;xa(!(void 0!==d&&b),4);xa(!b||b&&c,5);void 0!==d&&d.length||!b||(d=b.src||w(b).toString());xa(void 0!==d&&0<d.length,6);var e=void 0!==
+a.src?0:2;this.j=void 0!==a.color?ed(a.color):null;var f=this.ra,g=this.j,h=zh.get(d,f,g);h||(h=new bo(b,d,c,f,e,g),zh.set(d,f,g,h));this.b=h;this.oa=void 0!==a.offset?a.offset:[0,0];this.c=void 0!==a.offsetOrigin?a.offsetOrigin:"top-left";this.S=null;this.D=void 0!==a.size?a.size:null;Xk.call(this,{opacity:void 0!==a.opacity?a.opacity:1,rotation:void 0!==a.rotation?a.rotation:0,scale:void 0!==a.scale?a.scale:1,snapToPixel:void 0!==a.snapToPixel?a.snapToPixel:!0,rotateWithView:void 0!==a.rotateWithView?
+a.rotateWithView:!1})}v(eo,Xk);k=eo.prototype;
+k.clone=function(){var a=this.Y(1);if(2===this.b.i)if("IMG"===a.tagName.toUpperCase())var b=a.cloneNode(!0);else{b=document.createElement("canvas");var c=b.getContext("2d");b.width=a.width;b.height=a.height;c.drawImage(a,0,0)}return new eo({anchor:this.o.slice(),anchorOrigin:this.i,anchorXUnits:this.C,anchorYUnits:this.B,crossOrigin:this.ra,color:this.j&&this.j.slice?this.j.slice():this.j||void 0,img:b?b:void 0,imgSize:b?this.b.a.slice():void 0,src:b?void 0:this.b.o,offset:this.oa.slice(),offsetOrigin:this.c,
+size:null!==this.D?this.D.slice():void 0,opacity:this.f,scale:this.a,snapToPixel:this.v,rotation:this.g,rotateWithView:this.l})};
+k.Hc=function(){if(this.u)return this.u;var a=this.o,b=this.ic();if("fraction"==this.C||"fraction"==this.B){if(!b)return null;a=this.o.slice();"fraction"==this.C&&(a[0]*=b[0]);"fraction"==this.B&&(a[1]*=b[1])}if("top-left"!=this.i){if(!b)return null;a===this.o&&(a=this.o.slice());if("top-right"==this.i||"bottom-right"==this.i)a[0]=-a[0]+b[0];if("bottom-left"==this.i||"bottom-right"==this.i)a[1]=-a[1]+b[1]}return this.u=a};k.Lo=function(){return this.j};k.Y=function(a){return this.b.Y(a)};k.ye=function(){return this.b.a};
+k.Ye=function(){return this.b.i};k.qg=function(){var a=this.b;if(!a.j)if(a.l){var b=a.a[0],c=a.a[1],d=jd(b,c);d.fillRect(0,0,b,c);a.j=d.canvas}else a.j=a.M;return a.j};k.Oc=function(){if(this.S)return this.S;var a=this.oa;if("top-left"!=this.c){var b=this.ic(),c=this.b.a;if(!b||!c)return null;a=a.slice();if("top-right"==this.c||"bottom-right"==this.c)a[0]=c[0]-b[0]-a[0];if("bottom-left"==this.c||"bottom-right"==this.c)a[1]=c[1]-b[1]-a[1]}return this.S=a};k.Mo=function(){return this.b.o};
+k.ic=function(){return this.D?this.D:this.b.a};k.Nh=function(a,b){return y(this.b,"change",a,b)};k.load=function(){this.b.load()};k.Bj=function(a,b){Kc(this.b,"change",a,b)};function fo(a){a=a||{};this.a=a.font;this.f=a.rotation;this.o=a.rotateWithView;this.b=a.scale;this.Ia=a.text;this.g=a.textAlign;this.j=a.textBaseline;this.Va=void 0!==a.fill?a.fill:new al({color:"#333"});this.Ya=void 0!==a.stroke?a.stroke:null;this.i=void 0!==a.offsetX?a.offsetX:0;this.c=void 0!==a.offsetY?a.offsetY:0}k=fo.prototype;
+k.clone=function(){return new fo({font:this.a,rotation:this.f,rotateWithView:this.o,scale:this.b,text:this.Na(),textAlign:this.g,textBaseline:this.j,fill:this.Fa()?this.Fa().clone():void 0,stroke:this.Ga()?this.Ga().clone():void 0,offsetX:this.i,offsetY:this.c})};k.Nk=function(){return this.a};k.cl=function(){return this.i};k.dl=function(){return this.c};k.Fa=function(){return this.Va};k.Ro=function(){return this.o};k.So=function(){return this.f};k.To=function(){return this.b};k.Ga=function(){return this.Ya};
+k.Na=function(){return this.Ia};k.nl=function(){return this.g};k.ol=function(){return this.j};k.nj=function(a){this.a=a};k.sj=function(a){this.i=a};k.tj=function(a){this.c=a};k.pf=function(a){this.Va=a};k.Uo=function(a){this.f=a};k.Si=function(a){this.b=a};k.qf=function(a){this.Ya=a};k.xd=function(a){this.Ia=a};k.vj=function(a){this.g=a};k.hq=function(a){this.j=a};function go(a){a=a?a:{};Cm.call(this);ho||(io=[255,255,255,1],jo=new al({color:io}),ko=[20,2],lo=mo="pixels",no=[64,64],oo="https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png",po=.5,qo=new eo({anchor:ko,anchorOrigin:"bottom-left",anchorXUnits:mo,anchorYUnits:lo,crossOrigin:"anonymous",rotation:0,scale:po,size:no,src:oo}),ro="NO_IMAGE",so=new wj({color:io,width:1}),to=new wj({color:[51,51,51,1],width:2}),uo=new fo({font:"bold 16px Helvetica",fill:jo,stroke:to,scale:.8}),vo=new bl({fill:jo,
+image:qo,text:uo,stroke:so,zIndex:0}),ho=[vo]);this.defaultDataProjection=Tb("EPSG:4326");this.a=a.defaultStyle?a.defaultStyle:ho;this.c=void 0!==a.extractStyles?a.extractStyles:!0;this.j=void 0!==a.writeStyles?a.writeStyles:!0;this.b={};this.g=void 0!==a.showPointNames?a.showPointNames:!0}var ho,io,jo,ko,mo,lo,no,oo,po,qo,ro,so,to,uo,vo;v(go,Cm);
+var wo=["http://www.google.com/kml/ext/2.2"],xo=[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"],yo={fraction:"fraction",pixels:"pixels",insetPixels:"pixels"};
+function zo(a,b){var c=[0,0],d="start";if(a.Y()){var e=a.Y().ye();null===e&&(e=no);2==e.length&&(d=a.Y().a,c[0]=d*e[0]/2,c[1]=-d*e[1]/2,d="left")}null!==a.Na()?(e=a.Na(),a=e.clone(),a.nj(e.a||uo.a),a.Si(e.b||uo.b),a.pf(e.Fa()||uo.Fa()),a.qf(e.Ga()||to)):a=uo.clone();a.xd(b);a.sj(c[0]);a.tj(c[1]);a.vj(d);return new bl({text:a})}
+function Ao(a,b,c,d,e){return function(){var f=e,g="";f&&this.V()&&(f="Point"===this.V().U());f&&(g=this.get("name"),f=f&&g);if(a)return f?(f=zo(a[0],g),a.concat(f)):a;if(b){var h=Bo(b,c,d);return f?(f=zo(h[0],g),h.concat(f)):h}return f?(f=zo(c[0],g),c.concat(f)):c}}function Bo(a,b,c){return Array.isArray(a)?a:"string"===typeof a?(!(a in c)&&"#"+a in c&&(a="#"+a),Bo(c[a],b,c)):b}
+function Co(a){a=kl(a,!1);if(a=/^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(a))return a=a[1],[parseInt(a.substr(6,2),16),parseInt(a.substr(4,2),16),parseInt(a.substr(2,2),16),parseInt(a.substr(0,2),16)/255]}function Do(a){a=kl(a,!1);for(var b=[],c=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i,d;d=c.exec(a);)b.push(parseFloat(d[1]),parseFloat(d[2]),d[3]?parseFloat(d[3]):0),a=a.substr(d[0].length);if(""===a)return b}
+function Eo(a){var b=kl(a,!1).trim();return a.baseURI&&"about:blank"!==a.baseURI?(new URL(b,a.baseURI)).href:b}function Fo(a){return Km(a)}function Go(a,b){return N(null,Ho,a,b)}function Io(a,b){if(b=N({A:[],Ej:[]},Jo,a,b)){a=b.A;b=b.Ej;var c;var d=0;for(c=Math.min(a.length,b.length);d<c;++d)a[4*d+3]=b[d];b=new O(null);b.ba("XYZM",a);return b}}function Ko(a,b){var c=N({},Lo,a,b);if(a=N(null,Mo,a,b))return b=new O(null),b.ba("XYZ",a),b.H(c),b}
+function No(a,b){var c=N({},Lo,a,b);if(a=N(null,Mo,a,b))return b=new D(null),b.ba("XYZ",a,[a.length]),b.H(c),b}
+function Oo(a,b){a=N([],Po,a,b);if(!a)return null;if(!a.length)return new tm(a);var c=!0,d=a[0].U(),e;var f=1;for(e=a.length;f<e;++f)if(b=a[f],b.U()!=d){c=!1;break}if(c)if("Point"==d){var g=a[0];c=g.ja;d=g.ga();f=1;for(e=a.length;f<e;++f)b=a[f],la(d,b.ga());g=new Q(null);g.ba(c,d);Qo(g,a)}else"LineString"==d?(g=new P(null),Nl(g,a),Qo(g,a)):"Polygon"==d?(g=new R(null),Pl(g,a),Qo(g,a)):"GeometryCollection"==d?g=new tm(a):xa(!1,37);else g=new tm(a);return g}
+function Ro(a,b){var c=N({},Lo,a,b);if(a=N(null,Mo,a,b))return b=new C(null),b.ba("XYZ",a),b.H(c),b}function So(a,b){var c=N({},Lo,a,b);if((a=N([null],To,a,b))&&a[0]){b=new D(null);var d=a[0],e=[d.length],f;var g=1;for(f=a.length;g<f;++g)la(d,a[g]),e.push(d.length);b.ba("XYZ",d,e);b.H(c);return b}}
+function Uo(a,b){b=N({},Vo,a,b);if(!b)return null;a="fillStyle"in b?b.fillStyle:jo;var c=b.fill;void 0===c||c||(a=null);c="imageStyle"in b?b.imageStyle:qo;c==ro&&(c=void 0);var d="textStyle"in b?b.textStyle:uo,e="strokeStyle"in b?b.strokeStyle:so;b=b.outline;void 0===b||b||(e=null);return[new bl({fill:a,image:c,stroke:e,text:d,zIndex:void 0})]}
+function Qo(a,b){var c=b.length,d=Array(b.length),e=Array(b.length),f,g;var h=g=!1;for(f=0;f<c;++f){var l=b[f];d[f]=l.get("extrude");e[f]=l.get("altitudeMode");h=h||void 0!==d[f];g=g||e[f]}h&&a.set("extrude",d);g&&a.set("altitudeMode",e)}function Wo(a,b){Al(Xo,a,b)}function Yo(a,b){Al(Zo,a,b)}
+var $o=K(xo,{displayName:I(S),value:I(S)}),Xo=K(xo,{Data:function(a,b){var c=a.getAttribute("name");Al($o,a,b);a=b[b.length-1];null!==c?a[c]=a.value:null!==a.displayName&&(a[a.displayName]=a.value)},SchemaData:function(a,b){Al(ap,a,b)}}),Zo=K(xo,{LatLonAltBox:function(a,b){if(a=N({},bp,a,b))b=b[b.length-1],b.extent=[parseFloat(a.west),parseFloat(a.south),parseFloat(a.east),parseFloat(a.north)],b.altitudeMode=a.altitudeMode,b.minAltitude=parseFloat(a.minAltitude),b.maxAltitude=parseFloat(a.maxAltitude)},
+Lod:function(a,b){if(a=N({},cp,a,b))b=b[b.length-1],b.minLodPixels=parseFloat(a.minLodPixels),b.maxLodPixels=parseFloat(a.maxLodPixels),b.minFadeExtent=parseFloat(a.minFadeExtent),b.maxFadeExtent=parseFloat(a.maxFadeExtent)}}),bp=K(xo,{altitudeMode:I(S),minAltitude:I(Km),maxAltitude:I(Km),north:I(Km),south:I(Km),east:I(Km),west:I(Km)}),cp=K(xo,{minLodPixels:I(Km),maxLodPixels:I(Km),minFadeExtent:I(Km),maxFadeExtent:I(Km)}),Lo=K(xo,{extrude:I(Hm),altitudeMode:I(S)}),Ho=K(xo,{coordinates:sl(Do)}),To=
+K(xo,{innerBoundaryIs:function(a,b){(a=N(void 0,dp,a,b))&&b[b.length-1].push(a)},outerBoundaryIs:function(a,b){(a=N(void 0,ep,a,b))&&(b[b.length-1][0]=a)}}),Jo=K(xo,{when:function(a,b){b=b[b.length-1].Ej;a=kl(a,!1);a=Date.parse(a);b.push(isNaN(a)?0:a)}},K(wo,{coord:function(a,b){b=b[b.length-1].A;a=kl(a,!1);(a=/^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i.exec(a))?b.push(parseFloat(a[1]),parseFloat(a[2]),parseFloat(a[3]),
+0):b.push(0,0,0,0)}})),Mo=K(xo,{coordinates:sl(Do)}),fp=K(xo,{href:I(Eo)},K(wo,{x:I(Km),y:I(Km),w:I(Km),h:I(Km)})),gp=K(xo,{Icon:I(function(a,b){return(a=N({},fp,a,b))?a:null}),heading:I(Km),hotSpot:I(function(a){var b=a.getAttribute("xunits"),c=a.getAttribute("yunits");var d="insetPixels"!==b?"insetPixels"!==c?"bottom-left":"top-left":"insetPixels"!==c?"bottom-right":"top-right";return{x:parseFloat(a.getAttribute("x")),Xg:yo[b],y:parseFloat(a.getAttribute("y")),Yg:yo[c],origin:d}}),scale:I(Fo)}),
+dp=K(xo,{LinearRing:sl(Go)}),hp=K(xo,{color:I(Co),scale:I(Fo)}),ip=K(xo,{color:I(Co),width:I(Km)}),Po=K(xo,{LineString:rl(Ko),LinearRing:rl(No),MultiGeometry:rl(Oo),Point:rl(Ro),Polygon:rl(So)}),jp=K(wo,{Track:rl(Io)}),lp=K(xo,{ExtendedData:Wo,Region:Yo,Link:function(a,b){Al(kp,a,b)},address:I(S),description:I(S),name:I(S),open:I(Hm),phoneNumber:I(S),visibility:I(Hm)}),kp=K(xo,{href:I(Eo)}),ep=K(xo,{LinearRing:sl(Go)}),mp=K(xo,{Style:I(Uo),key:I(S),styleUrl:I(Eo)}),op=K(xo,{ExtendedData:Wo,Region:Yo,
+MultiGeometry:I(Oo,"geometry"),LineString:I(Ko,"geometry"),LinearRing:I(No,"geometry"),Point:I(Ro,"geometry"),Polygon:I(So,"geometry"),Style:I(Uo),StyleMap:function(a,b){if(a=N(void 0,np,a,b))b=b[b.length-1],Array.isArray(a)?b.Style=a:"string"===typeof a?b.styleUrl=a:xa(!1,38)},address:I(S),description:I(S),name:I(S),open:I(Hm),phoneNumber:I(S),styleUrl:I(Eo),visibility:I(Hm)},K(wo,{MultiTrack:I(function(a,b){if(a=N([],jp,a,b))return b=new P(null),Nl(b,a),b},"geometry"),Track:I(Io,"geometry")})),
+pp=K(xo,{color:I(Co),fill:I(Hm),outline:I(Hm)}),ap=K(xo,{SimpleData:function(a,b){var c=a.getAttribute("name");null!==c&&(a=S(a),b[b.length-1][c]=a)}}),Vo=K(xo,{IconStyle:function(a,b){if(a=N({},gp,a,b)){b=b[b.length-1];var c="Icon"in a?a.Icon:{},d=!("Icon"in a)||0<Object.keys(c).length,e,f=c.href;f?e=f:d&&(e=oo);var f="bottom-left",g=a.hotSpot;if(g){var h=[g.x,g.y];var l=g.Xg;var m=g.Yg;f=g.origin}else e===oo?(h=ko,l=mo,m=lo):/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(e)&&(h=[.5,0],m=l="fraction");
+var n,g=c.x,p=c.y;void 0!==g&&void 0!==p&&(n=[g,p]);var q,g=c.w,c=c.h;void 0!==g&&void 0!==c&&(q=[g,c]);var r,c=a.heading;void 0!==c&&(r=Ha(c));a=a.scale;d?(e==oo&&(q=no,void 0===a&&(a=po)),e=new eo({anchor:h,anchorOrigin:f,anchorXUnits:l,anchorYUnits:m,crossOrigin:"anonymous",offset:n,offsetOrigin:"bottom-left",rotation:r,scale:a,size:q,src:e}),b.imageStyle=e):b.imageStyle=ro}},LabelStyle:function(a,b){(a=N({},hp,a,b))&&(b[b.length-1].textStyle=new fo({fill:new al({color:"color"in a?a.color:io}),
+scale:a.scale}))},LineStyle:function(a,b){(a=N({},ip,a,b))&&(b[b.length-1].strokeStyle=new wj({color:"color"in a?a.color:io,width:"width"in a?a.width:1}))},PolyStyle:function(a,b){if(a=N({},pp,a,b)){b=b[b.length-1];b.fillStyle=new al({color:"color"in a?a.color:io});var c=a.fill;void 0!==c&&(b.fill=c);a=a.outline;void 0!==a&&(b.outline=a)}}}),np=K(xo,{Pair:function(a,b){if(a=N({},mp,a,b)){var c=a.key;c&&"normal"==c&&((c=a.styleUrl)&&(b[b.length-1]=c),(a=a.Style)&&(b[b.length-1]=a))}}});k=go.prototype;
+k.vg=function(a,b){var c=K(xo,{Document:ql(this.vg,this),Folder:ql(this.vg,this),Placemark:rl(this.Dg,this),Style:this.Jp.bind(this),StyleMap:this.Ip.bind(this)});if(a=N([],c,a,b,this))return a};k.Dg=function(a,b){var c=N({geometry:null},op,a,b);if(c){var d=new H;a=a.getAttribute("id");null!==a&&d.jc(a);b=b[0];(a=c.geometry)&&Hl(a,!1,b);d.Ra(a);delete c.geometry;this.c&&d.hg(Ao(c.Style,c.styleUrl,this.a,this.b,this.g));delete c.Style;d.H(c);return d}};
+k.Jp=function(a,b){var c=a.getAttribute("id");null!==c&&(b=Uo(a,b))&&(a=a.baseURI&&"about:blank"!==a.baseURI?(new URL("#"+c,a.baseURI)).href:"#"+c,this.b[a]=b)};k.Ip=function(a,b){var c=a.getAttribute("id");null!==c&&(b=N(void 0,np,a,b))&&(a=a.baseURI&&"about:blank"!==a.baseURI?(new URL("#"+c,a.baseURI)).href:"#"+c,this.b[a]=b)};k.xg=function(a,b){return ja(xo,a.namespaceURI)?(a=this.Dg(a,[Fl(this,a,b)]))?a:null:null};
+k.zc=function(a,b){if(!ja(xo,a.namespaceURI))return[];var c=a.localName;if("Document"==c||"Folder"==c)return(c=this.vg(a,[Fl(this,a,b)]))?c:[];if("Placemark"==c)return(b=this.Dg(a,[Fl(this,a,b)]))?[b]:[];if("kml"==c){c=[];for(a=a.firstElementChild;a;a=a.nextElementSibling){var d=this.zc(a,b);d&&la(c,d)}return c}return[]};k.Cp=function(a){if(ml(a))return qp(this,a);if(nl(a))return rp(this,a);if("string"===typeof a)return a=pl(a),qp(this,a)};
+function qp(a,b){for(b=b.firstChild;b;b=b.nextSibling)if(b.nodeType==Node.ELEMENT_NODE){var c=rp(a,b);if(c)return c}}function rp(a,b){var c;for(c=b.firstElementChild;c;c=c.nextElementSibling)if(ja(xo,c.namespaceURI)&&"name"==c.localName)return S(c);for(c=b.firstElementChild;c;c=c.nextElementSibling)if(b=c.localName,ja(xo,c.namespaceURI)&&("Document"==b||"Folder"==b||"Placemark"==b||"kml"==b)&&(b=rp(a,c)))return b}
+k.Dp=function(a){var b=[];ml(a)?la(b,sp(this,a)):nl(a)?la(b,tp(this,a)):"string"===typeof a&&(a=pl(a),la(b,sp(this,a)));return b};function sp(a,b){var c=[];for(b=b.firstChild;b;b=b.nextSibling)b.nodeType==Node.ELEMENT_NODE&&la(c,tp(a,b));return c}
+function tp(a,b){var c,d=[];for(c=b.firstElementChild;c;c=c.nextElementSibling)if(ja(xo,c.namespaceURI)&&"NetworkLink"==c.localName){var e=N({},lp,c,[]);d.push(e)}for(c=b.firstElementChild;c;c=c.nextElementSibling)b=c.localName,!ja(xo,c.namespaceURI)||"Document"!=b&&"Folder"!=b&&"kml"!=b||la(d,tp(a,c));return d}k.Gp=function(a){var b=[];ml(a)?la(b,up(this,a)):nl(a)?la(b,this.lf(a)):"string"===typeof a&&(a=pl(a),la(b,up(this,a)));return b};
+function up(a,b){var c=[];for(b=b.firstChild;b;b=b.nextSibling)b.nodeType==Node.ELEMENT_NODE&&la(c,a.lf(b));return c}k.lf=function(a){var b,c=[];for(b=a.firstElementChild;b;b=b.nextElementSibling)if(ja(xo,b.namespaceURI)&&"Region"==b.localName){var d=N({},Zo,b,[]);c.push(d)}for(b=a.firstElementChild;b;b=b.nextElementSibling)a=b.localName,!ja(xo,b.namespaceURI)||"Document"!=a&&"Folder"!=a&&"kml"!=a||la(c,this.lf(b));return c};
+function vp(a,b){b=ed(b);b=[255*(4==b.length?b[3]:1),b[2],b[1],b[0]];var c;for(c=0;4>c;++c){var d=parseInt(b[c],10).toString(16);b[c]=1==d.length?"0"+d:d}Pm(a,b.join(""))}function wp(a,b,c){a={node:a};var d=b.U();if("GeometryCollection"==d){var e=b.Vf();var f=xp}else"MultiPoint"==d?(e=b.Zd(),f=yp):"MultiLineString"==d?(e=b.gd(),f=zp):"MultiPolygon"==d?(e=b.Td(),f=Ap):xa(!1,39);Bl(a,Bp,f,e,c)}function Cp(a,b,c){Bl({node:a},Dp,Ep,[b],c)}
+function Fp(a,b,c){var d={node:a};b.a&&a.setAttribute("id",b.a);a=b.N();var e={address:1,description:1,name:1,open:1,phoneNumber:1,styleUrl:1,visibility:1};e[b.c]=1;var f=Object.keys(a||{}).sort().filter(function(a){return!e[a]});if(0<f.length){var g=zl(a,f);Bl(d,Gp,Hp,[{names:f,values:g}],c)}if(f=b.Lc())if(f=f.call(b,0))f=Array.isArray(f)?f[0]:f,this.j&&(a.Style=f),(f=f.Na())&&(a.name=f.Na());f=Ip[c[c.length-1].node.namespaceURI];a=zl(a,f);Bl(d,Gp,yl,a,c,f);a=c[0];(b=b.V())&&(b=Hl(b,!0,a));Bl(d,
+Gp,xp,[b],c)}function Jp(a,b,c){var d=b.ga();a={node:a};a.layout=b.ja;a.stride=b.qa();Bl(a,Kp,Lp,[d],c)}function Mp(a,b,c){b=b.Sd();var d=b.shift();a={node:a};Bl(a,Np,Op,b,c);Bl(a,Np,Pp,[d],c)}function Qp(a,b){Qm(a,Math.round(1E6*b)/1E6)}
+var Rp=K(xo,["Document","Placemark"]),Up=K(xo,{Document:J(function(a,b,c){Bl({node:a},Sp,Tp,b,c,void 0,this)}),Placemark:J(Fp)}),Sp=K(xo,{Placemark:J(Fp)}),Vp=K(xo,{Data:J(function(a,b,c){a.setAttribute("name",b.name);a={node:a};b=b.value;"object"==typeof b?(null!==b&&b.displayName&&Bl(a,Vp,yl,[b.displayName],c,["displayName"]),null!==b&&b.value&&Bl(a,Vp,yl,[b.value],c,["value"])):Bl(a,Vp,yl,[b],c,["value"])}),value:J(function(a,b){Pm(a,b)}),displayName:J(function(a,b){a.appendChild(il.createCDATASection(b))})}),
+Wp={Point:"Point",LineString:"LineString",LinearRing:"LinearRing",Polygon:"Polygon",MultiPoint:"MultiGeometry",MultiLineString:"MultiGeometry",MultiPolygon:"MultiGeometry",GeometryCollection:"MultiGeometry"},Xp=K(xo,["href"],K(wo,["x","y","w","h"])),Yp=K(xo,{href:J(Pm)},K(wo,{x:J(Qm),y:J(Qm),w:J(Qm),h:J(Qm)})),Zp=K(xo,["scale","heading","Icon","hotSpot"]),aq=K(xo,{Icon:J(function(a,b,c){a={node:a};var d=Xp[c[c.length-1].node.namespaceURI],e=zl(b,d);Bl(a,Yp,yl,e,c,d);d=Xp[wo[0]];e=zl(b,d);Bl(a,Yp,
+$p,e,c,d)}),heading:J(Qm),hotSpot:J(function(a,b){a.setAttribute("x",b.x);a.setAttribute("y",b.y);a.setAttribute("xunits",b.Xg);a.setAttribute("yunits",b.Yg)}),scale:J(Qp)}),bq=K(xo,["color","scale"]),cq=K(xo,{color:J(vp),scale:J(Qp)}),dq=K(xo,["color","width"]),eq=K(xo,{color:J(vp),width:J(Qm)}),Dp=K(xo,{LinearRing:J(Jp)}),Bp=K(xo,{LineString:J(Jp),Point:J(Jp),Polygon:J(Mp),GeometryCollection:J(wp)}),Ip=K(xo,"name open visibility address phoneNumber description styleUrl Style".split(" ")),Gp=K(xo,
+{ExtendedData:J(function(a,b,c){a={node:a};var d=b.names;b=b.values;for(var e=d.length,f=0;f<e;f++)Bl(a,Vp,fq,[{name:d[f],value:b[f]}],c)}),MultiGeometry:J(wp),LineString:J(Jp),LinearRing:J(Jp),Point:J(Jp),Polygon:J(Mp),Style:J(function(a,b,c){a={node:a};var d={},e=b.Fa(),f=b.Ga(),g=b.Y();b=b.Na();g instanceof eo&&(d.IconStyle=g);b&&(d.LabelStyle=b);f&&(d.LineStyle=f);e&&(d.PolyStyle=e);b=gq[c[c.length-1].node.namespaceURI];d=zl(d,b);Bl(a,hq,yl,d,c,b)}),address:J(Pm),description:J(Pm),name:J(Pm),
+open:J(Om),phoneNumber:J(Pm),styleUrl:J(Pm),visibility:J(Om)}),Kp=K(xo,{coordinates:J(function(a,b,c){c=c[c.length-1];var d=c.layout;c=c.stride;var e;"XY"==d||"XYM"==d?e=2:"XYZ"==d||"XYZM"==d?e=3:xa(!1,34);var f,g=b.length,h="";if(0<g){h+=b[0];for(d=1;d<e;++d)h+=","+b[d];for(f=c;f<g;f+=c)for(h+=" "+b[f],d=1;d<e;++d)h+=","+b[f+d]}Pm(a,h)})}),Np=K(xo,{outerBoundaryIs:J(Cp),innerBoundaryIs:J(Cp)}),iq=K(xo,{color:J(vp)}),gq=K(xo,["IconStyle","LabelStyle","LineStyle","PolyStyle"]),hq=K(xo,{IconStyle:J(function(a,
+b,c){a={node:a};var d={},e=b.ic(),f=b.ye(),g={href:b.b.o};if(e){g.w=e[0];g.h=e[1];var h=b.Hc(),l=b.Oc();l&&f&&l[0]&&l[1]!==e[1]&&(g.x=l[0],g.y=f[1]-(l[1]+e[1]));h&&h[0]&&h[1]!==e[1]&&(d.hotSpot={x:h[0],Xg:"pixels",y:e[1]-h[1],Yg:"pixels"})}d.Icon=g;e=b.a;1!==e&&(d.scale=e);(b=b.g)&&(d.heading=b);b=Zp[c[c.length-1].node.namespaceURI];d=zl(d,b);Bl(a,aq,yl,d,c,b)}),LabelStyle:J(function(a,b,c){a={node:a};var d={},e=b.Fa();e&&(d.color=e.b);(b=b.b)&&1!==b&&(d.scale=b);b=bq[c[c.length-1].node.namespaceURI];
+d=zl(d,b);Bl(a,cq,yl,d,c,b)}),LineStyle:J(function(a,b,c){a={node:a};var d=dq[c[c.length-1].node.namespaceURI];b=zl({color:b.a,width:b.c},d);Bl(a,eq,yl,b,c,d)}),PolyStyle:J(function(a,b,c){Bl({node:a},iq,jq,[b.b],c)})});function $p(a,b,c){return jl(wo[0],"gx:"+c)}function Tp(a,b){return jl(b[b.length-1].node.namespaceURI,"Placemark")}function xp(a,b){if(a)return jl(b[b.length-1].node.namespaceURI,Wp[a.U()])}
+var jq=wl("color"),Lp=wl("coordinates"),fq=wl("Data"),Hp=wl("ExtendedData"),Op=wl("innerBoundaryIs"),yp=wl("Point"),zp=wl("LineString"),Ep=wl("LinearRing"),Ap=wl("Polygon"),Pp=wl("outerBoundaryIs");
+go.prototype.Xb=function(a,b){b=Gl(this,b);var c=jl(xo[4],"kml");c.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:gx",wo[0]);c.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");c.setAttributeNS("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 d={node:c},e={};1<a.length?e.Document=a:1==a.length&&(e.Placemark=a[0]);a=Rp[c.namespaceURI];
+e=zl(e,a);Bl(d,Up,yl,e,[b],a,this);return c};Fj.Dd=function(){};
+(function(a){function b(a){this.lc=ArrayBuffer.isView&&ArrayBuffer.isView(a)?a:new Uint8Array(a||0);this.type=this.ea=0;this.length=this.lc.length}function c(a,b,c){var e=c.lc;var f=e[c.ea++];var g=(f&112)>>4;if(128>f)return d(a,g,b);f=e[c.ea++];g|=(f&127)<<3;if(128>f)return d(a,g,b);f=e[c.ea++];g|=(f&127)<<10;if(128>f)return d(a,g,b);f=e[c.ea++];g|=(f&127)<<17;if(128>f)return d(a,g,b);f=e[c.ea++];g|=(f&127)<<24;if(128>f)return d(a,g,b);f=e[c.ea++];if(128>f)return d(a,g|(f&1)<<31,b);throw Error("Expected varint not more than 10 bytes");
+}function d(a,b,c){return c?4294967296*b+(a>>>0):4294967296*(b>>>0)+(a>>>0)}var e={read:function(a,b,c,d,e){var f=8*e-d-1;var g=(1<<f)-1,h=g>>1,l=-7;e=c?e-1:0;var m=c?-1:1,x=a[b+e];e+=m;c=x&(1<<-l)-1;x>>=-l;for(l+=f;0<l;c=256*c+a[b+e],e+=m,l-=8);f=c&(1<<-l)-1;c>>=-l;for(l+=d;0<l;f=256*f+a[b+e],e+=m,l-=8);if(0===c)c=1-h;else{if(c===g)return f?NaN:Infinity*(x?-1:1);f+=Math.pow(2,d);c-=h}return(x?-1:1)*f*Math.pow(2,c-d)},write:function(a,b,c,d,e,n){var f,g=8*n-e-1,h=(1<<g)-1,l=h>>1,m=23===e?Math.pow(2,
+-24)-Math.pow(2,-77):0;n=d?0:n-1;var B=d?1:-1,E=0>b||0===b&&0>1/b?1:0;b=Math.abs(b);isNaN(b)||Infinity===b?(b=isNaN(b)?1:0,d=h):(d=Math.floor(Math.log(b)/Math.LN2),1>b*(f=Math.pow(2,-d))&&(d--,f*=2),b=1<=d+l?b+m/f:b+m*Math.pow(2,1-l),2<=b*f&&(d++,f/=2),d+l>=h?(b=0,d=h):1<=d+l?(b=(b*f-1)*Math.pow(2,e),d+=l):(b=b*Math.pow(2,l-1)*Math.pow(2,e),d=0));for(;8<=e;a[c+n]=b&255,n+=B,b/=256,e-=8);d=d<<e|b;for(g+=e;0<g;a[c+n]=d&255,n+=B,d/=256,g-=8);a[c+n-B]|=128*E}};b.c=0;b.i=1;b.b=2;b.a=5;b.prototype={Ag:function(a,
+b,c){for(c=c||this.length;this.ea<c;){var d=this.Ka(),e=d>>3,f=this.ea;this.type=d&7;a(e,b,this);this.ea===f&&this.mq(d)}return b},yp:function(){var a=e.read(this.lc,this.ea,!0,23,4);this.ea+=4;return a},up:function(){var a=e.read(this.lc,this.ea,!0,52,8);this.ea+=8;return a},Ka:function(a){var b=this.lc;var d=b[this.ea++];var e=d&127;if(128>d)return e;d=b[this.ea++];e|=(d&127)<<7;if(128>d)return e;d=b[this.ea++];e|=(d&127)<<14;if(128>d)return e;d=b[this.ea++];e|=(d&127)<<21;if(128>d)return e;d=b[this.ea];
+return c(e|(d&15)<<28,a,this)},Kp:function(){return this.Ka(!0)},ce:function(){var a=this.Ka();return 1===a%2?(a+1)/-2:a/2},sp:function(){return!!this.Ka()},Gg:function(){for(var a=this.Ka()+this.ea,b=this.lc,c="",d=this.ea;d<a;){var e=b[d],n=null,p=239<e?4:223<e?3:191<e?2:1;if(d+p>a)break;if(1===p)128>e&&(n=e);else if(2===p){var q=b[d+1];128===(q&192)&&(n=(e&31)<<6|q&63,127>=n&&(n=null))}else if(3===p){q=b[d+1];var r=b[d+2];128===(q&192)&&128===(r&192)&&(n=(e&15)<<12|(q&63)<<6|r&63,2047>=n||55296<=
+n&&57343>=n)&&(n=null)}else if(4===p){q=b[d+1];r=b[d+2];var u=b[d+3];128===(q&192)&&128===(r&192)&&128===(u&192)&&(n=(e&15)<<18|(q&63)<<12|(r&63)<<6|u&63,65535>=n||1114112<=n)&&(n=null)}null===n?(n=65533,p=1):65535<n&&(n-=65536,c+=String.fromCharCode(n>>>10&1023|55296),n=56320|n&1023);c+=String.fromCharCode(n);d+=p}this.ea=a;return c},mq:function(a){a&=7;if(a===b.c)for(;127<this.lc[this.ea++];);else if(a===b.b)this.ea=this.Ka()+this.ea;else if(a===b.a)this.ea+=4;else if(a===b.i)this.ea+=8;else throw Error("Unimplemented type: "+
+a);}};a["default"]=b})(Fj.Dd=Fj.Dd||{});Fj.Dd=Fj.Dd.default;Fj.xf={};Fj.xf.Bf=function(){};
+(function(a){function b(a,b){this.layers=a.Ag(l,{},b)}function c(a,b){this.x=a;this.y=b}function d(a,b,c,d,f){this.properties={};this.extent=c;this.type=0;this.Cc=a;this.Ef=-1;this.ne=d;this.pe=f;a.Ag(e,this,b)}function e(a,b,c){if(1==a)b.id=c.Ka();else if(2==a)for(a=c.Ka()+c.ea;c.ea<a;){var d=b.ne[c.Ka()],e=b.pe[c.Ka()];b.properties[d]=e}else 3==a?b.type=c.Ka():4==a&&(b.Ef=c.ea)}function f(a,b){this.version=1;this.name=null;this.extent=4096;this.length=0;this.Cc=a;this.ne=[];this.pe=[];this.me=[];
+a.Ag(g,this,b);this.length=this.me.length}function g(a,b,c){15===a?b.version=c.Ka():1===a?b.name=c.Gg():5===a?b.extent=c.Ka():2===a?b.me.push(c.ea):3===a?b.ne.push(c.Gg()):4===a&&b.pe.push(h(c))}function h(a){for(var b=null,c=a.Ka()+a.ea;a.ea<c;)b=a.Ka()>>3,b=1===b?a.Gg():2===b?a.yp():3===b?a.up():4===b?a.Kp():5===b?a.Ka():6===b?a.ce():7===b?a.sp():null;return b}function l(a,b,c){3===a&&(a=new m(c,c.Ka()+c.ea),a.length&&(b[a.name]=a))}c.prototype={clone:function(){return new c(this.x,this.y)},add:function(a){return this.clone().Yj(a)},
+rotate:function(a){return this.clone().hk(a)},round:function(){return this.clone().ik()},angle:function(){return Math.atan2(this.y,this.x)},Yj:function(a){this.x+=a.x;this.y+=a.y;return this},hk:function(a){var b=Math.cos(a);a=Math.sin(a);var c=a*this.x+b*this.y;this.x=b*this.x-a*this.y;this.y=c;return this},ik:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this}};c.Kq=function(a){return a instanceof c?a:Array.isArray(a)?new c(a[0],a[1]):a};d.b=["Unknown","Point","LineString",
+"Polygon"];d.prototype.Oh=function(){var a=this.Cc;a.ea=this.Ef;for(var b=a.Ka()+a.ea,d=1,e=0,f=0,g=0,h=[],l;a.ea<b;)if(e||(e=a.Ka(),d=e&7,e>>=3),e--,1===d||2===d)f+=a.ce(),g+=a.ce(),1===d&&(l&&h.push(l),l=[]),l.push(new c(f,g));else if(7===d)l&&l.push(l[0].clone());else throw Error("unknown command "+d);l&&h.push(l);return h};d.prototype.bbox=function(){var a=this.Cc;a.ea=this.Ef;for(var b=a.Ka()+a.ea,c=1,d=0,e=0,f=0,g=Infinity,h=-Infinity,l=Infinity,m=-Infinity;a.ea<b;)if(d||(d=a.Ka(),c=d&7,d>>=
+3),d--,1===c||2===c)e+=a.ce(),f+=a.ce(),e<g&&(g=e),e>h&&(h=e),f<l&&(l=f),f>m&&(m=f);else if(7!==c)throw Error("unknown command "+c);return[g,l,h,m]};var m=f;f.prototype.feature=function(a){if(0>a||a>=this.me.length)throw Error("feature index out of bounds");this.Cc.ea=this.me[a];a=this.Cc.Ka()+this.Cc.ea;return new d(this.Cc,a,this.extent,this.ne,this.pe)};var n=m;a["default"]={Bf:b,Wj:d,Xj:n};a.Bf=b;a.Wj=d;a.Xj=n})(Fj.xf=Fj.xf||{});function kq(a,b,c,d,e){this.g=e;this.i=a;this.b=b;this.f=c;this.c=d}k=kq.prototype;k.get=function(a){return this.c[a]};k.Bb=function(){return this.f};k.G=function(){this.a||(this.a="Point"===this.i?Za(this.b):$a(this.b,0,this.b.length,2));return this.a};k.Wn=function(){return this.g};k.ec=function(){return this.b};k.ga=kq.prototype.ec;k.V=function(){return this};k.Xn=function(){return this.c};k.Vd=kq.prototype.V;k.qa=function(){return 2};k.Lc=ua;k.U=function(){return this.i};function lq(a){El.call(this);a=a?a:{};this.defaultDataProjection=new Bb({code:"",units:"tile-pixels"});this.b=a.featureClass?a.featureClass:kq;this.a=a.geometryName;this.i=a.layerName?a.layerName:"layer";this.c=a.layers?a.layers:null}v(lq,El);k=lq.prototype;k.U=function(){return"arraybuffer"};
+k.Oa=function(a,b){var c=this.c;a=new Fj.Dd(a);a=new Fj.xf.Bf(a);var d=[],e=this.b,f;for(f in a.layers)if(!c||-1!=c.indexOf(f)){var g=a.layers[f];for(var h=0,l=g.length;h<l;++h){if(e===kq){var m=void 0;var n=g.feature(h),p=f,q=n.Oh(),r=[],u=[];mq(q,u,r);var x=n.type;1===x?m=1===q.length?"Point":"MultiPoint":2===x?m=1===q.length?"LineString":"MultiLineString":3===x&&(m="Polygon");q=n.properties;q[this.i]=p;m=new this.b(m,u,r,q,n.id)}else{x=g.feature(h);u=f;r=b;m=new this.b;n=x.id;p=x.properties;p[this.i]=
+u;this.a&&m.Tc(this.a);u=void 0;q=x.type;if(0===q)u=null;else{var x=x.Oh(),B=[],E=[];mq(x,E,B);1===q?u=1===x.length?new C(null):new Q(null):2===q?1===x.length?u=new O(null):u=new P(null):3===q&&(u=new D(null));u.ba("XY",E,B)}r=Hl(u,!1,Gl(this,r));m.Ra(r);m.jc(n);m.H(p)}d.push(m)}}return d};k.kb=function(){return this.defaultDataProjection};k.mn=function(a){this.c=a};
+function mq(a,b,c){for(var d=0,e=0,f=a.length;e<f;++e){var g=a[e],h;var l=0;for(h=g.length;l<h;++l){var m=g[l];b.push(m.x,m.y)}d+=2*l;c.push(d)}}k.Tb=function(){};k.Sc=function(){};k.Bd=function(){};k.$c=function(){};k.Wb=function(){};function nq(){Cm.call(this);this.defaultDataProjection=Tb("EPSG:4326")}v(nq,Cm);function oq(a,b){b[b.length-1].fe[a.getAttribute("k")]=a.getAttribute("v")}
+var pq=[null],qq=K(pq,{nd:function(a,b){b[b.length-1].md.push(a.getAttribute("ref"))},tag:oq}),sq=K(pq,{node:function(a,b){var c=b[0],d=b[b.length-1],e=a.getAttribute("id"),f=[parseFloat(a.getAttribute("lon")),parseFloat(a.getAttribute("lat"))];d.Sh[e]=f;a=N({fe:{}},rq,a,b);wb(a.fe)||(f=new C(f),Hl(f,!1,c),c=new H(f),c.jc(e),c.H(a.fe),d.features.push(c))},way:function(a,b){var c=b[0],d=a.getAttribute("id");a=N({md:[],fe:{}},qq,a,b);b=b[b.length-1];for(var e=[],f=0,g=a.md.length;f<g;f++)la(e,b.Sh[a.md[f]]);
+a.md[0]==a.md[a.md.length-1]?(f=new D(null),f.ba("XY",e,[e.length])):(f=new O(null),f.ba("XY",e));Hl(f,!1,c);c=new H(f);c.jc(d);c.H(a.fe);b.features.push(c)}}),rq=K(pq,{tag:oq});nq.prototype.zc=function(a,b){b=Fl(this,a,b);return"osm"==a.localName&&(a=N({Sh:{},features:[]},sq,a,[b]),a.features)?a.features:[]};nq.prototype.Vg=function(){};nq.prototype.Xb=function(){};nq.prototype.ie=function(){};function tq(a){return a.getAttributeNS("http://www.w3.org/1999/xlink","href")};function uq(){}uq.prototype.read=function(a){return ml(a)?this.a(a):nl(a)?this.b(a):"string"===typeof a?(a=pl(a),this.a(a)):null};function vq(){}v(vq,uq);vq.prototype.a=function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType==Node.ELEMENT_NODE)return this.b(a);return null};vq.prototype.b=function(a){return(a=N({},wq,a,[]))?a:null};
+var xq=[null,"http://www.opengis.net/ows/1.1"],wq=K(xq,{ServiceIdentification:I(function(a,b){return N({},yq,a,b)}),ServiceProvider:I(function(a,b){return N({},zq,a,b)}),OperationsMetadata:I(function(a,b){return N({},Aq,a,b)})}),Cq=K(xq,{DeliveryPoint:I(S),City:I(S),AdministrativeArea:I(S),PostalCode:I(S),Country:I(S),ElectronicMailAddress:I(S)}),Dq=K(xq,{Value:tl(function(a){return S(a)})}),Eq=K(xq,{AllowedValues:I(function(a,b){return N({},Dq,a,b)})}),Gq=K(xq,{Phone:I(function(a,b){return N({},
+Fq,a,b)}),Address:I(function(a,b){return N({},Cq,a,b)})}),Iq=K(xq,{HTTP:I(function(a,b){return N({},Hq,a,b)})}),Hq=K(xq,{Get:tl(function(a,b){var c=tq(a);if(c)return N({href:c},Jq,a,b)}),Post:void 0}),Kq=K(xq,{DCP:I(function(a,b){return N({},Iq,a,b)})}),Aq=K(xq,{Operation:function(a,b){var c=a.getAttribute("name");(a=N({},Kq,a,b))&&(b[b.length-1][c]=a)}}),Fq=K(xq,{Voice:I(S),Facsimile:I(S)}),Jq=K(xq,{Constraint:tl(function(a,b){var c=a.getAttribute("name");if(c)return N({name:c},Eq,a,b)})}),Lq=K(xq,
+{IndividualName:I(S),PositionName:I(S),ContactInfo:I(function(a,b){return N({},Gq,a,b)})}),yq=K(xq,{Title:I(S),ServiceTypeVersion:I(S),ServiceType:I(S)}),zq=K(xq,{ProviderName:I(S),ProviderSite:I(tq),ServiceContact:I(function(a,b){return N({},Lq,a,b)})});function Mq(a,b,c,d){var e;void 0!==d?e=d:e=[];for(var f=d=0;f<b;){var g=a[f++];e[d++]=a[f++];e[d++]=g;for(g=2;g<c;++g)e[d++]=a[f++]}e.length=d};function Nq(a){a=a?a:{};El.call(this);this.defaultDataProjection=Tb("EPSG:4326");this.b=a.factor?a.factor:1E5;this.a=a.geometryLayout?a.geometryLayout:"XY"}v(Nq,Vn);function Oq(a,b,c){var d,e=Array(b);for(d=0;d<b;++d)e[d]=0;var f;var g=0;for(f=a.length;g<f;)for(d=0;d<b;++d,++g){var h=a[g],l=h-e[d];e[d]=h;a[g]=l}return Pq(a,c?c:1E5)}function Qq(a,b,c){var d,e=Array(b);for(d=0;d<b;++d)e[d]=0;a=Rq(a,c?c:1E5);var f;c=0;for(f=a.length;c<f;)for(d=0;d<b;++d,++c)e[d]+=a[c],a[c]=e[d];return a}
+function Pq(a,b){b=b?b:1E5;var c;var d=0;for(c=a.length;d<c;++d)a[d]=Math.round(a[d]*b);b=0;for(d=a.length;b<d;++b)c=a[b],a[b]=0>c?~(c<<1):c<<1;b="";d=0;for(c=a.length;d<c;++d){for(var e,f=a[d],g="";32<=f;)e=(32|f&31)+63,g+=String.fromCharCode(e),f>>=5;g+=String.fromCharCode(f+63);b+=g}return b}
+function Rq(a,b){b=b?b:1E5;var c=[],d=0,e=0,f;var g=0;for(f=a.length;g<f;++g){var h=a.charCodeAt(g)-63,d=d|(h&31)<<e;32>h?(c.push(d),e=d=0):e+=5}a=0;for(d=c.length;a<d;++a)e=c[a],c[a]=e&1?~(e>>1):e>>1;a=0;for(d=c.length;a<d;++a)c[a]/=b;return c}k=Nq.prototype;k.ae=function(a,b){a=this.wd(a,b);return new H(a)};k.zg=function(a,b){return[this.ae(a,b)]};k.wd=function(a,b){var c=sf(this.a);a=Qq(a,c,this.b);Mq(a,a.length,c,a);c=Ff(a,0,a.length,c);return Hl(new O(c,this.a),!1,Gl(this,b))};
+k.ge=function(a,b){if(a=a.V())return this.Cd(a,b);xa(!1,40);return""};k.Wg=function(a,b){return this.ge(a[0],b)};k.Cd=function(a,b){a=Hl(a,!0,Gl(this,b));b=a.ga();a=a.qa();Mq(b,b.length,a,b);return Oq(b,a,this.b)};function Sq(a){a=a?a:{};El.call(this);this.a=a.layerName;this.b=a.layers?a.layers:null;this.defaultDataProjection=Tb(a.defaultDataProjection?a.defaultDataProjection:"EPSG:4326")}v(Sq,Il);function Tq(a,b){var c=[],d,e;var f=0;for(e=a.length;f<e;++f){var g=a[f];0<f&&c.pop();0<=g?d=b[g]:d=b[~g].slice().reverse();c.push.apply(c,d)}a=0;for(b=c.length;a<b;++a)c[a]=c[a].slice();return c}
+function Uq(a,b,c,d,e,f,g){a=a.geometries;var h=[],l;var m=0;for(l=a.length;m<l;++m)h[m]=Vq(a[m],b,c,d,e,f,g);return h}function Vq(a,b,c,d,e,f,g){var h=a.type,l=Wq[h];c="Point"===h||"MultiPoint"===h?l(a,c,d):l(a,b);b=new H;b.Ra(Hl(c,!1,g));void 0!==a.id&&b.jc(a.id);a=a.properties;e&&(a||(a={}),a[e]=f);a&&b.H(a);return b}
+Sq.prototype.yg=function(a,b){if("Topology"==a.type){var c=null,d=null;if(a.transform){var e=a.transform;c=e.scale;d=e.translate}var f=a.arcs;if(e){e=c;var g=d,h;var l=0;for(h=f.length;l<h;++l){var m,n=f[l],p=e,q=g,r=0,u=0;var x=0;for(m=n.length;x<m;++x){var B=n[x];r+=B[0];u+=B[1];B[0]=r;B[1]=u;Xq(B,p,q)}}}e=[];a=a.objects;var g=this.a,E;for(E in a)this.b&&-1==this.b.indexOf(E)||("GeometryCollection"===a[E].type?(l=a[E],e.push.apply(e,Uq(l,f,c,d,g,E,b))):(l=a[E],e.push(Vq(l,f,c,d,g,E,b))));return e}return[]};
+function Xq(a,b,c){a[0]=a[0]*b[0]+c[0];a[1]=a[1]*b[1]+c[1]}Sq.prototype.Fg=function(){return this.defaultDataProjection};
+var Wq={Point:function(a,b,c){a=a.coordinates;b&&c&&Xq(a,b,c);return new C(a)},LineString:function(a,b){a=Tq(a.arcs,b);return new O(a)},Polygon:function(a,b){var c=[],d;var e=0;for(d=a.arcs.length;e<d;++e)c[e]=Tq(a.arcs[e],b);return new D(c)},MultiPoint:function(a,b,c){a=a.coordinates;var d;if(b&&c){var e=0;for(d=a.length;e<d;++e)Xq(a[e],b,c)}return new Q(a)},MultiLineString:function(a,b){var c=[],d;var e=0;for(d=a.arcs.length;e<d;++e)c[e]=Tq(a.arcs[e],b);return new P(c)},MultiPolygon:function(a,
+b){var c=[],d,e;var f=0;for(e=a.arcs.length;f<e;++f){var g=a.arcs[f];var h=[];var l=0;for(d=g.length;l<d;++l)h[l]=Tq(g[l],b);c[f]=h}return new R(c)}};k=Sq.prototype;k.Zc=function(){};k.he=function(){};k.je=function(){};k.Cg=function(){};k.Rc=function(){};function Yq(a){a=a?a:{};this.c=a.featureType;this.a=a.featureNS;this.b=a.gmlFormat?a.gmlFormat:new Sm;this.o=a.schemaLocation?a.schemaLocation:Zq["1.1.0"];Cm.call(this)}v(Yq,Cm);var Zq={"1.1.0":"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd","1.0.0":"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd"};
+Yq.prototype.zc=function(a,b){var c={featureType:this.c,featureNS:this.a};tb(c,Fl(this,a,b?b:{}));b=[c];this.b.b["http://www.opengis.net/gml"].featureMember=rl(Fm.prototype.be);(a=N([],this.b.b,a,b,this.b))||(a=[]);return a};Yq.prototype.j=function(a){if(ml(a))return $q(a);if(nl(a))return N({},ar,a,[]);if("string"===typeof a)return a=pl(a),$q(a)};Yq.prototype.g=function(a){if(ml(a))return br(this,a);if(nl(a))return cr(this,a);if("string"===typeof a)return a=pl(a),br(this,a)};
+function br(a,b){for(b=b.firstChild;b;b=b.nextSibling)if(b.nodeType==Node.ELEMENT_NODE)return cr(a,b)}var dr={"http://www.opengis.net/gml":{boundedBy:I(Fm.prototype.gf,"bounds")}};function cr(a,b){var c={},d=Nm(b.getAttribute("numberOfFeatures"));c.numberOfFeatures=d;return N(c,dr,b,[],a.b)}
+var er={"http://www.opengis.net/wfs":{totalInserted:I(Mm),totalUpdated:I(Mm),totalDeleted:I(Mm)}},fr={"http://www.opengis.net/ogc":{FeatureId:rl(function(a){return a.getAttribute("fid")})}},gr={"http://www.opengis.net/wfs":{Feature:function(a,b){Al(fr,a,b)}}},ar={"http://www.opengis.net/wfs":{TransactionSummary:I(function(a,b){return N({},er,a,b)},"transactionSummary"),InsertResults:I(function(a,b){return N([],gr,a,b)},"insertIds")}};
+function $q(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType==Node.ELEMENT_NODE)return N({},ar,a,[])}var hr={"http://www.opengis.net/wfs":{PropertyName:J(Pm)}};function ir(a,b){var c=jl("http://www.opengis.net/ogc","Filter"),d=jl("http://www.opengis.net/ogc","FeatureId");c.appendChild(d);d.setAttribute("fid",b);a.appendChild(c)}function jr(a,b){a=(a?a:"feature")+":";return b.indexOf(a)?a+b:b}
+var kr={"http://www.opengis.net/wfs":{Insert:J(function(a,b,c){var d=c[c.length-1],e=d.gmlVersion,d=jl(d.featureNS,d.featureType);a.appendChild(d);if(2===e){a=an.prototype;(e=b.a)&&d.setAttribute("fid",e);var e=c[c.length-1],f=e.featureNS,g=b.c;e.lb||(e.lb={},e.lb[f]={});var h=b.N();b=[];var l=[];for(n in h){var m=h[n];null!==m&&(b.push(n),l.push(m),n==g||m instanceof of?n in e.lb[f]||(e.lb[f][n]=J(a.ai,a)):n in e.lb[f]||(e.lb[f][n]=J(Pm)))}var n=tb({},e);n.node=d;Bl(n,e.lb,wl(void 0,f),l,c,b)}else Sm.prototype.ii(d,
+b,c)}),Update:J(function(a,b,c){var d=c[c.length-1];xa(void 0!==b.a,27);var e=d.featurePrefix,f=d.featureNS;a.setAttribute("typeName",jr(e,d.featureType));a.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:"+e,f);e=b.a;if(void 0!==e){for(var f=b.O(),g=[],h=0,l=f.length;h<l;h++){var m=b.get(f[h]);void 0!==m&&g.push({name:f[h],value:m})}Bl({gmlVersion:d.gmlVersion,node:a,hasZ:d.hasZ,srsName:d.srsName},kr,wl("Property"),g,c);ir(a,e)}}),Delete:J(function(a,b,c){c=c[c.length-1];xa(void 0!==b.a,26);
+var d=c.featurePrefix,e=c.featureNS;a.setAttribute("typeName",jr(d,c.featureType));a.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:"+d,e);b=b.a;void 0!==b&&ir(a,b)}),Property:J(function(a,b,c){var d=jl("http://www.opengis.net/wfs","Name"),e=c[c.length-1].gmlVersion;a.appendChild(d);Pm(d,b.name);void 0!==b.value&&null!==b.value&&(d=jl("http://www.opengis.net/wfs","Value"),a.appendChild(d),b.value instanceof of?2===e?an.prototype.ai(d,b.value,c):Sm.prototype.od(d,b.value,c):Pm(d,b.value))}),
+Native:J(function(a,b){b.vq&&a.setAttribute("vendorId",b.vq);void 0!==b.Vp&&a.setAttribute("safeToIgnore",b.Vp);void 0!==b.value&&Pm(a,b.value)})}};function lr(a,b,c){var d={node:a};b.b.forEach(function(a){Bl(d,mr,wl(a.kc),[a],c)})}function nr(a,b){void 0!==b.a&&a.setAttribute("matchCase",b.a.toString());or(a,b.b);pr(a,""+b.i)}function qr(a,b,c){a=jl("http://www.opengis.net/ogc",a);Pm(a,c);b.appendChild(a)}function or(a,b){qr("PropertyName",a,b)}function pr(a,b){qr("Literal",a,b)}
+function rr(a,b){var c=jl("http://www.opengis.net/gml","TimeInstant");a.appendChild(c);a=jl("http://www.opengis.net/gml","timePosition");c.appendChild(a);Pm(a,b)}
+var mr={"http://www.opengis.net/wfs":{Query:J(function(a,b,c){var d=c[c.length-1],e=d.featurePrefix,f=d.featureNS,g=d.propertyNames,h=d.srsName;a.setAttribute("typeName",e?jr(e,b):b);h&&a.setAttribute("srsName",h);f&&a.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:"+e,f);b=tb({},d);b.node=a;Bl(b,hr,wl("PropertyName"),g,c);if(d=d.filter)g=jl("http://www.opengis.net/ogc","Filter"),a.appendChild(g),Bl({node:g},mr,wl(d.kc),[d],c)})},"http://www.opengis.net/ogc":{During:J(function(a,b){var c=jl("http://www.opengis.net/fes",
+"ValueReference");Pm(c,b.b);a.appendChild(c);c=jl("http://www.opengis.net/gml","TimePeriod");a.appendChild(c);a=jl("http://www.opengis.net/gml","begin");c.appendChild(a);rr(a,b.a);a=jl("http://www.opengis.net/gml","end");c.appendChild(a);rr(a,b.i)}),And:J(lr),Or:J(lr),Not:J(function(a,b,c){b=b.condition;Bl({node:a},mr,wl(b.kc),[b],c)}),BBOX:J(function(a,b,c){c[c.length-1].srsName=b.srsName;or(a,b.geometryName);Sm.prototype.od(a,b.extent,c)}),Intersects:J(function(a,b,c){c[c.length-1].srsName=b.srsName;
+or(a,b.geometryName);Sm.prototype.od(a,b.geometry,c)}),Within:J(function(a,b,c){c[c.length-1].srsName=b.srsName;or(a,b.geometryName);Sm.prototype.od(a,b.geometry,c)}),PropertyIsEqualTo:J(nr),PropertyIsNotEqualTo:J(nr),PropertyIsLessThan:J(nr),PropertyIsLessThanOrEqualTo:J(nr),PropertyIsGreaterThan:J(nr),PropertyIsGreaterThanOrEqualTo:J(nr),PropertyIsNull:J(function(a,b){or(a,b.b)}),PropertyIsBetween:J(function(a,b){or(a,b.b);var c=jl("http://www.opengis.net/ogc","LowerBoundary");a.appendChild(c);
+pr(c,""+b.a);c=jl("http://www.opengis.net/ogc","UpperBoundary");a.appendChild(c);pr(c,""+b.i)}),PropertyIsLike:J(function(a,b){a.setAttribute("wildCard",b.g);a.setAttribute("singleChar",b.f);a.setAttribute("escapeChar",b.i);void 0!==b.a&&a.setAttribute("matchCase",b.a.toString());or(a,b.b);pr(a,""+b.c)})}};
+Yq.prototype.l=function(a){var b=jl("http://www.opengis.net/wfs","GetFeature");b.setAttribute("service","WFS");b.setAttribute("version","1.1.0");if(a){a.handle&&b.setAttribute("handle",a.handle);a.outputFormat&&b.setAttribute("outputFormat",a.outputFormat);void 0!==a.maxFeatures&&b.setAttribute("maxFeatures",a.maxFeatures);a.resultType&&b.setAttribute("resultType",a.resultType);void 0!==a.startIndex&&b.setAttribute("startIndex",a.startIndex);void 0!==a.count&&b.setAttribute("count",a.count);var c=
+a.filter;if(a.bbox){xa(a.geometryName,12);var d=sm(a.geometryName,a.bbox,a.srsName);c?c=rm(c,d):c=d}}b.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.o);c={node:b,srsName:a.srsName,featureNS:a.featureNS?a.featureNS:this.a,featurePrefix:a.featurePrefix,geometryName:a.geometryName,filter:c,propertyNames:a.propertyNames?a.propertyNames:[]};xa(Array.isArray(a.featureTypes),11);a=a.featureTypes;c=[c];d=tb({},c[c.length-1]);d.node=b;Bl(d,mr,wl("Query"),a,c);return b};
+Yq.prototype.v=function(a,b,c,d){var e=[],f=jl("http://www.opengis.net/wfs","Transaction"),g=d.version?d.version:"1.1.0",h="1.0.0"===g?2:3;f.setAttribute("service","WFS");f.setAttribute("version",g);if(d){var l=d.gmlOptions?d.gmlOptions:{};d.handle&&f.setAttribute("handle",d.handle)}f.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",Zq[g]);a&&(g={node:f,featureNS:d.featureNS,featureType:d.featureType,featurePrefix:d.featurePrefix,gmlVersion:h,hasZ:d.hasZ,srsName:d.srsName},
+tb(g,l),Bl(g,kr,wl("Insert"),a,e));b&&(g={node:f,featureNS:d.featureNS,featureType:d.featureType,featurePrefix:d.featurePrefix,gmlVersion:h,hasZ:d.hasZ,srsName:d.srsName},tb(g,l),Bl(g,kr,wl("Update"),b,e));c&&Bl({node:f,featureNS:d.featureNS,featureType:d.featureType,featurePrefix:d.featurePrefix,gmlVersion:h,srsName:d.srsName},kr,wl("Delete"),c,e);d.nativeElements&&Bl({node:f,featureNS:d.featureNS,featureType:d.featureType,featurePrefix:d.featurePrefix,gmlVersion:h,srsName:d.srsName},kr,wl("Native"),
+d.nativeElements,e);return f};Yq.prototype.Eg=function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType==Node.ELEMENT_NODE)return this.kf(a);return null};Yq.prototype.kf=function(a){if(a.firstElementChild&&a.firstElementChild.firstElementChild)for(a=a.firstElementChild.firstElementChild,a=a.firstElementChild;a;a=a.nextElementSibling)if(0!==a.childNodes.length&&(1!==a.childNodes.length||3!==a.firstChild.nodeType)){var b=[{}];this.b.gf(a,b);return Tb(b.pop().srsName)}return null};function sr(a){a=a?a:{};El.call(this);this.b=void 0!==a.splitCollection?a.splitCollection:!1}v(sr,Vn);function tr(a){a=a.X();return a.length?a.join(" "):""}function ur(a){a=a.X();for(var b=[],c=0,d=a.length;c<d;++c)b.push(a[c].join(" "));return b.join(",")}function vr(a){var b=[];a=a.Sd();for(var c=0,d=a.length;c<d;++c)b.push("("+ur(a[c])+")");return b.join(",")}
+function wr(a){var b=a.U(),c=(0,xr[b])(a),b=b.toUpperCase();if(a instanceof rf){a=a.ja;var d="";if("XYZ"===a||"XYZM"===a)d+="Z";if("XYM"===a||"XYZM"===a)d+="M";a=d;0<a.length&&(b+=" "+a)}return c.length?b+"("+c+")":b+" EMPTY"}
+var xr={Point:tr,LineString:ur,Polygon:vr,MultiPoint:function(a){var b=[];a=a.Zd();for(var c=0,d=a.length;c<d;++c)b.push("("+tr(a[c])+")");return b.join(",")},MultiLineString:function(a){var b=[];a=a.gd();for(var c=0,d=a.length;c<d;++c)b.push("("+ur(a[c])+")");return b.join(",")},MultiPolygon:function(a){var b=[];a=a.Td();for(var c=0,d=a.length;c<d;++c)b.push("("+vr(a[c])+")");return b.join(",")},GeometryCollection:function(a){var b=[];a=a.Vf();for(var c=0,d=a.length;c<d;++c)b.push(wr(a[c]));return b.join(",")}};
+k=sr.prototype;k.ae=function(a,b){return(a=this.wd(a,b))?(b=new H,b.Ra(a),b):null};k.zg=function(a,b){var c=[];a=this.wd(a,b);this.b&&"GeometryCollection"==a.U()?c=a.a:c=[a];b=[];for(var d=0,e=c.length;d<e;++d)a=new H,a.Ra(c[d]),b.push(a);return b};k.wd=function(a,b){a=new yr(new zr(a));Ar(a);return(a=Br(a))?Hl(a,!1,b):null};k.ge=function(a,b){return(a=a.V())?this.Cd(a,b):""};
+k.Wg=function(a,b){if(1==a.length)return this.ge(a[0],b);for(var c=[],d=0,e=a.length;d<e;++d)c.push(a[d].V());a=new tm(c);return this.Cd(a,b)};k.Cd=function(a,b){return wr(Hl(a,!0,b))};function zr(a){this.a=a;this.b=-1}
+function Cr(a){var b=a.a.charAt(++a.b),c={position:a.b,value:b};if("("==b)c.type=2;else if(","==b)c.type=5;else if(")"==b)c.type=3;else if("0"<=b&&"9">=b||"."==b||"-"==b){c.type=4;var b=a.b,d=!1,e=!1;do{if("."==f)d=!0;else if("e"==f||"E"==f)e=!0;var f=a.a.charAt(++a.b)}while("0"<=f&&"9">=f||"."==f&&(void 0===d||!d)||!e&&("e"==f||"E"==f)||e&&("-"==f||"+"==f));a=parseFloat(a.a.substring(b,a.b--));c.value=a}else if("a"<=b&&"z">=b||"A"<=b&&"Z">=b){c.type=1;b=a.b;do f=a.a.charAt(++a.b);while("a"<=f&&"z">=
+f||"A"<=f&&"Z">=f);a=a.a.substring(b,a.b--).toUpperCase();c.value=a}else{if(" "==b||"\t"==b||"\r"==b||"\n"==b)return Cr(a);if(""===b)c.type=6;else throw Error("Unexpected character: "+b);}return c}function yr(a){this.i=a;this.a="XY"}function Ar(a){a.b=Cr(a.i)}function Dr(a,b){(b=a.b.type==b)&&Ar(a);return b}
+function Br(a){var b=a.b;if(Dr(a,1)){var b=b.value,c="XY",d=a.b;1==a.b.type&&(d=d.value,"Z"===d?c="XYZ":"M"===d?c="XYM":"ZM"===d&&(c="XYZM"),"XY"!==c&&Ar(a));a.a=c;if("GEOMETRYCOLLECTION"==b){a:{if(Dr(a,2)){b=[];do b.push(Br(a));while(Dr(a,5));if(Dr(a,3)){a=b;break a}}else if(Er(a)){a=[];break a}throw Error(Fr(a));}return new tm(a)}d=Gr[b];c=Hr[b];if(!d||!c)throw Error("Invalid geometry type: "+b);b=d.call(a);return new c(b,a.a)}throw Error(Fr(a));}k=yr.prototype;
+k.tg=function(){if(Dr(this,2)){var a=Ir(this);if(Dr(this,3))return a}else if(Er(this))return null;throw Error(Fr(this));};k.sg=function(){if(Dr(this,2)){var a=Jr(this);if(Dr(this,3))return a}else if(Er(this))return[];throw Error(Fr(this));};k.ug=function(){if(Dr(this,2)){var a=Kr(this);if(Dr(this,3))return a}else if(Er(this))return[];throw Error(Fr(this));};
+k.fp=function(){if(Dr(this,2)){var a;if(2==this.b.type)for(a=[this.tg()];Dr(this,5);)a.push(this.tg());else a=Jr(this);if(Dr(this,3))return a}else if(Er(this))return[];throw Error(Fr(this));};k.ep=function(){if(Dr(this,2)){var a=Kr(this);if(Dr(this,3))return a}else if(Er(this))return[];throw Error(Fr(this));};k.gp=function(){if(Dr(this,2)){for(var a=[this.ug()];Dr(this,5);)a.push(this.ug());if(Dr(this,3))return a}else if(Er(this))return[];throw Error(Fr(this));};
+function Ir(a){for(var b=[],c=a.a.length,d=0;d<c;++d){var e=a.b;if(Dr(a,4))b.push(e.value);else break}if(b.length==c)return b;throw Error(Fr(a));}function Jr(a){for(var b=[Ir(a)];Dr(a,5);)b.push(Ir(a));return b}function Kr(a){for(var b=[a.sg()];Dr(a,5);)b.push(a.sg());return b}function Er(a){var b=1==a.b.type&&"EMPTY"==a.b.value;b&&Ar(a);return b}function Fr(a){return"Unexpected `"+a.b.value+"` at position "+a.b.position+" in `"+a.i.a+"`"}
+var Hr={POINT:C,LINESTRING:O,POLYGON:D,MULTIPOINT:Q,MULTILINESTRING:P,MULTIPOLYGON:R},Gr={POINT:yr.prototype.tg,LINESTRING:yr.prototype.sg,POLYGON:yr.prototype.ug,MULTIPOINT:yr.prototype.fp,MULTILINESTRING:yr.prototype.ep,MULTIPOLYGON:yr.prototype.gp};function Lr(){this.version=void 0}v(Lr,uq);Lr.prototype.a=function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType==Node.ELEMENT_NODE)return this.b(a);return null};Lr.prototype.b=function(a){this.version=a.getAttribute("version").trim();return(a=N({version:this.version},Mr,a,[]))?a:null};function Nr(a,b){return N({},Or,a,b)}function Pr(a,b){return N({},Qr,a,b)}function Rr(a,b){if(b=Nr(a,b))return a=[Nm(a.getAttribute("width")),Nm(a.getAttribute("height"))],b.size=a,b}
+function Sr(a,b){return N([],Tr,a,b)}
+var Ur=[null,"http://www.opengis.net/wms"],Mr=K(Ur,{Service:I(function(a,b){return N({},Vr,a,b)}),Capability:I(function(a,b){return N({},Wr,a,b)})}),Wr=K(Ur,{Request:I(function(a,b){return N({},Xr,a,b)}),Exception:I(function(a,b){return N([],Yr,a,b)}),Layer:I(function(a,b){return N({},Zr,a,b)})}),Vr=K(Ur,{Name:I(S),Title:I(S),Abstract:I(S),KeywordList:I(Sr),OnlineResource:I(tq),ContactInformation:I(function(a,b){return N({},$r,a,b)}),Fees:I(S),AccessConstraints:I(S),LayerLimit:I(Mm),MaxWidth:I(Mm),
+MaxHeight:I(Mm)}),$r=K(Ur,{ContactPersonPrimary:I(function(a,b){return N({},as,a,b)}),ContactPosition:I(S),ContactAddress:I(function(a,b){return N({},bs,a,b)}),ContactVoiceTelephone:I(S),ContactFacsimileTelephone:I(S),ContactElectronicMailAddress:I(S)}),as=K(Ur,{ContactPerson:I(S),ContactOrganization:I(S)}),bs=K(Ur,{AddressType:I(S),Address:I(S),City:I(S),StateOrProvince:I(S),PostCode:I(S),Country:I(S)}),Yr=K(Ur,{Format:rl(S)}),Zr=K(Ur,{Name:I(S),Title:I(S),Abstract:I(S),KeywordList:I(Sr),CRS:tl(S),
+EX_GeographicBoundingBox:I(function(a,b){var c=N({},cs,a,b);if(c){a=c.westBoundLongitude;b=c.southBoundLatitude;var d=c.eastBoundLongitude,c=c.northBoundLatitude;if(void 0!==a&&void 0!==b&&void 0!==d&&void 0!==c)return[a,b,d,c]}}),BoundingBox:tl(function(a){var b=[Lm(a.getAttribute("minx")),Lm(a.getAttribute("miny")),Lm(a.getAttribute("maxx")),Lm(a.getAttribute("maxy"))],c=[Lm(a.getAttribute("resx")),Lm(a.getAttribute("resy"))];return{crs:a.getAttribute("CRS"),extent:b,res:c}}),Dimension:tl(function(a){return{name:a.getAttribute("name"),
+units:a.getAttribute("units"),unitSymbol:a.getAttribute("unitSymbol"),"default":a.getAttribute("default"),multipleValues:Im(a.getAttribute("multipleValues")),nearestValue:Im(a.getAttribute("nearestValue")),current:Im(a.getAttribute("current")),values:S(a)}}),Attribution:I(function(a,b){return N({},ds,a,b)}),AuthorityURL:tl(function(a,b){if(b=Nr(a,b))return b.name=a.getAttribute("name"),b}),Identifier:tl(S),MetadataURL:tl(function(a,b){if(b=Nr(a,b))return b.type=a.getAttribute("type"),b}),DataURL:tl(Nr),
+FeatureListURL:tl(Nr),Style:tl(function(a,b){return N({},es,a,b)}),MinScaleDenominator:I(Km),MaxScaleDenominator:I(Km),Layer:tl(function(a,b){var c=b[b.length-1],d=N({},Zr,a,b);if(d)return b=Im(a.getAttribute("queryable")),void 0===b&&(b=c.queryable),d.queryable=void 0!==b?b:!1,b=Nm(a.getAttribute("cascaded")),void 0===b&&(b=c.cascaded),d.cascaded=b,b=Im(a.getAttribute("opaque")),void 0===b&&(b=c.opaque),d.opaque=void 0!==b?b:!1,b=Im(a.getAttribute("noSubsets")),void 0===b&&(b=c.noSubsets),d.noSubsets=
+void 0!==b?b:!1,(b=Lm(a.getAttribute("fixedWidth")))||(b=c.fixedWidth),d.fixedWidth=b,(a=Lm(a.getAttribute("fixedHeight")))||(a=c.fixedHeight),d.fixedHeight=a,["Style","CRS","AuthorityURL"].forEach(function(a){a in c&&(d[a]=(d[a]||[]).concat(c[a]))}),"EX_GeographicBoundingBox BoundingBox Dimension Attribution MinScaleDenominator MaxScaleDenominator".split(" ").forEach(function(a){a in d||(d[a]=c[a])}),d})}),ds=K(Ur,{Title:I(S),OnlineResource:I(tq),LogoURL:I(Rr)}),cs=K(Ur,{westBoundLongitude:I(Km),
+eastBoundLongitude:I(Km),southBoundLatitude:I(Km),northBoundLatitude:I(Km)}),Xr=K(Ur,{GetCapabilities:I(Pr),GetMap:I(Pr),GetFeatureInfo:I(Pr)}),Qr=K(Ur,{Format:tl(S),DCPType:tl(function(a,b){return N({},fs,a,b)})}),fs=K(Ur,{HTTP:I(function(a,b){return N({},gs,a,b)})}),gs=K(Ur,{Get:I(Nr),Post:I(Nr)}),es=K(Ur,{Name:I(S),Title:I(S),Abstract:I(S),LegendURL:tl(Rr),StyleSheetURL:I(Nr),StyleURL:I(Nr)}),Or=K(Ur,{Format:I(S),OnlineResource:I(tq)}),Tr=K(Ur,{Keyword:rl(S)});function hs(a){a=a?a:{};this.a="http://mapserver.gis.umn.edu/mapserver";this.b=new an;this.c=a.layers?a.layers:null;Cm.call(this)}v(hs,Cm);
+hs.prototype.zc=function(a,b){var c={};b&&tb(c,Fl(this,a,b));c=[c];a.setAttribute("namespaceURI",this.a);var d=a.localName;b=[];if(a.childNodes.length){if("msGMLOutput"==d)for(var e=0,f=a.childNodes.length;e<f;e++){var g=a.childNodes[e];if(g.nodeType===Node.ELEMENT_NODE){var h=c[0],l=g.localName.replace("_layer","");if(!this.c||ja(this.c,l)){l+="_feature";h.featureType=l;h.featureNS=this.a;var m={};m[l]=rl(this.b.wg,this.b);h=K([h.featureNS,null],m);g.setAttribute("namespaceURI",this.a);(g=N([],h,
+g,c,this.b))&&la(b,g)}}}"FeatureCollection"==d&&(a=N([],this.b.b,a,[{}],this.b))&&(b=a)}return b};hs.prototype.Vg=function(){};hs.prototype.Xb=function(){};hs.prototype.ie=function(){};function is(){this.i=new vq}v(is,uq);is.prototype.a=function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType==Node.ELEMENT_NODE)return this.b(a);return null};is.prototype.b=function(a){var b=a.getAttribute("version").trim(),c=this.i.b(a);if(!c)return null;c.version=b;return(c=N(c,js,a,[]))?c:null};function ks(a){var b=S(a).split(" ");if(b&&2==b.length&&(a=+b[0],b=+b[1],!isNaN(a)&&!isNaN(b)))return[a,b]}
+var ls=[null,"http://www.opengis.net/wmts/1.0"],ms=[null,"http://www.opengis.net/ows/1.1"],js=K(ls,{Contents:I(function(a,b){return N({},ns,a,b)})}),ns=K(ls,{Layer:tl(function(a,b){return N({},os,a,b)}),TileMatrixSet:tl(function(a,b){return N({},ps,a,b)})}),os=K(ls,{Style:tl(function(a,b){if(b=N({},qs,a,b))return a="true"===a.getAttribute("isDefault"),b.isDefault=a,b}),Format:tl(S),TileMatrixSetLink:tl(function(a,b){return N({},rs,a,b)}),Dimension:tl(function(a,b){return N({},ss,a,b)}),ResourceURL:tl(function(a){var b=
+a.getAttribute("format"),c=a.getAttribute("template");a=a.getAttribute("resourceType");var d={};b&&(d.format=b);c&&(d.template=c);a&&(d.resourceType=a);return d})},K(ms,{Title:I(S),Abstract:I(S),WGS84BoundingBox:I(function(a,b){a=N([],ts,a,b);if(2==a.length)return Na(a)}),Identifier:I(S)})),qs=K(ls,{LegendURL:tl(function(a){var b={};b.format=a.getAttribute("format");b.href=tq(a);return b})},K(ms,{Title:I(S),Identifier:I(S)})),rs=K(ls,{TileMatrixSet:I(S),TileMatrixSetLimits:I(function(a,b){return N([],
+us,a,b)})}),us=K(ls,{TileMatrixLimits:rl(function(a,b){return N({},vs,a,b)})}),vs=K(ls,{TileMatrix:I(S),MinTileRow:I(Mm),MaxTileRow:I(Mm),MinTileCol:I(Mm),MaxTileCol:I(Mm)}),ss=K(ls,{Default:I(S),Value:tl(S)},K(ms,{Identifier:I(S)})),ts=K(ms,{LowerCorner:rl(ks),UpperCorner:rl(ks)}),ps=K(ls,{WellKnownScaleSet:I(S),TileMatrix:tl(function(a,b){return N({},ws,a,b)})},K(ms,{SupportedCRS:I(S),Identifier:I(S)})),ws=K(ls,{TopLeftCorner:I(ks),ScaleDenominator:I(Km),TileWidth:I(Mm),TileHeight:I(Mm),MatrixWidth:I(Mm),
+MatrixHeight:I(Mm)},K(ms,{Identifier:I(S)}));function xs(a){Tc.call(this);a=a||{};this.a=null;this.f=fc;this.c=void 0;y(this,Vc("projection"),this.Am,this);y(this,Vc("tracking"),this.Bm,this);void 0!==a.projection&&this.Wh(a.projection);void 0!==a.trackingOptions&&this.wj(a.trackingOptions);this.Ke(void 0!==a.tracking?a.tracking:!1)}v(xs,Tc);k=xs.prototype;k.ka=function(){this.Ke(!1);Tc.prototype.ka.call(this)};k.Am=function(){var a=this.Uh();a&&(this.f=Vb(Tb("EPSG:4326"),a),this.a&&this.set("position",this.f(this.a)))};
+k.Bm=function(){if(Wd){var a=this.Vh();a&&void 0===this.c?this.c=navigator.geolocation.watchPosition(this.np.bind(this),this.op.bind(this),this.Gh()):a||void 0===this.c||(navigator.geolocation.clearWatch(this.c),this.c=void 0)}};
+k.np=function(a){a=a.coords;this.set("accuracy",a.accuracy);this.set("altitude",null===a.altitude?void 0:a.altitude);this.set("altitudeAccuracy",null===a.altitudeAccuracy?void 0:a.altitudeAccuracy);this.set("heading",null===a.heading?void 0:Ha(a.heading));this.a?(this.a[0]=a.longitude,this.a[1]=a.latitude):this.a=[a.longitude,a.latitude];var b=this.f(this.a);this.set("position",b);this.set("speed",null===a.speed?void 0:a.speed);a=Xf(Jb,this.a,a.accuracy);a.Dc(this.f);this.set("accuracyGeometry",a);
+this.s()};k.op=function(a){a.type="error";this.Ke(!1);this.b(a)};k.Dk=function(){return this.get("accuracy")};k.Ek=function(){return this.get("accuracyGeometry")||null};k.Gk=function(){return this.get("altitude")};k.Hk=function(){return this.get("altitudeAccuracy")};k.ym=function(){return this.get("heading")};k.zm=function(){return this.get("position")};k.Uh=function(){return this.get("projection")};k.ll=function(){return this.get("speed")};k.Vh=function(){return this.get("tracking")};k.Gh=function(){return this.get("trackingOptions")};
+k.Wh=function(a){this.set("projection",Tb(a))};k.Ke=function(a){this.set("tracking",a)};k.wj=function(a){this.set("trackingOptions",a)};function ys(a,b,c){rf.call(this);this.Ng(a,b?b:0,c)}v(ys,rf);k=ys.prototype;k.clone=function(){var a=new ys(null);tf(a,this.ja,this.A.slice());a.s();return a};k.Kb=function(a,b,c,d){var e=this.A;a-=e[0];var f=b-e[1];b=a*a+f*f;if(b<d){if(b)for(d=this.pd()/Math.sqrt(b),c[0]=e[0]+d*a,c[1]=e[1]+d*f,d=2;d<this.a;++d)c[d]=e[d];else for(d=0;d<this.a;++d)c[d]=e[d];c.length=this.a;return b}return d};k.Mc=function(a,b){var c=this.A;a-=c[0];b-=c[1];return a*a+b*b<=zs(this)};
+k.wa=function(){return this.A.slice(0,this.a)};k.se=function(a){var b=this.A,c=b[this.a]-b[0];return Xa(b[0]-c,b[1]-c,b[0]+c,b[1]+c,a)};k.pd=function(){return Math.sqrt(zs(this))};function zs(a){var b=a.A[a.a]-a.A[0];a=a.A[a.a+1]-a.A[1];return b*b+a*a}k.U=function(){return"Circle"};k.Xa=function(a){var b=this.G();return qb(a,b)?(b=this.wa(),a[0]<=b[0]&&a[2]>=b[0]||a[1]<=b[1]&&a[3]>=b[1]?!0:db(a,this.sb,this)):!1};
+k.ob=function(a){var b=this.a,c=a.slice();c[b]=c[0]+(this.A[b]-this.A[0]);var d;for(d=1;d<b;++d)c[b+d]=a[d];tf(this,this.ja,c);this.s()};k.Ng=function(a,b,c){if(a){uf(this,c,a,0);this.A||(this.A=[]);c=this.A;a=Cf(c,a);c[a++]=c[0]+b;var d;b=1;for(d=this.a;b<d;++b)c[a++]=c[b];c.length=a}else tf(this,"XY",null);this.s()};k.X=function(){};k.ma=function(){};k.Uc=function(a){this.A[this.a]=this.A[0]+a;this.s()};function As(a,b,c){for(var d=[],e=a(0),f=a(1),g=b(e),h=b(f),l=[f,e],m=[h,g],n=[1,0],p={},q=1E5,r,u,x,B,E;0<--q&&0<n.length;)x=n.pop(),e=l.pop(),g=m.pop(),f=x.toString(),f in p||(d.push(g[0],g[1]),p[f]=!0),B=n.pop(),f=l.pop(),h=m.pop(),E=(x+B)/2,r=a(E),u=b(r),Fa(u[0],u[1],g[0],g[1],h[0],h[1])<c?(d.push(h[0],h[1]),f=B.toString(),p[f]=!0):(n.push(B,E,E,x),m.push(h,u,u,g),l.push(f,r,r,e));return d}function Bs(a,b,c,d,e){var f=Tb("EPSG:4326");return As(function(d){return[a,b+(c-b)*d]},ec(f,d),e)}
+function Cs(a,b,c,d,e){var f=Tb("EPSG:4326");return As(function(d){return[b+(c-b)*d,a]},ec(f,d),e)};function Ds(a){a=a||{};this.j=this.v=null;this.f=this.o=Infinity;this.g=this.l=-Infinity;this.ra=this.oa=Infinity;this.R=this.I=-Infinity;this.Jb=void 0!==a.targetSize?a.targetSize:100;this.fb=void 0!==a.maxLines?a.maxLines:100;this.i=[];this.c=[];this.pa=void 0!==a.strokeStyle?a.strokeStyle:Es;this.D=this.u=void 0;this.a=this.b=this.S=null;1==a.showLabels&&(this.na=a.lonLabelFormatter?a.lonLabelFormatter:bf.bind(this,"EW"),this.Ua=a.latLabelFormatter?a.latLabelFormatter:bf.bind(this,"NS"),this.fa=
+void 0==a.lonLabelPosition?0:a.lonLabelPosition,this.T=void 0==a.latLabelPosition?1:a.latLabelPosition,this.B=void 0!==a.lonLabelStyle?a.lonLabelStyle:new fo({font:"12px Calibri,sans-serif",textBaseline:"bottom",fill:new al({color:"rgba(0,0,0,1)"}),stroke:new wj({color:"rgba(255,255,255,1)",width:3})}),this.C=void 0!==a.latLabelStyle?a.latLabelStyle:new fo({font:"12px Calibri,sans-serif",textAlign:"end",fill:new al({color:"rgba(0,0,0,1)"}),stroke:new wj({color:"rgba(255,255,255,1)",width:3})}),this.b=
+[],this.a=[]);this.setMap(void 0!==a.map?a.map:null)}var Es=new wj({color:"rgba(0,0,0,0.2)"}),Fs=[90,45,30,20,10,5,2,1,.5,.2,.1,.05,.01,.005,.002,.001];function Gs(a,b,c,d,e,f,g){var h=g;c=Bs(b,c,d,a.j,e);h=void 0!==a.i[h]?a.i[h]:new O(null);h.ba("XY",c);qb(h.G(),f)&&(a.b&&(c=g,d=h.ga(),f=[d[0],Ca(f[1]+Math.abs(f[1]-f[3])*a.fa,Math.max(f[1],d[1]),Math.min(f[3],d[d.length-1]))],c=a.b[c]?a.b[c].Qd:new C(null),c.ma(f),a.b[g]={Qd:c,text:a.na(b)}),a.i[g++]=h);return g}
+function Hs(a,b,c,d,e){var f=e;c=Cs(b,a.g,a.f,a.j,c);f=void 0!==a.c[f]?a.c[f]:new O(null);f.ba("XY",c);if(qb(f.G(),d)){if(a.a){c=e;var g=f.ga();d=[Ca(d[0]+Math.abs(d[0]-d[2])*a.T,Math.max(d[0],g[0]),Math.min(d[2],g[g.length-2])),g[1]];c=a.a[c]?a.a[c].Qd:new C(null);c.ma(d);a.a[e]={Qd:c,text:a.Ua(b)}}a.c[e++]=f}return e}k=Ds.prototype;k.Cm=function(){return this.v};k.al=function(){return this.i};k.hl=function(){return this.c};
+k.Kh=function(a){var b=a.vectorContext,c=a.frameState,d=c.extent;a=c.viewState;var e=a.center,f=a.projection,g=a.resolution;a=c.pixelRatio;a=g*g/(4*a*a);if(!this.j||!dc(this.j,f)){var h=Tb("EPSG:4326"),l=f.G(),m=f.g,n=hc(m,h,f),p=m[2],q=m[1],r=m[0],u=n[3],x=n[2],B=n[1],n=n[0];this.o=m[3];this.f=p;this.l=q;this.g=r;this.oa=u;this.ra=x;this.I=B;this.R=n;this.u=ec(h,f);this.D=ec(f,h);this.S=this.D(nb(l));this.j=f}f.i&&(f=f.G(),h=lb(f),c=c.focus[0],c<f[0]||c>f[2])&&(c=h*Math.ceil((f[0]-c)/h),d=[d[0]+
+c,d[1],d[2]+c,d[3]]);c=this.S[0];f=this.S[1];h=-1;m=Math.pow(this.Jb*g,2);p=[];q=[];g=0;for(l=Fs.length;g<l;++g){r=Fs[g]/2;p[0]=c-r;p[1]=f-r;q[0]=c+r;q[1]=f+r;this.u(p,p);this.u(q,q);r=Math.pow(q[0]-p[0],2)+Math.pow(q[1]-p[1],2);if(r<=m)break;h=Fs[g]}g=h;if(-1==g)this.i.length=this.c.length=0,this.b&&(this.b.length=0),this.a&&(this.a.length=0);else{c=this.D(e);e=c[0];c=c[1];f=this.fb;h=[Math.max(d[0],this.R),Math.max(d[1],this.I),Math.min(d[2],this.ra),Math.min(d[3],this.oa)];h=hc(h,this.j,"EPSG:4326");
+m=h[3];q=h[1];e=Math.floor(e/g)*g;p=Ca(e,this.g,this.f);l=Gs(this,p,q,m,a,d,0);for(h=0;p!=this.g&&h++<f;)p=Math.max(p-g,this.g),l=Gs(this,p,q,m,a,d,l);p=Ca(e,this.g,this.f);for(h=0;p!=this.f&&h++<f;)p=Math.min(p+g,this.f),l=Gs(this,p,q,m,a,d,l);this.i.length=l;this.b&&(this.b.length=l);c=Math.floor(c/g)*g;e=Ca(c,this.l,this.o);l=Hs(this,e,a,d,0);for(h=0;e!=this.l&&h++<f;)e=Math.max(e-g,this.l),l=Hs(this,e,a,d,l);e=Ca(c,this.l,this.o);for(h=0;e!=this.o&&h++<f;)e=Math.min(e+g,this.o),l=Hs(this,e,a,
+d,l);this.c.length=l;this.a&&(this.a.length=l)}b.Ma(null,this.pa);a=0;for(e=this.i.length;a<e;++a)g=this.i[a],b.zb(g);a=0;for(e=this.c.length;a<e;++a)g=this.c[a],b.zb(g);if(this.b)for(a=0,e=this.b.length;a<e;++a)g=this.b[a],this.B.xd(g.text),b.Cb(this.B),b.zb(g.Qd);if(this.a)for(a=0,e=this.a.length;a<e;++a)g=this.a[a],this.C.xd(g.text),b.Cb(this.C),b.zb(g.Qd)};
+k.setMap=function(a){this.v&&(this.v.K("postcompose",this.Kh,this),this.v.render());a&&(a.J("postcompose",this.Kh,this),a.render());this.v=a};function Is(a,b,c,d,e){Qc.call(this);this.f=e;this.extent=a;this.a=c;this.resolution=b;this.state=d}v(Is,Qc);Is.prototype.s=function(){this.b("change")};Is.prototype.G=function(){return this.extent};Is.prototype.getState=function(){return this.state};function Js(a,b,c,d,e,f,g){Is.call(this,a,b,c,0,d);this.j=e;this.M=new Image;null!==f&&(this.M.crossOrigin=f);this.c={};this.i=null;this.state=0;this.g=g}v(Js,Is);k=Js.prototype;k.Y=function(a){if(void 0!==a){var b;a=w(a);if(a in this.c)return this.c[a];wb(this.c)?b=this.M:b=this.M.cloneNode(!1);return this.c[a]=b}return this.M};k.Fm=function(){this.state=3;this.i.forEach(Ec);this.i=null;this.s()};
+k.Gm=function(){void 0===this.resolution&&(this.resolution=mb(this.extent)/this.M.height);this.state=2;this.i.forEach(Ec);this.i=null;this.s()};k.load=function(){if(0==this.state||3==this.state)this.state=1,this.s(),this.i=[Jc(this.M,"error",this.Fm,this),Jc(this.M,"load",this.Gm,this)],this.g(this,this.j)};k.Og=function(a){this.M=a};function Ks(a,b,c,d,e,f){this.c=f?f:null;Is.call(this,a,b,c,f?0:2,d);this.i=e}v(Ks,Is);Ks.prototype.g=function(a){this.state=a?3:2;this.s()};Ks.prototype.load=function(){0==this.state&&(this.state=1,this.s(),this.c(this.g.bind(this)))};Ks.prototype.Y=function(){return this.i};function Ls(a,b){Qc.call(this);this.ta=a;this.state=b;this.i=null;this.key=""}v(Ls,Qc);Ls.prototype.s=function(){this.b("change")};Ls.prototype.bb=function(){return this.key+"/"+this.ta};function Ms(a){if(!a.i)return a;var b=a.i;do{if(2==b.getState())return b;b=b.i}while(b);return a}Ls.prototype.f=function(){return this.ta};Ls.prototype.getState=function(){return this.state};function Ns(a,b){a.state=b;a.s()};function Os(a,b,c,d,e){Ls.call(this,a,b);this.g=c;this.M=new Image;null!==d&&(this.M.crossOrigin=d);this.c=null;this.j=e}v(Os,Ls);k=Os.prototype;k.ka=function(){1==this.state&&Ps(this);this.i&&Nc(this.i);this.state=5;this.s();Ls.prototype.ka.call(this)};k.Y=function(){return this.M};k.bb=function(){return this.g};k.Dm=function(){this.state=3;this.M=Qs;Ps(this);this.s()};k.Em=function(){this.state=this.M.naturalWidth&&this.M.naturalHeight?2:4;Ps(this);this.s()};
+k.load=function(){if(0==this.state||3==this.state)this.state=1,this.s(),this.c=[Jc(this.M,"error",this.Dm,this),Jc(this.M,"load",this.Em,this)],this.j(this,this.g)};function Ps(a){a.c.forEach(Ec);a.c=null}var Qs=new Image;Qs.src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";function Rs(a){a=a?a:{};ng.call(this,{handleEvent:mf});this.g=a.formatConstructors?a.formatConstructors:[];this.o=a.projection?Tb(a.projection):null;this.a=null;this.target=a.target?a.target:null}v(Rs,ng);function Ss(a){a=a.dataTransfer.files;var b;var c=0;for(b=a.length;c<b;++c){var d=a.item(c);var e=new FileReader;e.addEventListener("load",this.j.bind(this,d));e.readAsText(d)}}function Ts(a){a.stopPropagation();a.preventDefault();a.dataTransfer.dropEffect="copy"}
+Rs.prototype.j=function(a,b){b=b.target.result;var c=this.v,d=this.o;d||(d=c.Z().v);var c=this.g,e=[],f;var g=0;for(f=c.length;g<f;++g){var h=new c[g];var l={featureProjection:d};try{e=h.Oa(b,l)}catch(m){e=null}if(e&&0<e.length)break}this.b(new Us(Vs,a,e,d))};function Ws(a){var b=a.v;b&&(b=a.target?a.target:b.a,a.a=[y(b,"drop",Ss,a),y(b,"dragenter",Ts,a),y(b,"dragover",Ts,a),y(b,"drop",Ts,a)])}Rs.prototype.Ha=function(a){ng.prototype.Ha.call(this,a);a?Ws(this):Xs(this)};
+Rs.prototype.setMap=function(a){Xs(this);ng.prototype.setMap.call(this,a);this.c()&&Ws(this)};function Xs(a){a.a&&(a.a.forEach(Ec),a.a=null)}var Vs="addfeatures";function Us(a,b,c,d){Oc.call(this,a);this.features=c;this.file=b;this.projection=d}v(Us,Oc);function Ys(a){a=a?a:{};Dg.call(this,{handleDownEvent:Zs,handleDragEvent:$s,handleUpEvent:at});this.l=a.condition?a.condition:yg;this.a=this.g=void 0;this.j=0;this.u=void 0!==a.duration?a.duration:400}v(Ys,Dg);
+function $s(a){if(Bg(a)){var b=a.map,c=b.Ob(),d=a.pixel;a=d[0]-c[0]/2;d=c[1]/2-d[1];c=Math.atan2(d,a);a=Math.sqrt(a*a+d*d);b=b.Z();b.g.rotation!==Te&&void 0!==this.g&&(d=c-this.g,og(b,b.Qa()-d));this.g=c;void 0!==this.a&&(c=this.a*(b.Pa()/a),qg(b,c));void 0!==this.a&&(this.j=this.a/a);this.a=a}}
+function at(a){if(!Bg(a))return!0;a=a.map.Z();cg(a,1,-1);var b=this.j-1,c=a.Qa(),c=a.constrainRotation(c,0);og(a,c,void 0,void 0);var c=a.Pa(),d=this.u,c=a.constrainResolution(c,0,b);qg(a,c,void 0,d);this.j=0;return!1}function Zs(a){return Bg(a)&&this.l(a)?(cg(a.map.Z(),1,1),this.a=this.g=void 0,!0):!1};function bt(a,b,c,d){this.fb=a;this.Ua=b;this.overlaps=d;this.c=0;this.resolution=c;this.ra=this.oa=null;this.a=[];this.coordinates=[];this.T=Bh();this.b=[];this.B=null;this.fa=Bh();this.na=Bh()}v(bt,Wh);
+function ct(a,b,c,d,e,f,g){var h=a.coordinates.length,l=a.Sf();g&&(c+=e);g=[b[c],b[c+1]];var m=[NaN,NaN],n=!0,p;for(p=c+e;p<d;p+=e){m[0]=b[p];m[1]=b[p+1];var q=Wa(l,m);q!==r?(n&&(a.coordinates[h++]=g[0],a.coordinates[h++]=g[1]),a.coordinates[h++]=m[0],a.coordinates[h++]=m[1],n=!1):1===q?(a.coordinates[h++]=m[0],a.coordinates[h++]=m[1],n=!1):n=!0;g[0]=m[0];g[1]=m[1];var r=q}if(f&&n||p===c+e)a.coordinates[h++]=g[0],a.coordinates[h++]=g[1];return h}
+function dt(a,b){a.oa=[0,b,0];a.a.push(a.oa);a.ra=[0,b,0];a.b.push(a.ra)}bt.prototype.Va=function(a,b){if(this.R){var c=Gh(this.T,this.R.slice());a.translate(c[0],c[1]);a.rotate(b)}a.fill();this.R&&a.setTransform.apply(a,this.na)};
+function et(a,b,c,d,e,f,g,h,l){if(a.B&&pa(d,a.T))var m=a.B;else a.B||(a.B=[]),m=pf(a.coordinates,0,a.coordinates.length,2,d,a.B),Fh(a.T,d);d=!wb(f);for(var n=0,p=g.length,q=0,r,u=a.fa,x=a.na,B,E,A,L,oa=0,ha=0,ga=a.a!=g||a.overlaps?0:200;n<p;){var z=g[n];switch(z[0]){case 0:q=z[1];d&&f[w(q).toString()]||!q.V()?n=z[2]:void 0===l||qb(l,q.V().G())?++n:n=z[2]+1;break;case 1:oa>ga&&(a.Va(b,e),oa=0);ha>ga&&(b.stroke(),ha=0);oa||ha||(b.beginPath(),B=E=NaN);++n;break;case 2:q=z[1];r=m[q];z=m[q+1];A=m[q+2]-
+r;q=m[q+3]-z;q=Math.sqrt(A*A+q*q);b.moveTo(r+q,z);b.arc(r,z,q,0,2*Math.PI,!0);++n;break;case 3:b.closePath();++n;break;case 4:q=z[1];r=z[2];var M=z[3];var ba=z[4]*c;var da=z[5]*c;var fb=z[6],ca=z[7],Ub=z[8],uc=z[9];var bc=z[10];A=z[11];L=z[12];var Je=z[13],zg=z[14];for(bc&&(A+=e);q<r;q+=2){z=m[q]-ba;bc=m[q+1]-da;Je&&(z=Math.round(z),bc=Math.round(bc));if(1!=L||A){var ff=z+ba,rh=bc+da;Kh(u,ff,rh,L,L,A,-ff,-rh);b.setTransform.apply(b,u)}ff=b.globalAlpha;1!=ca&&(b.globalAlpha=ff*ca);var rh=zg+Ub>M.width?
+M.width-Ub:zg,Bq=fb+uc>M.height?M.height-uc:fb;b.drawImage(M,Ub,uc,rh,Bq,z,bc,rh*c,Bq*c);1!=ca&&(b.globalAlpha=ff);(1!=L||A)&&b.setTransform.apply(b,x)}++n;break;case 5:q=z[1];r=z[2];da=z[3];fb=z[4]*c;ca=z[5]*c;A=z[6];L=z[7]*c;M=z[8];ba=z[9];for((bc=z[10])&&(A+=e);q<r;q+=2){z=m[q]+fb;bc=m[q+1]+ca;if(1!=L||A)Kh(u,z,bc,L,L,A,-z,-bc),b.setTransform.apply(b,u);Ub=da.split("\n");uc=Ub.length;1<uc?(Je=Math.round(1.5*b.measureText("M").width),bc-=(uc-1)/2*Je):Je=0;for(zg=0;zg<uc;zg++)ff=Ub[zg],ba&&b.strokeText(ff,
+z,bc),M&&b.fillText(ff,z,bc),bc+=Je;(1!=L||A)&&b.setTransform.apply(b,x)}++n;break;case 6:if(h&&(q=z[1],q=h(q)))return q;++n;break;case 7:ga?oa++:a.Va(b,e);++n;break;case 8:q=z[1];r=z[2];z=m[q];bc=m[q+1];A=z+.5|0;L=bc+.5|0;if(A!==B||L!==E)b.moveTo(z,bc),B=A,E=L;for(q+=2;q<r;q+=2)if(z=m[q],bc=m[q+1],A=z+.5|0,L=bc+.5|0,q==r-2||A!==B||L!==E)b.lineTo(z,bc),B=A,E=L;++n;break;case 9:a.R=z[2];oa&&(a.Va(b,e),oa=0,ha&&(b.stroke(),ha=0));b.fillStyle=z[1];++n;break;case 10:var q=void 0!==z[8]?z[8]:!0,ul=z[9];
+r=z[2];ha&&(b.stroke(),ha=0);b.strokeStyle=z[1];b.lineWidth=q?r*c:r;b.lineCap=z[3];b.lineJoin=z[4];b.miterLimit=z[5];Td&&(r=z[6],A=z[7],q&&c!==ul&&(r=r.map(function(a){return a*c/ul}),A*=c/ul,z[6]=r,z[7]=A,z[9]=c),b.lineDashOffset=A,b.setLineDash(r));++n;break;case 11:b.font=z[1];b.textAlign=z[2];b.textBaseline=z[3];++n;break;case 12:ga?ha++:b.stroke();++n;break;default:++n}}oa&&a.Va(b,e);ha&&b.stroke()}bt.prototype.La=function(a,b,c,d,e){et(this,a,b,c,d,e,this.a,void 0,void 0)};
+function ft(a){var b=a.b;b.reverse();var c,d=b.length,e=-1;for(c=0;c<d;++c){var f=b[c];var g=f[0];if(6==g)e=c;else if(0==g){f[2]=c;f=a.b;for(g=c;e<g;){var h=f[e];f[e]=f[g];f[g]=h;++e;--g}e=-1}}}function gt(a,b){a.oa[2]=a.a.length;a.oa=null;a.ra[2]=a.b.length;a.ra=null;b=[6,b];a.a.push(b);a.b.push(b)}bt.prototype.Te=ua;bt.prototype.Sf=function(){return this.Ua};function ht(a,b,c,d){bt.call(this,a,b,c,d);this.M=this.I=null;this.C=this.D=this.S=this.u=this.v=this.l=this.o=this.j=this.g=this.f=this.i=void 0}v(ht,bt);
+ht.prototype.qc=function(a,b){if(this.M){dt(this,b);var c=a.ga(),d=this.coordinates.length;a=ct(this,c,0,c.length,a.qa(),!1,!1);this.a.push([4,d,a,this.M,this.i,this.f,this.g,this.j,this.o,this.l,this.v,this.u,this.S,this.D,this.C]);this.b.push([4,d,a,this.I,this.i,this.f,this.g,this.j,this.o,this.l,this.v,this.u,this.S,this.D,this.C]);gt(this,b)}};
+ht.prototype.oc=function(a,b){if(this.M){dt(this,b);var c=a.ga(),d=this.coordinates.length;a=ct(this,c,0,c.length,a.qa(),!1,!1);this.a.push([4,d,a,this.M,this.i,this.f,this.g,this.j,this.o,this.l,this.v,this.u,this.S,this.D,this.C]);this.b.push([4,d,a,this.I,this.i,this.f,this.g,this.j,this.o,this.l,this.v,this.u,this.S,this.D,this.C]);gt(this,b)}};ht.prototype.Te=function(){ft(this);this.f=this.i=void 0;this.M=this.I=null;this.C=this.D=this.u=this.v=this.l=this.o=this.j=this.S=this.g=void 0};
+ht.prototype.Ub=function(a){var b=a.Hc(),c=a.ic(),d=a.qg(1),e=a.Y(1),f=a.Oc();this.i=b[0];this.f=b[1];this.I=d;this.M=e;this.g=c[1];this.j=a.f;this.o=f[0];this.l=f[1];this.v=a.l;this.u=a.g;this.S=a.a;this.D=a.v;this.C=c[0]};function it(a,b,c,d){bt.call(this,a,b,c,d);this.f=null;this.i={Md:void 0,Gd:void 0,Hd:null,Id:void 0,Jd:void 0,Kd:void 0,Ld:void 0,eg:0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineDashOffset:void 0,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}v(it,bt);function jt(a,b,c,d,e){var f=a.coordinates.length;b=ct(a,b,c,d,e,!1,!1);f=[8,f,b];a.a.push(f);a.b.push(f);return d}k=it.prototype;k.Sf=function(){this.f||(this.f=Ra(this.Ua),0<this.c&&Qa(this.f,this.resolution*(this.c+1)/2,this.f));return this.f};
+function kt(a){var b=a.i,c=b.strokeStyle,d=b.lineCap,e=b.lineDash,f=b.lineDashOffset,g=b.lineJoin,h=b.lineWidth,l=b.miterLimit;b.Md==c&&b.Gd==d&&pa(b.Hd,e)&&b.Id==f&&b.Jd==g&&b.Kd==h&&b.Ld==l||(b.eg!=a.coordinates.length&&(a.a.push([12]),b.eg=a.coordinates.length),a.a.push([10,c,h,d,g,l,e,f,!0,1],[1]),b.Md=c,b.Gd=d,b.Hd=e,b.Id=f,b.Jd=g,b.Kd=h,b.Ld=l)}
+k.mc=function(a,b){var c=this.i,d=c.lineWidth;void 0!==c.strokeStyle&&void 0!==d&&(kt(this),dt(this,b),this.b.push([10,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash,c.lineDashOffset,!0,1],[1]),c=a.ga(),jt(this,c,0,c.length,a.qa()),this.b.push([12]),gt(this,b))};
+k.nc=function(a,b){var c=this.i,d=c.lineWidth;if(void 0!==c.strokeStyle&&void 0!==d){kt(this);dt(this,b);this.b.push([10,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash,c.lineDashOffset,!0,1],[1]);c=a.Bb();d=a.ga();a=a.qa();var e=0,f;var g=0;for(f=c.length;g<f;++g)e=jt(this,d,e,c[g],a);this.b.push([12]);gt(this,b)}};k.Te=function(){this.i.eg!=this.coordinates.length&&this.a.push([12]);ft(this);this.i=null};
+k.Ma=function(a,b){a=b.a;this.i.strokeStyle=id(a?a:Uh);a=b.f;this.i.lineCap=void 0!==a?a:"round";a=b.i;this.i.lineDash=a?a:Th;a=b.g;this.i.lineDashOffset=a?a:0;a=b.j;this.i.lineJoin=void 0!==a?a:"round";a=b.c;this.i.lineWidth=void 0!==a?a:1;b=b.o;this.i.miterLimit=void 0!==b?b:10;this.i.lineWidth>this.c&&(this.c=this.i.lineWidth,this.f=null)};function lt(a,b,c,d){bt.call(this,a,b,c,d);this.f=null;this.i={oh:void 0,Md:void 0,Gd:void 0,Hd:null,Id:void 0,Jd:void 0,Kd:void 0,Ld:void 0,fillStyle:void 0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineDashOffset:void 0,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}v(lt,bt);
+function mt(a,b,c,d,e){var f=a.i,g=void 0!==f.fillStyle,f=void 0!=f.strokeStyle,h=d.length,l=[1];a.a.push(l);a.b.push(l);for(l=0;l<h;++l){var m=d[l],n=a.coordinates.length;c=ct(a,b,c,m,e,!0,!f);c=[8,n,c];a.a.push(c);a.b.push(c);f&&(c=[3],a.a.push(c),a.b.push(c));c=m}b=[7];a.b.push(b);g&&a.a.push(b);f&&(g=[12],a.a.push(g),a.b.push(g));return c}k=lt.prototype;
+k.Zb=function(a,b){var c=this.i,d=c.strokeStyle;if(void 0!==c.fillStyle||void 0!==d){nt(this,a);dt(this,b);this.b.push([9,gd(Sh)]);void 0!==c.strokeStyle&&this.b.push([10,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash,c.lineDashOffset,!0,1]);var e=a.ga(),d=this.coordinates.length;ct(this,e,0,e.length,a.qa(),!1,!1);a=[1];d=[2,d];this.a.push(a,d);this.b.push(a,d);a=[7];this.b.push(a);void 0!==c.fillStyle&&this.a.push(a);void 0!==c.strokeStyle&&(c=[12],this.a.push(c),this.b.push(c));
+gt(this,b)}};k.rc=function(a,b){var c=this.i;nt(this,a);dt(this,b);this.b.push([9,gd(Sh)]);void 0!==c.strokeStyle&&this.b.push([10,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash,c.lineDashOffset,!0,1]);var c=a.Bb(),d=a.ec();mt(this,d,0,c,a.qa());gt(this,b)};
+k.pc=function(a,b){var c=this.i,d=c.strokeStyle;if(void 0!==c.fillStyle||void 0!==d){nt(this,a);dt(this,b);this.b.push([9,gd(Sh)]);void 0!==c.strokeStyle&&this.b.push([10,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash,c.lineDashOffset,!0,1]);c=a.c;d=fi(a);a=a.qa();var e=0,f;var g=0;for(f=c.length;g<f;++g)e=mt(this,d,e,c[g],a);gt(this,b)}};
+k.Te=function(){ft(this);this.i=null;var a=this.fb;if(a){var b=this.coordinates,c;var d=0;for(c=b.length;d<c;++d)b[d]=a*Math.round(b[d]/a)}};k.Sf=function(){this.f||(this.f=Ra(this.Ua),0<this.c&&Qa(this.f,this.resolution*(this.c+1)/2,this.f));return this.f};
+k.Ma=function(a,b){var c=this.i;a?(a=a.b,c.fillStyle=id(a?a:Sh)):c.fillStyle=void 0;b?(a=b.a,c.strokeStyle=id(a?a:Uh),a=b.f,c.lineCap=void 0!==a?a:"round",a=b.i,c.lineDash=a?a.slice():Th,a=b.g,c.lineDashOffset=a?a:0,a=b.j,c.lineJoin=void 0!==a?a:"round",a=b.c,c.lineWidth=void 0!==a?a:1,b=b.o,c.miterLimit=void 0!==b?b:10,c.lineWidth>this.c&&(this.c=c.lineWidth,this.f=null)):(c.strokeStyle=void 0,c.lineCap=void 0,c.lineDash=null,c.lineDashOffset=void 0,c.lineJoin=void 0,c.lineWidth=void 0,c.miterLimit=
+void 0)};function nt(a,b){var c=a.i,d=c.fillStyle,e=c.strokeStyle,f=c.lineCap,g=c.lineDash,h=c.lineDashOffset,l=c.lineJoin,m=c.lineWidth,n=c.miterLimit;if(void 0!==d&&("string"!==typeof d||c.oh!=d)){var p=[9,d];"string"!==typeof d&&(b=b.G(),p.push([b[0],b[3]]));a.a.push(p);c.oh=c.fillStyle}void 0===e||c.Md==e&&c.Gd==f&&pa(c.Hd,g)&&c.Id==h&&c.Jd==l&&c.Kd==m&&c.Ld==n||(a.a.push([10,e,m,f,l,n,g,h,!0,1]),c.Md=e,c.Gd=f,c.Hd=g,c.Id=h,c.Jd=l,c.Kd=m,c.Ld=n)};function ot(a,b,c,d){bt.call(this,a,b,c,d);this.C=this.D=this.S=null;this.Ia="";this.o=this.j=0;this.l=void 0;this.u=this.v=0;this.g=this.f=this.i=null}v(ot,bt);
+ot.prototype.yc=function(a,b,c,d,e,f){if(""!==this.Ia&&this.g&&(this.i||this.f)){if(this.i){e=this.i;var g=this.S;if(!g||g.fillStyle!=e.fillStyle){var h=[9,e.fillStyle];this.a.push(h);this.b.push(h);g?g.fillStyle=e.fillStyle:this.S={fillStyle:e.fillStyle}}}this.f&&(e=this.f,g=this.D,g&&g.lineCap==e.lineCap&&g.lineDash==e.lineDash&&g.lineDashOffset==e.lineDashOffset&&g.lineJoin==e.lineJoin&&g.lineWidth==e.lineWidth&&g.miterLimit==e.miterLimit&&g.strokeStyle==e.strokeStyle||(h=[10,e.strokeStyle,e.lineWidth,
+e.lineCap,e.lineJoin,e.miterLimit,e.lineDash,e.lineDashOffset,!1,1],this.a.push(h),this.b.push(h),g?(g.lineCap=e.lineCap,g.lineDash=e.lineDash,g.lineDashOffset=e.lineDashOffset,g.lineJoin=e.lineJoin,g.lineWidth=e.lineWidth,g.miterLimit=e.miterLimit,g.strokeStyle=e.strokeStyle):this.D={lineCap:e.lineCap,lineDash:e.lineDash,lineDashOffset:e.lineDashOffset,lineJoin:e.lineJoin,lineWidth:e.lineWidth,miterLimit:e.miterLimit,strokeStyle:e.strokeStyle}));e=this.g;g=this.C;g&&g.font==e.font&&g.textAlign==
+e.textAlign&&g.textBaseline==e.textBaseline||(h=[11,e.font,e.textAlign,e.textBaseline],this.a.push(h),this.b.push(h),g?(g.font=e.font,g.textAlign=e.textAlign,g.textBaseline=e.textBaseline):this.C={font:e.font,textAlign:e.textAlign,textBaseline:e.textBaseline});dt(this,f);e=this.coordinates.length;a=ct(this,a,b,c,d,!1,!1);a=[5,e,a,this.Ia,this.j,this.o,this.v,this.u,!!this.i,!!this.f,this.l];this.a.push(a);this.b.push(a);gt(this,f)}};
+ot.prototype.Cb=function(a){if(a){var b=a.Fa();b?(b=b.b,b=id(b?b:Sh),this.i?this.i.fillStyle=b:this.i={fillStyle:b}):this.i=null;var c=a.Ga();if(c){var b=c.a,d=c.f,e=c.i,f=c.g,g=c.j,h=c.c,c=c.o,d=void 0!==d?d:"round",e=e?e.slice():Th,f=void 0!==f?f:0,g=void 0!==g?g:"round",h=void 0!==h?h:1,c=void 0!==c?c:10,b=id(b?b:Uh);if(this.f){var l=this.f;l.lineCap=d;l.lineDash=e;l.lineDashOffset=f;l.lineJoin=g;l.lineWidth=h;l.miterLimit=c;l.strokeStyle=b}else this.f={lineCap:d,lineDash:e,lineDashOffset:f,lineJoin:g,
+lineWidth:h,miterLimit:c,strokeStyle:b}}else this.f=null;var m=a.a,b=a.i,d=a.c,e=a.o,h=a.f,c=a.b,f=a.Na(),g=a.g,l=a.j;a=void 0!==m?m:"10px sans-serif";g=void 0!==g?g:"center";l=void 0!==l?l:"middle";this.g?(m=this.g,m.font=a,m.textAlign=g,m.textBaseline=l):this.g={font:a,textAlign:g,textBaseline:l};this.Ia=void 0!==f?f:"";this.j=void 0!==b?b:0;this.o=void 0!==d?d:0;this.l=void 0!==e?e:!1;this.v=void 0!==h?h:0;this.u=void 0!==c?c:1}else this.Ia=""};function pt(a,b,c,d,e){this.v=a;this.c=b;this.o=d;this.l=c;this.f=e;this.a={};this.g=jd(1,1);this.j=Bh()}v(pt,ki);var qt={0:[[!0]]};function rt(a,b,c){var d,e=Math.floor(a.length/2);if(b>=e)for(d=e;d<b;d++)a[d][c]=!0;else if(b<e)for(d=b+1;d<e;d++)a[d][c]=!0}
+function st(a){if(void 0!==qt[a])return qt[a];for(var b=2*a+1,c=Array(b),d=0;d<b;d++)c[d]=Array(b);for(var b=a,e=d=0;b>=d;)rt(c,a+b,a+d),rt(c,a+d,a+b),rt(c,a-d,a+b),rt(c,a-b,a+d),rt(c,a-b,a-d),rt(c,a-d,a-b),rt(c,a+d,a-b),rt(c,a+b,a-d),d++,e+=1+2*d,0<2*(e-b)+1&&(--b,e+=1-2*b);return qt[a]=c}function tt(a){for(var b in a.a){var c=a.a[b],d;for(d in c)c[d].Te()}}
+pt.prototype.Ea=function(a,b,c,d,e,f){d=Math.round(d);var g=2*d+1,h=Kh(this.j,d+.5,d+.5,1/b,-1/b,-c,-a[0],-a[1]),l=this.g;l.canvas.width!==g||l.canvas.height!==g?(l.canvas.width=g,l.canvas.height=g):l.clearRect(0,0,g,g);if(void 0!==this.f){var m=Oa();Pa(m,a);Qa(m,b*(this.f+d),m)}var n=st(d);return ut(this,l,h,c,e,function(a){for(var b=l.getImageData(0,0,g,g).data,c=0;c<g;c++)for(var d=0;d<g;d++)if(n[c][d]&&0<b[4*(d*g+c)+3]){if(a=f(a))return a;l.clearRect(0,0,g,g);return}},m)};
+function vt(a,b){var c=a.c;a=c[0];var d=c[1],e=c[2],c=c[3];a=[a,d,a,c,e,c,e,d];pf(a,0,8,2,b,a);return a}pt.prototype.b=function(a,b){var c=void 0!==a?a.toString():"0";a=this.a[c];void 0===a&&(a={},this.a[c]=a);c=a[b];void 0===c&&(c=new wt[b](this.v,this.c,this.l,this.o),a[b]=c);return c};pt.prototype.i=function(){return wb(this.a)};
+pt.prototype.La=function(a,b,c,d,e,f){var g=Object.keys(this.a).map(Number);g.sort(ia);var h=vt(this,c);a.save();a.beginPath();a.moveTo(h[0],h[1]);a.lineTo(h[2],h[3]);a.lineTo(h[4],h[5]);a.lineTo(h[6],h[7]);a.clip();f=f?f:ji;var l,m,h=0;for(l=g.length;h<l;++h){var n=this.a[g[h].toString()];var p=0;for(m=f.length;p<m;++p){var q=n[f[p]];void 0!==q&&q.La(a,b,c,d,e)}}a.restore()};
+function ut(a,b,c,d,e,f,g){var h=Object.keys(a.a).map(Number);h.sort(function(a,b){return b-a});var l,m;var n=0;for(l=h.length;n<l;++n){var p=a.a[h[n].toString()];for(m=ji.length-1;0<=m;--m){var q=p[ji[m]];if(void 0!==q&&(q=et(q,b,1,c,d,e,q.b,f,g)))return q}}}var wt={Circle:lt,Image:ht,LineString:it,Polygon:lt,Text:ot};function xt(a){Sc.call(this);this.a=a}v(xt,Sc);xt.prototype.Ea=ua;xt.prototype.Ue=nf;xt.prototype.Nf=function(a,b,c){return function(d,e){return yt(a,b,d,e,function(a){c[d]||(c[d]={});c[d][a.ta.toString()]=a})}};xt.prototype.na=function(a){2===a.target.getState()&&zt(this)};function At(a,b){var c=b.getState();2!=c&&3!=c&&y(b,"change",a.na,a);0==c&&(b.load(),c=b.getState());return 2==c}function zt(a){var b=a.a;b.Mb()&&"ready"==b.$f()&&a.s()}
+function Bt(a,b){b.Ki()&&a.postRenderFunctions.push(function(a,b,e){b=w(a).toString();a.fd(e.viewState.projection,e.usedTiles[b])}.bind(null,b))}function Ct(a,b){if(b){var c;var d=0;for(c=b.length;d<c;++d){var e=b[d];a[w(e).toString()]=e}}}function Dt(a,b){b=b.D;void 0!==b&&("string"===typeof b?a.logos[b]="":b&&(xa("string"==typeof b.href,44),xa("string"==typeof b.src,45),a.logos[b.src]=b.href))}
+function Et(a,b,c,d){b=w(b).toString();c=c.toString();b in a?c in a[b]?(a=a[b][c],d.ca<a.ca&&(a.ca=d.ca),d.$>a.$&&(a.$=d.$),d.da<a.da&&(a.da=d.da),d.ia>a.ia&&(a.ia=d.ia)):a[b][c]=d:(a[b]={},a[b][c]=d)}
+function Ft(a,b,c,d,e,f,g,h,l,m){var n=w(b).toString();n in a.wantedTiles||(a.wantedTiles[n]={});var p=a.wantedTiles[n];a=a.tileQueue;var q=c.minZoom,r,u,x;for(x=g;x>=q;--x){var B=oc(c,f,x,B);var E=c.Da(x);for(r=B.ca;r<=B.$;++r)for(u=B.da;u<=B.ia;++u)if(g-x<=h){var A=b.Nc(x,r,u,d,e);0==A.getState()&&(p[A.bb()]=!0,A.bb()in a.a||a.f([A,n,tc(c,A.ta),E]));l&&l.call(m,A)}else b.Ug(x,r,u,e)}};function Gt(a){xt.call(this,a);this.fa=Bh()}v(Gt,xt);function Ht(a,b,c){var d=b.pixelRatio,e=b.size[0]*d,f=b.size[1]*d,g=b.viewState.rotation,h=ib(c),l=hb(c),m=gb(c);c=eb(c);Gh(b.coordinateToPixelTransform,h);Gh(b.coordinateToPixelTransform,l);Gh(b.coordinateToPixelTransform,m);Gh(b.coordinateToPixelTransform,c);a.save();Vh(a,-g,e/2,f/2);a.beginPath();a.moveTo(h[0]*d,h[1]*d);a.lineTo(l[0]*d,l[1]*d);a.lineTo(m[0]*d,m[1]*d);a.lineTo(c[0]*d,c[1]*d);a.clip();Vh(a,g,e/2,f/2)}
+function It(a,b,c,d,e){var f=a.a;if(Rc(f,b)){var g=d.size[0]*d.pixelRatio,h=d.size[1]*d.pixelRatio,l=d.viewState.rotation;Vh(c,-l,g/2,h/2);a=e?e:Jt(a,d,0);f.b(new Rh(b,new Xh(c,d.pixelRatio,d.extent,a,d.viewState.rotation),d,c,null));Vh(c,l,g/2,h/2)}}Gt.prototype.u=function(a,b,c,d){if(this.Ea(a,b,0,mf,this))return c.call(d,this.a,null)};Gt.prototype.ef=function(a,b,c,d){It(this,"postcompose",a,b,d)};
+function Jt(a,b,c){var d=b.viewState,e=b.pixelRatio,f=e/d.resolution;return Kh(a.fa,e*b.size[0]/2,e*b.size[1]/2,f,-f,-d.rotation,-d.center[0]+c,-d.center[1])};function Kt(a,b){return w(a)-w(b)}function Lt(a,b){a=.5*a/b;return a*a}function Mt(a,b,c,d,e,f){var g=!1,h;if(h=c.Y()){var l=h.Ye();2==l||3==l?h.Bj(e,f):(0==l&&h.load(),h.Nh(e,f),g=!0)}if(e=(0,c.Za)(b))d=e.Vd(d),(0,Nt[d.U()])(a,d,c,b);return g}
+var Nt={Point:function(a,b,c,d){var e=c.Y();if(e){if(2!=e.Ye())return;var f=a.b(c.Ba(),"Image");f.Ub(e);f.qc(b,d)}if(e=c.Na())a=a.b(c.Ba(),"Text"),a.Cb(e),a.yc(b.ga(),0,2,2,b,d)},LineString:function(a,b,c,d){var e=c.Ga();if(e){var f=a.b(c.Ba(),"LineString");f.Ma(null,e);f.mc(b,d)}if(e=c.Na())a=a.b(c.Ba(),"Text"),a.Cb(e),a.yc(di(b),0,2,2,b,d)},Polygon:function(a,b,c,d){var e=c.Fa(),f=c.Ga();if(e||f){var g=a.b(c.Ba(),"Polygon");g.Ma(e,f);g.rc(b,d)}if(e=c.Na())a=a.b(c.Ba(),"Text"),a.Cb(e),a.yc(Wf(b),
+0,2,2,b,d)},MultiPoint:function(a,b,c,d){var e=c.Y();if(e){if(2!=e.Ye())return;var f=a.b(c.Ba(),"Image");f.Ub(e);f.oc(b,d)}if(e=c.Na())a=a.b(c.Ba(),"Text"),a.Cb(e),c=b.ga(),a.yc(c,0,c.length,b.qa(),b,d)},MultiLineString:function(a,b,c,d){var e=c.Ga();if(e){var f=a.b(c.Ba(),"LineString");f.Ma(null,e);f.nc(b,d)}if(e=c.Na())a=a.b(c.Ba(),"Text"),a.Cb(e),c=ei(b),a.yc(c,0,c.length,2,b,d)},MultiPolygon:function(a,b,c,d){var e=c.Fa(),f=c.Ga();if(f||e){var g=a.b(c.Ba(),"Polygon");g.Ma(e,f);g.pc(b,d)}if(e=
+c.Na())a=a.b(c.Ba(),"Text"),a.Cb(e),c=gi(b),a.yc(c,0,c.length,2,b,d)},GeometryCollection:function(a,b,c,d){b=b.a;var e;var f=0;for(e=b.length;f<e;++f)(0,Nt[b[f].U()])(a,b[f],c,d)},Circle:function(a,b,c,d){var e=c.Fa(),f=c.Ga();if(e||f){var g=a.b(c.Ba(),"Circle");g.Ma(e,f);g.Zb(b,d)}if(e=c.Na())a=a.b(c.Ba(),"Text"),a.Cb(e),a.yc(b.wa(),0,2,2,b,d)}};function Ot(a){Gt.call(this,a);this.c=!1;this.v=-1;this.l=NaN;this.j=Oa();this.f=this.o=null;this.g=jd()}v(Ot,Gt);
+Ot.prototype.S=function(a,b,c){var d=a.extent,e=a.pixelRatio,f=b.Je?a.skippedFeatureUids:{},g=a.viewState,h=g.projection,g=g.rotation,l=h.G(),m=this.a.ha(),n=Jt(this,a,0);It(this,"precompose",c,a,n);var p=b.extent,q=void 0!==p;q&&Ht(c,a,p);if((p=this.f)&&!p.i()){var r=0,u=0;if(Rc(this.a,"render")){var x=c.canvas.width;var B=c.canvas.height;if(g){var E=Math.round(Math.sqrt(x*x+B*B)),r=(E-x)/2,u=(E-B)/2;x=B=E}this.g.canvas.width=x;this.g.canvas.height=B;x=this.g}else x=c;B=x.globalAlpha;x.globalAlpha=
+b.opacity;x!=c&&x.translate(r,u);var E=a.size[0]*e,A=a.size[1]*e;Vh(x,-g,E/2,A/2);p.La(x,e,n,g,f);if(m.u&&h.i&&!Va(l,d)){for(var h=d[0],m=lb(l),L=0;h<l[0];)--L,n=m*L,n=Jt(this,a,n),p.La(x,e,n,g,f),h+=m;L=0;for(h=d[2];h>l[2];)++L,n=m*L,n=Jt(this,a,n),p.La(x,e,n,g,f),h-=m;n=Jt(this,a,0)}Vh(x,g,E/2,A/2);x!=c&&(It(this,"render",x,a,n),c.drawImage(x.canvas,-r,-u),x.translate(-r,-u));x.globalAlpha=B}q&&c.restore();this.ef(c,a,b,n)};
+Ot.prototype.Ea=function(a,b,c,d,e){if(this.f){var f=this.a,g={};return this.f.Ea(a,b.viewState.resolution,b.viewState.rotation,c,{},function(a){var b=w(a).toString();if(!(b in g))return g[b]=!0,d.call(e,a,f)})}};Ot.prototype.D=function(){zt(this)};
+Ot.prototype.sd=function(a){function b(a){var b=a.Lc();if(b)var d=b.call(a,m);else(b=c.f)&&(d=b(a,m));if(d){if(d){b=!1;if(Array.isArray(d))for(var e=0,f=d.length;e<f;++e)b=Mt(q,a,d[e],Lt(m,n),this.D,this)||b;else b=Mt(q,a,d,Lt(m,n),this.D,this)||b;a=b}else a=!1;this.c=this.c||a}}var c=this.a,d=c.ha();Ct(a.attributions,d.j);Dt(a,d);var e=a.viewHints[0],f=a.viewHints[1],g=c.T,h=c.na;if(!this.c&&!g&&e||!h&&f)return!0;var l=a.extent,h=a.viewState,e=h.projection,m=h.resolution,n=a.pixelRatio,f=c.i,p=c.c,
+g=c.get(Pt);void 0===g&&(g=Kt);l=Qa(l,p*m);p=h.projection.G();d.u&&h.projection.i&&!Va(p,a.extent)&&(a=Math.max(lb(l)/2,lb(p)),l[0]=p[0]-a,l[2]=p[2]+a);if(!this.c&&this.l==m&&this.v==f&&this.o==g&&Va(this.j,l))return!0;this.f=null;this.c=!1;var q=new pt(.5*m/n,l,m,d.T,c.c);d.Yd(l,m,e);if(g){var r=[];d.$b(l,function(a){r.push(a)},this);r.sort(g);r.forEach(b,this)}else d.$b(l,b,this);tt(q);this.l=m;this.v=f;this.o=g;this.j=l;this.f=q;return!0};function Qt(){this.b="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;}"}v(Qt,mi);var Rt=new Qt;function St(){this.b="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;}"}v(St,ni);var Tt=new St;
+function Ut(a,b){this.i=a.getUniformLocation(b,"f");this.c=a.getUniformLocation(b,"e");this.g=a.getUniformLocation(b,"d");this.f=a.getUniformLocation(b,"g");this.b=a.getAttribLocation(b,"b");this.a=a.getAttribLocation(b,"c")};function Vt(a,b){xt.call(this,b);this.c=a;this.T=new Di([-1,-1,0,0,1,-1,1,0,-1,1,0,1,1,1,1,1]);this.g=this.Ib=null;this.j=void 0;this.v=Bh();this.S=Bh();this.C=ti();this.u=null}v(Vt,xt);
+function Wt(a,b,c){var d=a.c.i;if(void 0===a.j||a.j!=c){b.postRenderFunctions.push(function(a,b,c){a.isContextLost()||(a.deleteFramebuffer(b),a.deleteTexture(c))}.bind(null,d,a.g,a.Ib));b=Qi(d,c,c);var e=d.createFramebuffer();d.bindFramebuffer(36160,e);d.framebufferTexture2D(36160,36064,3553,b,0);a.Ib=b;a.g=e;a.j=c}else d.bindFramebuffer(36160,a.g)}
+Vt.prototype.Gi=function(a,b,c){Xt(this,"precompose",c,a);wi(c,34962,this.T);var d=c.b,e=Hi(c,Rt,Tt);if(this.u)var f=this.u;else this.u=f=new Ut(d,e);c.Qc(e)&&(d.enableVertexAttribArray(f.b),d.vertexAttribPointer(f.b,2,5126,!1,16,0),d.enableVertexAttribArray(f.a),d.vertexAttribPointer(f.a,2,5126,!1,16,8),d.uniform1i(f.f,0));d.uniformMatrix4fv(f.g,!1,ui(this.C,this.v));d.uniformMatrix4fv(f.c,!1,ui(this.C,this.S));d.uniform1f(f.i,b.opacity);d.bindTexture(3553,this.Ib);d.drawArrays(5,0,4);Xt(this,"postcompose",
+c,a)};function Xt(a,b,c,d){a=a.a;if(Rc(a,b)){var e=d.viewState;a.b(new Rh(b,new kk(c,e.center,e.resolution,e.rotation,d.size,d.extent,d.pixelRatio),d,null,c))}}Vt.prototype.mg=function(){this.g=this.Ib=null;this.j=void 0};function Yt(a,b){Vt.call(this,a,b);this.l=!1;this.R=-1;this.I=NaN;this.D=Oa();this.o=this.f=this.B=null}v(Yt,Vt);k=Yt.prototype;k.Gi=function(a,b,c){this.o=b;var d=a.viewState,e=this.f,f=a.size,g=a.pixelRatio,h=this.c.i;e&&!e.i()&&(h.enable(h.SCISSOR_TEST),h.scissor(0,0,f[0]*g,f[1]*g),e.La(c,d.center,d.resolution,d.rotation,f,g,b.opacity,b.Je?a.skippedFeatureUids:{}),h.disable(h.SCISSOR_TEST))};k.ka=function(){var a=this.f;a&&(ek(a,this.c.f)(),this.f=null);Vt.prototype.ka.call(this)};
+k.Ea=function(a,b,c,d,e){if(this.f&&this.o){c=b.viewState;var f=this.a,g={};return this.f.Ea(a,this.c.f,c.center,c.resolution,c.rotation,b.size,b.pixelRatio,this.o.opacity,{},function(a){var b=w(a).toString();if(!(b in g))return g[b]=!0,d.call(e,a,f)})}};k.Ue=function(a,b){if(this.f&&this.o){var c=b.viewState;return jk(this.f,a,this.c.f,c.resolution,c.rotation,b.pixelRatio,this.o.opacity,b.skippedFeatureUids)}return!1};
+k.lg=function(a,b,c,d){a=Gh(b.pixelToCoordinateTransform,a.slice());if(this.Ue(a,b))return c.call(d,this.a,null)};k.Hi=function(){zt(this)};
+k.ng=function(a,b,c){function d(a){var b=a.Lc();if(b)var c=b.call(a,m);else(b=e.f)&&(c=b(a,m));if(c){if(c){b=!1;if(Array.isArray(c))for(var d=c.length-1;0<=d;--d)b=Mt(q,a,c[d],Lt(m,n),this.Hi,this)||b;else b=Mt(q,a,c,Lt(m,n),this.Hi,this)||b;a=b}else a=!1;this.l=this.l||a}}var e=this.a;b=e.ha();Ct(a.attributions,b.j);Dt(a,b);var f=a.viewHints[0],g=a.viewHints[1],h=e.T,l=e.na;if(!this.l&&!h&&f||!l&&g)return!0;var g=a.extent,h=a.viewState,f=h.projection,m=h.resolution,n=a.pixelRatio,h=e.i,p=e.c,l=e.get(Pt);
+void 0===l&&(l=Kt);g=Qa(g,p*m);if(!this.l&&this.I==m&&this.R==h&&this.B==l&&Va(this.D,g))return!0;this.f&&a.postRenderFunctions.push(ek(this.f,c));this.l=!1;var q=new dk(.5*m/n,g,e.c);b.Yd(g,m,f);if(l){var r=[];b.$b(g,function(a){r.push(a)},this);r.sort(l);r.forEach(d,this)}else b.$b(g,d,this);fk(q,c);this.I=m;this.R=h;this.B=l;this.D=g;this.f=q;return!0};function T(a){a=a?a:{};var b=tb({},a);delete b.style;delete b.renderBuffer;delete b.updateWhileAnimating;delete b.updateWhileInteracting;wh.call(this,b);this.c=void 0!==a.renderBuffer?a.renderBuffer:100;this.u=null;this.f=void 0;this.g(a.style);this.T=void 0!==a.updateWhileAnimating?a.updateWhileAnimating:!1;this.na=void 0!==a.updateWhileInteracting?a.updateWhileInteracting:!1}v(T,wh);T.prototype.Fd=function(a){var b=null,c=a.U();"canvas"===c?b=new Ot(this):"webgl"===c&&(b=new Yt(a,this));return b};
+T.prototype.D=function(){return this.u};T.prototype.C=function(){return this.f};T.prototype.g=function(a){this.u=void 0!==a?a:fl;this.f=null===a?void 0:dl(this.u);this.s()};var Pt="renderOrder";function Zt(){return[[-Infinity,-Infinity,Infinity,Infinity]]};function $t(a){Tc.call(this);this.c=Tb(a.projection);this.j=au(a.attributions);this.D=a.logo;this.na=void 0!==a.state?a.state:"ready";this.u=void 0!==a.wrapX?a.wrapX:!1}v($t,Tc);function au(a){if("string"===typeof a)return[new Ac({html:a})];if(a instanceof Ac)return[a];if(Array.isArray(a)){for(var b=a.length,c=Array(b),d=0;d<b;d++){var e=a[d];c[d]="string"===typeof e?new Ac({html:e}):e}return c}return null}k=$t.prototype;k.Ea=ua;k.ya=function(){return this.j};k.xa=function(){return this.D};k.za=function(){return this.c};
+k.getState=function(){return this.na};k.sa=function(){this.s()};k.ua=function(a){this.j=au(a);this.s()};function bu(a,b){a.na=b;a.s()};function U(a){a=a||{};$t.call(this,{attributions:a.attributions,logo:a.logo,projection:void 0,state:"ready",wrapX:void 0!==a.wrapX?a.wrapX:!0});this.B=ua;this.C=a.format;this.T=void 0==a.overlaps?!0:a.overlaps;this.I=a.url;a.loader?this.B=a.loader:void 0!==this.I&&(xa(this.C,7),this.B=Dl(this.I,this.C));this.fa=a.strategy?a.strategy:Zt;var b=void 0!==a.useSpatialIndex?a.useSpatialIndex:!0;this.a=b?new Gj:null;this.R=new Gj;this.g={};this.o={};this.l={};this.v={};this.f=null;if(a.features instanceof
+Yc){var c=a.features;var d=c.a}else Array.isArray(a.features)&&(d=a.features);b||c||(c=new Yc(d));d&&cu(this,d);c&&du(this,c)}v(U,$t);k=U.prototype;k.yb=function(a){var b=w(a).toString();if(eu(this,b,a)){fu(this,b,a);var c=a.V();c?(b=c.G(),this.a&&this.a.Ca(b,a)):this.g[b]=a;this.b(new gu("addfeature",a))}this.s()};function fu(a,b,c){a.v[b]=[y(c,"change",a.Oi,a),y(c,"propertychange",a.Oi,a)]}
+function eu(a,b,c){var d=!0,e=c.a;void 0!==e?e.toString()in a.o?d=!1:a.o[e.toString()]=c:(xa(!(b in a.l),30),a.l[b]=c);return d}k.cd=function(a){cu(this,a);this.s()};function cu(a,b){var c,d=[],e=[],f=[];var g=0;for(c=b.length;g<c;g++){var h=b[g];var l=w(h).toString();eu(a,l,h)&&e.push(h)}g=0;for(c=e.length;g<c;g++)h=e[g],l=w(h).toString(),fu(a,l,h),(b=h.V())?(l=b.G(),d.push(l),f.push(h)):a.g[l]=h;a.a&&a.a.load(d,f);g=0;for(c=e.length;g<c;g++)a.b(new gu("addfeature",e[g]))}
+function du(a,b){var c=!1;y(a,"addfeature",function(a){c||(c=!0,b.push(a.feature),c=!1)});y(a,"removefeature",function(a){c||(c=!0,b.remove(a.feature),c=!1)});y(b,"add",function(a){c||(c=!0,this.yb(a.element),c=!1)},a);y(b,"remove",function(a){c||(c=!0,this.Gb(a.element),c=!1)},a);a.f=b}
+k.clear=function(a){if(a){for(var b in this.v)this.v[b].forEach(Ec);this.f||(this.v={},this.o={},this.l={})}else if(this.a){this.a.forEach(this.Ig,this);for(var c in this.g)this.Ig(this.g[c])}this.f&&this.f.clear();this.a&&this.a.clear();this.R.clear();this.g={};this.b(new gu("clear"));this.s()};k.sh=function(a,b){if(this.a)return this.a.forEach(a,b);if(this.f)return this.f.forEach(a,b)};function hu(a,b,c){a.$b([b[0],b[1],b[0],b[1]],function(a){if(a.V().sb(b))return c.call(void 0,a)})}
+k.$b=function(a,b,c){if(this.a)return Lj(this.a,a,b,c);if(this.f)return this.f.forEach(b,c)};k.th=function(a,b,c){return this.$b(a,function(d){if(d.V().Xa(a)&&(d=b.call(c,d)))return d})};k.Ah=function(){return this.f};k.Xe=function(){if(this.f)var a=this.f.a;else this.a&&(a=Ij(this.a),wb(this.g)||la(a,vb(this.g)));return a};k.zh=function(a){var b=[];hu(this,a,function(a){b.push(a)});return b};k.Uf=function(a){return Jj(this.a,a)};
+k.vh=function(a,b){var c=a[0],d=a[1],e=null,f=[NaN,NaN],g=Infinity,h=[-Infinity,-Infinity,Infinity,Infinity],l=b?b:mf;Lj(this.a,h,function(a){if(l(a)){var b=a.V(),m=g;g=b.Kb(c,d,f,g);g<m&&(e=a,a=Math.sqrt(g),h[0]=c-a,h[1]=d-a,h[2]=c+a,h[3]=d+a)}});return e};k.G=function(a){return this.a.G(a)};k.yh=function(a){a=this.o[a.toString()];return void 0!==a?a:null};k.Mi=function(){return this.C};k.Ni=function(){return this.I};
+k.Oi=function(a){a=a.target;var b=w(a).toString(),c=a.V();c?(c=c.G(),b in this.g?(delete this.g[b],this.a&&this.a.Ca(c,a)):this.a&&Hj(this.a,c,a)):b in this.g||(this.a&&this.a.remove(a),this.g[b]=a);c=a.a;void 0!==c?(c=c.toString(),b in this.l?(delete this.l[b],this.o[c]=a):this.o[c]!==a&&(iu(this,a),this.o[c]=a)):b in this.l||(iu(this,a),this.l[b]=a);this.s();this.b(new gu("changefeature",a))};
+k.Yd=function(a,b,c){var d=this.R;a=this.fa(a,b);var e;var f=0;for(e=a.length;f<e;++f){var g=a[f];Lj(d,g,function(a){return Va(a.extent,g)})||(this.B.call(this,g,b,c),d.Ca(g,{extent:g.slice()}))}};k.Gb=function(a){var b=w(a).toString();b in this.g?delete this.g[b]:this.a&&this.a.remove(a);this.Ig(a);this.s()};k.Ig=function(a){var b=w(a).toString();this.v[b].forEach(Ec);delete this.v[b];var c=a.a;void 0!==c?delete this.o[c.toString()]:delete this.l[b];this.b(new gu("removefeature",a))};
+function iu(a,b){for(var c in a.o)if(a.o[c]===b){delete a.o[c];break}}function gu(a,b){Oc.call(this,a);this.feature=b}v(gu,Oc);function ju(a){Dg.call(this,{handleDownEvent:ku,handleEvent:lu,handleUpEvent:mu});this.T=!1;this.fa=null;this.u=!1;this.Yb=a.source?a.source:null;this.$a=a.features?a.features:null;this.wk=a.snapTolerance?a.snapTolerance:12;this.R=a.type;this.g=nu(this.R);this.Sa=a.minPoints?a.minPoints:this.g===ou?3:2;this.va=a.maxPoints?a.maxPoints:Infinity;this.Cf=a.finishCondition?a.finishCondition:mf;var b=a.geometryFunction;if(!b)if("Circle"===this.R)b=function(a,b){b=b?b:new ys([NaN,NaN]);b.Ng(a[0],Math.sqrt(hf(a[0],
+a[1])));return b};else{var c,d=this.g;d===pu?c=C:d===qu?c=O:d===ou&&(c=D);b=function(a,b){b?d===ou?b.ma([a[0].concat([a[0][0]])]):b.ma(a):b=new c(a);return b}}this.Za=b;this.I=this.C=this.a=this.B=this.j=this.l=null;this.ad=a.clickTolerance?a.clickTolerance*a.clickTolerance:36;this.pa=new T({source:new U({useSpatialIndex:!1,wrapX:a.wrapX?a.wrapX:!1}),style:a.style?a.style:ru()});this.xb=a.geometryName;this.vk=a.condition?a.condition:xg;this.Df=a.freehand?mf:a.freehandCondition?a.freehandCondition:
+yg;y(this,Vc("active"),this.ri,this)}v(ju,Dg);function ru(){var a=gl();return function(b){return a[b.V().U()]}}k=ju.prototype;k.setMap=function(a){Dg.prototype.setMap.call(this,a);this.ri()};function lu(a){this.u=this.g!==pu&&this.Df(a);var b=!this.u;this.u&&"pointerdrag"===a.type&&null!==this.j?(su(this,a),b=!1):"pointermove"===a.type?b=tu(this,a):"dblclick"===a.type&&(b=!1);return Eg.call(this,a)&&b}
+function ku(a){this.T=!this.u;return this.u?(this.fa=a.pixel,this.l||uu(this,a),!0):this.vk(a)?(this.fa=a.pixel,!0):!1}function mu(a){var b=!0;tu(this,a);var c=this.g===vu;this.T?(this.l?this.u||c?this.Pd():wu(this,a)?this.Cf(a)&&this.Pd():su(this,a):(uu(this,a),this.g===pu&&this.Pd()),b=!1):this.u&&(this.l=null,xu(this));return b}
+function tu(a,b){if(a.fa&&(!a.u&&a.T||a.u&&!a.T)){var c=a.fa,d=b.pixel,e=c[0]-d[0],c=c[1]-d[1],e=e*e+c*c;a.T=a.u?e>a.ad:e<=a.ad}a.l?(e=b.coordinate,c=a.j.V(),a.g===pu?d=a.a:a.g===ou?(d=a.a[0],d=d[d.length-1],wu(a,b)&&(e=a.l.slice())):(d=a.a,d=d[d.length-1]),d[0]=e[0],d[1]=e[1],a.Za(a.a,c),a.B&&a.B.V().ma(e),c instanceof D&&a.g!==ou?(a.C||(a.C=new H(new O(null))),e=c.Ch(0),b=a.C.V(),b.ba(e.ja,e.ga())):a.I&&(b=a.C.V(),b.ma(a.I)),yu(a)):(b=b.coordinate.slice(),a.B?a.B.V().ma(b):(a.B=new H(new C(b)),
+yu(a)));return!0}function wu(a,b){var c=!1;if(a.j){var d=!1,e=[a.l];a.g===qu?d=a.a.length>a.Sa:a.g===ou&&(d=a.a[0].length>a.Sa,e=[a.a[0][0],a.a[0][a.a[0].length-2]]);if(d)for(var d=b.map,f=0,g=e.length;f<g;f++){var h=e[f],l=d.Ja(h),m=b.pixel,c=m[0]-l[0],l=m[1]-l[1];if(c=Math.sqrt(c*c+l*l)<=(a.u?1:a.wk)){a.l=h;break}}}return c}
+function uu(a,b){b=b.coordinate;a.l=b;a.g===pu?a.a=b.slice():a.g===ou?(a.a=[[b.slice(),b.slice()]],a.I=a.a[0]):(a.a=[b.slice(),b.slice()],a.g===vu&&(a.I=a.a));a.I&&(a.C=new H(new O(a.I)));b=a.Za(a.a);a.j=new H;a.xb&&a.j.Tc(a.xb);a.j.Ra(b);yu(a);a.b(new zu("drawstart",a.j))}
+function su(a,b){b=b.coordinate;var c=a.j.V(),d;if(a.g===qu){a.l=b.slice();var e=a.a;e.length>=a.va&&(a.u?e.pop():d=!0);e.push(b.slice());a.Za(e,c)}else a.g===ou&&(e=a.a[0],e.length>=a.va&&(a.u?e.pop():d=!0),e.push(b.slice()),d&&(a.l=e[0]),a.Za(a.a,c));yu(a);d&&a.Pd()}
+k.Op=function(){if(this.j){var a=this.j.V();if(this.g===qu){var b=this.a;b.splice(-2,1);this.Za(b,a);2<=b.length&&(this.l=b[b.length-2].slice())}else if(this.g===ou){b=this.a[0];b.splice(-2,1);var c=this.C.V();c.ma(b);this.Za(this.a,a)}0===b.length&&(this.l=null);yu(this)}};
+k.Pd=function(){var a=xu(this),b=this.a,c=a.V();this.g===qu?(b.pop(),this.Za(b,c)):this.g===ou&&(b[0].pop(),this.Za(b,c),b=c.X());"MultiPoint"===this.R?a.Ra(new Q([b])):"MultiLineString"===this.R?a.Ra(new P([b])):"MultiPolygon"===this.R&&a.Ra(new R([b]));this.b(new zu("drawend",a));this.$a&&this.$a.push(a);this.Yb&&this.Yb.yb(a)};function xu(a){a.l=null;var b=a.j;b&&(a.j=null,a.B=null,a.C=null,a.pa.ha().clear(!0));return b}
+k.vn=function(a){var b=a.V();this.j=a;this.a=b.X();a=this.a[this.a.length-1];this.l=a.slice();this.a.push(a.slice());yu(this);this.b(new zu("drawstart",this.j))};k.Xc=nf;function yu(a){var b=[];a.j&&b.push(a.j);a.C&&b.push(a.C);a.B&&b.push(a.B);a=a.pa.ha();a.clear(!0);a.cd(b)}k.ri=function(){var a=this.v,b=this.c();a&&b||xu(this);this.pa.setMap(b?a:null)};
+function nu(a){var b;"Point"===a||"MultiPoint"===a?b=pu:"LineString"===a||"MultiLineString"===a?b=qu:"Polygon"===a||"MultiPolygon"===a?b=ou:"Circle"===a&&(b=vu);return b}var pu="Point",qu="LineString",ou="Polygon",vu="Circle";function zu(a,b){Oc.call(this,a);this.feature=b}v(zu,Oc);function Au(a){this.a=this.j=null;this.C=!1;this.B=this.l=null;a||(a={});a.extent&&this.g(a.extent);Dg.call(this,{handleDownEvent:Bu,handleDragEvent:Cu,handleEvent:Du,handleUpEvent:Eu});this.u=new T({source:new U({useSpatialIndex:!1,wrapX:!!a.wrapX}),style:a.boxStyle?a.boxStyle:Fu(),updateWhileAnimating:!0,updateWhileInteracting:!0});this.I=new T({source:new U({useSpatialIndex:!1,wrapX:!!a.wrapX}),style:a.pointerStyle?a.pointerStyle:Gu(),updateWhileAnimating:!0,updateWhileInteracting:!0})}v(Au,Dg);
+function Du(a){if(!(a instanceof ee))return!0;if("pointermove"==a.type&&!this.D){var b=a.pixel,c=a.map,d=Hu(this,b,c);d||(d=c.Wa(b));Iu(this,d)}Eg.call(this,a);return!1}
+function Bu(a){function b(a){var b=null,c=null;a[0]==e[0]?b=e[2]:a[0]==e[2]&&(b=e[0]);a[1]==e[1]?c=e[3]:a[1]==e[3]&&(c=e[1]);return null!==b&&null!==c?[b,c]:null}var c=a.pixel,d=a.map,e=this.G();(a=Hu(this,c,d))&&e?(c=a[0]==e[0]||a[0]==e[2]?a[0]:null,d=a[1]==e[1]||a[1]==e[3]?a[1]:null,null!==c&&null!==d?this.a=Ju(b(a)):null!==c?this.a=Ku(b([c,e[1]]),b([c,e[3]])):null!==d&&(this.a=Ku(b([e[0],d]),b([e[2],d])))):(a=d.Wa(c),this.g([a[0],a[1],a[0],a[1]]),this.a=Ju(a));return!0}
+function Cu(a){this.a&&(a=a.coordinate,this.g(this.a(a)),Iu(this,a));return!0}function Eu(){this.a=null;var a=this.G();a&&jb(a)||this.g(null);return!1}function Fu(){var a=gl();return function(){return a.Polygon}}function Gu(){var a=gl();return function(){return a.Point}}function Ju(a){return function(b){return Na([a,b])}}function Ku(a,b){return a[0]==b[0]?function(c){return Na([a,[c[0],b[1]]])}:a[1]==b[1]?function(c){return Na([a,[b[0],c[1]]])}:null}
+function Hu(a,b,c){function d(a,b){return kf(e,a)-kf(e,b)}var e=c.Wa(b),f=a.G();if(f){f=[[[f[0],f[1]],[f[0],f[3]]],[[f[0],f[3]],[f[2],f[3]]],[[f[2],f[3]],[f[2],f[1]]],[[f[2],f[1]],[f[0],f[1]]]];f.sort(d);var f=f[0],g=af(e,f),h=c.Ja(g);if(10>=jf(b,h))return b=c.Ja(f[0]),c=c.Ja(f[1]),b=hf(h,b),c=hf(h,c),a.C=10>=Math.sqrt(Math.min(b,c)),a.C&&(g=b>c?f[1]:f[0]),g}return null}function Iu(a,b){var c=a.B;c?c.V().ma(b):(c=new H(new C(b)),a.B=c,a.I.ha().yb(c))}
+Au.prototype.setMap=function(a){this.u.setMap(a);this.I.setMap(a);Dg.prototype.setMap.call(this,a)};Au.prototype.G=function(){return this.j};Au.prototype.g=function(a){this.j=a?a:null;var b=this.l;b?a?b.Ra(Yf(a)):b.Ra(void 0):(this.l=b=a?new H(Yf(a)):new H({}),this.u.ha().yb(b));this.b(new Lu(this.j))};function Lu(a){Oc.call(this,Mu);this.b=a}v(Lu,Oc);var Mu="extentchanged";function Nu(a){Dg.call(this,{handleDownEvent:Ou,handleDragEvent:Pu,handleEvent:Qu,handleUpEvent:Ru});this.ad=a.condition?a.condition:Cg;this.$a=function(a){return xg(a)&&wg(a)};this.xb=a.deleteCondition?a.deleteCondition:this.$a;this.Yb=a.insertVertexCondition?a.insertVertexCondition:mf;this.Sa=this.g=null;this.va=[0,0];this.C=this.I=!1;this.a=new Gj;this.fa=void 0!==a.pixelTolerance?a.pixelTolerance:10;this.l=this.pa=!1;this.j=[];this.B=new T({source:new U({useSpatialIndex:!1,wrapX:!!a.wrapX}),style:a.style?
+a.style:Su(),updateWhileAnimating:!0,updateWhileInteracting:!0});this.T={Point:this.Dn,LineString:this.ti,LinearRing:this.ti,Polygon:this.En,MultiPoint:this.Bn,MultiLineString:this.An,MultiPolygon:this.Cn,Circle:this.yn,GeometryCollection:this.zn};this.u=a.features;this.u.forEach(this.kg,this);y(this.u,"add",this.wn,this);y(this.u,"remove",this.xn,this);this.R=null}v(Nu,Dg);k=Nu.prototype;
+k.kg=function(a){var b=a.V();b&&b.U()in this.T&&this.T[b.U()].call(this,a,b);(b=this.v)&&b.c&&this.c()&&Tu(this,this.va,b);y(a,"change",this.si,this)};function Uu(a,b){a.C||(a.C=!0,a.b(new Vu("modifystart",a.u,b)))}function Wu(a,b){Xu(a,b);a.g&&!a.u.dc()&&(a.B.ha().Gb(a.g),a.g=null);Kc(b,"change",a.si,a)}function Xu(a,b){a=a.a;var c=[];a.forEach(function(a){b===a.feature&&c.push(a)});for(var d=c.length-1;0<=d;--d)a.remove(c[d])}
+k.Ha=function(a){this.g&&!a&&(this.B.ha().Gb(this.g),this.g=null);Dg.prototype.Ha.call(this,a)};k.setMap=function(a){this.B.setMap(a);Dg.prototype.setMap.call(this,a)};k.wn=function(a){this.kg(a.element)};k.si=function(a){this.l||(a=a.target,Wu(this,a),this.kg(a))};k.xn=function(a){Wu(this,a.element)};k.Dn=function(a,b){var c=b.X();a={feature:a,geometry:b,la:[c,c]};this.a.Ca(b.G(),a)};
+k.Bn=function(a,b){var c=b.X(),d;var e=0;for(d=c.length;e<d;++e){var f=c[e];f={feature:a,geometry:b,depth:[e],index:e,la:[f,f]};this.a.Ca(b.G(),f)}};k.ti=function(a,b){var c=b.X(),d;var e=0;for(d=c.length-1;e<d;++e){var f=c.slice(e,e+2);var g={feature:a,geometry:b,index:e,la:f};this.a.Ca(Na(f),g)}};
+k.An=function(a,b){var c=b.X(),d,e;var f=0;for(e=c.length;f<e;++f){var g=c[f];var h=0;for(d=g.length-1;h<d;++h){var l=g.slice(h,h+2);var m={feature:a,geometry:b,depth:[f],index:h,la:l};this.a.Ca(Na(l),m)}}};k.En=function(a,b){var c=b.X(),d,e;var f=0;for(e=c.length;f<e;++f){var g=c[f];var h=0;for(d=g.length-1;h<d;++h){var l=g.slice(h,h+2);var m={feature:a,geometry:b,depth:[f],index:h,la:l};this.a.Ca(Na(l),m)}}};
+k.Cn=function(a,b){var c=b.X(),d,e,f;var g=0;for(f=c.length;g<f;++g){var h=c[g];var l=0;for(e=h.length;l<e;++l){var m=h[l];var n=0;for(d=m.length-1;n<d;++n){var p=m.slice(n,n+2);var q={feature:a,geometry:b,depth:[l,g],index:n,la:p};this.a.Ca(Na(p),q)}}}};k.yn=function(a,b){var c=b.wa(),d={feature:a,geometry:b,index:0,la:[c,c]};a={feature:a,geometry:b,index:1,la:[c,c]};d.Pf=a.Pf=[d,a];this.a.Ca(Za(c),d);this.a.Ca(b.G(),a)};
+k.zn=function(a,b){var c=b.a;for(b=0;b<c.length;++b)this.T[c[b].U()].call(this,a,c[b])};function Yu(a,b){var c=a.g;c?c.V().ma(b):(c=new H(new C(b)),a.g=c,a.B.ha().yb(c))}function Zu(a,b){return a.index-b.index}
+function Ou(a){if(!this.ad(a))return!1;Tu(this,a.pixel,a.map);var b=a.map.Wa(a.pixel);this.j.length=0;this.C=!1;var c=this.g;if(c){var d=[],c=c.V().X(),e=Na([c]),e=Jj(this.a,e),f={};e.sort(Zu);for(var g=0,h=e.length;g<h;++g){var l=e[g],m=l.la,n=w(l.feature),p=l.depth;p&&(n+="-"+p.join("-"));f[n]||(f[n]=Array(2));if("Circle"===l.geometry.U()&&1===l.index)m=$u(b,l),df(m,c)&&!f[n][0]&&(this.j.push([l,0]),f[n][0]=l);else if(df(m[0],c)&&!f[n][0])this.j.push([l,0]),f[n][0]=l;else if(df(m[1],c)&&!f[n][1]){if("LineString"!==
+l.geometry.U()&&"MultiLineString"!==l.geometry.U()||!f[n][0]||0!==f[n][0].index)this.j.push([l,1]),f[n][1]=l}else this.Yb(a)&&w(m)in this.Sa&&!f[n][0]&&!f[n][1]&&d.push([l,c])}d.length&&Uu(this,a);for(a=d.length-1;0<=a;--a)this.bm.apply(this,d[a])}return!!this.g}
+function Pu(a){this.I=!1;Uu(this,a);a=a.coordinate;for(var b=0,c=this.j.length;b<c;++b){for(var d=this.j[b],e=d[0],f=e.depth,g=e.geometry,h,l=e.la,d=d[1];a.length<g.qa();)a.push(l[d][a.length]);switch(g.U()){case "Point":h=a;l[0]=l[1]=a;break;case "MultiPoint":h=g.X();h[e.index]=a;l[0]=l[1]=a;break;case "LineString":h=g.X();h[e.index+d]=a;l[d]=a;break;case "MultiLineString":h=g.X();h[f[0]][e.index+d]=a;l[d]=a;break;case "Polygon":h=g.X();h[f[0]][e.index+d]=a;l[d]=a;break;case "MultiPolygon":h=g.X();
+h[f[1]][f[0]][e.index+d]=a;l[d]=a;break;case "Circle":l[0]=l[1]=a,0===e.index?(this.l=!0,g.ob(a)):(this.l=!0,g.Uc(jf(g.wa(),a))),this.l=!1}h&&(e=g,f=h,this.l=!0,e.ma(f),this.l=!1)}Yu(this,a)}function Ru(a){for(var b,c,d=this.j.length-1;0<=d;--d)if(b=this.j[d][0],c=b.geometry,"Circle"===c.U()){var e=c.wa(),f=b.Pf[0];b=b.Pf[1];f.la[0]=f.la[1]=e;b.la[0]=b.la[1]=e;Hj(this.a,Za(e),f);Hj(this.a,c.G(),b)}else Hj(this.a,Na(b.la),b);this.C&&(this.b(new Vu("modifyend",this.u,a)),this.C=!1);return!1}
+function Qu(a){if(!(a instanceof ee))return!0;this.R=a;var b;dg(a.map.Z())[1]||"pointermove"!=a.type||this.D||(this.va=a.pixel,Tu(this,a.pixel,a.map));this.g&&this.xb(a)&&(b="singleclick"==a.type&&this.I?!0:this.hj());"singleclick"==a.type&&(this.I=!1);return Eg.call(this,a)&&!b}
+function Tu(a,b,c){function d(a,b){return av(e,a)-av(e,b)}var e=c.Wa(b),f=Qa(Za(e),c.Z().Pa()*a.fa),f=Jj(a.a,f);if(0<f.length){f.sort(d);var g=f[0],h=g.la,l=$u(e,g),m=c.Ja(l),n=jf(b,m);if(n<=a.fa){b={};if("Circle"===g.geometry.U()&&1===g.index)a.pa=!0,Yu(a,l);else for(n=c.Ja(h[0]),g=c.Ja(h[1]),c=hf(m,n),m=hf(m,g),n=Math.sqrt(Math.min(c,m)),a.pa=n<=a.fa,a.pa&&(l=c>m?h[1]:h[0]),Yu(a,l),m=1,c=f.length;m<c;++m)if(l=f[m].la,df(h[0],l[0])&&df(h[1],l[1])||df(h[0],l[1])&&df(h[1],l[0]))b[w(l)]=!0;else break;
+b[w(h)]=!0;a.Sa=b;return}}a.g&&(a.B.ha().Gb(a.g),a.g=null)}function av(a,b){var c=b.geometry;return"Circle"===c.U()&&1===b.index?(a=hf(c.wa(),a),c=Math.sqrt(a)-c.pd(),c*c):kf(a,b.la)}function $u(a,b){var c=b.geometry;return"Circle"===c.U()&&1===b.index?c.Ab(a):af(a,b.la)}
+k.bm=function(a,b){for(var c=a.la,d=a.feature,e=a.geometry,f=a.depth,g=a.index,h;b.length<e.qa();)b.push(0);switch(e.U()){case "MultiLineString":h=e.X();h[f[0]].splice(g+1,0,b);break;case "Polygon":h=e.X();h[f[0]].splice(g+1,0,b);break;case "MultiPolygon":h=e.X();h[f[1]][f[0]].splice(g+1,0,b);break;case "LineString":h=e.X();h.splice(g+1,0,b);break;default:return}this.l=!0;e.ma(h);this.l=!1;h=this.a;h.remove(a);bv(this,e,g,f,1);a={la:[c[0],b],feature:d,geometry:e,depth:f,index:g};h.Ca(Na(a.la),a);
+this.j.push([a,1]);b={la:[b,c[1]],feature:d,geometry:e,depth:f,index:g+1};h.Ca(Na(b.la),b);this.j.push([b,0]);this.I=!0};
+k.hj=function(){if(this.R&&"pointerdrag"!=this.R.type){var a=this.R;Uu(this,a);var b=this.j,c={},d,e;for(e=b.length-1;0<=e;--e){var f=b[e];var g=f[0];var h=w(g.feature);g.depth&&(h+="-"+g.depth.join("-"));h in c||(c[h]={});0===f[1]?(c[h].right=g,c[h].index=g.index):1==f[1]&&(c[h].left=g,c[h].index=g.index+1)}for(h in c){var l=c[h].right;var m=c[h].left;e=c[h].index;var n=e-1;g=void 0!==m?m:l;0>n&&(n=0);f=g.geometry;var p=d=f.X();var q=!1;switch(f.U()){case "MultiLineString":2<d[g.depth[0]].length&&
+(d[g.depth[0]].splice(e,1),q=!0);break;case "LineString":2<d.length&&(d.splice(e,1),q=!0);break;case "MultiPolygon":p=p[g.depth[1]];case "Polygon":p=p[g.depth[0]],4<p.length&&(e==p.length-1&&(e=0),p.splice(e,1),q=!0,0===e&&(p.pop(),p.push(p[0]),n=p.length-1))}q&&(q=f,this.l=!0,q.ma(d),this.l=!1,d=[],void 0!==m&&(this.a.remove(m),d.push(m.la[0])),void 0!==l&&(this.a.remove(l),d.push(l.la[1])),void 0!==m&&void 0!==l&&(m={depth:g.depth,feature:g.feature,geometry:g.geometry,index:n,la:d},this.a.Ca(Na(m.la),
+m)),bv(this,f,e,g.depth,-1),this.g&&(this.B.ha().Gb(this.g),this.g=null),b.length=0)}this.b(new Vu("modifyend",this.u,a));this.C=!1;return!0}return!1};function bv(a,b,c,d,e){Lj(a.a,b.G(),function(a){a.geometry===b&&(void 0===d||void 0===a.depth||pa(a.depth,d))&&a.index>c&&(a.index+=e)})}function Su(){var a=gl();return function(){return a.Point}}function Vu(a,b,c){Oc.call(this,a);this.features=b;this.mapBrowserEvent=c}v(Vu,Oc);function cv(a){ng.call(this,{handleEvent:dv});a=a?a:{};this.C=a.condition?a.condition:wg;this.D=a.addCondition?a.addCondition:nf;this.B=a.removeCondition?a.removeCondition:nf;this.I=a.toggleCondition?a.toggleCondition:yg;this.l=a.multi?a.multi:!1;this.o=a.filter?a.filter:mf;this.j=a.hitTolerance?a.hitTolerance:0;this.g=new T({source:new U({useSpatialIndex:!1,features:a.features,wrapX:a.wrapX}),style:a.style?a.style:ev(),updateWhileAnimating:!0,updateWhileInteracting:!0});if(a.layers)if("function"===
+typeof a.layers)a=a.layers;else{var b=a.layers;a=function(a){return ja(b,a)}}else a=mf;this.u=a;this.a={};a=this.g.ha().f;y(a,"add",this.Fn,this);y(a,"remove",this.Jn,this)}v(cv,ng);k=cv.prototype;k.Gn=function(){return this.g.ha().f};k.Hn=function(){return this.j};k.In=function(a){a=w(a);return this.a[a]};
+function dv(a){if(!this.C(a))return!0;var b=this.D(a),c=this.B(a),d=this.I(a),e=!b&&!c&&!d,f=a.map,g=this.g.ha().f,h=[],l=[];if(e){ub(this.a);f.we(a.pixel,function(a,b){if(this.o(a,b))return l.push(a),a=w(a),this.a[a]=b,!this.l}.bind(this),{layerFilter:this.u,hitTolerance:this.j});for(e=g.dc()-1;0<=e;--e){var f=g.item(e),m=l.indexOf(f);-1<m?l.splice(m,1):(g.remove(f),h.push(f))}l.length&&g.fg(l)}else{f.we(a.pixel,function(a,e){if(this.o(a,e))return!b&&!d||ja(g.a,a)?(c||d)&&ja(g.a,a)&&(h.push(a),e=
+w(a),delete this.a[e]):(l.push(a),a=w(a),this.a[a]=e),!this.l}.bind(this),{layerFilter:this.u,hitTolerance:this.j});for(e=h.length-1;0<=e;--e)g.remove(h[e]);g.fg(l)}(0<l.length||0<h.length)&&this.b(new fv(gv,l,h,a));return vg(a)}k.Kn=function(a){this.j=a};k.setMap=function(a){var b=this.v,c=this.g.ha().f;b&&c.forEach(b.Cj,b);ng.prototype.setMap.call(this,a);this.g.setMap(a);a&&c.forEach(a.xj,a)};
+function ev(){var a=gl();la(a.Polygon,a.LineString);la(a.GeometryCollection,a.LineString);return function(b){return b.V()?a[b.V().U()]:null}}k.Fn=function(a){var b=this.v;b&&b.xj(a.element)};k.Jn=function(a){var b=this.v;b&&b.Cj(a.element)};function fv(a,b,c,d){Oc.call(this,a);this.selected=b;this.deselected=c;this.mapBrowserEvent=d}v(fv,Oc);var gv="select";function hv(a){Dg.call(this,{handleEvent:iv,handleDownEvent:mf,handleUpEvent:jv});a=a?a:{};this.l=a.source?a.source:null;this.R=void 0!==a.vertex?a.vertex:!0;this.C=void 0!==a.edge?a.edge:!0;this.j=a.features?a.features:null;this.pa=[];this.B={};this.T={};this.u={};this.I=null;this.g=void 0!==a.pixelTolerance?a.pixelTolerance:10;this.va=kv.bind(this);this.a=new Gj;this.fa={Point:this.Rn,LineString:this.wi,LinearRing:this.wi,Polygon:this.Sn,MultiPoint:this.Pn,MultiLineString:this.On,MultiPolygon:this.Qn,
+GeometryCollection:this.Nn,Circle:this.Mn}}v(hv,Dg);k=hv.prototype;k.yb=function(a,b){b=void 0!==b?b:!0;var c=w(a),d=a.V();if(d){var e=this.fa[d.U()];e&&(this.T[c]=d.G(Oa()),e.call(this,a,d))}b&&(this.B[c]=y(a,"change",this.Ln,this))};k.Ak=function(a){this.yb(a)};k.Bk=function(a){this.Gb(a)};k.ui=function(a){if(a instanceof gu)var b=a.feature;else a instanceof bd&&(b=a.element);this.yb(b)};k.vi=function(a){if(a instanceof gu)var b=a.feature;else a instanceof bd&&(b=a.element);this.Gb(b)};
+k.Ln=function(a){a=a.target;if(this.D){var b=w(a);b in this.u||(this.u[b]=a)}else this.Dj(a)};k.Gb=function(a,b){b=void 0!==b?b:!0;var c=w(a),d=this.T[c];if(d){var e=this.a,f=[];Lj(e,d,function(b){a===b.feature&&f.push(b)});for(d=f.length-1;0<=d;--d)e.remove(f[d])}b&&(Ec(this.B[c]),delete this.B[c])};
+k.setMap=function(a){var b=this.v,c=this.pa,d;this.j?d=this.j:this.l&&(d=this.l.Xe());b&&(c.forEach(Ec),c.length=0,d.forEach(this.Bk,this));Dg.prototype.setMap.call(this,a);a&&(this.j?c.push(y(this.j,"add",this.ui,this),y(this.j,"remove",this.vi,this)):this.l&&c.push(y(this.l,"addfeature",this.ui,this),y(this.l,"removefeature",this.vi,this)),d.forEach(this.Ak,this))};k.Xc=nf;
+function lv(a,b,c,d){var e=d.Wa([b[0]-a.g,b[1]+a.g]),f=d.Wa([b[0]+a.g,b[1]-a.g]),e=Na([e,f]),g=Jj(a.a,e);a.R&&!a.C&&(g=g.filter(function(a){return"Circle"!==a.feature.V().U()}));var h=!1,e=!1,l=f=null;if(0<g.length){a.I=c;g.sort(a.va);var m=g[0].la,h="Circle"===g[0].feature.V().U();if(a.R&&!a.C){if(c=d.Ja(m[0]),h=d.Ja(m[1]),c=hf(b,c),b=hf(b,h),h=Math.sqrt(Math.min(c,b)),h=h<=a.g)e=!0,f=c>b?m[1]:m[0],l=d.Ja(f)}else a.C&&(f=h?$e(c,g[0].feature.V()):af(c,m),l=d.Ja(f),jf(b,l)<=a.g&&(e=!0,a.R&&!h&&(c=
+d.Ja(m[0]),h=d.Ja(m[1]),c=hf(l,c),b=hf(l,h),h=Math.sqrt(Math.min(c,b)),h=h<=a.g)))&&(f=c>b?m[1]:m[0],l=d.Ja(f));e&&(l=[Math.round(l[0]),Math.round(l[1])])}return{nq:e,vertex:f,wq:l}}k.Dj=function(a){this.Gb(a,!1);this.yb(a,!1)};k.Mn=function(a,b){b=Zf(b).X()[0];var c;var d=0;for(c=b.length-1;d<c;++d){var e=b.slice(d,d+2);var f={feature:a,la:e};this.a.Ca(Na(e),f)}};k.Nn=function(a,b){var c=b.a;for(b=0;b<c.length;++b){var d=this.fa[c[b].U()];d&&d.call(this,a,c[b])}};
+k.wi=function(a,b){b=b.X();var c;var d=0;for(c=b.length-1;d<c;++d){var e=b.slice(d,d+2);var f={feature:a,la:e};this.a.Ca(Na(e),f)}};k.On=function(a,b){b=b.X();var c,d;var e=0;for(d=b.length;e<d;++e){var f=b[e];var g=0;for(c=f.length-1;g<c;++g){var h=f.slice(g,g+2);var l={feature:a,la:h};this.a.Ca(Na(h),l)}}};k.Pn=function(a,b){var c=b.X(),d;var e=0;for(d=c.length;e<d;++e){var f=c[e];f={feature:a,la:[f,f]};this.a.Ca(b.G(),f)}};
+k.Qn=function(a,b){b=b.X();var c,d,e;var f=0;for(e=b.length;f<e;++f){var g=b[f];var h=0;for(d=g.length;h<d;++h){var l=g[h];var m=0;for(c=l.length-1;m<c;++m){var n=l.slice(m,m+2);var p={feature:a,la:n};this.a.Ca(Na(n),p)}}}};k.Rn=function(a,b){var c=b.X();a={feature:a,la:[c,c]};this.a.Ca(b.G(),a)};k.Sn=function(a,b){b=b.X();var c,d;var e=0;for(d=b.length;e<d;++e){var f=b[e];var g=0;for(c=f.length-1;g<c;++g){var h=f.slice(g,g+2);var l={feature:a,la:h};this.a.Ca(Na(h),l)}}};
+function iv(a){var b=lv(this,a.pixel,a.coordinate,a.map);b.nq&&(a.coordinate=b.vertex.slice(0,2),a.pixel=b.wq);return Eg.call(this,a)}function jv(){var a=vb(this.u);a.length&&(a.forEach(this.Dj,this),this.u={});return!1}function kv(a,b){return kf(this.I,a.la)-kf(this.I,b.la)};function mv(a){Dg.call(this,{handleDownEvent:nv,handleDragEvent:ov,handleMoveEvent:pv,handleUpEvent:qv});a=a?a:{};this.a=null;this.j=void 0!==a.features?a.features:null;if(a.layers)if("function"===typeof a.layers)var b=a.layers;else{var c=a.layers;b=function(a){return ja(c,a)}}else b=mf;this.C=b;this.l=a.hitTolerance?a.hitTolerance:0;this.g=null;y(this,Vc("active"),this.u,this)}v(mv,Dg);
+function nv(a){this.g=rv(this,a.pixel,a.map);if(!this.a&&this.g){this.a=a.coordinate;pv.call(this,a);var b=this.j||new Yc([this.g]);this.b(new sv("translatestart",b,a.coordinate));return!0}return!1}function qv(a){if(this.a){this.a=null;pv.call(this,a);var b=this.j||new Yc([this.g]);this.b(new sv("translateend",b,a.coordinate));return!0}return!1}
+function ov(a){if(this.a){a=a.coordinate;var b=a[0]-this.a[0],c=a[1]-this.a[1],d=this.j||new Yc([this.g]);d.forEach(function(a){var d=a.V();d.translate(b,c);a.Ra(d)});this.a=a;this.b(new sv("translating",d,a))}}function pv(a){var b=a.map.a;rv(this,a.pixel,a.map)?(b.classList.remove(this.a?"ol-grab":"ol-grabbing"),b.classList.add(this.a?"ol-grabbing":"ol-grab")):b.classList.remove("ol-grab","ol-grabbing")}
+function rv(a,b,c){return c.we(b,function(a){if(!this.j||ja(this.j.a,a))return a}.bind(a),{layerFilter:a.C,hitTolerance:a.l})}mv.prototype.B=function(){return this.l};mv.prototype.I=function(a){this.l=a};mv.prototype.setMap=function(a){var b=this.v;Dg.prototype.setMap.call(this,a);tv(this,b)};mv.prototype.u=function(){tv(this,null)};function tv(a,b){var c=a.v;a=a.c();c&&a||(c||(c=b),c.a.classList.remove("ol-grab","ol-grabbing"))}
+function sv(a,b,c){Oc.call(this,a);this.features=b;this.coordinate=c}v(sv,Oc);function V(a){a=a?a:{};var b=tb({},a);delete b.gradient;delete b.radius;delete b.blur;delete b.shadow;delete b.weight;T.call(this,b);this.j=null;this.R=void 0!==a.shadow?a.shadow:250;this.I=void 0;this.B=null;y(this,Vc(uv),this.Bl,this);this.pj(a.gradient?a.gradient:vv);this.jj(void 0!==a.blur?a.blur:15);this.Uc(void 0!==a.radius?a.radius:8);y(this,Vc(wv),this.cg,this);y(this,Vc(xv),this.cg,this);this.cg();var c=a.weight?a.weight:"weight",d;"string"===typeof c?d=function(a){return a.get(c)}:d=c;this.g(function(a){a=
+d(a);a=void 0!==a?Ca(a,0,1):1;var b=255*a|0,c=this.B[b];c||(c=[new bl({image:new eo({opacity:a,src:this.I})})],this.B[b]=c);return c}.bind(this));this.set(Pt,null);y(this,"render",this.Sl,this)}v(V,T);var vv=["#00f","#0ff","#0f0","#ff0","#f00"];k=V.prototype;k.uh=function(){return this.get(wv)};k.Bh=function(){return this.get(uv)};k.yi=function(){return this.get(xv)};
+k.Bl=function(){for(var a=this.Bh(),b=jd(1,256),c=b.createLinearGradient(0,0,1,256),d=1/(a.length-1),e=0,f=a.length;e<f;++e)c.addColorStop(e*d,a[e]);b.fillStyle=c;b.fillRect(0,0,1,256);this.j=b.getImageData(0,0,1,256).data};k.cg=function(){var a=this.yi(),b=this.uh(),c=a+b+1,d=2*c,d=jd(d,d);d.shadowOffsetX=d.shadowOffsetY=this.R;d.shadowBlur=b;d.shadowColor="#000";d.beginPath();b=c-this.R;d.arc(b,b,a,0,2*Math.PI,!0);d.fill();this.I=d.canvas.toDataURL();this.B=Array(256);this.s()};
+k.Sl=function(a){a=a.context;var b=a.canvas,b=a.getImageData(0,0,b.width,b.height),c=b.data,d,e;var f=0;for(d=c.length;f<d;f+=4)if(e=4*c[f+3])c[f]=this.j[e],c[f+1]=this.j[e+1],c[f+2]=this.j[e+2];a.putImageData(b,0,0)};k.jj=function(a){this.set(wv,a)};k.pj=function(a){this.set(uv,a)};k.Uc=function(a){this.set(xv,a)};var wv="blur",uv="gradient",xv="radius";function yv(a){Gt.call(this,a);this.v=Bh();this.j=null}v(yv,Gt);yv.prototype.S=function(a,b,c){It(this,"precompose",c,a,void 0);var d=this.Y();if(d){var e=b.extent,f=void 0!==e&&!Va(e,a.extent)&&qb(e,a.extent);f&&Ht(c,a,e);var e=this.C(),g=c.globalAlpha;c.globalAlpha=b.opacity;c.drawImage(d,0,0,+d.width,+d.height,Math.round(e[4]),Math.round(e[5]),Math.round(d.width*e[0]),Math.round(d.height*e[3]));c.globalAlpha=g;f&&c.restore()}this.ef(c,a,b)};
+yv.prototype.Ea=function(a,b,c,d,e){var f=this.a;return f.ha().Ea(a,b.viewState.resolution,b.viewState.rotation,c,b.skippedFeatureUids,function(a){return d.call(e,a,f)})};
+yv.prototype.u=function(a,b,c,d){if(this.Y()){if(this.a.ha().Ea!==ua)return Gt.prototype.u.apply(this,arguments);var e=Gh(this.v,a.slice());gf(e,b.viewState.resolution/this.f);this.j||(this.j=jd(1,1));this.j.clearRect(0,0,1,1);this.j.drawImage(this.Y(),e[0],e[1],1,1,0,0,1,1);e=this.j.getImageData(0,0,1,1).data;if(0<e[3])return c.call(d,this.a,e)}};function zv(a){yv.call(this,a);this.M=null;this.c=Bh()}v(zv,yv);zv.prototype.Y=function(){return this.M?this.M.Y():null};zv.prototype.C=function(){return this.c};
+zv.prototype.sd=function(a,b){var c=a.pixelRatio,d=a.size,e=a.viewState,f=e.center,g=e.resolution,h=this.a.ha(),l=a.viewHints,m=a.extent;void 0!==b.extent&&(m=pb(m,b.extent));l[0]||l[1]||kb(m)||(b=h.Y(m,g,c,e.projection))&&At(this,b)&&(this.M=b);if(this.M){b=this.M;var l=b.G(),m=b.resolution,e=b.a,n=c*m/(g*e),l=Kh(this.c,c*d[0]/2,c*d[1]/2,n,n,0,e*(l[0]-f[0])/m,e*(f[1]-l[3])/m);Kh(this.v,c*d[0]/2-l[4],c*d[1]/2-l[5],c/g,-c/g,0,-f[0],-f[1]);Ct(a.attributions,b.f);Dt(a,h);this.f=g*c/e}return!!this.M};function Av(a,b,c,d){var e=gc(c,b,a);c=Sb(b,d,c);b=b.sc();void 0!==b&&(c*=b);b=a.sc();void 0!==b&&(c/=b);a=Sb(a,c,e)/c;isFinite(a)&&0<a&&(c/=a);return c}function Bv(a,b,c,d){a=c-a;b=d-b;var e=Math.sqrt(a*a+b*b);return[Math.round(c+a/e),Math.round(d+b/e)]}
+function Cv(a,b,c,d,e,f,g,h,l,m,n){var p=jd(Math.round(c*a),Math.round(c*b));if(!l.length)return p.canvas;p.scale(c,c);var q=Oa();l.forEach(function(a){cb(q,a.extent)});var r=jd(Math.round(c*lb(q)/d),Math.round(c*mb(q)/d)),u=c/d;l.forEach(function(a){r.drawImage(a.image,m,m,a.image.width-2*m,a.image.height-2*m,(a.extent[0]-q[0])*u,-(a.extent[3]-q[3])*u,lb(a.extent)*u,mb(a.extent)*u)});var x=ib(g);h.c.forEach(function(a){var b=a.source,e=a.target,g=b[1][0],h=b[1][1],l=b[2][0],m=b[2][1];a=(e[0][0]-
+x[0])/f;var n=-(e[0][1]-x[1])/f,u=(e[1][0]-x[0])/f,B=-(e[1][1]-x[1])/f,da=(e[2][0]-x[0])/f,fb=-(e[2][1]-x[1])/f,e=b[0][0],b=b[0][1],g=g-e,h=h-b,l=l-e,m=m-b;a:{g=[[g,h,0,0,u-a],[l,m,0,0,da-a],[0,0,g,h,B-n],[0,0,l,m,fb-n]];h=g.length;for(l=0;l<h;l++){for(var m=l,ca=Math.abs(g[l][l]),Ub=l+1;Ub<h;Ub++){var uc=Math.abs(g[Ub][l]);uc>ca&&(ca=uc,m=Ub)}if(!ca){g=null;break a}ca=g[m];g[m]=g[l];g[l]=ca;for(m=l+1;m<h;m++)for(ca=-g[m][l]/g[l][l],Ub=l;Ub<h+1;Ub++)g[m][Ub]=l==Ub?0:g[m][Ub]+ca*g[l][Ub]}l=Array(h);
+for(m=h-1;0<=m;m--)for(l[m]=g[m][h]/g[m][m],ca=m-1;0<=ca;ca--)g[ca][h]-=g[ca][m]*l[m];g=l}g&&(p.save(),p.beginPath(),l=(a+u+da)/3,m=(n+B+fb)/3,h=Bv(l,m,a,n),u=Bv(l,m,u,B),da=Bv(l,m,da,fb),p.moveTo(u[0],u[1]),p.lineTo(h[0],h[1]),p.lineTo(da[0],da[1]),p.clip(),p.transform(g[0],g[2],g[1],g[3],a,n),p.translate(q[0]-e,q[3]-b),p.scale(d/c,-d/c),p.drawImage(r.canvas,0,0),p.restore())});n&&(p.save(),p.strokeStyle="black",p.lineWidth=1,h.c.forEach(function(a){var b=a.target;a=(b[0][0]-x[0])/f;var c=-(b[0][1]-
+x[1])/f,d=(b[1][0]-x[0])/f,e=-(b[1][1]-x[1])/f,g=(b[2][0]-x[0])/f,b=-(b[2][1]-x[1])/f;p.beginPath();p.moveTo(d,e);p.lineTo(a,c);p.lineTo(g,b);p.closePath();p.stroke()}),p.restore());return p.canvas};function Dv(a,b,c,d,e){this.i=a;this.f=b;var f={},g=ec(this.f,this.i);this.a=function(a){var b=a[0]+"/"+a[1];f[b]||(f[b]=g(a));return f[b]};this.g=d;this.v=e*e;this.c=[];this.o=!1;this.l=this.i.i&&!!d&&!!this.i.G()&&lb(d)==lb(this.i.G());this.b=this.i.G()?lb(this.i.G()):null;this.j=this.f.G()?lb(this.f.G()):null;a=ib(c);b=hb(c);d=gb(c);c=eb(c);e=this.a(a);var h=this.a(b),l=this.a(d),m=this.a(c);Ev(this,a,b,d,c,e,h,l,m,10);if(this.o){var n=Infinity;this.c.forEach(function(a){n=Math.min(n,a.source[0][0],
+a.source[1][0],a.source[2][0])});this.c.forEach(function(a){if(Math.max(a.source[0][0],a.source[1][0],a.source[2][0])-n>this.b/2){var b=[[a.source[0][0],a.source[0][1]],[a.source[1][0],a.source[1][1]],[a.source[2][0],a.source[2][1]]];b[0][0]-n>this.b/2&&(b[0][0]-=this.b);b[1][0]-n>this.b/2&&(b[1][0]-=this.b);b[2][0]-n>this.b/2&&(b[2][0]-=this.b);Math.max(b[0][0],b[1][0],b[2][0])-Math.min(b[0][0],b[1][0],b[2][0])<this.b/2&&(a.source=b)}},this)}f={}}
+function Ev(a,b,c,d,e,f,g,h,l,m){var n=Na([f,g,h,l]),p=a.b?lb(n)/a.b:null,q=a.b,r=a.i.i&&.5<p&&1>p,u=!1;if(0<m){if(a.f.c&&a.j)var x=Na([b,c,d,e]),u=u|.25<lb(x)/a.j;!r&&a.i.c&&p&&(u|=.25<p)}if(u||!a.g||qb(n,a.g)){if(!(u||isFinite(f[0])&&isFinite(f[1])&&isFinite(g[0])&&isFinite(g[1])&&isFinite(h[0])&&isFinite(h[1])&&isFinite(l[0])&&isFinite(l[1])))if(0<m)u=!0;else return;if(0<m&&(u||(n=a.a([(b[0]+d[0])/2,(b[1]+d[1])/2]),q=r?(Ia(f[0],q)+Ia(h[0],q))/2-Ia(n[0],q):(f[0]+h[0])/2-n[0],n=(f[1]+h[1])/2-n[1],
+u=q*q+n*n>a.v),u)){Math.abs(b[0]-d[0])<=Math.abs(b[1]-d[1])?(r=[(c[0]+d[0])/2,(c[1]+d[1])/2],q=a.a(r),n=[(e[0]+b[0])/2,(e[1]+b[1])/2],p=a.a(n),Ev(a,b,c,r,n,f,g,q,p,m-1),Ev(a,n,r,d,e,p,q,h,l,m-1)):(r=[(b[0]+c[0])/2,(b[1]+c[1])/2],q=a.a(r),n=[(d[0]+e[0])/2,(d[1]+e[1])/2],p=a.a(n),Ev(a,b,r,n,e,f,q,p,l,m-1),Ev(a,r,c,d,n,q,g,h,p,m-1));return}if(r){if(!a.l)return;a.o=!0}a.c.push({source:[f,h,l],target:[b,d,e]});a.c.push({source:[f,g,h],target:[b,c,d]})}}
+function Fv(a){var b=Oa();a.c.forEach(function(a){a=a.source;Pa(b,a[0]);Pa(b,a[1]);Pa(b,a[2])});return b};function Gv(a,b,c,d,e,f){this.v=b;this.l=a.G();var g=b.G(),h=g?pb(c,g):c,g=Av(a,b,nb(h),d);this.j=new Dv(a,b,h,this.l,.5*g);this.c=d;this.i=c;a=Fv(this.j);this.o=(this.Hb=f(a,g,e))?this.Hb.a:1;this.ee=this.g=null;e=2;f=[];this.Hb&&(e=0,f=this.Hb.f);Is.call(this,c,d,this.o,e,f)}v(Gv,Is);Gv.prototype.ka=function(){1==this.state&&(Ec(this.ee),this.ee=null);Is.prototype.ka.call(this)};Gv.prototype.Y=function(){return this.g};
+Gv.prototype.de=function(){var a=this.Hb.getState();2==a&&(this.g=Cv(lb(this.i)/this.c,mb(this.i)/this.c,this.o,this.Hb.resolution,0,this.c,this.i,this.j,[{extent:this.Hb.G(),image:this.Hb.Y()}],0));this.state=a;this.s()};Gv.prototype.load=function(){if(0==this.state){this.state=1;this.s();var a=this.Hb.getState();2==a||3==a?this.de():(this.ee=y(this.Hb,"change",function(){var a=this.Hb.getState();if(2==a||3==a)Ec(this.ee),this.ee=null,this.de()},this),this.Hb.load())}};function Hv(a){$t.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,projection:a.projection,state:a.state});this.C=void 0!==a.resolutions?a.resolutions:null;this.a=null;this.fa=0}v(Hv,$t);function Iv(a,b){a.C&&(b=a.C[ka(a.C,b,0)]);return b}
+Hv.prototype.Y=function(a,b,c,d){var e=this.c;if(e&&d&&!dc(e,d)){if(this.a){if(this.fa==this.i&&dc(this.a.v,d)&&this.a.resolution==b&&this.a.a==c&&bb(this.a.G(),a))return this.a;Nc(this.a);this.a=null}this.a=new Gv(e,d,a,b,c,function(a,b,c){return this.Jc(a,b,c,e)}.bind(this));this.fa=this.i;return this.a}e&&(d=e);return this.Jc(a,b,c,d)};Hv.prototype.o=function(a){a=a.target;switch(a.getState()){case 1:this.b(new Jv(Kv,a));break;case 2:this.b(new Jv(Lv,a));break;case 3:this.b(new Jv(Mv,a))}};
+function Nv(a,b){a.Y().src=b}function Jv(a,b){Oc.call(this,a);this.image=b}v(Jv,Oc);var Kv="imageloadstart",Lv="imageloadend",Mv="imageloaderror";function Ov(a){Hv.call(this,{attributions:a.attributions,logo:a.logo,projection:a.projection,resolutions:a.resolutions,state:a.state});this.pa=a.canvasFunction;this.R=null;this.T=0;this.va=void 0!==a.ratio?a.ratio:1.5}v(Ov,Hv);Ov.prototype.Jc=function(a,b,c,d){b=Iv(this,b);var e=this.R;if(e&&this.T==this.i&&e.resolution==b&&e.a==c&&Va(e.G(),a))return e;a=a.slice();rb(a,this.va);(d=this.pa(a,b,c,[lb(a)/b*c,mb(a)/b*c],d))&&(e=new Ks(a,b,c,this.j,d));this.R=e;this.T=this.i;return e};function Pv(a){this.f=a.source;this.$a=Bh();this.g=jd();this.l=[0,0];this.Sa=void 0==a.renderBuffer?100:a.renderBuffer;this.B=null;Ov.call(this,{attributions:a.attributions,canvasFunction:this.tk.bind(this),logo:a.logo,projection:a.projection,ratio:a.ratio,resolutions:a.resolutions,state:this.f.getState()});this.I=null;this.v=void 0;this.Ii(a.style);y(this.f,"change",this.ro,this)}v(Pv,Ov);k=Pv.prototype;
+k.tk=function(a,b,c,d,e){var f=new pt(.5*b/c,a,b,this.f.T,this.Sa);this.f.Yd(a,b,e);var g=!1;this.f.$b(a,function(a){var d;if(!(d=g)){var e;(d=a.Lc())?e=d.call(a,b):this.v&&(e=this.v(a,b));if(e){var h,p=!1;Array.isArray(e)||(e=[e]);d=0;for(h=e.length;d<h;++d)p=Mt(f,a,e[d],Lt(b,c),this.qo,this)||p;d=p}else d=!1}g=d},this);tt(f);if(g)return null;this.l[0]!=d[0]||this.l[1]!=d[1]?(this.g.canvas.width=d[0],this.g.canvas.height=d[1],this.l[0]=d[0],this.l[1]=d[1]):this.g.clearRect(0,0,d[0],d[1]);a=Qv(this,
+nb(a),b,c,d);f.La(this.g,c,a,0,{});this.B=f;return this.g.canvas};k.Ea=function(a,b,c,d,e,f){if(this.B){var g={};return this.B.Ea(a,b,0,d,e,function(a){var b=w(a).toString();if(!(b in g))return g[b]=!0,f(a)})}};k.no=function(){return this.f};k.oo=function(){return this.I};k.po=function(){return this.v};function Qv(a,b,c,d,e){c=d/c;return Kh(a.$a,e[0]/2,e[1]/2,c,-c,0,-b[0],-b[1])}k.qo=function(){this.s()};k.ro=function(){bu(this,this.f.getState())};
+k.Ii=function(a){this.I=void 0!==a?a:fl;this.v=a?dl(this.I):void 0;this.s()};function Rv(a,b){Vt.call(this,a,b);this.o=this.f=this.M=null}v(Rv,Vt);function Sv(a,b){b=b.Y();return Ti(a.c.i,b)}Rv.prototype.Ea=function(a,b,c,d,e){var f=this.a;return f.ha().Ea(a,b.viewState.resolution,b.viewState.rotation,c,b.skippedFeatureUids,function(a){return d.call(e,a,f)})};
+Rv.prototype.ng=function(a,b){var c=this.c.i,d=a.pixelRatio,e=a.viewState,f=e.center,g=e.resolution,h=e.rotation,l=this.M,m=this.Ib,n=this.a.ha(),p=a.viewHints,q=a.extent;void 0!==b.extent&&(q=pb(q,b.extent));p[0]||p[1]||kb(q)||(b=n.Y(q,g,d,e.projection))&&At(this,b)&&(l=b,m=Sv(this,b),this.Ib&&a.postRenderFunctions.push(function(a,b){a.isContextLost()||a.deleteTexture(b)}.bind(null,c,this.Ib)));l&&(c=this.c.f.j,Tv(this,c.width,c.height,d,f,g,h,l.G()),this.o=null,d=this.v,Ch(d),Ih(d,1,-1),Jh(d,0,
+-1),this.M=l,this.Ib=m,Ct(a.attributions,l.f),Dt(a,n));return!!l};function Tv(a,b,c,d,e,f,g,h){b*=f;c*=f;a=a.S;Ch(a);Ih(a,2*d/b,2*d/c);Hh(a,-g);Jh(a,h[0]-e[0],h[1]-e[1]);Ih(a,(h[2]-h[0])/2,(h[3]-h[1])/2);Jh(a,1,1)}Rv.prototype.Ue=function(a,b){return void 0!==this.Ea(a,b,0,mf,this)};
+Rv.prototype.lg=function(a,b,c,d){if(this.M&&this.M.Y())if(this.a.ha()instanceof Pv){var e=Gh(b.pixelToCoordinateTransform,a.slice());if(this.Ea(e,b,0,mf,this))return c.call(d,this.a,null)}else{e=[this.M.Y().width,this.M.Y().height];if(!this.o){var f=b.size;b=Bh();Jh(b,-1,-1);Ih(b,2/f[0],2/f[1]);Jh(b,0,f[1]);Ih(b,1,-1);var f=Lh(this.S.slice()),g=Bh();Jh(g,0,e[1]);Ih(g,1,-1);Ih(g,e[0]/2,e[1]/2);Jh(g,1,1);Eh(g,f);Eh(g,b);this.o=g}a=Gh(this.o,a.slice());if(!(0>a[0]||a[0]>e[0]||0>a[1]||a[1]>e[1])&&(this.f||
+(this.f=jd(1,1)),this.f.clearRect(0,0,1,1),this.f.drawImage(this.M.Y(),a[0],a[1],1,1,0,0,1,1),e=this.f.getImageData(0,0,1,1).data,0<e[3]))return c.call(d,this.a,e)}};function Uv(a){wh.call(this,a?a:{})}v(Uv,wh);Uv.prototype.Fd=function(a){var b=null,c=a.U();"canvas"===c?b=new zv(this):"webgl"===c&&(b=new Rv(a,this));return b};function Vv(a){yv.call(this,a);this.c=null===this.c?null:jd();this.o=null;this.g=[];this.l=Oa();this.va=new ya(0,0,0,0);this.B=Bh();this.T=0}v(Vv,yv);function Wv(a,b){b=b.getState();a=a.a.kd();return 2==b||4==b||3==b&&!a}
+Vv.prototype.sd=function(a,b){var c=a.pixelRatio,d=a.size,e=a.viewState,f=e.projection,g=e.resolution,e=e.center,h=this.a,l=h.ha(),m=l.i,n=l.Ta(f),p=n.tc(g,this.T),q=n.Da(p),r=Math.round(g/q)||1,u=a.extent;void 0!==b.extent&&(u=pb(u,b.extent));if(kb(u))return!1;var x=rc(n,u,q);var B=n.Pc(p);var E=n.Da(p),A=Ma(n.gb(p),n.j);B=Xa(B[0]+x.ca*A[0]*E,B[1]+x.da*A[1]*E,B[0]+(x.$+1)*A[0]*E,B[1]+(x.ia+1)*A[1]*E,void 0);E=l.nb(c);A={};A[p]={};var L=this.Nf(l,f,A),oa=this.l,ha=this.va,ga=!1,z,M;for(z=x.ca;z<=
+x.$;++z)for(M=x.da;M<=x.ia;++M){var ba=l.Nc(p,z,M,c,f);3!=ba.getState()||this.a.kd()||Ns(ba,2);Wv(this,ba)||(ba=Ms(ba));Wv(this,ba)?2==ba.getState()&&(A[p][ba.ta.toString()]=ba,ga||-1!=this.g.indexOf(ba)||(ga=!0)):pc(n,ba.ta,L,ha,oa)||(ba=qc(n,ba.ta,ha,oa))&&L(p+1,ba)}z=a.viewHints;z=z[0]||z[1];if(!(this.f&&16<Date.now()-a.time&&z||!ga&&this.o&&Va(this.o,u)&&this.mf==m&&r==this.R&&(z||q*c/E*r==this.f))){if(z=this.c)M=l.Xd(p,c,f),ba=Math.round((x.$-x.ca+1)*M[0]/r),M=Math.round((x.ia-x.da+1)*M[1]/r),
+ga=z.canvas,ga.width!=ba||ga.height!=M?(this.R=r,ga.width=ba,ga.height=M):(z.clearRect(0,0,ba,M),r=this.R);this.g.length=0;ga=Object.keys(A).map(Number);ga.sort(ia);var da,ha=0;for(da=ga.length;ha<da;++ha){z=ga[ha];L=l.Xd(z,c,f);ba=n.Da(z);var fb=ba/q;var ca=E*l.Wf(f);var Ub=A[z];for(var uc in Ub){ba=Ub[uc];M=n.Aa(ba.ta,oa);z=(M[0]-B[0])/q*E/r;M=(B[3]-M[3])/q*E/r;var bc=L[0]*fb/r;var Je=L[1]*fb/r;this.Of(ba,a,b,z,M,bc,Je,ca);this.g.push(ba)}}this.mf=m;this.f=q*c/E*r;this.o=B}b=this.f/g;b=Kh(this.B,
+c*d[0]/2,c*d[1]/2,b,b,0,(this.o[0]-e[0])/this.f*c,(e[1]-this.o[3])/this.f*c);Kh(this.v,c*d[0]/2-b[4],c*d[1]/2-b[5],c/g,-c/g,0,-e[0],-e[1]);Et(a.usedTiles,l,p,x);Ft(a,l,n,c,f,u,p,h.Ud());Bt(a,l);Dt(a,l);return 0<this.g.length};Vv.prototype.Of=function(a,b,c,d,e,f,g,h){this.a.ha().Zf(b.viewState.projection)||this.c.clearRect(d,e,f,g);(a=a.Y())&&this.c.drawImage(a,h,h,a.width-2*h,a.height-2*h,d,e,f,g)};Vv.prototype.Y=function(){var a=this.c;return a?a.canvas:null};Vv.prototype.C=function(){return this.B};function Xv(){this.b="precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}"}v(Xv,mi);var Yv=new Xv;function Zv(){this.b="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;}"}v(Zv,ni);var $v=new Zv;function aw(a,b){this.i=a.getUniformLocation(b,"e");this.c=a.getUniformLocation(b,"d");this.b=a.getAttribLocation(b,"b");this.a=a.getAttribLocation(b,"c")};function bw(a,b){Vt.call(this,a,b);this.I=Yv;this.fa=$v;this.f=null;this.B=new Di([0,0,0,1,1,0,1,1,0,1,0,0,1,1,1,0]);this.D=this.o=null;this.l=-1;this.R=[0,0]}v(bw,Vt);k=bw.prototype;k.ka=function(){Gi(this.c.f,this.B);Vt.prototype.ka.call(this)};k.Nf=function(a,b,c){var d=this.c;return function(e,f){return yt(a,b,e,f,function(a){var b=d.a.b.hasOwnProperty(a.bb());b&&(c[e]||(c[e]={}),c[e][a.ta.toString()]=a);return b})}};k.mg=function(){Vt.prototype.mg.call(this);this.f=null};
+k.ng=function(a,b,c){var d=this.c,e=c.b,f=a.viewState,g=f.projection,h=this.a,l=h.ha(),m=l.Ta(g),n=m.tc(f.resolution),p=m.Da(n),q=l.Xd(n,a.pixelRatio,g),r=q[0]/Ma(m.gb(n),this.R)[0],u=p/r,x=l.nb(r)*l.Wf(g),B=f.center,E=a.extent,A=rc(m,E,p);if(this.o&&Aa(this.o,A)&&this.l==l.i)u=this.D;else{var L=[A.$-A.ca+1,A.ia-A.da+1],oa=Ea(Math.max(L[0]*q[0],L[1]*q[1])),L=u*oa,ha=m.Pc(n),ga=ha[0]+A.ca*q[0]*u,u=ha[1]+A.da*q[1]*u,u=[ga,u,ga+L,u+L];Wt(this,a,oa);e.viewport(0,0,oa,oa);e.clearColor(0,0,0,0);e.clear(16384);
+e.disable(3042);oa=Hi(c,this.I,this.fa);c.Qc(oa);this.f||(this.f=new aw(e,oa));wi(c,34962,this.B);e.enableVertexAttribArray(this.f.b);e.vertexAttribPointer(this.f.b,2,5126,!1,16,0);e.enableVertexAttribArray(this.f.a);e.vertexAttribPointer(this.f.a,2,5126,!1,16,8);e.uniform1i(this.f.i,0);c={};c[n]={};var z=this.Nf(l,g,c),M=h.kd(),oa=!0,ga=Oa(),ba=new ya(0,0,0,0),da,fb;for(da=A.ca;da<=A.$;++da)for(fb=A.da;fb<=A.ia;++fb){ha=l.Nc(n,da,fb,r,g);if(void 0!==b.extent){var ca=m.Aa(ha.ta,ga);if(!qb(ca,b.extent))continue}ca=
+ha.getState();(ca=2==ca||4==ca||3==ca&&!M)||(ha=Ms(ha));ca=ha.getState();if(2==ca){if(d.a.b.hasOwnProperty(ha.bb())){c[n][ha.ta.toString()]=ha;continue}}else if(4==ca||3==ca&&!M)continue;oa=!1;ca=pc(m,ha.ta,z,ba,ga);ca||(ha=qc(m,ha.ta,ba,ga))&&z(n+1,ha)}b=Object.keys(c).map(Number);b.sort(ia);for(var z=new Float32Array(4),Ub,M=0,ba=b.length;M<ba;++M)for(Ub in da=c[b[M]],da)ha=da[Ub],ca=m.Aa(ha.ta,ga),z[0]=2*(ca[2]-ca[0])/L,z[1]=2*(ca[3]-ca[1])/L,z[2]=2*(ca[0]-u[0])/L-1,z[3]=2*(ca[1]-u[1])/L-1,e.uniform4fv(this.f.c,
+z),nk(d,ha,q,x*r),e.drawArrays(5,0,4);oa?(this.o=A,this.D=u,this.l=l.i):(this.D=this.o=null,this.l=-1,a.animate=!0)}Et(a.usedTiles,l,n,A);var uc=d.j;Ft(a,l,m,r,g,E,n,h.Ud(),function(a){2!=a.getState()||d.a.b.hasOwnProperty(a.bb())||a.bb()in uc.a||uc.f([a,tc(m,a.ta),m.Da(a.ta[0]),q,x*r])},this);Bt(a,l);Dt(a,l);e=this.v;Ch(e);Jh(e,(Math.round(B[0]/p)*p-u[0])/(u[2]-u[0]),(Math.round(B[1]/p)*p-u[1])/(u[3]-u[1]));f.rotation&&Hh(e,f.rotation);Ih(e,a.size[0]*f.resolution/(u[2]-u[0]),a.size[1]*f.resolution/
+(u[3]-u[1]));Jh(e,-.5,-.5);return!0};k.lg=function(a,b,c,d){if(this.g){a=Gh(this.v,[a[0]/b.size[0],(b.size[1]-a[1])/b.size[1]].slice());a=[a[0]*this.j,a[1]*this.j];b=this.c.f.b;b.bindFramebuffer(b.FRAMEBUFFER,this.g);var e=new Uint8Array(4);b.readPixels(a[0],a[1],1,1,b.RGBA,b.UNSIGNED_BYTE,e);if(0<e[3])return c.call(d,this.a,e)}};function cw(a){a=a?a:{};var b=tb({},a);delete b.preload;delete b.useInterimTilesOnError;wh.call(this,b);this.zi(void 0!==a.preload?a.preload:0);this.Ai(void 0!==a.useInterimTilesOnError?a.useInterimTilesOnError:!0)}v(cw,wh);k=cw.prototype;k.Fd=function(a){var b=null,c=a.U();"canvas"===c?b=new Vv(this):"webgl"===c&&(b=new bw(a,this));return b};k.Ud=function(){return this.get("preload")};k.zi=function(a){this.set("preload",a)};k.kd=function(){return this.get("useInterimTilesOnError")};
+k.Ai=function(a){this.set("useInterimTilesOnError",a)};function dw(a){this.c=null;Vv.call(this,a);this.I=!1;this.D=Bh();this.T="vector"==a.j?1:0}v(dw,Vv);var ew={image:ji,hybrid:["Polygon","LineString"]},fw={hybrid:["Image","Text"],vector:ji};k=dw.prototype;k.sd=function(a,b){var c=this.a,d=c.i;this.pa!=d&&(this.g.length=0,c=c.j,this.c||"vector"==c||(this.c=jd()),this.c&&"vector"==c&&(this.c=null));this.pa=d;return Vv.prototype.sd.apply(this,arguments)};
+k.Of=function(a,b,c,d,e,f,g,h){var l=a,m=this.a,n=b.pixelRatio,p=b.viewState.projection,q=m.i,r=m.get(Pt)||null,u=l.o;if(u.Nd||u.mf!=q||u.Kg!=r){for(var x=0,B=l.a.length;x<B;++x){var E=l.c[l.a[x]];E.S=null;u.Nd=!1;var A=m.ha(),L=A.tileGrid,oa=E.ta,ha=E.a,ga=A.Ta(p),z=ga.Da(l.ta[0]),M=L.Da(E.ta[0]),ga=ga.Aa(l.v),oa=L.Aa(oa),ga=pb(ga,oa);if("tile-pixels"==ha.a)var ba=L=A.nb(),M=Kh(this.D,0,0,1/M*ba,-1/M*ba,0,-oa[0],-oa[3]),M=Gh(M,[ga[0],ga[3]]).concat(Gh(M,[ga[2],ga[1]]));else if(L=z,M=ga,!dc(p,ha)){var da=
+!0;E.ig(p)}u.Nd=!1;A=new pt(0,M,L,A.l,m.c);M=Lt(L,n);L=E.g;r&&r!==u.Kg&&L.sort(r);oa=0;for(ga=L.length;oa<ga;++oa){ba=L[oa];da&&ba.V().tb(ha,p);var fb=void 0,ca=ba.Lc();ca?fb=ca.call(ba,z):(ca=m.f)&&(fb=ca(ba,z));if(fb){Array.isArray(fb)||(fb=[fb]);var ca=M,Ub=A;if(fb){var uc=!1;if(Array.isArray(fb))for(var bc=0,Je=fb.length;bc<Je;++bc)uc=Mt(Ub,ba,fb[bc],ca,this.Fi,this)||uc;else uc=Mt(Ub,ba,fb,ca,this.Fi,this)||uc;ba=uc}else ba=!1;this.I=this.I||ba;u.Nd=u.Nd||ba}}tt(A);E.c[l.ta.toString()]=A}u.mf=
+q;u.Kg=r}if(this.c){x=b;p=this.a;n=l.o;q=p.i;if((m=ew[p.j])&&n.Lg!==q)for(n.Lg=q,B=l.v,E=B[0],n=x.pixelRatio,z=p.ha(),p=z.tileGrid,ha=z.Ta(x.viewState.projection),q=ha.Da(E),r=z.nb(),l.j||(l.j=jd()),u=l.j,x=z.Xd(E,n,x.viewState.projection),u.canvas.width=x[0],u.canvas.height=x[1],x=ha.Aa(B),B=0,E=l.a.length;B<E;++B)ha=l.c[l.a[B]],A=ha.ta,da=n/q,z=Ch(this.D),"tile-pixels"==ha.a.a?(da=p.Aa(A,this.l),A=p.Da(A[0]),M=n/r*A/q,Ih(z,M,M),Jh(z,Math.round((da[0]-x[0])/A*r),Math.round((x[3]-da[3])/A*r))):(Ih(z,
+da,-da),Jh(z,-x[0],-x[3])),ha.c[l.ta.toString()].La(u,n,z,0,{},m);Vv.prototype.Of.apply(this,arguments)}};
+k.Ea=function(a,b,c,d,e){var f=b.viewState.resolution,g=b.viewState.rotation;c=void 0==c?0:c;var h=this.a,l={},m=this.g,n=h.ha();b=n.Ta(b.viewState.projection);var p=n.tileGrid,q;var r=0;for(q=m.length;r<q;++r){var u=m[r];var x=u.ta;x=b.Aa(x,this.l);var B=Qa(x,c*f,B);if(Ta(B,a)){x=0;for(var E=u.a.length;x<E;++x){var A=u.c[u.a[x]];if("tile-pixels"===A.a.a){var L=A.ta;f=p.Aa(L,this.l);var oa=ib(f);f=n.nb();L=p.Da(L[0])/f;oa=[(a[0]-oa[0])/L,(oa[1]-a[1])/L]}else oa=a;A=A.c[u.ta];var ha=ha||A.Ea(oa,f,
+g,c,{},function(a){var b=w(a).toString();if(!(b in l))return l[b]=!0,d.call(e,a,h)})}}}return ha};k.Fi=function(){zt(this)};
+k.ef=function(a,b,c){var d=this.a,e=d.ha(),f=fw[d.j];if(f)for(var g=b.pixelRatio,h=b.viewState.rotation,l=b.size,m=Math.round(g*l[0]/2),l=Math.round(g*l[1]/2),n=this.g,d=d.ha().nb(),p=e.tileGrid,e=e.Ta(b.viewState.projection),q=[],r=[],u=n.length-1;0<=u;--u){var x=n[u];if(5!=x.getState())for(var B=x.ta,E=e.Aa(B)[0]-e.Aa(x.v)[0],A=0,L=x.a.length;A<L;++A){var oa=x.c[x.a[A]],ha=oa.ta[0],ga=p.Da(ha);var z=oa;var M=b;if("tile-pixels"==z.a.a){var ba=this.a.ha(),da=ba.tileGrid,fb=z.ta,ba=da.Da(fb[0])/ba.nb(),
+z=M.viewState,ca=M.pixelRatio,Ub=z.resolution/ca,fb=da.Aa(fb,this.l),da=z.center,fb=ib(fb);M=M.size;M=Kh(this.D,Math.round(ca*M[0]/2),Math.round(ca*M[1]/2),ba/Ub,ba/Ub,z.rotation,(fb[0]-da[0])/ba,(da[1]-fb[1])/ba)}else M=Jt(this,M,0);Jh(M,E*d/ga,0);oa=oa.c[B.toString()];ga=vt(oa,M);a.save();a.globalAlpha=c.opacity;Vh(a,-h,m,l);ba=0;for(z=q.length;ba<z;++ba)ca=q[ba],ha<r[ba]&&(a.beginPath(),a.moveTo(ga[0],ga[1]),a.lineTo(ga[2],ga[3]),a.lineTo(ga[4],ga[5]),a.lineTo(ga[6],ga[7]),a.moveTo(ca[6],ca[7]),
+a.lineTo(ca[4],ca[5]),a.lineTo(ca[2],ca[3]),a.lineTo(ca[0],ca[1]),a.clip());oa.La(a,g,M,h,{},f);a.restore();q.push(ga);r.push(ha)}}Vv.prototype.ef.apply(this,arguments)};function W(a){a=a?a:{};var b=tb({},a);delete b.preload;delete b.useInterimTilesOnError;T.call(this,b);this.Bi(a.preload?a.preload:0);this.Ci(a.useInterimTilesOnError?a.useInterimTilesOnError:!0);xa(void 0==a.renderMode||"image"==a.renderMode||"hybrid"==a.renderMode||"vector"==a.renderMode,28);this.j=a.renderMode||"hybrid"}v(W,T);k=W.prototype;k.Fd=function(a){var b=null;"canvas"===a.U()&&(b=new dw(this));return b};k.Ud=function(){return this.get("preload")};k.kd=function(){return this.get("useInterimTilesOnError")};
+k.Bi=function(a){this.set("preload",a)};k.Ci=function(a){this.set("useInterimTilesOnError",a)};function gw(a,b,c,d){function e(){delete window[g];f.parentNode.removeChild(f)}var f=document.createElement("script"),g="olc_"+w(b);f.async=!0;f.src=a+(-1==a.indexOf("?")?"?":"&")+(d||"callback")+"="+g;var h=setTimeout(function(){e();c&&c()},1E4);window[g]=function(a){clearTimeout(h);e();b(a)};document.getElementsByTagName("head")[0].appendChild(f)};function hw(a,b,c,d,e,f,g,h,l,m,n){Ls.call(this,e,0);this.D=void 0!==n?n:!1;this.S=g;this.u=h;this.v=null;this.c=b;this.j=d;this.o=f?f:e;this.a=[];this.yd=null;this.g=0;f=d.Aa(this.o);h=this.j.G();e=this.c.G();f=h?pb(f,h):f;if(jb(f))if((h=a.G())&&(e?e=pb(e,h):e=h),d=Av(a,c,nb(f),d.Da(this.o[0])),!isFinite(d)||0>=d)this.state=4;else if(this.l=new Dv(a,c,f,e,d*(void 0!==m?m:.5)),this.l.c.length)if(this.g=b.tc(d),c=Fv(this.l),e&&(a.i?(c[1]=Ca(c[1],e[1],e[3]),c[3]=Ca(c[3],e[1],e[3])):c=pb(c,e)),jb(c)){a=
+oc(b,c,this.g);for(b=a.ca;b<=a.$;b++)for(c=a.da;c<=a.ia;c++)(m=l(this.g,b,c,g))&&this.a.push(m);this.a.length||(this.state=4)}else this.state=4;else this.state=4;else this.state=4}v(hw,Ls);hw.prototype.ka=function(){1==this.state&&(this.yd.forEach(Ec),this.yd=null);Ls.prototype.ka.call(this)};hw.prototype.Y=function(){return this.v};
+hw.prototype.de=function(){var a=[];this.a.forEach(function(b){b&&2==b.getState()&&a.push({extent:this.c.Aa(b.ta),image:b.Y()})},this);this.a.length=0;if(a.length){var b=this.o[0],c=this.j.gb(b),d="number"===typeof c?c:c[0],c="number"===typeof c?c:c[1],b=this.j.Da(b),e=this.c.Da(this.g),f=this.j.Aa(this.o);this.v=Cv(d,c,this.S,e,this.c.G(),b,f,this.l,a,this.u,this.D);this.state=2}else this.state=3;this.s()};
+hw.prototype.load=function(){if(0==this.state){this.state=1;this.s();var a=0;this.yd=[];this.a.forEach(function(b){var c=b.getState();if(0==c||1==c){a++;var d=y(b,"change",function(){var c=b.getState();if(2==c||3==c||4==c)Ec(d),a--,a||(this.yd.forEach(Ec),this.yd=null,this.de())},this);this.yd.push(d)}},this);this.a.forEach(function(a){0==a.getState()&&a.load()});a||setTimeout(this.de.bind(this),0)}};function iw(a,b){var c=/\{z\}/g,d=/\{x\}/g,e=/\{y\}/g,f=/\{-y\}/g;return function(g){if(g)return a.replace(c,g[0].toString()).replace(d,g[1].toString()).replace(e,function(){return(-g[2]-1).toString()}).replace(f,function(){var a=b.a?b.a[g[0]]:null;xa(a,55);return(a.ia-a.da+1+g[2]).toString()})}}function jw(a,b){for(var c=a.length,d=Array(c),e=0;e<c;++e)d[e]=iw(a[e],b);return kw(d)}function kw(a){return 1===a.length?a[0]:function(b,c,d){if(b)return a[Ia((b[1]<<b[0])+b[2],a.length)](b,c,d)}}
+function lw(){}function mw(a){var b=[],c=/\{([a-z])-([a-z])\}/.exec(a);if(c){var d=c[2].charCodeAt(0),e;for(e=c[1].charCodeAt(0);e<=d;++e)b.push(a.replace(c[0],String.fromCharCode(e)));return b}if(c=c=/\{(\d+)-(\d+)\}/.exec(a)){d=parseInt(c[2],10);for(e=parseInt(c[1],10);e<=d;e++)b.push(a.replace(c[0],e.toString()));return b}b.push(a);return b};function nw(a){lk.call(this);this.highWaterMark=void 0!==a?a:2048}v(nw,lk);function ow(a){return a.c>a.highWaterMark}nw.prototype.fd=function(a){for(var b,c;ow(this);){b=this.a.Yc;c=b.ta[0].toString();var d;if(d=c in a)b=b.ta,d=za(a[c],b[1],b[2]);if(d)break;else Nc(this.pop())}};function pw(a){$t.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,projection:a.projection,state:a.state,wrapX:a.wrapX});this.va=void 0!==a.opaque?a.opaque:!1;this.$a=void 0!==a.tilePixelRatio?a.tilePixelRatio:1;this.tileGrid=void 0!==a.tileGrid?a.tileGrid:null;this.a=new nw(a.cacheSize);this.o=[0,0];this.uc=""}v(pw,$t);k=pw.prototype;k.Ki=function(){return ow(this.a)};k.fd=function(a,b){(a=this.Wd(a))&&a.fd(b)};
+function yt(a,b,c,d,e){b=a.Wd(b);if(!b)return!1;for(var f=!0,g,h,l=d.ca;l<=d.$;++l)for(var m=d.da;m<=d.ia;++m)g=a.Sb(c,l,m),h=!1,b.b.hasOwnProperty(g)&&(g=b.get(g),(h=2===g.getState())&&(h=!1!==e(g))),h||(f=!1);return f}k.Wf=function(){return 0};function qw(a,b){a.uc!==b&&(a.uc=b,a.s())}k.Sb=function(a,b,c){return a+"/"+b+"/"+c};k.Zf=function(){return this.va};k.ab=function(){return this.tileGrid};k.Ta=function(a){return this.tileGrid?this.tileGrid:vc(a)};
+k.Wd=function(a){var b=this.c;return b&&!dc(b,a)?null:this.a};k.nb=function(){return this.$a};k.Xd=function(a,b,c){c=this.Ta(c);b=this.nb(b);a=Ma(c.gb(a),this.o);return 1==b?a:La(a,b,this.o)};function rw(a,b,c){var d=void 0!==c?c:a.c;c=a.Ta(d);if(a.u&&d.c){var e=b;b=e[0];a=tc(c,e);d=zc(d);Ta(d,a)?b=e:(e=lb(d),a[0]+=e*Math.ceil((d[0]-a[0])/e),b=c.bg(a,b))}e=b[0];d=b[1];a=b[2];if(c.minZoom>e||e>c.maxZoom)c=!1;else{var f=c.G();c=(c=f?oc(c,f,e):c.a?c.a[e]:null)?za(c,d,a):!0}return c?b:null}
+k.sa=function(){this.a.clear();this.s()};k.Ug=ua;function sw(a,b){Oc.call(this,a);this.tile=b}v(sw,Oc);function tw(a){pw.call(this,{attributions:a.attributions,cacheSize:a.cacheSize,extent:a.extent,logo:a.logo,opaque:a.opaque,projection:a.projection,state:a.state,tileGrid:a.tileGrid,tilePixelRatio:a.tilePixelRatio,wrapX:a.wrapX});this.tileLoadFunction=a.tileLoadFunction;this.tileUrlFunction=this.Fc?this.Fc.bind(this):lw;this.urls=null;a.urls?this.eb(a.urls):a.url&&this.jb(a.url);a.tileUrlFunction&&this.cb(a.tileUrlFunction)}v(tw,pw);k=tw.prototype;k.pb=function(){return this.tileLoadFunction};
+k.qb=function(){return this.tileUrlFunction};k.rb=function(){return this.urls};k.Li=function(a){a=a.target;switch(a.getState()){case 1:this.b(new sw("tileloadstart",a));break;case 2:this.b(new sw("tileloadend",a));break;case 3:this.b(new sw("tileloaderror",a))}};k.vb=function(a){this.a.clear();this.tileLoadFunction=a;this.s()};k.cb=function(a,b){this.tileUrlFunction=a;"undefined"!==typeof b?qw(this,b):this.s()};
+k.jb=function(a){var b=this.urls=mw(a);this.cb(this.Fc?this.Fc.bind(this):jw(b,this.tileGrid),a)};k.eb=function(a){this.urls=a;var b=a.join("\n");this.cb(this.Fc?this.Fc.bind(this):jw(a,this.tileGrid),b)};k.Ug=function(a,b,c){a=this.Sb(a,b,c);this.a.b.hasOwnProperty(a)&&this.a.get(a)};function X(a){tw.call(this,{attributions:a.attributions,cacheSize:a.cacheSize,extent:a.extent,logo:a.logo,opaque:a.opaque,projection:a.projection,state:a.state,tileGrid:a.tileGrid,tileLoadFunction:a.tileLoadFunction?a.tileLoadFunction:uw,tilePixelRatio:a.tilePixelRatio,tileUrlFunction:a.tileUrlFunction,url:a.url,urls:a.urls,wrapX:a.wrapX});this.crossOrigin=void 0!==a.crossOrigin?a.crossOrigin:null;this.tileClass=a.tileClass?a.tileClass:Os;this.g={};this.v={};this.Sa=a.reprojectionErrorThreshold;this.I=
+!1}v(X,tw);k=X.prototype;k.Ki=function(){if(ow(this.a))return!0;for(var a in this.g)if(ow(this.g[a]))return!0;return!1};k.fd=function(a,b){a=this.Wd(a);this.a.fd(this.a==a?b:{});for(var c in this.g){var d=this.g[c];d.fd(d==a?b:{})}};k.Wf=function(a){return this.c&&a&&!dc(this.c,a)?0:this.Xf()};k.Xf=function(){return 0};k.Zf=function(a){return this.c&&a&&!dc(this.c,a)?!1:tw.prototype.Zf.call(this,a)};
+k.Ta=function(a){var b=this.c;return!this.tileGrid||b&&!dc(b,a)?(b=w(a).toString(),b in this.v||(this.v[b]=vc(a)),this.v[b]):this.tileGrid};k.Wd=function(a){var b=this.c;if(!b||dc(b,a))return this.a;a=w(a).toString();a in this.g||(this.g[a]=new nw(this.a.highWaterMark));return this.g[a]};function vw(a,b,c,d,e,f,g){b=[b,c,d];e=(c=rw(a,b,f))?a.tileUrlFunction(c,e,f):void 0;e=new a.tileClass(b,void 0!==e?0:4,void 0!==e?e:"",a.crossOrigin,a.tileLoadFunction);e.key=g;y(e,"change",a.Li,a);return e}
+k.Nc=function(a,b,c,d,e){if(this.c&&e&&!dc(this.c,e)){var f=this.Wd(e);c=[a,b,c];var g;a=this.Sb.apply(this,c);f.b.hasOwnProperty(a)&&(g=f.get(a));b=this.uc;if(g&&g.key==b)return g;var h=this.c,l=this.Ta(h),m=this.Ta(e),n=rw(this,c,e);d=new hw(h,l,e,m,c,n,this.nb(d),this.Xf(),function(a,b,c,d){return ww(this,a,b,c,d,h)}.bind(this),this.Sa,this.I);d.key=b;g?(d.i=g,f.replace(a,d)):f.set(a,d);return d}return ww(this,a,b,c,d,e)};
+function ww(a,b,c,d,e,f){var g=a.Sb(b,c,d),h=a.uc;if(a.a.b.hasOwnProperty(g)){var l=a.a.get(g);if(l.key!=h){var m=l;l=vw(a,b,c,d,e,f,h);0==m.getState()?l.i=m.i:l.i=m;if(l.i){b=l.i;c=l;do{if(2==b.getState()){b.i=null;break}else 1==b.getState()?c=b:0==b.getState()?c.i=b.i:c=b;b=c.i}while(b)}a.a.replace(g,l)}}else l=vw(a,b,c,d,e,f,h),a.a.set(g,l);return l}k.Pb=function(a){if(this.I!=a){this.I=a;for(var b in this.g)this.g[b].clear();this.s()}};
+k.Qb=function(a,b){if(a=Tb(a))a=w(a).toString(),a in this.v||(this.v[a]=b)};function uw(a,b){a.Y().src=b};function xw(a){this.B=void 0!==a.hidpi?a.hidpi:!1;X.call(this,{cacheSize:a.cacheSize,crossOrigin:"anonymous",opaque:!0,projection:Tb("EPSG:3857"),reprojectionErrorThreshold:a.reprojectionErrorThreshold,state:"loading",tileLoadFunction:a.tileLoadFunction,tilePixelRatio:this.B?2:1,wrapX:void 0!==a.wrapX?a.wrapX:!0});this.R=void 0!==a.culture?a.culture:"en-us";this.C=void 0!==a.maxZoom?a.maxZoom:-1;this.f=a.key;this.l=a.imagerySet;gw("https://dev.virtualearth.net/REST/v1/Imagery/Metadata/"+this.l+"?uriScheme=https&include=ImageryProviders&key="+
+this.f,this.pa.bind(this),void 0,"jsonp")}v(xw,X);var yw=new Ac({html:'<a class="ol-attribution-bing-tos" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'});xw.prototype.T=function(){return this.f};xw.prototype.fa=function(){return this.l};
+xw.prototype.pa=function(a){if(200!=a.statusCode||"OK"!=a.statusDescription||"ValidCredentials"!=a.authenticationResultCode||1!=a.resourceSets.length||1!=a.resourceSets[0].resources.length)bu(this,"error");else{var b=a.brandLogoUri;-1==b.indexOf("https")&&(b=b.replace("http","https"));var c=a.resourceSets[0].resources[0],d=-1==this.C?c.zoomMax:this.C;a=zc(this.c);var e=xc({extent:a,minZoom:c.zoomMin,maxZoom:d,tileSize:(c.imageWidth==c.imageHeight?c.imageWidth:[c.imageWidth,c.imageHeight])/this.nb()});
+this.tileGrid=e;var f=this.R,g=this.B;this.tileUrlFunction=kw(c.imageUrlSubdomains.map(function(a){var b=[0,0,0],d=c.imageUrl.replace("{subdomain}",a).replace("{culture}",f);return function(a){if(a)return jc(a[0],a[1],-a[2]-1,b),a=d,g&&(a+="&dpi=d1&device=mobile"),a.replace("{quadkey}",kc(b))}}));if(c.imageryProviders){var h=Vb(Tb("EPSG:4326"),this.c);a=c.imageryProviders.map(function(a){var b=a.attribution,c={};a.coverageAreas.forEach(function(a){var b=a.zoomMin,f=Math.min(a.zoomMax,d);a=a.bbox;
+a=sb([a[1],a[0],a[3],a[2]],h);var g;for(g=b;g<=f;++g){var l=g.toString();b=oc(e,a,g);l in c?c[l].push(b):c[l]=[b]}});return new Ac({html:b,tileRanges:c})});a.push(yw);this.ua(a)}this.D=b;bu(this,"ready")}};function zw(a){a=a||{};var b=void 0!==a.projection?a.projection:"EPSG:3857",c=void 0!==a.tileGrid?a.tileGrid:xc({extent:zc(b),maxZoom:a.maxZoom,minZoom:a.minZoom,tileSize:a.tileSize});X.call(this,{attributions:a.attributions,cacheSize:a.cacheSize,crossOrigin:a.crossOrigin,logo:a.logo,opaque:a.opaque,projection:b,reprojectionErrorThreshold:a.reprojectionErrorThreshold,tileGrid:c,tileLoadFunction:a.tileLoadFunction,tilePixelRatio:a.tilePixelRatio,tileUrlFunction:a.tileUrlFunction,url:a.url,urls:a.urls,
+wrapX:void 0!==a.wrapX?a.wrapX:!0})}v(zw,X);function Aw(a){this.C=a.account;this.B=a.map||"";this.f=a.config||{};this.l={};zw.call(this,{attributions:a.attributions,cacheSize:a.cacheSize,crossOrigin:a.crossOrigin,logo:a.logo,maxZoom:void 0!==a.maxZoom?a.maxZoom:18,minZoom:a.minZoom,projection:a.projection,state:"loading",wrapX:a.wrapX});Bw(this)}v(Aw,zw);k=Aw.prototype;k.Kk=function(){return this.f};k.tq=function(a){tb(this.f,a);Bw(this)};k.Xp=function(a){this.f=a||{};Bw(this)};
+function Bw(a){var b=JSON.stringify(a.f);if(a.l[b])Cw(a,a.l[b]);else{var c="https://"+a.C+".cartodb.com/api/v1/map";a.B&&(c+="/named/"+a.B);var d=new XMLHttpRequest;d.addEventListener("load",a.Dl.bind(a,b));d.addEventListener("error",a.Cl.bind(a));d.open("POST",c);d.setRequestHeader("Content-type","application/json");d.send(JSON.stringify(a.f))}}
+k.Dl=function(a,b){b=b.target;if(!b.status||200<=b.status&&300>b.status){try{var c=JSON.parse(b.responseText)}catch(d){bu(this,"error");return}Cw(this,c);this.l[a]=c;bu(this,"ready")}else bu(this,"error")};k.Cl=function(){bu(this,"error")};function Cw(a,b){a.jb("https://"+b.cdn_url.https+"/"+a.C+"/api/v1/map/"+b.layergroupid+"/{z}/{x}/{y}.png")};function Y(a){U.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,projection:a.projection,wrapX:a.wrapX});this.resolution=void 0;this.distance=void 0!==a.distance?a.distance:20;this.features=[];this.geometryFunction=a.geometryFunction||function(a){a=a.V();xa(a instanceof C,10);return a};this.source=a.source;this.source.J("change",Y.prototype.sa,this)}v(Y,U);k=Y.prototype;k.$n=function(){return this.distance};k.ao=function(){return this.source};
+k.Yd=function(a,b,c){this.source.Yd(a,b,c);b!==this.resolution&&(this.clear(),this.resolution=b,Dw(this),this.cd(this.features))};k.Yp=function(a){this.distance=a;this.sa()};k.sa=function(){this.clear();Dw(this);this.cd(this.features);U.prototype.sa.call(this)};
+function Dw(a){if(void 0!==a.resolution){a.features.length=0;for(var b=Oa(),c=a.distance*a.resolution,d=a.source.Xe(),e={},f=0,g=d.length;f<g;f++){var h=d[f];w(h).toString()in e||!(h=a.geometryFunction(h))||(h=h.X(),Za(h,b),Qa(b,c,b),h=a.source.Uf(b),h=h.filter(function(a){a=w(a).toString();return a in e?!1:e[a]=!0}),a.features.push(Ew(a,h)))}}}
+function Ew(a,b){for(var c=[0,0],d=b.length-1;0<=d;--d){var e=a.geometryFunction(b[d]);e?Ze(c,e.X()):b.splice(d,1)}gf(c,1/b.length);a=new H(new C(c));a.set("features",b);return a};function Fw(a,b){var c=[];Object.keys(b).forEach(function(a){null!==b[a]&&void 0!==b[a]&&c.push(a+"="+encodeURIComponent(b[a]))});var d=c.join("&");a=a.replace(/[?&]$/,"");a=-1===a.indexOf("?")?a+"?":a+"&";return a+d};function Gw(a){a=a||{};Hv.call(this,{attributions:a.attributions,logo:a.logo,projection:a.projection,resolutions:a.resolutions});this.R=void 0!==a.crossOrigin?a.crossOrigin:null;this.T=void 0!==a.hidpi?a.hidpi:!0;this.f=a.url;this.g=a.imageLoadFunction?a.imageLoadFunction:Nv;this.v=a.params||{};this.M=null;this.l=[0,0];this.I=0;this.B=void 0!==a.ratio?a.ratio:1.5}v(Gw,Hv);k=Gw.prototype;k.co=function(){return this.v};
+k.Jc=function(a,b,c,d){if(void 0===this.f)return null;b=Iv(this,b);c=this.T?c:1;var e=this.M;if(e&&this.I==this.i&&e.resolution==b&&e.a==c&&Va(e.G(),a))return e;e={F:"image",FORMAT:"PNG32",TRANSPARENT:!0};tb(e,this.v);a=a.slice();var f=(a[0]+a[2])/2,g=(a[1]+a[3])/2;if(1!=this.B){var h=this.B*lb(a)/2,l=this.B*mb(a)/2;a[0]=f-h;a[1]=g-l;a[2]=f+h;a[3]=g+l}var h=b/c,l=Math.ceil(lb(a)/h),m=Math.ceil(mb(a)/h);a[0]=f-h*l/2;a[2]=f+h*l/2;a[1]=g-h*m/2;a[3]=g+h*m/2;this.l[0]=l;this.l[1]=m;f=a;g=this.l;h=c;d=
+d.mb.split(":").pop();e.SIZE=g[0]+","+g[1];e.BBOX=f.join(",");e.BBOXSR=d;e.IMAGESR=d;e.DPI=Math.round(90*h);d=this.f;f=d.replace(/MapServer\/?$/,"MapServer/export").replace(/ImageServer\/?$/,"ImageServer/exportImage");f==d&&xa(!1,50);e=Fw(f,e);this.M=new Js(a,b,c,this.j,e,this.R,this.g);this.I=this.i;y(this.M,"change",this.o,this);return this.M};k.bo=function(){return this.g};k.eo=function(){return this.f};k.fo=function(a){this.M=null;this.g=a;this.s()};
+k.ho=function(a){a!=this.f&&(this.f=a,this.M=null,this.s())};k.io=function(a){tb(this.v,a);this.M=null;this.s()};function Hw(a){Hv.call(this,{projection:a.projection,resolutions:a.resolutions});this.R=void 0!==a.crossOrigin?a.crossOrigin:null;this.l=void 0!==a.displayDpi?a.displayDpi:96;this.g=a.params||{};this.I=a.url;this.f=a.imageLoadFunction?a.imageLoadFunction:Nv;this.T=void 0!==a.hidpi?a.hidpi:!0;this.pa=void 0!==a.metersPerUnit?a.metersPerUnit:1;this.v=void 0!==a.ratio?a.ratio:1;this.va=void 0!==a.useOverlay?a.useOverlay:!1;this.M=null;this.B=0}v(Hw,Hv);k=Hw.prototype;k.ko=function(){return this.g};
+k.Jc=function(a,b,c){b=Iv(this,b);c=this.T?c:1;var d=this.M;if(d&&this.B==this.i&&d.resolution==b&&d.a==c&&Va(d.G(),a))return d;1!=this.v&&(a=a.slice(),rb(a,this.v));var e=[lb(a)/b*c,mb(a)/b*c];if(void 0!==this.I){var d=this.I,f=nb(a),g=this.pa,h=lb(a),l=mb(a),m=e[0],n=e[1],p=.0254/this.l,e={OPERATION:this.va?"GETDYNAMICMAPOVERLAYIMAGE":"GETMAPIMAGE",VERSION:"2.0.0",LOCALE:"en",CLIENTAGENT:"ol.source.ImageMapGuide source",CLIP:"1",SETDISPLAYDPI:this.l,SETDISPLAYWIDTH:Math.round(e[0]),SETDISPLAYHEIGHT:Math.round(e[1]),
+SETVIEWSCALE:n*h>m*l?h*g/(m*p):l*g/(n*p),SETVIEWCENTERX:f[0],SETVIEWCENTERY:f[1]};tb(e,this.g);d=Fw(d,e);d=new Js(a,b,c,this.j,d,this.R,this.f);y(d,"change",this.o,this)}else d=null;this.M=d;this.B=this.i;return d};k.jo=function(){return this.f};k.mo=function(a){tb(this.g,a);this.s()};k.lo=function(a){this.M=null;this.f=a;this.s()};function Iw(a){var b=a.imageExtent,c=void 0!==a.crossOrigin?a.crossOrigin:null,d=a.imageLoadFunction?a.imageLoadFunction:Nv;Hv.call(this,{attributions:a.attributions,logo:a.logo,projection:Tb(a.projection)});this.M=new Js(b,void 0,1,this.j,a.url,c,d);this.f=a.imageSize?a.imageSize:null;y(this.M,"change",this.o,this)}v(Iw,Hv);Iw.prototype.Jc=function(a){return qb(a,this.M.G())?this.M:null};
+Iw.prototype.o=function(a){if(2==this.M.getState()){var b=this.M.G(),c=this.M.Y();if(this.f){var d=this.f[0];var e=this.f[1]}else d=c.width,e=c.height;b=Math.ceil(lb(b)/(mb(b)/e));if(b!=d){var b=jd(b,e),f=b.canvas;b.drawImage(c,0,0,d,e,0,0,f.width,f.height);this.M.Og(f)}}Hv.prototype.o.call(this,a)};function Jw(a){a=a||{};Hv.call(this,{attributions:a.attributions,logo:a.logo,projection:a.projection,resolutions:a.resolutions});this.pa=void 0!==a.crossOrigin?a.crossOrigin:null;this.g=a.url;this.v=a.imageLoadFunction?a.imageLoadFunction:Nv;this.f=a.params||{};this.l=!0;Kw(this);this.T=a.serverType;this.va=void 0!==a.hidpi?a.hidpi:!0;this.M=null;this.B=[0,0];this.R=0;this.I=void 0!==a.ratio?a.ratio:1.5}v(Jw,Hv);var Lw=[101,101];k=Jw.prototype;
+k.so=function(a,b,c,d){if(void 0!==this.g){var e=ob(a,b,0,Lw),f={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:this.f.LAYERS};tb(f,this.f,d);d=Math.floor((e[3]-a[1])/b);f[this.l?"I":"X"]=Math.floor((a[0]-e[0])/b);f[this.l?"J":"Y"]=d;return Mw(this,e,Lw,1,Tb(c),f)}};k.uo=function(){return this.f};
+k.Jc=function(a,b,c,d){if(void 0===this.g)return null;b=Iv(this,b);1==c||this.va&&void 0!==this.T||(c=1);var e=b/c,f=nb(a),g=ob(f,e,0,[Math.ceil(lb(a)/e),Math.ceil(mb(a)/e)]);a=ob(f,e,0,[Math.ceil(this.I*lb(a)/e),Math.ceil(this.I*mb(a)/e)]);if((f=this.M)&&this.R==this.i&&f.resolution==b&&f.a==c&&Va(f.G(),g))return f;g={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};tb(g,this.f);this.B[0]=Math.round(lb(a)/e);this.B[1]=Math.round(mb(a)/e);d=Mw(this,a,this.B,c,d,g);
+this.M=new Js(a,b,c,this.j,d,this.pa,this.v);this.R=this.i;y(this.M,"change",this.o,this);return this.M};k.to=function(){return this.v};
+function Mw(a,b,c,d,e,f){xa(void 0!==a.g,9);f[a.l?"CRS":"SRS"]=e.mb;"STYLES"in a.f||(f.STYLES="");if(1!=d)switch(a.T){case "geoserver":d=90*d+.5|0;f.FORMAT_OPTIONS="FORMAT_OPTIONS"in f?f.FORMAT_OPTIONS+(";dpi:"+d):"dpi:"+d;break;case "mapserver":f.MAP_RESOLUTION=90*d;break;case "carmentaserver":case "qgis":f.DPI=90*d;break;default:xa(!1,8)}f.WIDTH=c[0];f.HEIGHT=c[1];c=e.b;var g;a.l&&"ne"==c.substr(0,2)?g=[b[1],b[0],b[3],b[2]]:g=b;f.BBOX=g.join(",");return Fw(a.g,f)}k.vo=function(){return this.g};
+k.wo=function(a){this.M=null;this.v=a;this.s()};k.xo=function(a){a!=this.g&&(this.g=a,this.M=null,this.s())};k.yo=function(a){tb(this.f,a);Kw(this);this.M=null;this.s()};function Kw(a){a.l=0<=Ye(a.f.VERSION||"1.3.0")};function Nw(a){a=a||{};var b;void 0!==a.attributions?b=a.attributions:b=[Ow];zw.call(this,{attributions:b,cacheSize:a.cacheSize,crossOrigin:void 0!==a.crossOrigin?a.crossOrigin:"anonymous",opaque:void 0!==a.opaque?a.opaque:!0,maxZoom:void 0!==a.maxZoom?a.maxZoom:19,reprojectionErrorThreshold:a.reprojectionErrorThreshold,tileLoadFunction:a.tileLoadFunction,url:void 0!==a.url?a.url:"https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png",wrapX:a.wrapX})}v(Nw,zw);var Ow=new Ac({html:'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.'});Fj.df={};Fj.df.Af=function(){};
+(function(a){function b(a,b,c){if(g)return new ImageData(a,b,c);b=h.createImageData(b,c);b.data.set(a);return b}function c(a){var b=!0;try{new ImageData(10,10)}catch(n){b=!1}return function(c){var d=c.buffers,e=c.meta,f=c.width,g=c.height,h=d.length,l=d[0].byteLength;if(c.imageOps){l=Array(h);for(c=0;c<h;++c){var m=c;var n=new Uint8ClampedArray(d[c]);var L=f,oa=g;n=b?new ImageData(n,L,oa):{data:n,width:L,height:oa};l[m]=n}f=a(l,e).data}else{f=new Uint8ClampedArray(l);g=Array(h);m=Array(h);for(c=0;c<
+h;++c)g[c]=new Uint8ClampedArray(d[c]),m[c]=[0,0,0,0];for(d=0;d<l;d+=4){for(c=0;c<h;++c)n=g[c],m[c][0]=n[d],m[c][1]=n[d+1],m[c][2]=n[d+2],m[c][3]=n[d+3];c=a(m,e);f[d]=c[0];f[d+1]=c[1];f[d+2]=c[2];f[d+3]=c[3]}}return f.buffer}}function d(a,b){var d=Object.keys(a.lib||{}).map(function(b){return"var "+b+" = "+a.lib[b].toString()+";"}).concat(["var __minion__ = ("+c.toString()+")(",a.operation.toString(),");",'self.addEventListener("message", function(event) {',"  var buffer = __minion__(event.data);",
+"  self.postMessage({buffer: buffer, meta: event.data.meta}, [buffer]);","});"]),d=URL.createObjectURL(new Blob(d,{type:"text/javascript"})),d=new Worker(d);d.addEventListener("message",b);return d}function e(a,b){var d=c(a.operation);return{postMessage:function(a){setTimeout(function(){b({data:{buffer:d(a),meta:a.meta}})},0)}}}function f(a){this.Ff=!!a.$l;var b;0===a.threads?b=0:this.Ff?b=1:b=a.threads||1;var c=[];if(b)for(var f=0;f<b;++f)c[f]=d(a,this.gh.bind(this,f));else c[0]=e(a,this.gh.bind(this,
+0));this.qe=c;this.Ed=[];this.fk=a.rp||Infinity;this.oe=0;this.bd={};this.Gf=null}var g=!0;try{new ImageData(10,10)}catch(l){g=!1}var h=document.createElement("canvas").getContext("2d");f.prototype.pp=function(a,b,c){this.dk({inputs:a,Qh:b,callback:c});this.dh()};f.prototype.dk=function(a){for(this.Ed.push(a);this.Ed.length>this.fk;)this.Ed.shift().callback(null,null)};f.prototype.dh=function(){if(0===this.oe&&0<this.Ed.length){var a=this.Gf=this.Ed.shift(),b=a.inputs[0].width,c=a.inputs[0].height,
+d=a.inputs.map(function(a){return a.data.buffer}),e=this.qe.length;this.oe=e;if(1===e)this.qe[0].postMessage({buffers:d,meta:a.Qh,imageOps:this.Ff,width:b,height:c},d);else for(var f=4*Math.ceil(a.inputs[0].data.length/4/e),g=0;g<e;++g){for(var h=g*f,B=[],E=0,A=d.length;E<A;++E)B.push(d[g].slice(h,h+f));this.qe[g].postMessage({buffers:B,meta:a.Qh,imageOps:this.Ff,width:b,height:c},B)}}};f.prototype.gh=function(a,b){this.Jq||(this.bd[a]=b.data,--this.oe,0===this.oe&&this.gk())};f.prototype.gk=function(){var a=
+this.Gf,c=this.qe.length;if(1===c){var d=new Uint8ClampedArray(this.bd[0].buffer);var e=this.bd[0].meta}else{var f=a.inputs[0].data.length;d=new Uint8ClampedArray(f);e=Array(f);for(var f=4*Math.ceil(f/4/c),g=0;g<c;++g){var h=g*f;d.set(new Uint8ClampedArray(this.bd[g].buffer),h);e[g]=this.bd[g].meta}}this.Gf=null;this.bd={};a.callback(null,b(d,a.inputs[0].width,a.inputs[0].height),e);this.dh()};a["default"]={Af:f};a.Af=f})(Fj.df=Fj.df||{});function Pw(a){this.B=null;this.va=void 0!==a.operationType?a.operationType:"pixel";this.Sa=void 0!==a.threads?a.threads:1;this.g=Qw(a.sources);for(var b=0,c=this.g.length;b<c;++b)y(this.g[b],"change",this.s,this);this.T=new Pe(function(){return 1},this.s.bind(this));for(var b=Rw(this.g),c={},d=0,e=b.length;d<e;++d)c[w(b[d].layer)]=b[d];this.f=null;this.I={animate:!1,attributions:{},coordinateToPixelTransform:Bh(),extent:null,focus:null,index:0,layerStates:c,layerStatesArray:b,logos:{},pixelRatio:1,
+pixelToCoordinateTransform:Bh(),postRenderFunctions:[],size:[0,0],skippedFeatureUids:{},tileQueue:this.T,time:Date.now(),usedTiles:{},viewState:{rotation:0},viewHints:[],wantedTiles:{}};Hv.call(this,{});a.operation&&this.v(a.operation,a.lib)}v(Pw,Hv);Pw.prototype.v=function(a,b){this.B=new Fj.df.Af({operation:a,$l:"image"===this.va,rp:1,lib:b,threads:this.Sa});this.s()};
+Pw.prototype.Y=function(a,b,c,d){c=!0;for(var e,f=0,g=this.g.length;f<g;++f)if(e=this.g[f].a.ha(),"ready"!==e.getState()){c=!1;break}if(!c)return null;c=tb({},this.I);c.viewState=tb({},c.viewState);e=nb(a);c.extent=a.slice();c.focus=e;c.size[0]=Math.round(lb(a)/b);c.size[1]=Math.round(mb(a)/b);f=c.viewState;f.center=e;f.projection=d;f.resolution=b;this.l=c;Qe(c.tileQueue,16,16);this.f&&(d=this.f.resolution,c=this.f.G(),b===d&&bb(a,c)||(this.f=null));if(!this.f||this.i!==this.R)a:{a=this.l;d=this.g.length;
+b=Array(d);for(c=0;c<d;++c){e=this.g[c];f=a;g=a.layerStatesArray[c];if(e.sd(f,g)){var h=f.size[0],l=f.size[1];if(Sw){var m=Sw.canvas;m.width!==h||m.height!==l?Sw=jd(h,l):Sw.clearRect(0,0,h,l)}else Sw=jd(h,l);e.S(f,g,Sw);e=Sw.getImageData(0,0,h,l)}else e=null;if(e)b[c]=e;else break a}d={};this.b(new Tw(Uw,a,d));this.B.pp(b,d,this.pa.bind(this,a))}return this.f};
+Pw.prototype.pa=function(a,b,c,d){if(!b&&c){b=a.extent;var e=a.viewState.resolution;if(e===this.l.viewState.resolution&&bb(b,this.l.extent)){if(this.f)var f=this.f.Y().getContext("2d");else f=jd(Math.round(lb(b)/e),Math.round(mb(b)/e)),this.f=new Ks(b,e,1,this.j,f.canvas);f.putImageData(c,0,0);this.s();this.R=this.i;this.b(new Tw(Vw,a,d))}}};var Sw=null;function Rw(a){return a.map(function(a){return th(a.a)})}
+function Qw(a){for(var b=a.length,c=Array(b),d=0;d<b;++d){var e=d,f=a[d],g=null;f instanceof pw?(f=new cw({source:f}),g=new Vv(f)):f instanceof Hv&&(f=new Uv({source:f}),g=new zv(f));c[e]=g}return c}function Tw(a,b,c){Oc.call(this,a);this.extent=b.extent;this.resolution=b.viewState.resolution/b.pixelRatio;this.data=c}v(Tw,Oc);Pw.prototype.Jc=function(){return null};var Uw="beforeoperations",Vw="afteroperations";function Ww(a){var b=a.layer.indexOf("-"),b=Xw[-1==b?a.layer:a.layer.slice(0,b)],c=Yw[a.layer];zw.call(this,{attributions:Zw,cacheSize:a.cacheSize,crossOrigin:"anonymous",maxZoom:void 0!=a.maxZoom?a.maxZoom:b.maxZoom,minZoom:void 0!=a.minZoom?a.minZoom:b.minZoom,opaque:c.opaque,reprojectionErrorThreshold:a.reprojectionErrorThreshold,tileLoadFunction:a.tileLoadFunction,url:void 0!==a.url?a.url:"https://stamen-tiles-{a-d}.a.ssl.fastly.net/"+a.layer+"/{z}/{x}/{y}."+c.Lb,wrapX:a.wrapX})}v(Ww,zw);
+var Zw=[new Ac({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>.'}),Ow],Yw={terrain:{Lb:"jpg",opaque:!0},"terrain-background":{Lb:"jpg",opaque:!0},"terrain-labels":{Lb:"png",opaque:!1},"terrain-lines":{Lb:"png",opaque:!1},"toner-background":{Lb:"png",opaque:!0},toner:{Lb:"png",opaque:!0},"toner-hybrid":{Lb:"png",opaque:!1},"toner-labels":{Lb:"png",opaque:!1},"toner-lines":{Lb:"png",opaque:!1},"toner-lite":{Lb:"png",
+opaque:!0},watercolor:{Lb:"jpg",opaque:!0}},Xw={terrain:{minZoom:4,maxZoom:18},toner:{minZoom:0,maxZoom:20},watercolor:{minZoom:1,maxZoom:16}};function $w(a){a=a||{};X.call(this,{attributions:a.attributions,cacheSize:a.cacheSize,crossOrigin:a.crossOrigin,logo:a.logo,projection:a.projection,reprojectionErrorThreshold:a.reprojectionErrorThreshold,tileGrid:a.tileGrid,tileLoadFunction:a.tileLoadFunction,url:a.url,urls:a.urls,wrapX:void 0!==a.wrapX?a.wrapX:!0});this.f=a.params||{};this.l=Oa();qw(this,ax(this))}v($w,X);function ax(a){var b=0,c=[],d;for(d in a.f)c[b++]=d+"-"+a.f[d];return c.join("/")}$w.prototype.C=function(){return this.f};
+$w.prototype.nb=function(a){return a};
+$w.prototype.Fc=function(a,b,c){var d=this.tileGrid;d||(d=this.Ta(c));if(!(d.b.length<=a[0])){var e=d.Aa(a,this.l),f=Ma(d.gb(a[0]),this.o);1!=b&&(f=La(f,b,this.o));d={F:"image",FORMAT:"PNG32",TRANSPARENT:!0};tb(d,this.f);var g=this.urls;g?(c=c.mb.split(":").pop(),d.SIZE=f[0]+","+f[1],d.BBOX=e.join(","),d.BBOXSR=c,d.IMAGESR=c,d.DPI=Math.round(d.DPI?d.DPI*b:90*b),a=(1==g.length?g[0]:g[Ia((a[1]<<a[0])+a[2],g.length)]).replace(/MapServer\/?$/,"MapServer/export").replace(/ImageServer\/?$/,"ImageServer/exportImage"),
+a=Fw(a,d)):a=void 0;return a}};$w.prototype.B=function(a){tb(this.f,a);qw(this,ax(this))};function bx(a){pw.call(this,{opaque:!1,projection:a.projection,tileGrid:a.tileGrid,wrapX:void 0!==a.wrapX?a.wrapX:!0})}v(bx,pw);bx.prototype.Nc=function(a,b,c){var d=this.Sb(a,b,c);if(this.a.b.hasOwnProperty(d))return this.a.get(d);var e=Ma(this.tileGrid.gb(a));a=[a,b,c];b=(b=rw(this,a))?rw(this,b).toString():"";e=new cx(a,e,b);this.a.set(d,e);return e};function cx(a,b,c){Ls.call(this,a,2);this.c=b;this.Ia=c;this.a=null}v(cx,Ls);
+cx.prototype.Y=function(){if(this.a)return this.a;var a=this.c,b=jd(a[0],a[1]);b.strokeStyle="black";b.strokeRect(.5,.5,a[0]+.5,a[1]+.5);b.fillStyle="black";b.textAlign="center";b.textBaseline="middle";b.font="24px sans-serif";b.fillText(this.Ia,a[0]/2,a[1]/2);return this.a=b.canvas};cx.prototype.load=function(){};function dx(a){this.f=null;X.call(this,{attributions:a.attributions,cacheSize:a.cacheSize,crossOrigin:a.crossOrigin,projection:Tb("EPSG:3857"),reprojectionErrorThreshold:a.reprojectionErrorThreshold,state:"loading",tileLoadFunction:a.tileLoadFunction,wrapX:void 0!==a.wrapX?a.wrapX:!0});if(a.url)if(a.jsonp)gw(a.url,this.og.bind(this),this.Ve.bind(this));else{var b=new XMLHttpRequest;b.addEventListener("load",this.Ao.bind(this));b.addEventListener("error",this.zo.bind(this));b.open("GET",a.url);b.send()}else a.tileJSON?
+this.og(a.tileJSON):xa(!1,51)}v(dx,X);k=dx.prototype;k.Ao=function(a){a=a.target;if(!a.status||200<=a.status&&300>a.status){try{var b=JSON.parse(a.responseText)}catch(c){this.Ve();return}this.og(b)}else this.Ve()};k.zo=function(){this.Ve()};k.pl=function(){return this.f};
+k.og=function(a){var b=Tb("EPSG:4326"),c=this.c;if(a.bounds){var d=Vb(b,c);var e=sb(a.bounds,d)}var f=a.minzoom||0,d=a.maxzoom||22;this.tileGrid=c=xc({extent:zc(c),maxZoom:d,minZoom:f});this.tileUrlFunction=jw(a.tiles,c);if(void 0!==a.attribution&&!this.j){b=void 0!==e?e:b.G();e={};for(var g;f<=d;++f)g=f.toString(),e[g]=[oc(c,b,f)];this.ua([new Ac({html:a.attribution,tileRanges:e})])}this.f=a;bu(this,"ready")};k.Ve=function(){bu(this,"error")};function ex(a){pw.call(this,{projection:Tb("EPSG:3857"),state:"loading"});this.v=void 0!==a.preemptive?a.preemptive:!0;this.l=lw;this.g=void 0;this.f=a.jsonp||!1;if(a.url)if(this.f)gw(a.url,this.pg.bind(this),this.We.bind(this));else{var b=new XMLHttpRequest;b.addEventListener("load",this.Eo.bind(this));b.addEventListener("error",this.Do.bind(this));b.open("GET",a.url);b.send()}else a.tileJSON?this.pg(a.tileJSON):xa(!1,51)}v(ex,pw);k=ex.prototype;
+k.Eo=function(a){a=a.target;if(!a.status||200<=a.status&&300>a.status){try{var b=JSON.parse(a.responseText)}catch(c){this.We();return}this.pg(b)}else this.We()};k.Do=function(){this.We()};k.ml=function(){return this.g};k.zk=function(a,b,c,d,e){this.tileGrid?(b=this.tileGrid.Be(a,b),fx(this.Nc(b[0],b[1],b[2],1,this.c),a,c,d,e)):!0===e?setTimeout(function(){c.call(d,null)},0):c.call(d,null)};k.We=function(){bu(this,"error")};
+k.pg=function(a){var b=Tb("EPSG:4326"),c=this.c;if(a.bounds){var d=Vb(b,c);var e=sb(a.bounds,d)}var f=a.minzoom||0,d=a.maxzoom||22;this.tileGrid=c=xc({extent:zc(c),maxZoom:d,minZoom:f});this.g=a.template;var g=a.grids;if(g){this.l=jw(g,c);if(void 0!==a.attribution){b=void 0!==e?e:b.G();for(e={};f<=d;++f)g=f.toString(),e[g]=[oc(c,b,f)];this.ua([new Ac({html:a.attribution,tileRanges:e})])}bu(this,"ready")}else bu(this,"error")};
+k.Nc=function(a,b,c,d,e){var f=this.Sb(a,b,c);if(this.a.b.hasOwnProperty(f))return this.a.get(f);a=[a,b,c];b=rw(this,a,e);d=this.l(b,d,e);d=new gx(a,void 0!==d?0:4,void 0!==d?d:"",this.tileGrid.Aa(a),this.v,this.f);this.a.set(f,d);return d};k.Ug=function(a,b,c){a=this.Sb(a,b,c);this.a.b.hasOwnProperty(a)&&this.a.get(a)};function gx(a,b,c,d,e,f){Ls.call(this,a,b);this.o=c;this.a=d;this.v=e;this.c=this.j=this.g=null;this.l=f}v(gx,Ls);k=gx.prototype;k.Y=function(){return null};
+k.getData=function(a){if(!this.g||!this.j)return null;var b=this.g[Math.floor((1-(a[1]-this.a[1])/(this.a[3]-this.a[1]))*this.g.length)];if("string"!==typeof b)return null;b=b.charCodeAt(Math.floor((a[0]-this.a[0])/(this.a[2]-this.a[0])*b.length));93<=b&&b--;35<=b&&b--;b-=32;a=null;b in this.j&&(b=this.j[b],this.c&&b in this.c?a=this.c[b]:a=b);return a};
+function fx(a,b,c,d,e){0==a.state&&!0===e?(Jc(a,"change",function(){c.call(d,this.getData(b))},a),hx(a)):!0===e?setTimeout(function(){c.call(d,this.getData(b))}.bind(a),0):c.call(d,a.getData(b))}k.bb=function(){return this.o};k.De=function(){this.state=3;this.s()};k.Ji=function(a){this.g=a.grid;this.j=a.keys;this.c=a.data;this.state=4;this.s()};
+function hx(a){if(0==a.state)if(a.state=1,a.l)gw(a.o,a.Ji.bind(a),a.De.bind(a));else{var b=new XMLHttpRequest;b.addEventListener("load",a.Co.bind(a));b.addEventListener("error",a.Bo.bind(a));b.open("GET",a.o);b.send()}}k.Co=function(a){a=a.target;if(!a.status||200<=a.status&&300>a.status){try{var b=JSON.parse(a.responseText)}catch(c){this.De();return}this.Ji(b)}else this.De()};k.Bo=function(){this.De()};k.load=function(){this.v&&hx(this)};function ix(a){a=a||{};var b=a.params||{};X.call(this,{attributions:a.attributions,cacheSize:a.cacheSize,crossOrigin:a.crossOrigin,logo:a.logo,opaque:!("TRANSPARENT"in b?b.TRANSPARENT:1),projection:a.projection,reprojectionErrorThreshold:a.reprojectionErrorThreshold,tileGrid:a.tileGrid,tileLoadFunction:a.tileLoadFunction,url:a.url,urls:a.urls,wrapX:void 0!==a.wrapX?a.wrapX:!0});this.C=void 0!==a.gutter?a.gutter:0;this.f=b;this.l=!0;this.B=a.serverType;this.T=void 0!==a.hidpi?a.hidpi:!0;this.R="";
+jx(this);this.fa=Oa();kx(this);qw(this,lx(this))}v(ix,X);k=ix.prototype;
+k.Fo=function(a,b,c,d){c=Tb(c);var e=this.tileGrid;e||(e=this.Ta(c));b=e.Be(a,b);if(!(e.b.length<=b[0])){var f=e.Da(b[0]),g=e.Aa(b,this.fa),e=Ma(e.gb(b[0]),this.o),h=this.C;h&&(e=Ka(e,h,this.o),g=Qa(g,f*h,g));h={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:this.f.LAYERS};tb(h,this.f,d);d=Math.floor((g[3]-a[1])/f);h[this.l?"I":"X"]=Math.floor((a[0]-g[0])/f);h[this.l?"J":"Y"]=d;return mx(this,b,e,g,1,c,h)}};k.Xf=function(){return this.C};
+k.Sb=function(a,b,c){return this.R+X.prototype.Sb.call(this,a,b,c)};k.Go=function(){return this.f};
+function mx(a,b,c,d,e,f,g){var h=a.urls;if(h){g.WIDTH=c[0];g.HEIGHT=c[1];g[a.l?"CRS":"SRS"]=f.mb;"STYLES"in a.f||(g.STYLES="");if(1!=e)switch(a.B){case "geoserver":c=90*e+.5|0;g.FORMAT_OPTIONS="FORMAT_OPTIONS"in g?g.FORMAT_OPTIONS+(";dpi:"+c):"dpi:"+c;break;case "mapserver":g.MAP_RESOLUTION=90*e;break;case "carmentaserver":case "qgis":g.DPI=90*e;break;default:xa(!1,52)}f=f.b;a.l&&"ne"==f.substr(0,2)&&(a=d[0],d[0]=d[1],d[1]=a,a=d[2],d[2]=d[3],d[3]=a);g.BBOX=d.join(",");return Fw(1==h.length?h[0]:h[Ia((b[1]<<
+b[0])+b[2],h.length)],g)}}k.nb=function(a){return this.T&&void 0!==this.B?a:1};function jx(a){var b=0,c=[];if(a.urls){var d;var e=0;for(d=a.urls.length;e<d;++e)c[b++]=a.urls[e]}a.R=c.join("#")}function lx(a){var b=0,c=[],d;for(d in a.f)c[b++]=d+"-"+a.f[d];return c.join("/")}
+k.Fc=function(a,b,c){var d=this.tileGrid;d||(d=this.Ta(c));if(!(d.b.length<=a[0])){1==b||this.T&&void 0!==this.B||(b=1);var e=d.Da(a[0]),f=d.Aa(a,this.fa),d=Ma(d.gb(a[0]),this.o),g=this.C;g&&(d=Ka(d,g,this.o),f=Qa(f,e*g,f));1!=b&&(d=La(d,b,this.o));e={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};tb(e,this.f);return mx(this,a,d,f,b,c,e)}};k.eb=function(a){X.prototype.eb.call(this,a);jx(this)};k.Ho=function(a){tb(this.f,a);jx(this);kx(this);qw(this,lx(this))};
+function kx(a){a.l=0<=Ye(a.f.VERSION||"1.3.0")};function nx(a,b,c,d,e,f,g,h,l,m,n,p,q,r){Ls.call(this,a,b);this.j=null;this.o={Nd:!1,Kg:null,mf:-1,Lg:-1};this.c=m;this.a=[];this.u=c;this.v=f;this.g=[];this.l=[];if(f){var u=l.Aa(f),x=l.Da(a[0]);h.Rf(u,h.tc(x),function(a){var b=pb(u,h.Aa(a));if(.5<=lb(b)/x&&.5<=mb(b)/x){var b=a.toString(),c=m[b];c||(c=g(a,n,p),c=m[b]=new q(a,void 0==c?4:0,void 0==c?"":c,d,e),this.l.push(y(c,"change",r)));c.j++;this.a.push(b)}}.bind(this))}}v(nx,Ls);
+nx.prototype.ka=function(){for(var a=0,b=this.a.length;a<b;++a){var c=this.a[a],d=this.c[c];d.j--;d.j||(delete this.c[c],Nc(d))}this.a.length=0;this.c=null;1==this.state&&(this.g.forEach(Ec),this.g.length=0);this.i&&Nc(this.i);this.state=5;this.s();this.l.forEach(Ec);this.l.length=0;Ls.prototype.ka.call(this)};nx.prototype.Y=function(){return-1==this.o.Lg?null:this.j.canvas};nx.prototype.bb=function(){return this.a.join("/")+"/"+this.u};
+nx.prototype.load=function(){var a=0,b=!1;0==this.state&&Ns(this,1);1==this.state&&this.a.forEach(function(c){var d=this.c[c];0==d.state?(d.Pg(this.S),d.load()):3==d.state?b=!0:4==d.state&&ma(this.a,c);if(1==d.state){var e=y(d,"change",function(){var f=d.getState();if(2==f||3==f)--a,Ec(e),ma(this.g,e),3==f&&(ma(this.a,c),b=!0),a||Ns(this,0<this.a.length?2:3)}.bind(this));this.g.push(e);++a}}.bind(this));a||setTimeout(function(){Ns(this,0<this.a.length?2:b?3:4)}.bind(this),0)};
+function ox(a,b){a.Pg(Cl(b,a.o,a.$o.bind(a),a.Zo.bind(a)))};function px(a,b,c,d,e){Ls.call(this,a,b);this.j=0;this.o=d;this.g=null;this.c={};this.u=e;this.l=c}v(px,Ls);k=px.prototype;k.ka=function(){this.g=null;this.c={};this.state=5;this.s();Ls.prototype.ka.call(this)};k.Lm=function(){return this.o};k.Km=function(){return this.g};k.bb=function(){return this.l};k.Mm=function(){return this.a};k.load=function(){0==this.state&&(Ns(this,1),this.u(this,this.l),this.v(null,NaN,null))};k.$o=function(a,b){this.ig(b);this.mj(a)};k.Zo=function(){Ns(this,3)};
+k.mj=function(a){this.g=a;Ns(this,2)};k.ig=function(a){this.a=a};k.Pg=function(a){this.v=a};function qx(a){tw.call(this,{attributions:a.attributions,cacheSize:void 0!==a.cacheSize?a.cacheSize:128,extent:a.extent,logo:a.logo,opaque:!1,projection:a.projection,state:a.state,tileGrid:a.tileGrid,tileLoadFunction:a.tileLoadFunction?a.tileLoadFunction:ox,tileUrlFunction:a.tileUrlFunction,tilePixelRatio:a.tilePixelRatio,url:a.url,urls:a.urls,wrapX:void 0===a.wrapX?!0:a.wrapX});this.g=a.format?a.format:null;this.v={};this.l=void 0==a.overlaps?!0:a.overlaps;this.tileClass=a.tileClass?a.tileClass:
+px;this.f={};this.tileGrid||(this.tileGrid=this.Ta(Tb(a.projection||"EPSG:3857")))}v(qx,tw);qx.prototype.Nc=function(a,b,c,d,e){var f=this.Sb(a,b,c);if(this.a.b.hasOwnProperty(f))return this.a.get(f);a=[a,b,c];c=(b=rw(this,a,e))?this.tileUrlFunction(b,d,e):void 0;d=new nx(a,void 0!==c?0:4,void 0!==c?c:"",this.g,this.tileLoadFunction,b,this.tileUrlFunction,this.tileGrid,this.Ta(e),this.v,d,e,this.tileClass,this.Li.bind(this));this.a.set(f,d);return d};
+qx.prototype.Ta=function(a){var b=a.mb,c=this.f[b];c||(c=this.tileGrid,c=this.f[b]=wc(a,void 0,c?c.gb(c.minZoom):void 0));return c};qx.prototype.nb=function(a){return void 0==a?tw.prototype.nb.call(this,a):a};qx.prototype.Xd=function(a,b,c){a=Ma(this.Ta(c).gb(a));return[Math.round(a[0]*b),Math.round(a[1]*b)]};function rx(a){this.o=a.matrixIds;lc.call(this,{extent:a.extent,origin:a.origin,origins:a.origins,resolutions:a.resolutions,tileSize:a.tileSize,tileSizes:a.tileSizes,sizes:a.sizes})}v(rx,lc);rx.prototype.l=function(){return this.o};
+function sx(a,b,c){var d=[],e=[],f=[],g=[],h=[],l=void 0!==c?c:[];c=Tb(a.SupportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"));var m=c.sc(),n="ne"==c.b.substr(0,2);a.TileMatrix.sort(function(a,b){return b.ScaleDenominator-a.ScaleDenominator});a.TileMatrix.forEach(function(a){var b;0<l.length?b=na(l,function(b){return a.Identifier==b.TileMatrix}):b=!0;if(b){e.push(a.Identifier);b=2.8E-4*a.ScaleDenominator/m;var c=a.TileWidth,p=a.TileHeight;n?f.push([a.TopLeftCorner[1],a.TopLeftCorner[0]]):
+f.push(a.TopLeftCorner);d.push(b);g.push(c==p?c:[c,p]);h.push([a.MatrixWidth,-a.MatrixHeight])}});return new rx({extent:b,origins:f,resolutions:d,matrixIds:e,tileSizes:g,sizes:h})};function Z(a){function b(a){a="KVP"==d?Fw(a,f):a.replace(/\{(\w+?)\}/g,function(a,b){return b.toLowerCase()in f?f[b.toLowerCase()]:a});return function(b){if(b){var c={TileMatrix:e.o[b[0]],TileCol:b[1],TileRow:-b[2]-1};tb(c,g);b=a;return b="KVP"==d?Fw(b,c):b.replace(/\{(\w+?)\}/g,function(a,b){return c[b]})}}}this.fa=void 0!==a.version?a.version:"1.0.0";this.C=void 0!==a.format?a.format:"image/jpeg";this.f=a.dimensions?a.dimensions:{};this.B=a.layer;this.l=a.matrixSet;this.R=a.style;var c=a.urls;void 0===
+c&&void 0!==a.url&&(c=mw(a.url));var d=this.T=void 0!==a.requestEncoding?a.requestEncoding:"KVP",e=a.tileGrid,f={layer:this.B,style:this.R,tilematrixset:this.l};"KVP"==d&&tb(f,{Service:"WMTS",Request:"GetTile",Version:this.fa,Format:this.C});var g=this.f,h=c&&0<c.length?kw(c.map(b)):lw;X.call(this,{attributions:a.attributions,cacheSize:a.cacheSize,crossOrigin:a.crossOrigin,logo:a.logo,projection:a.projection,reprojectionErrorThreshold:a.reprojectionErrorThreshold,tileClass:a.tileClass,tileGrid:e,
+tileLoadFunction:a.tileLoadFunction,tilePixelRatio:a.tilePixelRatio,tileUrlFunction:h,urls:c,wrapX:void 0!==a.wrapX?a.wrapX:!1});qw(this,tx(this))}v(Z,X);k=Z.prototype;k.Mk=function(){return this.f};k.Io=function(){return this.C};k.Jo=function(){return this.B};k.$k=function(){return this.l};k.kl=function(){return this.T};k.Ko=function(){return this.R};k.rl=function(){return this.fa};function tx(a){var b=0,c=[],d;for(d in a.f)c[b++]=d+"-"+a.f[d];return c.join("/")}
+k.uq=function(a){tb(this.f,a);qw(this,tx(this))};function ux(a){a=a||{};var b=a.size,c=b[0],d=b[1],e=[],f=256;switch(void 0!==a.tierSizeCalculation?a.tierSizeCalculation:vx){case vx:for(;c>f||d>f;)e.push([Math.ceil(c/f),Math.ceil(d/f)]),f+=f;break;case wx:for(;c>f||d>f;)e.push([Math.ceil(c/f),Math.ceil(d/f)]),c>>=1,d>>=1;break;default:xa(!1,53)}e.push([1,1]);e.reverse();for(var f=[1],g=[0],d=1,c=e.length;d<c;d++)f.push(1<<d),g.push(e[d-1][0]*e[d-1][1]+g[d-1]);f.reverse();b=[0,-b[1],b[0],0];b=new lc({extent:b,origin:ib(b),resolutions:f});(f=a.url)&&
+-1==f.indexOf("{TileGroup}")&&(f+="{TileGroup}/{z}-{x}-{y}.jpg");f=mw(f);f=kw(f.map(function(a){return function(b){if(b){var c=b[0],d=b[1];b=-b[2]-1;var f={z:c,x:d,y:b,TileGroup:"TileGroup"+((d+b*e[c][0]+g[c])/256|0)};return a.replace(/\{(\w+?)\}/g,function(a,b){return f[b]})}}}));X.call(this,{attributions:a.attributions,cacheSize:a.cacheSize,crossOrigin:a.crossOrigin,logo:a.logo,projection:a.projection,reprojectionErrorThreshold:a.reprojectionErrorThreshold,tileClass:xx,tileGrid:b,tileUrlFunction:f})}
+v(ux,X);function xx(a,b,c,d,e){Os.call(this,a,b,c,d,e);this.a=null}v(xx,Os);xx.prototype.Y=function(){if(this.a)return this.a;var a=Os.prototype.Y.call(this);if(2==this.state){if(256==a.width&&256==a.height)return this.a=a;var b=jd(256,256);b.drawImage(a,0,0);return this.a=b.canvas}return a};var vx="default",wx="truncated";function yx(a,b){this.b=b;this.a=[{x:0,y:0,width:a,height:a}];this.c={};this.i=jd(a,a);this.f=this.i.canvas}yx.prototype.get=function(a){return this.c[a]||null};
+yx.prototype.add=function(a,b,c,d,e){var f;var g=0;for(f=this.a.length;g<f;++g){var h=this.a[g];if(h.width>=b+this.b&&h.height>=c+this.b)return f={offsetX:h.x+this.b,offsetY:h.y+this.b,image:this.f},this.c[a]=f,d.call(e,this.i,h.x+this.b,h.y+this.b),a=g,b+=this.b,d=c+this.b,h.width-b>h.height-d?(c={x:h.x+b,y:h.y,width:h.width-b,height:h.height},b={x:h.x,y:h.y+d,width:b,height:h.height-d},zx(this,a,c,b)):(c={x:h.x+b,y:h.y,width:h.width-b,height:d},b={x:h.x,y:h.y+d,width:h.width,height:h.height-d},
+zx(this,a,c,b)),f}return null};function zx(a,b,c,d){b=[b,1];0<c.width&&0<c.height&&b.push(c);0<d.width&&0<d.height&&b.push(d);a.a.splice.apply(a.a,b)};function Ax(a){a=a||{};this.a=void 0!==a.initialSize?a.initialSize:256;this.i=void 0!==a.maxSize?a.maxSize:void 0!==ea?ea:2048;this.b=void 0!==a.space?a.space:1;this.f=[new yx(this.a,this.b)];this.c=this.a;this.g=[new yx(this.c,this.b)]}Ax.prototype.add=function(a,b,c,d,e,f){if(b+this.b>this.i||c+this.b>this.i)return null;d=Bx(this,!1,a,b,c,d,f);if(!d)return null;a=Bx(this,!0,a,b,c,e?e:ua,f);return{offsetX:d.offsetX,offsetY:d.offsetY,image:d.image,Zl:a.image}};
+function Bx(a,b,c,d,e,f,g){var h=b?a.g:a.f,l;var m=0;for(l=h.length;m<l;++m){var n=h[m];if(n=n.add(c,d,e,f,g))return n;n||m!==l-1||(b?(n=Math.min(2*a.c,a.i),a.c=n):(n=Math.min(2*a.a,a.i),a.a=n),n=new yx(n,a.b),h.push(n),++l)}return null};wa.prototype.code=wa.prototype.code;t("ol.Attribution",Ac);Ac.prototype.getHTML=Ac.prototype.i;t("ol.Collection",Yc);Yc.prototype.clear=Yc.prototype.clear;Yc.prototype.extend=Yc.prototype.fg;Yc.prototype.forEach=Yc.prototype.forEach;Yc.prototype.getArray=Yc.prototype.tm;Yc.prototype.item=Yc.prototype.item;Yc.prototype.getLength=Yc.prototype.dc;Yc.prototype.insertAt=Yc.prototype.He;Yc.prototype.pop=Yc.prototype.pop;Yc.prototype.push=Yc.prototype.push;Yc.prototype.remove=Yc.prototype.remove;
+Yc.prototype.removeAt=Yc.prototype.Hg;Yc.prototype.setAt=Yc.prototype.Wp;bd.prototype.element=bd.prototype.element;t("ol.color.asArray",ed);t("ol.color.asString",gd);t("ol.colorlike.asColorLike",id);t("ol.control.defaults",xd);t("ol.coordinate.add",Ze);t("ol.coordinate.createStringXY",function(a){return function(b){return lf(b,a)}});t("ol.coordinate.format",cf);t("ol.coordinate.rotate",ef);t("ol.coordinate.toStringHDMS",function(a,b){return a?bf("NS",a[1],b)+" "+bf("EW",a[0],b):""});
+t("ol.coordinate.toStringXY",lf);t("ol.DeviceOrientation",Rk);Rk.prototype.getAlpha=Rk.prototype.Fk;Rk.prototype.getBeta=Rk.prototype.Ik;Rk.prototype.getGamma=Rk.prototype.Ok;Rk.prototype.getHeading=Rk.prototype.um;Rk.prototype.getTracking=Rk.prototype.Th;Rk.prototype.setTracking=Rk.prototype.gg;t("ol.easing.easeIn",qd);t("ol.easing.easeOut",rd);t("ol.easing.inAndOut",sd);t("ol.easing.linear",td);t("ol.easing.upAndDown",function(a){return.5>a?sd(2*a):1-sd(2*(a-.5))});
+t("ol.extent.boundingExtent",Na);t("ol.extent.buffer",Qa);t("ol.extent.containsCoordinate",Ta);t("ol.extent.containsExtent",Va);t("ol.extent.containsXY",Ua);t("ol.extent.createEmpty",Oa);t("ol.extent.equals",bb);t("ol.extent.extend",cb);t("ol.extent.getArea",jb);t("ol.extent.getBottomLeft",eb);t("ol.extent.getBottomRight",gb);t("ol.extent.getCenter",nb);t("ol.extent.getHeight",mb);t("ol.extent.getIntersection",pb);t("ol.extent.getSize",function(a){return[a[2]-a[0],a[3]-a[1]]});
+t("ol.extent.getTopLeft",ib);t("ol.extent.getTopRight",hb);t("ol.extent.getWidth",lb);t("ol.extent.intersects",qb);t("ol.extent.isEmpty",kb);t("ol.extent.applyTransform",sb);t("ol.Feature",H);H.prototype.clone=H.prototype.clone;H.prototype.getGeometry=H.prototype.V;H.prototype.getId=H.prototype.wm;H.prototype.getGeometryName=H.prototype.Qk;H.prototype.getStyle=H.prototype.xm;H.prototype.getStyleFunction=H.prototype.Lc;H.prototype.setGeometry=H.prototype.Ra;H.prototype.setStyle=H.prototype.hg;
+H.prototype.setId=H.prototype.jc;H.prototype.setGeometryName=H.prototype.Tc;t("ol.featureloader.xhr",Dl);t("ol.Geolocation",xs);xs.prototype.getAccuracy=xs.prototype.Dk;xs.prototype.getAccuracyGeometry=xs.prototype.Ek;xs.prototype.getAltitude=xs.prototype.Gk;xs.prototype.getAltitudeAccuracy=xs.prototype.Hk;xs.prototype.getHeading=xs.prototype.ym;xs.prototype.getPosition=xs.prototype.zm;xs.prototype.getProjection=xs.prototype.Uh;xs.prototype.getSpeed=xs.prototype.ll;xs.prototype.getTracking=xs.prototype.Vh;
+xs.prototype.getTrackingOptions=xs.prototype.Gh;xs.prototype.setProjection=xs.prototype.Wh;xs.prototype.setTracking=xs.prototype.Ke;xs.prototype.setTrackingOptions=xs.prototype.wj;t("ol.Graticule",Ds);Ds.prototype.getMap=Ds.prototype.Cm;Ds.prototype.getMeridians=Ds.prototype.al;Ds.prototype.getParallels=Ds.prototype.hl;Ds.prototype.setMap=Ds.prototype.setMap;t("ol.has.DEVICE_PIXEL_RATIO",Sd);t("ol.has.CANVAS",Ud);t("ol.has.DEVICE_ORIENTATION",Vd);t("ol.has.GEOLOCATION",Wd);t("ol.has.TOUCH",Xd);
+t("ol.has.WEBGL",Md);Js.prototype.getImage=Js.prototype.Y;Js.prototype.load=Js.prototype.load;Os.prototype.getImage=Os.prototype.Y;t("ol.inherits",v);t("ol.interaction.defaults",qh);t("ol.Kinetic",kg);t("ol.loadingstrategy.all",Zt);t("ol.loadingstrategy.bbox",function(a){return[a]});t("ol.loadingstrategy.tile",function(a){return function(b,c){c=a.tc(c);b=oc(a,b,c);var d=[];c=[c,0,0];for(c[1]=b.ca;c[1]<=b.$;++c[1])for(c[2]=b.da;c[2]<=b.ia;++c[2])d.push(a.Aa(c));return d}});t("ol.Map",G);
+G.prototype.addControl=G.prototype.kk;G.prototype.addInteraction=G.prototype.lk;G.prototype.addLayer=G.prototype.ih;G.prototype.addOverlay=G.prototype.jh;G.prototype.forEachFeatureAtPixel=G.prototype.we;G.prototype.forEachLayerAtPixel=G.prototype.Im;G.prototype.hasFeatureAtPixel=G.prototype.Yl;G.prototype.getEventCoordinate=G.prototype.Tf;G.prototype.getEventPixel=G.prototype.xe;G.prototype.getTarget=G.prototype.ag;G.prototype.getTargetElement=G.prototype.jd;G.prototype.getCoordinateFromPixel=G.prototype.Wa;
+G.prototype.getControls=G.prototype.Lk;G.prototype.getOverlays=G.prototype.fl;G.prototype.getOverlayById=G.prototype.el;G.prototype.getInteractions=G.prototype.Sk;G.prototype.getLayerGroup=G.prototype.Kc;G.prototype.getLayers=G.prototype.Xh;G.prototype.getPixelFromCoordinate=G.prototype.Ja;G.prototype.getSize=G.prototype.Ob;G.prototype.getView=G.prototype.Z;G.prototype.getViewport=G.prototype.sl;G.prototype.renderSync=G.prototype.Tp;G.prototype.render=G.prototype.render;
+G.prototype.removeControl=G.prototype.Mp;G.prototype.removeInteraction=G.prototype.Np;G.prototype.removeLayer=G.prototype.Pp;G.prototype.removeOverlay=G.prototype.Qp;G.prototype.setLayerGroup=G.prototype.qj;G.prototype.setSize=G.prototype.Qg;G.prototype.setTarget=G.prototype.Le;G.prototype.setView=G.prototype.iq;G.prototype.updateSize=G.prototype.Ad;Jd.prototype.originalEvent=Jd.prototype.originalEvent;Jd.prototype.pixel=Jd.prototype.pixel;Jd.prototype.coordinate=Jd.prototype.coordinate;
+Jd.prototype.dragging=Jd.prototype.dragging;Id.prototype.map=Id.prototype.map;Id.prototype.frameState=Id.prototype.frameState;t("ol.Object",Tc);Tc.prototype.get=Tc.prototype.get;Tc.prototype.getKeys=Tc.prototype.O;Tc.prototype.getProperties=Tc.prototype.N;Tc.prototype.set=Tc.prototype.set;Tc.prototype.setProperties=Tc.prototype.H;Tc.prototype.unset=Tc.prototype.P;Xc.prototype.key=Xc.prototype.key;Xc.prototype.oldValue=Xc.prototype.oldValue;t("ol.Observable",Sc);
+t("ol.Observable.unByKey",function(a){if(Array.isArray(a))for(var b=0,c=a.length;b<c;++b)Ec(a[b]);else Ec(a)});Sc.prototype.changed=Sc.prototype.s;Sc.prototype.dispatchEvent=Sc.prototype.b;Sc.prototype.getRevision=Sc.prototype.L;Sc.prototype.on=Sc.prototype.J;Sc.prototype.once=Sc.prototype.once;Sc.prototype.un=Sc.prototype.K;t("ol.Overlay",sk);sk.prototype.getElement=sk.prototype.Rd;sk.prototype.getId=sk.prototype.Jm;sk.prototype.getMap=sk.prototype.Me;sk.prototype.getOffset=sk.prototype.Dh;
+sk.prototype.getPosition=sk.prototype.Yh;sk.prototype.getPositioning=sk.prototype.Eh;sk.prototype.setElement=sk.prototype.lj;sk.prototype.setMap=sk.prototype.setMap;sk.prototype.setOffset=sk.prototype.rj;sk.prototype.setPosition=sk.prototype.Ne;sk.prototype.setPositioning=sk.prototype.uj;t("ol.proj.METERS_PER_UNIT",zb);t("ol.proj.setProj4",function(a){Ab=a});t("ol.proj.getPointResolution",Sb);t("ol.proj.addEquivalentProjections",Wb);t("ol.proj.addProjection",Xb);
+t("ol.proj.addCoordinateTransforms",ac);t("ol.proj.fromLonLat",function(a,b){return gc(a,"EPSG:4326",void 0!==b?b:"EPSG:3857")});t("ol.proj.toLonLat",function(a,b){return gc(a,void 0!==b?b:"EPSG:3857","EPSG:4326")});t("ol.proj.get",Tb);t("ol.proj.equivalent",dc);t("ol.proj.getTransform",ec);t("ol.proj.transform",gc);t("ol.proj.transformExtent",hc);
+t("ol.render.toContext",function(a,b){var c=a.canvas,d=b?b:{};b=d.pixelRatio||Sd;if(d=d.size)c.width=d[0]*b,c.height=d[1]*b,c.style.width=d[0]+"px",c.style.height=d[1]+"px";c=[0,0,c.width,c.height];d=Ih(Bh(),b,b);return new Xh(a,b,c,d,0)});t("ol.size.toSize",Ma);t("ol.Sphere",xb);xb.prototype.geodesicArea=xb.prototype.a;xb.prototype.haversineDistance=xb.prototype.b;Ls.prototype.getTileCoord=Ls.prototype.f;Ls.prototype.load=Ls.prototype.load;t("ol.tilegrid.createXYZ",xc);px.prototype.getFormat=px.prototype.Lm;
+px.prototype.getFeatures=px.prototype.Km;px.prototype.getProjection=px.prototype.Mm;px.prototype.setFeatures=px.prototype.mj;px.prototype.setProjection=px.prototype.ig;px.prototype.setLoader=px.prototype.Pg;t("ol.View",F);F.prototype.animate=F.prototype.animate;F.prototype.getAnimating=F.prototype.Ic;F.prototype.getInteracting=F.prototype.Rk;F.prototype.cancelAnimations=F.prototype.ed;F.prototype.constrainCenter=F.prototype.Ec;F.prototype.constrainResolution=F.prototype.constrainResolution;
+F.prototype.constrainRotation=F.prototype.constrainRotation;F.prototype.getCenter=F.prototype.wa;F.prototype.calculateExtent=F.prototype.dd;F.prototype.getMaxResolution=F.prototype.Nm;F.prototype.getMinResolution=F.prototype.Pm;F.prototype.getMaxZoom=F.prototype.Om;F.prototype.setMaxZoom=F.prototype.eq;F.prototype.getMinZoom=F.prototype.Qm;F.prototype.setMinZoom=F.prototype.fq;F.prototype.getProjection=F.prototype.Rm;F.prototype.getResolution=F.prototype.Pa;F.prototype.getResolutions=F.prototype.Sm;
+F.prototype.getResolutionForExtent=F.prototype.ze;F.prototype.getRotation=F.prototype.Qa;F.prototype.getZoom=F.prototype.Hh;F.prototype.getZoomForResolution=F.prototype.Ce;F.prototype.fit=F.prototype.Qf;F.prototype.centerOn=F.prototype.uk;F.prototype.rotate=F.prototype.rotate;F.prototype.setCenter=F.prototype.ob;F.prototype.setResolution=F.prototype.Vc;F.prototype.setRotation=F.prototype.Oe;F.prototype.setZoom=F.prototype.lq;t("ol.xml.getAllTextContent",kl);t("ol.xml.parse",pl);
+Oi.prototype.getGL=Oi.prototype.Wo;Oi.prototype.useProgram=Oi.prototype.Qc;t("ol.tilegrid.TileGrid",lc);lc.prototype.forEachTileCoord=lc.prototype.Rf;lc.prototype.getMaxZoom=lc.prototype.Ti;lc.prototype.getMinZoom=lc.prototype.Ui;lc.prototype.getOrigin=lc.prototype.Pc;lc.prototype.getResolution=lc.prototype.Da;lc.prototype.getResolutions=lc.prototype.Vi;lc.prototype.getTileCoordExtent=lc.prototype.Aa;lc.prototype.getTileCoordForCoordAndResolution=lc.prototype.Be;
+lc.prototype.getTileCoordForCoordAndZ=lc.prototype.bg;lc.prototype.getTileSize=lc.prototype.gb;lc.prototype.getZForResolution=lc.prototype.tc;t("ol.tilegrid.WMTS",rx);rx.prototype.getMatrixIds=rx.prototype.l;t("ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet",sx);t("ol.style.AtlasManager",Ax);t("ol.style.Circle",$k);$k.prototype.setRadius=$k.prototype.Uc;t("ol.style.Fill",al);al.prototype.clone=al.prototype.clone;al.prototype.getColor=al.prototype.i;al.prototype.setColor=al.prototype.c;
+t("ol.style.Icon",eo);eo.prototype.clone=eo.prototype.clone;eo.prototype.getAnchor=eo.prototype.Hc;eo.prototype.getColor=eo.prototype.Lo;eo.prototype.getImage=eo.prototype.Y;eo.prototype.getOrigin=eo.prototype.Oc;eo.prototype.getSrc=eo.prototype.Mo;eo.prototype.getSize=eo.prototype.ic;eo.prototype.load=eo.prototype.load;t("ol.style.Image",Xk);Xk.prototype.getOpacity=Xk.prototype.Ze;Xk.prototype.getRotateWithView=Xk.prototype.$e;Xk.prototype.getRotation=Xk.prototype.af;Xk.prototype.getScale=Xk.prototype.bf;
+Xk.prototype.getSnapToPixel=Xk.prototype.Ae;Xk.prototype.setOpacity=Xk.prototype.td;Xk.prototype.setRotation=Xk.prototype.cf;Xk.prototype.setScale=Xk.prototype.ud;t("ol.style.RegularShape",Yk);Yk.prototype.clone=Yk.prototype.clone;Yk.prototype.getAnchor=Yk.prototype.Hc;Yk.prototype.getAngle=Yk.prototype.Pi;Yk.prototype.getFill=Yk.prototype.Fa;Yk.prototype.getImage=Yk.prototype.Y;Yk.prototype.getOrigin=Yk.prototype.Oc;Yk.prototype.getPoints=Yk.prototype.Qi;Yk.prototype.getRadius=Yk.prototype.Ri;
+Yk.prototype.getRadius2=Yk.prototype.Fh;Yk.prototype.getSize=Yk.prototype.ic;Yk.prototype.getStroke=Yk.prototype.Ga;t("ol.style.Stroke",wj);wj.prototype.clone=wj.prototype.clone;wj.prototype.getColor=wj.prototype.No;wj.prototype.getLineCap=wj.prototype.Vk;wj.prototype.getLineDash=wj.prototype.Oo;wj.prototype.getLineDashOffset=wj.prototype.Wk;wj.prototype.getLineJoin=wj.prototype.Xk;wj.prototype.getMiterLimit=wj.prototype.bl;wj.prototype.getWidth=wj.prototype.Po;wj.prototype.setColor=wj.prototype.Qo;
+wj.prototype.setLineCap=wj.prototype.aq;wj.prototype.setLineDash=wj.prototype.setLineDash;wj.prototype.setLineDashOffset=wj.prototype.bq;wj.prototype.setLineJoin=wj.prototype.cq;wj.prototype.setMiterLimit=wj.prototype.gq;wj.prototype.setWidth=wj.prototype.jq;t("ol.style.Style",bl);bl.prototype.clone=bl.prototype.clone;bl.prototype.getGeometry=bl.prototype.V;bl.prototype.getGeometryFunction=bl.prototype.Pk;bl.prototype.getFill=bl.prototype.Fa;bl.prototype.setFill=bl.prototype.pf;
+bl.prototype.getImage=bl.prototype.Y;bl.prototype.setImage=bl.prototype.Og;bl.prototype.getStroke=bl.prototype.Ga;bl.prototype.setStroke=bl.prototype.qf;bl.prototype.getText=bl.prototype.Na;bl.prototype.setText=bl.prototype.xd;bl.prototype.getZIndex=bl.prototype.Ba;bl.prototype.setGeometry=bl.prototype.Ra;bl.prototype.setZIndex=bl.prototype.Vb;t("ol.style.Text",fo);fo.prototype.clone=fo.prototype.clone;fo.prototype.getFont=fo.prototype.Nk;fo.prototype.getOffsetX=fo.prototype.cl;
+fo.prototype.getOffsetY=fo.prototype.dl;fo.prototype.getFill=fo.prototype.Fa;fo.prototype.getRotateWithView=fo.prototype.Ro;fo.prototype.getRotation=fo.prototype.So;fo.prototype.getScale=fo.prototype.To;fo.prototype.getStroke=fo.prototype.Ga;fo.prototype.getText=fo.prototype.Na;fo.prototype.getTextAlign=fo.prototype.nl;fo.prototype.getTextBaseline=fo.prototype.ol;fo.prototype.setFont=fo.prototype.nj;fo.prototype.setOffsetX=fo.prototype.sj;fo.prototype.setOffsetY=fo.prototype.tj;
+fo.prototype.setFill=fo.prototype.pf;fo.prototype.setRotation=fo.prototype.Uo;fo.prototype.setScale=fo.prototype.Si;fo.prototype.setStroke=fo.prototype.qf;fo.prototype.setText=fo.prototype.xd;fo.prototype.setTextAlign=fo.prototype.vj;fo.prototype.setTextBaseline=fo.prototype.hq;t("ol.source.BingMaps",xw);t("ol.source.BingMaps.TOS_ATTRIBUTION",yw);xw.prototype.getApiKey=xw.prototype.T;xw.prototype.getImagerySet=xw.prototype.fa;t("ol.source.CartoDB",Aw);Aw.prototype.getConfig=Aw.prototype.Kk;
+Aw.prototype.updateConfig=Aw.prototype.tq;Aw.prototype.setConfig=Aw.prototype.Xp;t("ol.source.Cluster",Y);Y.prototype.getDistance=Y.prototype.$n;Y.prototype.getSource=Y.prototype.ao;Y.prototype.setDistance=Y.prototype.Yp;t("ol.source.Image",Hv);Jv.prototype.image=Jv.prototype.image;t("ol.source.ImageArcGISRest",Gw);Gw.prototype.getParams=Gw.prototype.co;Gw.prototype.getImageLoadFunction=Gw.prototype.bo;Gw.prototype.getUrl=Gw.prototype.eo;Gw.prototype.setImageLoadFunction=Gw.prototype.fo;
+Gw.prototype.setUrl=Gw.prototype.ho;Gw.prototype.updateParams=Gw.prototype.io;t("ol.source.ImageCanvas",Ov);t("ol.source.ImageMapGuide",Hw);Hw.prototype.getParams=Hw.prototype.ko;Hw.prototype.getImageLoadFunction=Hw.prototype.jo;Hw.prototype.updateParams=Hw.prototype.mo;Hw.prototype.setImageLoadFunction=Hw.prototype.lo;t("ol.source.ImageStatic",Iw);t("ol.source.ImageVector",Pv);Pv.prototype.getSource=Pv.prototype.no;Pv.prototype.getStyle=Pv.prototype.oo;Pv.prototype.getStyleFunction=Pv.prototype.po;
+Pv.prototype.setStyle=Pv.prototype.Ii;t("ol.source.ImageWMS",Jw);Jw.prototype.getGetFeatureInfoUrl=Jw.prototype.so;Jw.prototype.getParams=Jw.prototype.uo;Jw.prototype.getImageLoadFunction=Jw.prototype.to;Jw.prototype.getUrl=Jw.prototype.vo;Jw.prototype.setImageLoadFunction=Jw.prototype.wo;Jw.prototype.setUrl=Jw.prototype.xo;Jw.prototype.updateParams=Jw.prototype.yo;t("ol.source.OSM",Nw);t("ol.source.OSM.ATTRIBUTION",Ow);t("ol.source.Raster",Pw);Pw.prototype.setOperation=Pw.prototype.v;
+Tw.prototype.extent=Tw.prototype.extent;Tw.prototype.resolution=Tw.prototype.resolution;Tw.prototype.data=Tw.prototype.data;t("ol.source.Source",$t);$t.prototype.getAttributions=$t.prototype.ya;$t.prototype.getLogo=$t.prototype.xa;$t.prototype.getProjection=$t.prototype.za;$t.prototype.getState=$t.prototype.getState;$t.prototype.refresh=$t.prototype.sa;$t.prototype.setAttributions=$t.prototype.ua;t("ol.source.Stamen",Ww);t("ol.source.Tile",pw);pw.prototype.getTileGrid=pw.prototype.ab;
+sw.prototype.tile=sw.prototype.tile;t("ol.source.TileArcGISRest",$w);$w.prototype.getParams=$w.prototype.C;$w.prototype.updateParams=$w.prototype.B;t("ol.source.TileDebug",bx);t("ol.source.TileImage",X);X.prototype.setRenderReprojectionEdges=X.prototype.Pb;X.prototype.setTileGridForProjection=X.prototype.Qb;t("ol.source.TileJSON",dx);dx.prototype.getTileJSON=dx.prototype.pl;t("ol.source.TileUTFGrid",ex);ex.prototype.getTemplate=ex.prototype.ml;ex.prototype.forDataAtCoordinateAndResolution=ex.prototype.zk;
+t("ol.source.TileWMS",ix);ix.prototype.getGetFeatureInfoUrl=ix.prototype.Fo;ix.prototype.getParams=ix.prototype.Go;ix.prototype.updateParams=ix.prototype.Ho;tw.prototype.getTileLoadFunction=tw.prototype.pb;tw.prototype.getTileUrlFunction=tw.prototype.qb;tw.prototype.getUrls=tw.prototype.rb;tw.prototype.setTileLoadFunction=tw.prototype.vb;tw.prototype.setTileUrlFunction=tw.prototype.cb;tw.prototype.setUrl=tw.prototype.jb;tw.prototype.setUrls=tw.prototype.eb;t("ol.source.Vector",U);
+U.prototype.addFeature=U.prototype.yb;U.prototype.addFeatures=U.prototype.cd;U.prototype.clear=U.prototype.clear;U.prototype.forEachFeature=U.prototype.sh;U.prototype.forEachFeatureInExtent=U.prototype.$b;U.prototype.forEachFeatureIntersectingExtent=U.prototype.th;U.prototype.getFeaturesCollection=U.prototype.Ah;U.prototype.getFeatures=U.prototype.Xe;U.prototype.getFeaturesAtCoordinate=U.prototype.zh;U.prototype.getFeaturesInExtent=U.prototype.Uf;U.prototype.getClosestFeatureToCoordinate=U.prototype.vh;
+U.prototype.getExtent=U.prototype.G;U.prototype.getFeatureById=U.prototype.yh;U.prototype.getFormat=U.prototype.Mi;U.prototype.getUrl=U.prototype.Ni;U.prototype.removeFeature=U.prototype.Gb;gu.prototype.feature=gu.prototype.feature;t("ol.source.VectorTile",qx);t("ol.source.WMTS",Z);Z.prototype.getDimensions=Z.prototype.Mk;Z.prototype.getFormat=Z.prototype.Io;Z.prototype.getLayer=Z.prototype.Jo;Z.prototype.getMatrixSet=Z.prototype.$k;Z.prototype.getRequestEncoding=Z.prototype.kl;
+Z.prototype.getStyle=Z.prototype.Ko;Z.prototype.getVersion=Z.prototype.rl;Z.prototype.updateDimensions=Z.prototype.uq;
+t("ol.source.WMTS.optionsFromCapabilities",function(a,b){var c=na(a.Contents.Layer,function(a){return a.Identifier==b.layer});if(null===c)return null;var d=a.Contents.TileMatrixSet;var e=1<c.TileMatrixSetLink.length?"projection"in b?sa(c.TileMatrixSetLink,function(a){var c=na(d,function(b){return b.Identifier==a.TileMatrixSet}).SupportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"),e=Tb(c),f=Tb(b.projection);return e&&f?dc(e,f):c==b.projection}):sa(c.TileMatrixSetLink,function(a){return a.TileMatrixSet==
+b.matrixSet}):0;0>e&&(e=0);var f=c.TileMatrixSetLink[e].TileMatrixSet;var g=c.TileMatrixSetLink[e].TileMatrixSetLimits;var h=c.Format[0];"format"in b&&(h=b.format);e=sa(c.Style,function(a){return"style"in b?a.Title==b.style:a.isDefault});0>e&&(e=0);e=c.Style[e].Identifier;var l={};"Dimension"in c&&c.Dimension.forEach(function(a){var b=a.Identifier,c=a.Default;void 0===c&&(c=a.Value[0]);l[b]=c});var m=na(a.Contents.TileMatrixSet,function(a){return a.Identifier==f});var n="projection"in b?Tb(b.projection):
+Tb(m.SupportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"));var p=c.WGS84BoundingBox;if(void 0!==p){var q=Tb("EPSG:4326").G();q=p[0]==q[0]&&p[2]==q[2];var r=hc(p,"EPSG:4326",n);(p=n.G())&&(Va(p,r)||(r=void 0))}g=sx(m,r,g);var u=[],m=b.requestEncoding,m=void 0!==m?m:"";if("OperationsMetadata"in a&&"GetTile"in a.OperationsMetadata)for(a=a.OperationsMetadata.GetTile.DCP.HTTP.Get,r=0,p=a.length;r<p;++r){var x=na(a[r].Constraint,function(a){return"GetEncoding"==a.name}).AllowedValues.Value;
+""===m&&(m=x[0]);if("KVP"===m)ja(x,"KVP")&&u.push(a[r].href);else break}u.length||(m="REST",c.ResourceURL.forEach(function(a){"tile"===a.resourceType&&(h=a.format,u.push(a.template))}));return{urls:u,layer:b.layer,matrixSet:f,format:h,projection:n,requestEncoding:m,tileGrid:g,style:e,dimensions:l,wrapX:q,crossOrigin:b.crossOrigin}});t("ol.source.XYZ",zw);t("ol.source.Zoomify",ux);Rh.prototype.vectorContext=Rh.prototype.vectorContext;Rh.prototype.frameState=Rh.prototype.frameState;
+Rh.prototype.context=Rh.prototype.context;Rh.prototype.glContext=Rh.prototype.glContext;kq.prototype.get=kq.prototype.get;kq.prototype.getExtent=kq.prototype.G;kq.prototype.getId=kq.prototype.Wn;kq.prototype.getGeometry=kq.prototype.V;kq.prototype.getProperties=kq.prototype.Xn;kq.prototype.getType=kq.prototype.U;t("ol.render.VectorContext",Wh);kk.prototype.setStyle=kk.prototype.rd;kk.prototype.drawGeometry=kk.prototype.zb;kk.prototype.drawFeature=kk.prototype.te;Xh.prototype.drawCircle=Xh.prototype.Zb;
+Xh.prototype.setStyle=Xh.prototype.rd;Xh.prototype.drawGeometry=Xh.prototype.zb;Xh.prototype.drawFeature=Xh.prototype.te;t("ol.proj.common.add",ic);t("ol.proj.Projection",Bb);Bb.prototype.getCode=Bb.prototype.Jk;Bb.prototype.getExtent=Bb.prototype.G;Bb.prototype.getUnits=Bb.prototype.Un;Bb.prototype.getMetersPerUnit=Bb.prototype.sc;Bb.prototype.getWorldExtent=Bb.prototype.tl;Bb.prototype.isGlobal=Bb.prototype.dm;Bb.prototype.setGlobal=Bb.prototype.$p;Bb.prototype.setExtent=Bb.prototype.Vn;
+Bb.prototype.setWorldExtent=Bb.prototype.kq;Bb.prototype.setGetPointResolution=Bb.prototype.Zp;t("ol.proj.Units.METERS_PER_UNIT",zb);t("ol.layer.Base",sh);sh.prototype.getExtent=sh.prototype.G;sh.prototype.getMaxResolution=sh.prototype.fc;sh.prototype.getMinResolution=sh.prototype.gc;sh.prototype.getOpacity=sh.prototype.hc;sh.prototype.getVisible=sh.prototype.Mb;sh.prototype.getZIndex=sh.prototype.Ba;sh.prototype.setExtent=sh.prototype.vc;sh.prototype.setMaxResolution=sh.prototype.Ac;
+sh.prototype.setMinResolution=sh.prototype.Bc;sh.prototype.setOpacity=sh.prototype.wc;sh.prototype.setVisible=sh.prototype.xc;sh.prototype.setZIndex=sh.prototype.Vb;t("ol.layer.Group",uh);uh.prototype.getLayers=uh.prototype.qd;uh.prototype.setLayers=uh.prototype.xi;t("ol.layer.Heatmap",V);V.prototype.getBlur=V.prototype.uh;V.prototype.getGradient=V.prototype.Bh;V.prototype.getRadius=V.prototype.yi;V.prototype.setBlur=V.prototype.jj;V.prototype.setGradient=V.prototype.pj;V.prototype.setRadius=V.prototype.Uc;
+t("ol.layer.Image",Uv);Uv.prototype.getSource=Uv.prototype.ha;t("ol.layer.Layer",wh);wh.prototype.getSource=wh.prototype.ha;wh.prototype.setMap=wh.prototype.setMap;wh.prototype.setSource=wh.prototype.Wc;t("ol.layer.Tile",cw);cw.prototype.getPreload=cw.prototype.Ud;cw.prototype.getSource=cw.prototype.ha;cw.prototype.setPreload=cw.prototype.zi;cw.prototype.getUseInterimTilesOnError=cw.prototype.kd;cw.prototype.setUseInterimTilesOnError=cw.prototype.Ai;t("ol.layer.Vector",T);T.prototype.getSource=T.prototype.ha;
+T.prototype.getStyle=T.prototype.D;T.prototype.getStyleFunction=T.prototype.C;T.prototype.setStyle=T.prototype.g;t("ol.layer.VectorTile",W);W.prototype.getPreload=W.prototype.Ud;W.prototype.getUseInterimTilesOnError=W.prototype.kd;W.prototype.setPreload=W.prototype.Bi;W.prototype.setUseInterimTilesOnError=W.prototype.Ci;t("ol.interaction.DoubleClickZoom",rg);t("ol.interaction.DoubleClickZoom.handleEvent",sg);t("ol.interaction.DragAndDrop",Rs);t("ol.interaction.DragAndDrop.handleEvent",mf);
+Us.prototype.features=Us.prototype.features;Us.prototype.file=Us.prototype.file;Us.prototype.projection=Us.prototype.projection;t("ol.interaction.DragBox",Rg);Rg.prototype.getGeometry=Rg.prototype.V;Wg.prototype.coordinate=Wg.prototype.coordinate;Wg.prototype.mapBrowserEvent=Wg.prototype.mapBrowserEvent;t("ol.interaction.DragPan",Gg);t("ol.interaction.DragRotate",Kg);t("ol.interaction.DragRotateAndZoom",Ys);t("ol.interaction.DragZoom",$g);t("ol.interaction.Draw",ju);
+t("ol.interaction.Draw.handleEvent",lu);ju.prototype.removeLastPoint=ju.prototype.Op;ju.prototype.finishDrawing=ju.prototype.Pd;ju.prototype.extend=ju.prototype.vn;t("ol.interaction.Draw.createRegularPolygon",function(a,b){return function(c,d){var e=c[0];c=c[1];var f=Math.sqrt(hf(e,c));d=d?d:Zf(new ys(e),a);$f(d,e,f,b?b:Math.atan((c[1]-e[1])/(c[0]-e[0])));return d}});
+t("ol.interaction.Draw.createBox",function(){return function(a,b){a=Na(a);b=b||new D(null);b.ma([[eb(a),gb(a),hb(a),ib(a),eb(a)]]);return b}});zu.prototype.feature=zu.prototype.feature;t("ol.interaction.Extent",Au);Au.prototype.getExtent=Au.prototype.G;Au.prototype.setExtent=Au.prototype.g;Lu.prototype.extent_=Lu.prototype.b;t("ol.interaction.Interaction",ng);ng.prototype.getActive=ng.prototype.c;ng.prototype.getMap=ng.prototype.f;ng.prototype.setActive=ng.prototype.Ha;
+t("ol.interaction.KeyboardPan",ah);t("ol.interaction.KeyboardPan.handleEvent",bh);t("ol.interaction.KeyboardZoom",ch);t("ol.interaction.KeyboardZoom.handleEvent",dh);t("ol.interaction.Modify",Nu);t("ol.interaction.Modify.handleEvent",Qu);Nu.prototype.removePoint=Nu.prototype.hj;Vu.prototype.features=Vu.prototype.features;Vu.prototype.mapBrowserEvent=Vu.prototype.mapBrowserEvent;t("ol.interaction.MouseWheelZoom",eh);t("ol.interaction.MouseWheelZoom.handleEvent",fh);eh.prototype.setMouseAnchor=eh.prototype.T;
+t("ol.interaction.PinchRotate",ih);t("ol.interaction.PinchZoom",mh);t("ol.interaction.Pointer",Dg);t("ol.interaction.Pointer.handleEvent",Eg);t("ol.interaction.Select",cv);cv.prototype.getFeatures=cv.prototype.Gn;cv.prototype.getHitTolerance=cv.prototype.Hn;cv.prototype.getLayer=cv.prototype.In;t("ol.interaction.Select.handleEvent",dv);cv.prototype.setHitTolerance=cv.prototype.Kn;cv.prototype.setMap=cv.prototype.setMap;fv.prototype.selected=fv.prototype.selected;fv.prototype.deselected=fv.prototype.deselected;
+fv.prototype.mapBrowserEvent=fv.prototype.mapBrowserEvent;t("ol.interaction.Snap",hv);hv.prototype.addFeature=hv.prototype.yb;hv.prototype.removeFeature=hv.prototype.Gb;t("ol.interaction.Translate",mv);mv.prototype.getHitTolerance=mv.prototype.B;mv.prototype.setHitTolerance=mv.prototype.I;sv.prototype.features=sv.prototype.features;sv.prototype.coordinate=sv.prototype.coordinate;t("ol.geom.Circle",ys);ys.prototype.clone=ys.prototype.clone;ys.prototype.getCenter=ys.prototype.wa;
+ys.prototype.getRadius=ys.prototype.pd;ys.prototype.getType=ys.prototype.U;ys.prototype.intersectsExtent=ys.prototype.Xa;ys.prototype.setCenter=ys.prototype.ob;ys.prototype.setCenterAndRadius=ys.prototype.Ng;ys.prototype.setRadius=ys.prototype.Uc;ys.prototype.transform=ys.prototype.tb;t("ol.geom.Geometry",of);of.prototype.getClosestPoint=of.prototype.Ab;of.prototype.intersectsCoordinate=of.prototype.sb;of.prototype.getExtent=of.prototype.G;of.prototype.rotate=of.prototype.rotate;
+of.prototype.scale=of.prototype.scale;of.prototype.simplify=of.prototype.Rb;of.prototype.transform=of.prototype.tb;t("ol.geom.GeometryCollection",tm);tm.prototype.clone=tm.prototype.clone;tm.prototype.getGeometries=tm.prototype.Vf;tm.prototype.getType=tm.prototype.U;tm.prototype.intersectsExtent=tm.prototype.Xa;tm.prototype.setGeometries=tm.prototype.oj;tm.prototype.applyTransform=tm.prototype.Dc;tm.prototype.translate=tm.prototype.translate;t("ol.geom.LinearRing",Jf);Jf.prototype.clone=Jf.prototype.clone;
+Jf.prototype.getArea=Jf.prototype.qn;Jf.prototype.getCoordinates=Jf.prototype.X;Jf.prototype.getType=Jf.prototype.U;Jf.prototype.setCoordinates=Jf.prototype.ma;t("ol.geom.LineString",O);O.prototype.appendCoordinate=O.prototype.mk;O.prototype.clone=O.prototype.clone;O.prototype.forEachSegment=O.prototype.Ck;O.prototype.getCoordinateAtM=O.prototype.nn;O.prototype.getCoordinates=O.prototype.X;O.prototype.getCoordinateAt=O.prototype.wh;O.prototype.getLength=O.prototype.pn;O.prototype.getType=O.prototype.U;
+O.prototype.intersectsExtent=O.prototype.Xa;O.prototype.setCoordinates=O.prototype.ma;t("ol.geom.MultiLineString",P);P.prototype.appendLineString=P.prototype.nk;P.prototype.clone=P.prototype.clone;P.prototype.getCoordinateAtM=P.prototype.rn;P.prototype.getCoordinates=P.prototype.X;P.prototype.getLineString=P.prototype.Yk;P.prototype.getLineStrings=P.prototype.gd;P.prototype.getType=P.prototype.U;P.prototype.intersectsExtent=P.prototype.Xa;P.prototype.setCoordinates=P.prototype.ma;
+t("ol.geom.MultiPoint",Q);Q.prototype.appendPoint=Q.prototype.qk;Q.prototype.clone=Q.prototype.clone;Q.prototype.getCoordinates=Q.prototype.X;Q.prototype.getPoint=Q.prototype.il;Q.prototype.getPoints=Q.prototype.Zd;Q.prototype.getType=Q.prototype.U;Q.prototype.intersectsExtent=Q.prototype.Xa;Q.prototype.setCoordinates=Q.prototype.ma;t("ol.geom.MultiPolygon",R);R.prototype.appendPolygon=R.prototype.rk;R.prototype.clone=R.prototype.clone;R.prototype.getArea=R.prototype.sn;
+R.prototype.getCoordinates=R.prototype.X;R.prototype.getInteriorPoints=R.prototype.Uk;R.prototype.getPolygon=R.prototype.jl;R.prototype.getPolygons=R.prototype.Td;R.prototype.getType=R.prototype.U;R.prototype.intersectsExtent=R.prototype.Xa;R.prototype.setCoordinates=R.prototype.ma;t("ol.geom.Point",C);C.prototype.clone=C.prototype.clone;C.prototype.getCoordinates=C.prototype.X;C.prototype.getType=C.prototype.U;C.prototype.intersectsExtent=C.prototype.Xa;C.prototype.setCoordinates=C.prototype.ma;
+t("ol.geom.Polygon",D);D.prototype.appendLinearRing=D.prototype.pk;D.prototype.clone=D.prototype.clone;D.prototype.getArea=D.prototype.tn;D.prototype.getCoordinates=D.prototype.X;D.prototype.getInteriorPoint=D.prototype.Tk;D.prototype.getLinearRingCount=D.prototype.Zk;D.prototype.getLinearRing=D.prototype.Ch;D.prototype.getLinearRings=D.prototype.Sd;D.prototype.getType=D.prototype.U;D.prototype.intersectsExtent=D.prototype.Xa;D.prototype.setCoordinates=D.prototype.ma;
+t("ol.geom.Polygon.circular",Xf);t("ol.geom.Polygon.fromExtent",Yf);t("ol.geom.Polygon.fromCircle",Zf);t("ol.geom.SimpleGeometry",rf);rf.prototype.getFirstCoordinate=rf.prototype.ac;rf.prototype.getLastCoordinate=rf.prototype.bc;rf.prototype.getLayout=rf.prototype.cc;rf.prototype.applyTransform=rf.prototype.Dc;rf.prototype.translate=rf.prototype.translate;t("ol.format.EsriJSON",Ql);Ql.prototype.readFeature=Ql.prototype.Tb;Ql.prototype.readFeatures=Ql.prototype.Oa;Ql.prototype.readGeometry=Ql.prototype.Sc;
+Ql.prototype.readProjection=Ql.prototype.kb;Ql.prototype.writeGeometry=Ql.prototype.$c;Ql.prototype.writeGeometryObject=Ql.prototype.je;Ql.prototype.writeFeature=Ql.prototype.Bd;Ql.prototype.writeFeatureObject=Ql.prototype.Zc;Ql.prototype.writeFeatures=Ql.prototype.Wb;Ql.prototype.writeFeaturesObject=Ql.prototype.he;t("ol.format.Feature",El);t("ol.format.filter.and",rm);
+t("ol.format.filter.or",function(a){var b=[null].concat(Array.prototype.slice.call(arguments));return new (Function.prototype.bind.apply(pm,b))});t("ol.format.filter.not",function(a){return new nm(a)});t("ol.format.filter.bbox",sm);t("ol.format.filter.intersects",function(a,b,c){return new hm(a,b,c)});t("ol.format.filter.within",function(a,b,c){return new qm(a,b,c)});t("ol.format.filter.equalTo",function(a,b,c){return new dm(a,b,c)});
+t("ol.format.filter.notEqualTo",function(a,b,c){return new om(a,b,c)});t("ol.format.filter.lessThan",function(a,b){return new lm(a,b)});t("ol.format.filter.lessThanOrEqualTo",function(a,b){return new mm(a,b)});t("ol.format.filter.greaterThan",function(a,b){return new em(a,b)});t("ol.format.filter.greaterThanOrEqualTo",function(a,b){return new fm(a,b)});t("ol.format.filter.isNull",function(a){return new km(a)});t("ol.format.filter.between",function(a,b,c){return new im(a,b,c)});
+t("ol.format.filter.like",function(a,b,c,d,e,f){return new jm(a,b,c,d,e,f)});t("ol.format.filter.during",function(a,b,c){return new bm(a,b,c)});t("ol.format.GeoJSON",xm);xm.prototype.readFeature=xm.prototype.Tb;xm.prototype.readFeatures=xm.prototype.Oa;xm.prototype.readGeometry=xm.prototype.Sc;xm.prototype.readProjection=xm.prototype.kb;xm.prototype.writeFeature=xm.prototype.Bd;xm.prototype.writeFeatureObject=xm.prototype.Zc;xm.prototype.writeFeatures=xm.prototype.Wb;
+xm.prototype.writeFeaturesObject=xm.prototype.he;xm.prototype.writeGeometry=xm.prototype.$c;xm.prototype.writeGeometryObject=xm.prototype.je;t("ol.format.GML",Sm);Sm.prototype.writeFeatures=Sm.prototype.Wb;Sm.prototype.writeFeaturesNode=Sm.prototype.Xb;t("ol.format.GML2",an);t("ol.format.GML3",Sm);Sm.prototype.writeGeometryNode=Sm.prototype.ie;Sm.prototype.writeFeatures=Sm.prototype.Wb;Sm.prototype.writeFeaturesNode=Sm.prototype.Xb;Fm.prototype.readFeatures=Fm.prototype.Oa;t("ol.format.GPX",mn);
+mn.prototype.readFeature=mn.prototype.Tb;mn.prototype.readFeatures=mn.prototype.Oa;mn.prototype.readProjection=mn.prototype.kb;mn.prototype.writeFeatures=mn.prototype.Wb;mn.prototype.writeFeaturesNode=mn.prototype.Xb;t("ol.format.IGC",Xn);Xn.prototype.readFeature=Xn.prototype.Tb;Xn.prototype.readFeatures=Xn.prototype.Oa;Xn.prototype.readProjection=Xn.prototype.kb;t("ol.format.KML",go);go.prototype.readFeature=go.prototype.Tb;go.prototype.readFeatures=go.prototype.Oa;go.prototype.readName=go.prototype.Cp;
+go.prototype.readNetworkLinks=go.prototype.Dp;go.prototype.readRegion=go.prototype.Gp;go.prototype.readRegionFromNode=go.prototype.lf;go.prototype.readProjection=go.prototype.kb;go.prototype.writeFeatures=go.prototype.Wb;go.prototype.writeFeaturesNode=go.prototype.Xb;t("ol.format.MVT",lq);lq.prototype.readFeatures=lq.prototype.Oa;lq.prototype.readProjection=lq.prototype.kb;lq.prototype.setLayers=lq.prototype.mn;t("ol.format.OSMXML",nq);nq.prototype.readFeatures=nq.prototype.Oa;
+nq.prototype.readProjection=nq.prototype.kb;t("ol.format.Polyline",Nq);t("ol.format.Polyline.encodeDeltas",Oq);t("ol.format.Polyline.decodeDeltas",Qq);t("ol.format.Polyline.encodeFloats",Pq);t("ol.format.Polyline.decodeFloats",Rq);Nq.prototype.readFeature=Nq.prototype.Tb;Nq.prototype.readFeatures=Nq.prototype.Oa;Nq.prototype.readGeometry=Nq.prototype.Sc;Nq.prototype.readProjection=Nq.prototype.kb;Nq.prototype.writeGeometry=Nq.prototype.$c;t("ol.format.TopoJSON",Sq);Sq.prototype.readFeatures=Sq.prototype.Oa;
+Sq.prototype.readProjection=Sq.prototype.kb;t("ol.format.WFS",Yq);Yq.prototype.readFeatures=Yq.prototype.Oa;Yq.prototype.readTransactionResponse=Yq.prototype.j;Yq.prototype.readFeatureCollectionMetadata=Yq.prototype.g;t("ol.format.WFS.writeFilter",function(a){var b=jl("http://www.opengis.net/ogc","Filter");Bl({node:b},mr,wl(a.kc),[a],[]);return b});Yq.prototype.writeGetFeature=Yq.prototype.l;Yq.prototype.writeTransaction=Yq.prototype.v;Yq.prototype.readProjection=Yq.prototype.kb;
+t("ol.format.WKT",sr);sr.prototype.readFeature=sr.prototype.Tb;sr.prototype.readFeatures=sr.prototype.Oa;sr.prototype.readGeometry=sr.prototype.Sc;sr.prototype.writeFeature=sr.prototype.Bd;sr.prototype.writeFeatures=sr.prototype.Wb;sr.prototype.writeGeometry=sr.prototype.$c;t("ol.format.WMSCapabilities",Lr);Lr.prototype.read=Lr.prototype.read;t("ol.format.WMSGetFeatureInfo",hs);hs.prototype.readFeatures=hs.prototype.Oa;t("ol.format.WMTSCapabilities",is);is.prototype.read=is.prototype.read;
+t("ol.format.filter.And",Zl);t("ol.format.filter.Bbox",$l);t("ol.format.filter.Comparison",am);t("ol.format.filter.ComparisonBinary",cm);t("ol.format.filter.During",bm);t("ol.format.filter.EqualTo",dm);t("ol.format.filter.Filter",Xl);t("ol.format.filter.GreaterThan",em);t("ol.format.filter.GreaterThanOrEqualTo",fm);t("ol.format.filter.Intersects",hm);t("ol.format.filter.IsBetween",im);t("ol.format.filter.IsLike",jm);t("ol.format.filter.IsNull",km);t("ol.format.filter.LessThan",lm);
+t("ol.format.filter.LessThanOrEqualTo",mm);t("ol.format.filter.Not",nm);t("ol.format.filter.NotEqualTo",om);t("ol.format.filter.Or",pm);t("ol.format.filter.Spatial",gm);t("ol.format.filter.Within",qm);t("ol.events.condition.altKeyOnly",function(a){a=a.originalEvent;return a.altKey&&!(a.metaKey||a.ctrlKey)&&!a.shiftKey});t("ol.events.condition.altShiftKeysOnly",tg);t("ol.events.condition.always",mf);t("ol.events.condition.click",function(a){return"click"==a.type});t("ol.events.condition.never",nf);
+t("ol.events.condition.pointerMove",vg);t("ol.events.condition.singleClick",wg);t("ol.events.condition.doubleClick",function(a){return"dblclick"==a.type});t("ol.events.condition.noModifierKeys",xg);t("ol.events.condition.platformModifierKeyOnly",function(a){a=a.originalEvent;return!a.altKey&&(Rd?a.metaKey:a.ctrlKey)&&!a.shiftKey});t("ol.events.condition.shiftKeyOnly",yg);t("ol.events.condition.targetNotEditable",Ag);t("ol.events.condition.mouseOnly",Bg);t("ol.events.condition.primaryAction",Cg);
+Oc.prototype.type=Oc.prototype.type;Oc.prototype.target=Oc.prototype.target;Oc.prototype.preventDefault=Oc.prototype.preventDefault;Oc.prototype.stopPropagation=Oc.prototype.stopPropagation;t("ol.control.Attribution",nd);t("ol.control.Attribution.render",od);nd.prototype.getCollapsible=nd.prototype.Um;nd.prototype.setCollapsible=nd.prototype.Xm;nd.prototype.setCollapsed=nd.prototype.Wm;nd.prototype.getCollapsed=nd.prototype.Tm;t("ol.control.Control",md);md.prototype.getMap=md.prototype.g;
+md.prototype.setMap=md.prototype.setMap;md.prototype.setTarget=md.prototype.f;t("ol.control.FullScreen",yd);t("ol.control.MousePosition",Dd);t("ol.control.MousePosition.render",Ed);Dd.prototype.getCoordinateFormat=Dd.prototype.xh;Dd.prototype.getProjection=Dd.prototype.Zh;Dd.prototype.setCoordinateFormat=Dd.prototype.kj;Dd.prototype.setProjection=Dd.prototype.$h;t("ol.control.OverviewMap",Bk);t("ol.control.OverviewMap.render",Ck);Bk.prototype.getCollapsible=Bk.prototype.$m;
+Bk.prototype.setCollapsible=Bk.prototype.cn;Bk.prototype.setCollapsed=Bk.prototype.bn;Bk.prototype.getCollapsed=Bk.prototype.Zm;Bk.prototype.getOverviewMap=Bk.prototype.gl;t("ol.control.Rotate",ud);t("ol.control.Rotate.render",vd);t("ol.control.ScaleLine",Gk);Gk.prototype.getUnits=Gk.prototype.C;t("ol.control.ScaleLine.render",Hk);Gk.prototype.setUnits=Gk.prototype.I;t("ol.control.Zoom",wd);t("ol.control.ZoomSlider",Lk);t("ol.control.ZoomSlider.render",Nk);t("ol.control.ZoomToExtent",Qk);
+Tc.prototype.changed=Tc.prototype.s;Tc.prototype.dispatchEvent=Tc.prototype.b;Tc.prototype.getRevision=Tc.prototype.L;Tc.prototype.on=Tc.prototype.J;Tc.prototype.once=Tc.prototype.once;Tc.prototype.un=Tc.prototype.K;Yc.prototype.get=Yc.prototype.get;Yc.prototype.getKeys=Yc.prototype.O;Yc.prototype.getProperties=Yc.prototype.N;Yc.prototype.set=Yc.prototype.set;Yc.prototype.setProperties=Yc.prototype.H;Yc.prototype.unset=Yc.prototype.P;Yc.prototype.changed=Yc.prototype.s;
+Yc.prototype.dispatchEvent=Yc.prototype.b;Yc.prototype.getRevision=Yc.prototype.L;Yc.prototype.on=Yc.prototype.J;Yc.prototype.once=Yc.prototype.once;Yc.prototype.un=Yc.prototype.K;bd.prototype.type=bd.prototype.type;bd.prototype.target=bd.prototype.target;bd.prototype.preventDefault=bd.prototype.preventDefault;bd.prototype.stopPropagation=bd.prototype.stopPropagation;Rk.prototype.get=Rk.prototype.get;Rk.prototype.getKeys=Rk.prototype.O;Rk.prototype.getProperties=Rk.prototype.N;Rk.prototype.set=Rk.prototype.set;
+Rk.prototype.setProperties=Rk.prototype.H;Rk.prototype.unset=Rk.prototype.P;Rk.prototype.changed=Rk.prototype.s;Rk.prototype.dispatchEvent=Rk.prototype.b;Rk.prototype.getRevision=Rk.prototype.L;Rk.prototype.on=Rk.prototype.J;Rk.prototype.once=Rk.prototype.once;Rk.prototype.un=Rk.prototype.K;H.prototype.get=H.prototype.get;H.prototype.getKeys=H.prototype.O;H.prototype.getProperties=H.prototype.N;H.prototype.set=H.prototype.set;H.prototype.setProperties=H.prototype.H;H.prototype.unset=H.prototype.P;
+H.prototype.changed=H.prototype.s;H.prototype.dispatchEvent=H.prototype.b;H.prototype.getRevision=H.prototype.L;H.prototype.on=H.prototype.J;H.prototype.once=H.prototype.once;H.prototype.un=H.prototype.K;xs.prototype.get=xs.prototype.get;xs.prototype.getKeys=xs.prototype.O;xs.prototype.getProperties=xs.prototype.N;xs.prototype.set=xs.prototype.set;xs.prototype.setProperties=xs.prototype.H;xs.prototype.unset=xs.prototype.P;xs.prototype.changed=xs.prototype.s;xs.prototype.dispatchEvent=xs.prototype.b;
+xs.prototype.getRevision=xs.prototype.L;xs.prototype.on=xs.prototype.J;xs.prototype.once=xs.prototype.once;xs.prototype.un=xs.prototype.K;Os.prototype.getTileCoord=Os.prototype.f;Os.prototype.load=Os.prototype.load;G.prototype.get=G.prototype.get;G.prototype.getKeys=G.prototype.O;G.prototype.getProperties=G.prototype.N;G.prototype.set=G.prototype.set;G.prototype.setProperties=G.prototype.H;G.prototype.unset=G.prototype.P;G.prototype.changed=G.prototype.s;G.prototype.dispatchEvent=G.prototype.b;
+G.prototype.getRevision=G.prototype.L;G.prototype.on=G.prototype.J;G.prototype.once=G.prototype.once;G.prototype.un=G.prototype.K;Id.prototype.type=Id.prototype.type;Id.prototype.target=Id.prototype.target;Id.prototype.preventDefault=Id.prototype.preventDefault;Id.prototype.stopPropagation=Id.prototype.stopPropagation;Jd.prototype.map=Jd.prototype.map;Jd.prototype.frameState=Jd.prototype.frameState;Jd.prototype.type=Jd.prototype.type;Jd.prototype.target=Jd.prototype.target;
+Jd.prototype.preventDefault=Jd.prototype.preventDefault;Jd.prototype.stopPropagation=Jd.prototype.stopPropagation;ee.prototype.originalEvent=ee.prototype.originalEvent;ee.prototype.pixel=ee.prototype.pixel;ee.prototype.coordinate=ee.prototype.coordinate;ee.prototype.dragging=ee.prototype.dragging;ee.prototype.preventDefault=ee.prototype.preventDefault;ee.prototype.stopPropagation=ee.prototype.stopPropagation;ee.prototype.map=ee.prototype.map;ee.prototype.frameState=ee.prototype.frameState;
+ee.prototype.type=ee.prototype.type;ee.prototype.target=ee.prototype.target;Xc.prototype.type=Xc.prototype.type;Xc.prototype.target=Xc.prototype.target;Xc.prototype.preventDefault=Xc.prototype.preventDefault;Xc.prototype.stopPropagation=Xc.prototype.stopPropagation;sk.prototype.get=sk.prototype.get;sk.prototype.getKeys=sk.prototype.O;sk.prototype.getProperties=sk.prototype.N;sk.prototype.set=sk.prototype.set;sk.prototype.setProperties=sk.prototype.H;sk.prototype.unset=sk.prototype.P;
+sk.prototype.changed=sk.prototype.s;sk.prototype.dispatchEvent=sk.prototype.b;sk.prototype.getRevision=sk.prototype.L;sk.prototype.on=sk.prototype.J;sk.prototype.once=sk.prototype.once;sk.prototype.un=sk.prototype.K;nx.prototype.getTileCoord=nx.prototype.f;nx.prototype.load=nx.prototype.load;px.prototype.getTileCoord=px.prototype.f;px.prototype.load=px.prototype.load;F.prototype.get=F.prototype.get;F.prototype.getKeys=F.prototype.O;F.prototype.getProperties=F.prototype.N;F.prototype.set=F.prototype.set;
+F.prototype.setProperties=F.prototype.H;F.prototype.unset=F.prototype.P;F.prototype.changed=F.prototype.s;F.prototype.dispatchEvent=F.prototype.b;F.prototype.getRevision=F.prototype.L;F.prototype.on=F.prototype.J;F.prototype.once=F.prototype.once;F.prototype.un=F.prototype.K;rx.prototype.forEachTileCoord=rx.prototype.Rf;rx.prototype.getMaxZoom=rx.prototype.Ti;rx.prototype.getMinZoom=rx.prototype.Ui;rx.prototype.getOrigin=rx.prototype.Pc;rx.prototype.getResolution=rx.prototype.Da;
+rx.prototype.getResolutions=rx.prototype.Vi;rx.prototype.getTileCoordExtent=rx.prototype.Aa;rx.prototype.getTileCoordForCoordAndResolution=rx.prototype.Be;rx.prototype.getTileCoordForCoordAndZ=rx.prototype.bg;rx.prototype.getTileSize=rx.prototype.gb;rx.prototype.getZForResolution=rx.prototype.tc;Yk.prototype.getOpacity=Yk.prototype.Ze;Yk.prototype.getRotateWithView=Yk.prototype.$e;Yk.prototype.getRotation=Yk.prototype.af;Yk.prototype.getScale=Yk.prototype.bf;Yk.prototype.getSnapToPixel=Yk.prototype.Ae;
+Yk.prototype.setOpacity=Yk.prototype.td;Yk.prototype.setRotation=Yk.prototype.cf;Yk.prototype.setScale=Yk.prototype.ud;$k.prototype.clone=$k.prototype.clone;$k.prototype.getAngle=$k.prototype.Pi;$k.prototype.getFill=$k.prototype.Fa;$k.prototype.getPoints=$k.prototype.Qi;$k.prototype.getRadius=$k.prototype.Ri;$k.prototype.getRadius2=$k.prototype.Fh;$k.prototype.getStroke=$k.prototype.Ga;$k.prototype.getOpacity=$k.prototype.Ze;$k.prototype.getRotateWithView=$k.prototype.$e;
+$k.prototype.getRotation=$k.prototype.af;$k.prototype.getScale=$k.prototype.bf;$k.prototype.getSnapToPixel=$k.prototype.Ae;$k.prototype.setOpacity=$k.prototype.td;$k.prototype.setRotation=$k.prototype.cf;$k.prototype.setScale=$k.prototype.ud;eo.prototype.getOpacity=eo.prototype.Ze;eo.prototype.getRotateWithView=eo.prototype.$e;eo.prototype.getRotation=eo.prototype.af;eo.prototype.getScale=eo.prototype.bf;eo.prototype.getSnapToPixel=eo.prototype.Ae;eo.prototype.setOpacity=eo.prototype.td;
+eo.prototype.setRotation=eo.prototype.cf;eo.prototype.setScale=eo.prototype.ud;$t.prototype.get=$t.prototype.get;$t.prototype.getKeys=$t.prototype.O;$t.prototype.getProperties=$t.prototype.N;$t.prototype.set=$t.prototype.set;$t.prototype.setProperties=$t.prototype.H;$t.prototype.unset=$t.prototype.P;$t.prototype.changed=$t.prototype.s;$t.prototype.dispatchEvent=$t.prototype.b;$t.prototype.getRevision=$t.prototype.L;$t.prototype.on=$t.prototype.J;$t.prototype.once=$t.prototype.once;
+$t.prototype.un=$t.prototype.K;pw.prototype.getAttributions=pw.prototype.ya;pw.prototype.getLogo=pw.prototype.xa;pw.prototype.getProjection=pw.prototype.za;pw.prototype.getState=pw.prototype.getState;pw.prototype.refresh=pw.prototype.sa;pw.prototype.setAttributions=pw.prototype.ua;pw.prototype.get=pw.prototype.get;pw.prototype.getKeys=pw.prototype.O;pw.prototype.getProperties=pw.prototype.N;pw.prototype.set=pw.prototype.set;pw.prototype.setProperties=pw.prototype.H;pw.prototype.unset=pw.prototype.P;
+pw.prototype.changed=pw.prototype.s;pw.prototype.dispatchEvent=pw.prototype.b;pw.prototype.getRevision=pw.prototype.L;pw.prototype.on=pw.prototype.J;pw.prototype.once=pw.prototype.once;pw.prototype.un=pw.prototype.K;tw.prototype.getTileGrid=tw.prototype.ab;tw.prototype.refresh=tw.prototype.sa;tw.prototype.getAttributions=tw.prototype.ya;tw.prototype.getLogo=tw.prototype.xa;tw.prototype.getProjection=tw.prototype.za;tw.prototype.getState=tw.prototype.getState;tw.prototype.setAttributions=tw.prototype.ua;
+tw.prototype.get=tw.prototype.get;tw.prototype.getKeys=tw.prototype.O;tw.prototype.getProperties=tw.prototype.N;tw.prototype.set=tw.prototype.set;tw.prototype.setProperties=tw.prototype.H;tw.prototype.unset=tw.prototype.P;tw.prototype.changed=tw.prototype.s;tw.prototype.dispatchEvent=tw.prototype.b;tw.prototype.getRevision=tw.prototype.L;tw.prototype.on=tw.prototype.J;tw.prototype.once=tw.prototype.once;tw.prototype.un=tw.prototype.K;X.prototype.getTileLoadFunction=X.prototype.pb;
+X.prototype.getTileUrlFunction=X.prototype.qb;X.prototype.getUrls=X.prototype.rb;X.prototype.setTileLoadFunction=X.prototype.vb;X.prototype.setTileUrlFunction=X.prototype.cb;X.prototype.setUrl=X.prototype.jb;X.prototype.setUrls=X.prototype.eb;X.prototype.getTileGrid=X.prototype.ab;X.prototype.refresh=X.prototype.sa;X.prototype.getAttributions=X.prototype.ya;X.prototype.getLogo=X.prototype.xa;X.prototype.getProjection=X.prototype.za;X.prototype.getState=X.prototype.getState;
+X.prototype.setAttributions=X.prototype.ua;X.prototype.get=X.prototype.get;X.prototype.getKeys=X.prototype.O;X.prototype.getProperties=X.prototype.N;X.prototype.set=X.prototype.set;X.prototype.setProperties=X.prototype.H;X.prototype.unset=X.prototype.P;X.prototype.changed=X.prototype.s;X.prototype.dispatchEvent=X.prototype.b;X.prototype.getRevision=X.prototype.L;X.prototype.on=X.prototype.J;X.prototype.once=X.prototype.once;X.prototype.un=X.prototype.K;xw.prototype.setRenderReprojectionEdges=xw.prototype.Pb;
+xw.prototype.setTileGridForProjection=xw.prototype.Qb;xw.prototype.getTileLoadFunction=xw.prototype.pb;xw.prototype.getTileUrlFunction=xw.prototype.qb;xw.prototype.getUrls=xw.prototype.rb;xw.prototype.setTileLoadFunction=xw.prototype.vb;xw.prototype.setTileUrlFunction=xw.prototype.cb;xw.prototype.setUrl=xw.prototype.jb;xw.prototype.setUrls=xw.prototype.eb;xw.prototype.getTileGrid=xw.prototype.ab;xw.prototype.refresh=xw.prototype.sa;xw.prototype.getAttributions=xw.prototype.ya;
+xw.prototype.getLogo=xw.prototype.xa;xw.prototype.getProjection=xw.prototype.za;xw.prototype.getState=xw.prototype.getState;xw.prototype.setAttributions=xw.prototype.ua;xw.prototype.get=xw.prototype.get;xw.prototype.getKeys=xw.prototype.O;xw.prototype.getProperties=xw.prototype.N;xw.prototype.set=xw.prototype.set;xw.prototype.setProperties=xw.prototype.H;xw.prototype.unset=xw.prototype.P;xw.prototype.changed=xw.prototype.s;xw.prototype.dispatchEvent=xw.prototype.b;xw.prototype.getRevision=xw.prototype.L;
+xw.prototype.on=xw.prototype.J;xw.prototype.once=xw.prototype.once;xw.prototype.un=xw.prototype.K;zw.prototype.setRenderReprojectionEdges=zw.prototype.Pb;zw.prototype.setTileGridForProjection=zw.prototype.Qb;zw.prototype.getTileLoadFunction=zw.prototype.pb;zw.prototype.getTileUrlFunction=zw.prototype.qb;zw.prototype.getUrls=zw.prototype.rb;zw.prototype.setTileLoadFunction=zw.prototype.vb;zw.prototype.setTileUrlFunction=zw.prototype.cb;zw.prototype.setUrl=zw.prototype.jb;zw.prototype.setUrls=zw.prototype.eb;
+zw.prototype.getTileGrid=zw.prototype.ab;zw.prototype.refresh=zw.prototype.sa;zw.prototype.getAttributions=zw.prototype.ya;zw.prototype.getLogo=zw.prototype.xa;zw.prototype.getProjection=zw.prototype.za;zw.prototype.getState=zw.prototype.getState;zw.prototype.setAttributions=zw.prototype.ua;zw.prototype.get=zw.prototype.get;zw.prototype.getKeys=zw.prototype.O;zw.prototype.getProperties=zw.prototype.N;zw.prototype.set=zw.prototype.set;zw.prototype.setProperties=zw.prototype.H;zw.prototype.unset=zw.prototype.P;
+zw.prototype.changed=zw.prototype.s;zw.prototype.dispatchEvent=zw.prototype.b;zw.prototype.getRevision=zw.prototype.L;zw.prototype.on=zw.prototype.J;zw.prototype.once=zw.prototype.once;zw.prototype.un=zw.prototype.K;Aw.prototype.setRenderReprojectionEdges=Aw.prototype.Pb;Aw.prototype.setTileGridForProjection=Aw.prototype.Qb;Aw.prototype.getTileLoadFunction=Aw.prototype.pb;Aw.prototype.getTileUrlFunction=Aw.prototype.qb;Aw.prototype.getUrls=Aw.prototype.rb;Aw.prototype.setTileLoadFunction=Aw.prototype.vb;
+Aw.prototype.setTileUrlFunction=Aw.prototype.cb;Aw.prototype.setUrl=Aw.prototype.jb;Aw.prototype.setUrls=Aw.prototype.eb;Aw.prototype.getTileGrid=Aw.prototype.ab;Aw.prototype.refresh=Aw.prototype.sa;Aw.prototype.getAttributions=Aw.prototype.ya;Aw.prototype.getLogo=Aw.prototype.xa;Aw.prototype.getProjection=Aw.prototype.za;Aw.prototype.getState=Aw.prototype.getState;Aw.prototype.setAttributions=Aw.prototype.ua;Aw.prototype.get=Aw.prototype.get;Aw.prototype.getKeys=Aw.prototype.O;
+Aw.prototype.getProperties=Aw.prototype.N;Aw.prototype.set=Aw.prototype.set;Aw.prototype.setProperties=Aw.prototype.H;Aw.prototype.unset=Aw.prototype.P;Aw.prototype.changed=Aw.prototype.s;Aw.prototype.dispatchEvent=Aw.prototype.b;Aw.prototype.getRevision=Aw.prototype.L;Aw.prototype.on=Aw.prototype.J;Aw.prototype.once=Aw.prototype.once;Aw.prototype.un=Aw.prototype.K;U.prototype.getAttributions=U.prototype.ya;U.prototype.getLogo=U.prototype.xa;U.prototype.getProjection=U.prototype.za;
+U.prototype.getState=U.prototype.getState;U.prototype.refresh=U.prototype.sa;U.prototype.setAttributions=U.prototype.ua;U.prototype.get=U.prototype.get;U.prototype.getKeys=U.prototype.O;U.prototype.getProperties=U.prototype.N;U.prototype.set=U.prototype.set;U.prototype.setProperties=U.prototype.H;U.prototype.unset=U.prototype.P;U.prototype.changed=U.prototype.s;U.prototype.dispatchEvent=U.prototype.b;U.prototype.getRevision=U.prototype.L;U.prototype.on=U.prototype.J;U.prototype.once=U.prototype.once;
+U.prototype.un=U.prototype.K;Y.prototype.addFeature=Y.prototype.yb;Y.prototype.addFeatures=Y.prototype.cd;Y.prototype.clear=Y.prototype.clear;Y.prototype.forEachFeature=Y.prototype.sh;Y.prototype.forEachFeatureInExtent=Y.prototype.$b;Y.prototype.forEachFeatureIntersectingExtent=Y.prototype.th;Y.prototype.getFeaturesCollection=Y.prototype.Ah;Y.prototype.getFeatures=Y.prototype.Xe;Y.prototype.getFeaturesAtCoordinate=Y.prototype.zh;Y.prototype.getFeaturesInExtent=Y.prototype.Uf;
+Y.prototype.getClosestFeatureToCoordinate=Y.prototype.vh;Y.prototype.getExtent=Y.prototype.G;Y.prototype.getFeatureById=Y.prototype.yh;Y.prototype.getFormat=Y.prototype.Mi;Y.prototype.getUrl=Y.prototype.Ni;Y.prototype.removeFeature=Y.prototype.Gb;Y.prototype.getAttributions=Y.prototype.ya;Y.prototype.getLogo=Y.prototype.xa;Y.prototype.getProjection=Y.prototype.za;Y.prototype.getState=Y.prototype.getState;Y.prototype.refresh=Y.prototype.sa;Y.prototype.setAttributions=Y.prototype.ua;
+Y.prototype.get=Y.prototype.get;Y.prototype.getKeys=Y.prototype.O;Y.prototype.getProperties=Y.prototype.N;Y.prototype.set=Y.prototype.set;Y.prototype.setProperties=Y.prototype.H;Y.prototype.unset=Y.prototype.P;Y.prototype.changed=Y.prototype.s;Y.prototype.dispatchEvent=Y.prototype.b;Y.prototype.getRevision=Y.prototype.L;Y.prototype.on=Y.prototype.J;Y.prototype.once=Y.prototype.once;Y.prototype.un=Y.prototype.K;Hv.prototype.getAttributions=Hv.prototype.ya;Hv.prototype.getLogo=Hv.prototype.xa;
+Hv.prototype.getProjection=Hv.prototype.za;Hv.prototype.getState=Hv.prototype.getState;Hv.prototype.refresh=Hv.prototype.sa;Hv.prototype.setAttributions=Hv.prototype.ua;Hv.prototype.get=Hv.prototype.get;Hv.prototype.getKeys=Hv.prototype.O;Hv.prototype.getProperties=Hv.prototype.N;Hv.prototype.set=Hv.prototype.set;Hv.prototype.setProperties=Hv.prototype.H;Hv.prototype.unset=Hv.prototype.P;Hv.prototype.changed=Hv.prototype.s;Hv.prototype.dispatchEvent=Hv.prototype.b;Hv.prototype.getRevision=Hv.prototype.L;
+Hv.prototype.on=Hv.prototype.J;Hv.prototype.once=Hv.prototype.once;Hv.prototype.un=Hv.prototype.K;Jv.prototype.type=Jv.prototype.type;Jv.prototype.target=Jv.prototype.target;Jv.prototype.preventDefault=Jv.prototype.preventDefault;Jv.prototype.stopPropagation=Jv.prototype.stopPropagation;Gw.prototype.getAttributions=Gw.prototype.ya;Gw.prototype.getLogo=Gw.prototype.xa;Gw.prototype.getProjection=Gw.prototype.za;Gw.prototype.getState=Gw.prototype.getState;Gw.prototype.refresh=Gw.prototype.sa;
+Gw.prototype.setAttributions=Gw.prototype.ua;Gw.prototype.get=Gw.prototype.get;Gw.prototype.getKeys=Gw.prototype.O;Gw.prototype.getProperties=Gw.prototype.N;Gw.prototype.set=Gw.prototype.set;Gw.prototype.setProperties=Gw.prototype.H;Gw.prototype.unset=Gw.prototype.P;Gw.prototype.changed=Gw.prototype.s;Gw.prototype.dispatchEvent=Gw.prototype.b;Gw.prototype.getRevision=Gw.prototype.L;Gw.prototype.on=Gw.prototype.J;Gw.prototype.once=Gw.prototype.once;Gw.prototype.un=Gw.prototype.K;
+Ov.prototype.getAttributions=Ov.prototype.ya;Ov.prototype.getLogo=Ov.prototype.xa;Ov.prototype.getProjection=Ov.prototype.za;Ov.prototype.getState=Ov.prototype.getState;Ov.prototype.refresh=Ov.prototype.sa;Ov.prototype.setAttributions=Ov.prototype.ua;Ov.prototype.get=Ov.prototype.get;Ov.prototype.getKeys=Ov.prototype.O;Ov.prototype.getProperties=Ov.prototype.N;Ov.prototype.set=Ov.prototype.set;Ov.prototype.setProperties=Ov.prototype.H;Ov.prototype.unset=Ov.prototype.P;Ov.prototype.changed=Ov.prototype.s;
+Ov.prototype.dispatchEvent=Ov.prototype.b;Ov.prototype.getRevision=Ov.prototype.L;Ov.prototype.on=Ov.prototype.J;Ov.prototype.once=Ov.prototype.once;Ov.prototype.un=Ov.prototype.K;Hw.prototype.getAttributions=Hw.prototype.ya;Hw.prototype.getLogo=Hw.prototype.xa;Hw.prototype.getProjection=Hw.prototype.za;Hw.prototype.getState=Hw.prototype.getState;Hw.prototype.refresh=Hw.prototype.sa;Hw.prototype.setAttributions=Hw.prototype.ua;Hw.prototype.get=Hw.prototype.get;Hw.prototype.getKeys=Hw.prototype.O;
+Hw.prototype.getProperties=Hw.prototype.N;Hw.prototype.set=Hw.prototype.set;Hw.prototype.setProperties=Hw.prototype.H;Hw.prototype.unset=Hw.prototype.P;Hw.prototype.changed=Hw.prototype.s;Hw.prototype.dispatchEvent=Hw.prototype.b;Hw.prototype.getRevision=Hw.prototype.L;Hw.prototype.on=Hw.prototype.J;Hw.prototype.once=Hw.prototype.once;Hw.prototype.un=Hw.prototype.K;Iw.prototype.getAttributions=Iw.prototype.ya;Iw.prototype.getLogo=Iw.prototype.xa;Iw.prototype.getProjection=Iw.prototype.za;
+Iw.prototype.getState=Iw.prototype.getState;Iw.prototype.refresh=Iw.prototype.sa;Iw.prototype.setAttributions=Iw.prototype.ua;Iw.prototype.get=Iw.prototype.get;Iw.prototype.getKeys=Iw.prototype.O;Iw.prototype.getProperties=Iw.prototype.N;Iw.prototype.set=Iw.prototype.set;Iw.prototype.setProperties=Iw.prototype.H;Iw.prototype.unset=Iw.prototype.P;Iw.prototype.changed=Iw.prototype.s;Iw.prototype.dispatchEvent=Iw.prototype.b;Iw.prototype.getRevision=Iw.prototype.L;Iw.prototype.on=Iw.prototype.J;
+Iw.prototype.once=Iw.prototype.once;Iw.prototype.un=Iw.prototype.K;Pv.prototype.getAttributions=Pv.prototype.ya;Pv.prototype.getLogo=Pv.prototype.xa;Pv.prototype.getProjection=Pv.prototype.za;Pv.prototype.getState=Pv.prototype.getState;Pv.prototype.refresh=Pv.prototype.sa;Pv.prototype.setAttributions=Pv.prototype.ua;Pv.prototype.get=Pv.prototype.get;Pv.prototype.getKeys=Pv.prototype.O;Pv.prototype.getProperties=Pv.prototype.N;Pv.prototype.set=Pv.prototype.set;Pv.prototype.setProperties=Pv.prototype.H;
+Pv.prototype.unset=Pv.prototype.P;Pv.prototype.changed=Pv.prototype.s;Pv.prototype.dispatchEvent=Pv.prototype.b;Pv.prototype.getRevision=Pv.prototype.L;Pv.prototype.on=Pv.prototype.J;Pv.prototype.once=Pv.prototype.once;Pv.prototype.un=Pv.prototype.K;Jw.prototype.getAttributions=Jw.prototype.ya;Jw.prototype.getLogo=Jw.prototype.xa;Jw.prototype.getProjection=Jw.prototype.za;Jw.prototype.getState=Jw.prototype.getState;Jw.prototype.refresh=Jw.prototype.sa;Jw.prototype.setAttributions=Jw.prototype.ua;
+Jw.prototype.get=Jw.prototype.get;Jw.prototype.getKeys=Jw.prototype.O;Jw.prototype.getProperties=Jw.prototype.N;Jw.prototype.set=Jw.prototype.set;Jw.prototype.setProperties=Jw.prototype.H;Jw.prototype.unset=Jw.prototype.P;Jw.prototype.changed=Jw.prototype.s;Jw.prototype.dispatchEvent=Jw.prototype.b;Jw.prototype.getRevision=Jw.prototype.L;Jw.prototype.on=Jw.prototype.J;Jw.prototype.once=Jw.prototype.once;Jw.prototype.un=Jw.prototype.K;Nw.prototype.setRenderReprojectionEdges=Nw.prototype.Pb;
+Nw.prototype.setTileGridForProjection=Nw.prototype.Qb;Nw.prototype.getTileLoadFunction=Nw.prototype.pb;Nw.prototype.getTileUrlFunction=Nw.prototype.qb;Nw.prototype.getUrls=Nw.prototype.rb;Nw.prototype.setTileLoadFunction=Nw.prototype.vb;Nw.prototype.setTileUrlFunction=Nw.prototype.cb;Nw.prototype.setUrl=Nw.prototype.jb;Nw.prototype.setUrls=Nw.prototype.eb;Nw.prototype.getTileGrid=Nw.prototype.ab;Nw.prototype.refresh=Nw.prototype.sa;Nw.prototype.getAttributions=Nw.prototype.ya;
+Nw.prototype.getLogo=Nw.prototype.xa;Nw.prototype.getProjection=Nw.prototype.za;Nw.prototype.getState=Nw.prototype.getState;Nw.prototype.setAttributions=Nw.prototype.ua;Nw.prototype.get=Nw.prototype.get;Nw.prototype.getKeys=Nw.prototype.O;Nw.prototype.getProperties=Nw.prototype.N;Nw.prototype.set=Nw.prototype.set;Nw.prototype.setProperties=Nw.prototype.H;Nw.prototype.unset=Nw.prototype.P;Nw.prototype.changed=Nw.prototype.s;Nw.prototype.dispatchEvent=Nw.prototype.b;Nw.prototype.getRevision=Nw.prototype.L;
+Nw.prototype.on=Nw.prototype.J;Nw.prototype.once=Nw.prototype.once;Nw.prototype.un=Nw.prototype.K;Pw.prototype.getAttributions=Pw.prototype.ya;Pw.prototype.getLogo=Pw.prototype.xa;Pw.prototype.getProjection=Pw.prototype.za;Pw.prototype.getState=Pw.prototype.getState;Pw.prototype.refresh=Pw.prototype.sa;Pw.prototype.setAttributions=Pw.prototype.ua;Pw.prototype.get=Pw.prototype.get;Pw.prototype.getKeys=Pw.prototype.O;Pw.prototype.getProperties=Pw.prototype.N;Pw.prototype.set=Pw.prototype.set;
+Pw.prototype.setProperties=Pw.prototype.H;Pw.prototype.unset=Pw.prototype.P;Pw.prototype.changed=Pw.prototype.s;Pw.prototype.dispatchEvent=Pw.prototype.b;Pw.prototype.getRevision=Pw.prototype.L;Pw.prototype.on=Pw.prototype.J;Pw.prototype.once=Pw.prototype.once;Pw.prototype.un=Pw.prototype.K;Tw.prototype.type=Tw.prototype.type;Tw.prototype.target=Tw.prototype.target;Tw.prototype.preventDefault=Tw.prototype.preventDefault;Tw.prototype.stopPropagation=Tw.prototype.stopPropagation;
+Ww.prototype.setRenderReprojectionEdges=Ww.prototype.Pb;Ww.prototype.setTileGridForProjection=Ww.prototype.Qb;Ww.prototype.getTileLoadFunction=Ww.prototype.pb;Ww.prototype.getTileUrlFunction=Ww.prototype.qb;Ww.prototype.getUrls=Ww.prototype.rb;Ww.prototype.setTileLoadFunction=Ww.prototype.vb;Ww.prototype.setTileUrlFunction=Ww.prototype.cb;Ww.prototype.setUrl=Ww.prototype.jb;Ww.prototype.setUrls=Ww.prototype.eb;Ww.prototype.getTileGrid=Ww.prototype.ab;Ww.prototype.refresh=Ww.prototype.sa;
+Ww.prototype.getAttributions=Ww.prototype.ya;Ww.prototype.getLogo=Ww.prototype.xa;Ww.prototype.getProjection=Ww.prototype.za;Ww.prototype.getState=Ww.prototype.getState;Ww.prototype.setAttributions=Ww.prototype.ua;Ww.prototype.get=Ww.prototype.get;Ww.prototype.getKeys=Ww.prototype.O;Ww.prototype.getProperties=Ww.prototype.N;Ww.prototype.set=Ww.prototype.set;Ww.prototype.setProperties=Ww.prototype.H;Ww.prototype.unset=Ww.prototype.P;Ww.prototype.changed=Ww.prototype.s;Ww.prototype.dispatchEvent=Ww.prototype.b;
+Ww.prototype.getRevision=Ww.prototype.L;Ww.prototype.on=Ww.prototype.J;Ww.prototype.once=Ww.prototype.once;Ww.prototype.un=Ww.prototype.K;sw.prototype.type=sw.prototype.type;sw.prototype.target=sw.prototype.target;sw.prototype.preventDefault=sw.prototype.preventDefault;sw.prototype.stopPropagation=sw.prototype.stopPropagation;$w.prototype.setRenderReprojectionEdges=$w.prototype.Pb;$w.prototype.setTileGridForProjection=$w.prototype.Qb;$w.prototype.getTileLoadFunction=$w.prototype.pb;
+$w.prototype.getTileUrlFunction=$w.prototype.qb;$w.prototype.getUrls=$w.prototype.rb;$w.prototype.setTileLoadFunction=$w.prototype.vb;$w.prototype.setTileUrlFunction=$w.prototype.cb;$w.prototype.setUrl=$w.prototype.jb;$w.prototype.setUrls=$w.prototype.eb;$w.prototype.getTileGrid=$w.prototype.ab;$w.prototype.refresh=$w.prototype.sa;$w.prototype.getAttributions=$w.prototype.ya;$w.prototype.getLogo=$w.prototype.xa;$w.prototype.getProjection=$w.prototype.za;$w.prototype.getState=$w.prototype.getState;
+$w.prototype.setAttributions=$w.prototype.ua;$w.prototype.get=$w.prototype.get;$w.prototype.getKeys=$w.prototype.O;$w.prototype.getProperties=$w.prototype.N;$w.prototype.set=$w.prototype.set;$w.prototype.setProperties=$w.prototype.H;$w.prototype.unset=$w.prototype.P;$w.prototype.changed=$w.prototype.s;$w.prototype.dispatchEvent=$w.prototype.b;$w.prototype.getRevision=$w.prototype.L;$w.prototype.on=$w.prototype.J;$w.prototype.once=$w.prototype.once;$w.prototype.un=$w.prototype.K;
+bx.prototype.getTileGrid=bx.prototype.ab;bx.prototype.refresh=bx.prototype.sa;bx.prototype.getAttributions=bx.prototype.ya;bx.prototype.getLogo=bx.prototype.xa;bx.prototype.getProjection=bx.prototype.za;bx.prototype.getState=bx.prototype.getState;bx.prototype.setAttributions=bx.prototype.ua;bx.prototype.get=bx.prototype.get;bx.prototype.getKeys=bx.prototype.O;bx.prototype.getProperties=bx.prototype.N;bx.prototype.set=bx.prototype.set;bx.prototype.setProperties=bx.prototype.H;bx.prototype.unset=bx.prototype.P;
+bx.prototype.changed=bx.prototype.s;bx.prototype.dispatchEvent=bx.prototype.b;bx.prototype.getRevision=bx.prototype.L;bx.prototype.on=bx.prototype.J;bx.prototype.once=bx.prototype.once;bx.prototype.un=bx.prototype.K;dx.prototype.setRenderReprojectionEdges=dx.prototype.Pb;dx.prototype.setTileGridForProjection=dx.prototype.Qb;dx.prototype.getTileLoadFunction=dx.prototype.pb;dx.prototype.getTileUrlFunction=dx.prototype.qb;dx.prototype.getUrls=dx.prototype.rb;dx.prototype.setTileLoadFunction=dx.prototype.vb;
+dx.prototype.setTileUrlFunction=dx.prototype.cb;dx.prototype.setUrl=dx.prototype.jb;dx.prototype.setUrls=dx.prototype.eb;dx.prototype.getTileGrid=dx.prototype.ab;dx.prototype.refresh=dx.prototype.sa;dx.prototype.getAttributions=dx.prototype.ya;dx.prototype.getLogo=dx.prototype.xa;dx.prototype.getProjection=dx.prototype.za;dx.prototype.getState=dx.prototype.getState;dx.prototype.setAttributions=dx.prototype.ua;dx.prototype.get=dx.prototype.get;dx.prototype.getKeys=dx.prototype.O;
+dx.prototype.getProperties=dx.prototype.N;dx.prototype.set=dx.prototype.set;dx.prototype.setProperties=dx.prototype.H;dx.prototype.unset=dx.prototype.P;dx.prototype.changed=dx.prototype.s;dx.prototype.dispatchEvent=dx.prototype.b;dx.prototype.getRevision=dx.prototype.L;dx.prototype.on=dx.prototype.J;dx.prototype.once=dx.prototype.once;dx.prototype.un=dx.prototype.K;ex.prototype.getTileGrid=ex.prototype.ab;ex.prototype.refresh=ex.prototype.sa;ex.prototype.getAttributions=ex.prototype.ya;
+ex.prototype.getLogo=ex.prototype.xa;ex.prototype.getProjection=ex.prototype.za;ex.prototype.getState=ex.prototype.getState;ex.prototype.setAttributions=ex.prototype.ua;ex.prototype.get=ex.prototype.get;ex.prototype.getKeys=ex.prototype.O;ex.prototype.getProperties=ex.prototype.N;ex.prototype.set=ex.prototype.set;ex.prototype.setProperties=ex.prototype.H;ex.prototype.unset=ex.prototype.P;ex.prototype.changed=ex.prototype.s;ex.prototype.dispatchEvent=ex.prototype.b;ex.prototype.getRevision=ex.prototype.L;
+ex.prototype.on=ex.prototype.J;ex.prototype.once=ex.prototype.once;ex.prototype.un=ex.prototype.K;ix.prototype.setRenderReprojectionEdges=ix.prototype.Pb;ix.prototype.setTileGridForProjection=ix.prototype.Qb;ix.prototype.getTileLoadFunction=ix.prototype.pb;ix.prototype.getTileUrlFunction=ix.prototype.qb;ix.prototype.getUrls=ix.prototype.rb;ix.prototype.setTileLoadFunction=ix.prototype.vb;ix.prototype.setTileUrlFunction=ix.prototype.cb;ix.prototype.setUrl=ix.prototype.jb;ix.prototype.setUrls=ix.prototype.eb;
+ix.prototype.getTileGrid=ix.prototype.ab;ix.prototype.refresh=ix.prototype.sa;ix.prototype.getAttributions=ix.prototype.ya;ix.prototype.getLogo=ix.prototype.xa;ix.prototype.getProjection=ix.prototype.za;ix.prototype.getState=ix.prototype.getState;ix.prototype.setAttributions=ix.prototype.ua;ix.prototype.get=ix.prototype.get;ix.prototype.getKeys=ix.prototype.O;ix.prototype.getProperties=ix.prototype.N;ix.prototype.set=ix.prototype.set;ix.prototype.setProperties=ix.prototype.H;ix.prototype.unset=ix.prototype.P;
+ix.prototype.changed=ix.prototype.s;ix.prototype.dispatchEvent=ix.prototype.b;ix.prototype.getRevision=ix.prototype.L;ix.prototype.on=ix.prototype.J;ix.prototype.once=ix.prototype.once;ix.prototype.un=ix.prototype.K;gu.prototype.type=gu.prototype.type;gu.prototype.target=gu.prototype.target;gu.prototype.preventDefault=gu.prototype.preventDefault;gu.prototype.stopPropagation=gu.prototype.stopPropagation;qx.prototype.getTileLoadFunction=qx.prototype.pb;qx.prototype.getTileUrlFunction=qx.prototype.qb;
+qx.prototype.getUrls=qx.prototype.rb;qx.prototype.setTileLoadFunction=qx.prototype.vb;qx.prototype.setTileUrlFunction=qx.prototype.cb;qx.prototype.setUrl=qx.prototype.jb;qx.prototype.setUrls=qx.prototype.eb;qx.prototype.getTileGrid=qx.prototype.ab;qx.prototype.refresh=qx.prototype.sa;qx.prototype.getAttributions=qx.prototype.ya;qx.prototype.getLogo=qx.prototype.xa;qx.prototype.getProjection=qx.prototype.za;qx.prototype.getState=qx.prototype.getState;qx.prototype.setAttributions=qx.prototype.ua;
+qx.prototype.get=qx.prototype.get;qx.prototype.getKeys=qx.prototype.O;qx.prototype.getProperties=qx.prototype.N;qx.prototype.set=qx.prototype.set;qx.prototype.setProperties=qx.prototype.H;qx.prototype.unset=qx.prototype.P;qx.prototype.changed=qx.prototype.s;qx.prototype.dispatchEvent=qx.prototype.b;qx.prototype.getRevision=qx.prototype.L;qx.prototype.on=qx.prototype.J;qx.prototype.once=qx.prototype.once;qx.prototype.un=qx.prototype.K;Z.prototype.setRenderReprojectionEdges=Z.prototype.Pb;
+Z.prototype.setTileGridForProjection=Z.prototype.Qb;Z.prototype.getTileLoadFunction=Z.prototype.pb;Z.prototype.getTileUrlFunction=Z.prototype.qb;Z.prototype.getUrls=Z.prototype.rb;Z.prototype.setTileLoadFunction=Z.prototype.vb;Z.prototype.setTileUrlFunction=Z.prototype.cb;Z.prototype.setUrl=Z.prototype.jb;Z.prototype.setUrls=Z.prototype.eb;Z.prototype.getTileGrid=Z.prototype.ab;Z.prototype.refresh=Z.prototype.sa;Z.prototype.getAttributions=Z.prototype.ya;Z.prototype.getLogo=Z.prototype.xa;
+Z.prototype.getProjection=Z.prototype.za;Z.prototype.getState=Z.prototype.getState;Z.prototype.setAttributions=Z.prototype.ua;Z.prototype.get=Z.prototype.get;Z.prototype.getKeys=Z.prototype.O;Z.prototype.getProperties=Z.prototype.N;Z.prototype.set=Z.prototype.set;Z.prototype.setProperties=Z.prototype.H;Z.prototype.unset=Z.prototype.P;Z.prototype.changed=Z.prototype.s;Z.prototype.dispatchEvent=Z.prototype.b;Z.prototype.getRevision=Z.prototype.L;Z.prototype.on=Z.prototype.J;Z.prototype.once=Z.prototype.once;
+Z.prototype.un=Z.prototype.K;ux.prototype.setRenderReprojectionEdges=ux.prototype.Pb;ux.prototype.setTileGridForProjection=ux.prototype.Qb;ux.prototype.getTileLoadFunction=ux.prototype.pb;ux.prototype.getTileUrlFunction=ux.prototype.qb;ux.prototype.getUrls=ux.prototype.rb;ux.prototype.setTileLoadFunction=ux.prototype.vb;ux.prototype.setTileUrlFunction=ux.prototype.cb;ux.prototype.setUrl=ux.prototype.jb;ux.prototype.setUrls=ux.prototype.eb;ux.prototype.getTileGrid=ux.prototype.ab;
+ux.prototype.refresh=ux.prototype.sa;ux.prototype.getAttributions=ux.prototype.ya;ux.prototype.getLogo=ux.prototype.xa;ux.prototype.getProjection=ux.prototype.za;ux.prototype.getState=ux.prototype.getState;ux.prototype.setAttributions=ux.prototype.ua;ux.prototype.get=ux.prototype.get;ux.prototype.getKeys=ux.prototype.O;ux.prototype.getProperties=ux.prototype.N;ux.prototype.set=ux.prototype.set;ux.prototype.setProperties=ux.prototype.H;ux.prototype.unset=ux.prototype.P;ux.prototype.changed=ux.prototype.s;
+ux.prototype.dispatchEvent=ux.prototype.b;ux.prototype.getRevision=ux.prototype.L;ux.prototype.on=ux.prototype.J;ux.prototype.once=ux.prototype.once;ux.prototype.un=ux.prototype.K;hw.prototype.getTileCoord=hw.prototype.f;hw.prototype.load=hw.prototype.load;xt.prototype.changed=xt.prototype.s;xt.prototype.dispatchEvent=xt.prototype.b;xt.prototype.getRevision=xt.prototype.L;xt.prototype.on=xt.prototype.J;xt.prototype.once=xt.prototype.once;xt.prototype.un=xt.prototype.K;Vt.prototype.changed=Vt.prototype.s;
+Vt.prototype.dispatchEvent=Vt.prototype.b;Vt.prototype.getRevision=Vt.prototype.L;Vt.prototype.on=Vt.prototype.J;Vt.prototype.once=Vt.prototype.once;Vt.prototype.un=Vt.prototype.K;Rv.prototype.changed=Rv.prototype.s;Rv.prototype.dispatchEvent=Rv.prototype.b;Rv.prototype.getRevision=Rv.prototype.L;Rv.prototype.on=Rv.prototype.J;Rv.prototype.once=Rv.prototype.once;Rv.prototype.un=Rv.prototype.K;bw.prototype.changed=bw.prototype.s;bw.prototype.dispatchEvent=bw.prototype.b;bw.prototype.getRevision=bw.prototype.L;
+bw.prototype.on=bw.prototype.J;bw.prototype.once=bw.prototype.once;bw.prototype.un=bw.prototype.K;Yt.prototype.changed=Yt.prototype.s;Yt.prototype.dispatchEvent=Yt.prototype.b;Yt.prototype.getRevision=Yt.prototype.L;Yt.prototype.on=Yt.prototype.J;Yt.prototype.once=Yt.prototype.once;Yt.prototype.un=Yt.prototype.K;Gt.prototype.changed=Gt.prototype.s;Gt.prototype.dispatchEvent=Gt.prototype.b;Gt.prototype.getRevision=Gt.prototype.L;Gt.prototype.on=Gt.prototype.J;Gt.prototype.once=Gt.prototype.once;
+Gt.prototype.un=Gt.prototype.K;yv.prototype.changed=yv.prototype.s;yv.prototype.dispatchEvent=yv.prototype.b;yv.prototype.getRevision=yv.prototype.L;yv.prototype.on=yv.prototype.J;yv.prototype.once=yv.prototype.once;yv.prototype.un=yv.prototype.K;zv.prototype.changed=zv.prototype.s;zv.prototype.dispatchEvent=zv.prototype.b;zv.prototype.getRevision=zv.prototype.L;zv.prototype.on=zv.prototype.J;zv.prototype.once=zv.prototype.once;zv.prototype.un=zv.prototype.K;Vv.prototype.changed=Vv.prototype.s;
+Vv.prototype.dispatchEvent=Vv.prototype.b;Vv.prototype.getRevision=Vv.prototype.L;Vv.prototype.on=Vv.prototype.J;Vv.prototype.once=Vv.prototype.once;Vv.prototype.un=Vv.prototype.K;Ot.prototype.changed=Ot.prototype.s;Ot.prototype.dispatchEvent=Ot.prototype.b;Ot.prototype.getRevision=Ot.prototype.L;Ot.prototype.on=Ot.prototype.J;Ot.prototype.once=Ot.prototype.once;Ot.prototype.un=Ot.prototype.K;dw.prototype.changed=dw.prototype.s;dw.prototype.dispatchEvent=dw.prototype.b;dw.prototype.getRevision=dw.prototype.L;
+dw.prototype.on=dw.prototype.J;dw.prototype.once=dw.prototype.once;dw.prototype.un=dw.prototype.K;Rh.prototype.type=Rh.prototype.type;Rh.prototype.target=Rh.prototype.target;Rh.prototype.preventDefault=Rh.prototype.preventDefault;Rh.prototype.stopPropagation=Rh.prototype.stopPropagation;pe.prototype.type=pe.prototype.type;pe.prototype.target=pe.prototype.target;pe.prototype.preventDefault=pe.prototype.preventDefault;pe.prototype.stopPropagation=pe.prototype.stopPropagation;sh.prototype.get=sh.prototype.get;
+sh.prototype.getKeys=sh.prototype.O;sh.prototype.getProperties=sh.prototype.N;sh.prototype.set=sh.prototype.set;sh.prototype.setProperties=sh.prototype.H;sh.prototype.unset=sh.prototype.P;sh.prototype.changed=sh.prototype.s;sh.prototype.dispatchEvent=sh.prototype.b;sh.prototype.getRevision=sh.prototype.L;sh.prototype.on=sh.prototype.J;sh.prototype.once=sh.prototype.once;sh.prototype.un=sh.prototype.K;uh.prototype.getExtent=uh.prototype.G;uh.prototype.getMaxResolution=uh.prototype.fc;
+uh.prototype.getMinResolution=uh.prototype.gc;uh.prototype.getOpacity=uh.prototype.hc;uh.prototype.getVisible=uh.prototype.Mb;uh.prototype.getZIndex=uh.prototype.Ba;uh.prototype.setExtent=uh.prototype.vc;uh.prototype.setMaxResolution=uh.prototype.Ac;uh.prototype.setMinResolution=uh.prototype.Bc;uh.prototype.setOpacity=uh.prototype.wc;uh.prototype.setVisible=uh.prototype.xc;uh.prototype.setZIndex=uh.prototype.Vb;uh.prototype.get=uh.prototype.get;uh.prototype.getKeys=uh.prototype.O;
+uh.prototype.getProperties=uh.prototype.N;uh.prototype.set=uh.prototype.set;uh.prototype.setProperties=uh.prototype.H;uh.prototype.unset=uh.prototype.P;uh.prototype.changed=uh.prototype.s;uh.prototype.dispatchEvent=uh.prototype.b;uh.prototype.getRevision=uh.prototype.L;uh.prototype.on=uh.prototype.J;uh.prototype.once=uh.prototype.once;uh.prototype.un=uh.prototype.K;wh.prototype.getExtent=wh.prototype.G;wh.prototype.getMaxResolution=wh.prototype.fc;wh.prototype.getMinResolution=wh.prototype.gc;
+wh.prototype.getOpacity=wh.prototype.hc;wh.prototype.getVisible=wh.prototype.Mb;wh.prototype.getZIndex=wh.prototype.Ba;wh.prototype.setExtent=wh.prototype.vc;wh.prototype.setMaxResolution=wh.prototype.Ac;wh.prototype.setMinResolution=wh.prototype.Bc;wh.prototype.setOpacity=wh.prototype.wc;wh.prototype.setVisible=wh.prototype.xc;wh.prototype.setZIndex=wh.prototype.Vb;wh.prototype.get=wh.prototype.get;wh.prototype.getKeys=wh.prototype.O;wh.prototype.getProperties=wh.prototype.N;wh.prototype.set=wh.prototype.set;
+wh.prototype.setProperties=wh.prototype.H;wh.prototype.unset=wh.prototype.P;wh.prototype.changed=wh.prototype.s;wh.prototype.dispatchEvent=wh.prototype.b;wh.prototype.getRevision=wh.prototype.L;wh.prototype.on=wh.prototype.J;wh.prototype.once=wh.prototype.once;wh.prototype.un=wh.prototype.K;T.prototype.setMap=T.prototype.setMap;T.prototype.setSource=T.prototype.Wc;T.prototype.getExtent=T.prototype.G;T.prototype.getMaxResolution=T.prototype.fc;T.prototype.getMinResolution=T.prototype.gc;
+T.prototype.getOpacity=T.prototype.hc;T.prototype.getVisible=T.prototype.Mb;T.prototype.getZIndex=T.prototype.Ba;T.prototype.setExtent=T.prototype.vc;T.prototype.setMaxResolution=T.prototype.Ac;T.prototype.setMinResolution=T.prototype.Bc;T.prototype.setOpacity=T.prototype.wc;T.prototype.setVisible=T.prototype.xc;T.prototype.setZIndex=T.prototype.Vb;T.prototype.get=T.prototype.get;T.prototype.getKeys=T.prototype.O;T.prototype.getProperties=T.prototype.N;T.prototype.set=T.prototype.set;
+T.prototype.setProperties=T.prototype.H;T.prototype.unset=T.prototype.P;T.prototype.changed=T.prototype.s;T.prototype.dispatchEvent=T.prototype.b;T.prototype.getRevision=T.prototype.L;T.prototype.on=T.prototype.J;T.prototype.once=T.prototype.once;T.prototype.un=T.prototype.K;V.prototype.getSource=V.prototype.ha;V.prototype.getStyle=V.prototype.D;V.prototype.getStyleFunction=V.prototype.C;V.prototype.setStyle=V.prototype.g;V.prototype.setMap=V.prototype.setMap;V.prototype.setSource=V.prototype.Wc;
+V.prototype.getExtent=V.prototype.G;V.prototype.getMaxResolution=V.prototype.fc;V.prototype.getMinResolution=V.prototype.gc;V.prototype.getOpacity=V.prototype.hc;V.prototype.getVisible=V.prototype.Mb;V.prototype.getZIndex=V.prototype.Ba;V.prototype.setExtent=V.prototype.vc;V.prototype.setMaxResolution=V.prototype.Ac;V.prototype.setMinResolution=V.prototype.Bc;V.prototype.setOpacity=V.prototype.wc;V.prototype.setVisible=V.prototype.xc;V.prototype.setZIndex=V.prototype.Vb;V.prototype.get=V.prototype.get;
+V.prototype.getKeys=V.prototype.O;V.prototype.getProperties=V.prototype.N;V.prototype.set=V.prototype.set;V.prototype.setProperties=V.prototype.H;V.prototype.unset=V.prototype.P;V.prototype.changed=V.prototype.s;V.prototype.dispatchEvent=V.prototype.b;V.prototype.getRevision=V.prototype.L;V.prototype.on=V.prototype.J;V.prototype.once=V.prototype.once;V.prototype.un=V.prototype.K;Uv.prototype.setMap=Uv.prototype.setMap;Uv.prototype.setSource=Uv.prototype.Wc;Uv.prototype.getExtent=Uv.prototype.G;
+Uv.prototype.getMaxResolution=Uv.prototype.fc;Uv.prototype.getMinResolution=Uv.prototype.gc;Uv.prototype.getOpacity=Uv.prototype.hc;Uv.prototype.getVisible=Uv.prototype.Mb;Uv.prototype.getZIndex=Uv.prototype.Ba;Uv.prototype.setExtent=Uv.prototype.vc;Uv.prototype.setMaxResolution=Uv.prototype.Ac;Uv.prototype.setMinResolution=Uv.prototype.Bc;Uv.prototype.setOpacity=Uv.prototype.wc;Uv.prototype.setVisible=Uv.prototype.xc;Uv.prototype.setZIndex=Uv.prototype.Vb;Uv.prototype.get=Uv.prototype.get;
+Uv.prototype.getKeys=Uv.prototype.O;Uv.prototype.getProperties=Uv.prototype.N;Uv.prototype.set=Uv.prototype.set;Uv.prototype.setProperties=Uv.prototype.H;Uv.prototype.unset=Uv.prototype.P;Uv.prototype.changed=Uv.prototype.s;Uv.prototype.dispatchEvent=Uv.prototype.b;Uv.prototype.getRevision=Uv.prototype.L;Uv.prototype.on=Uv.prototype.J;Uv.prototype.once=Uv.prototype.once;Uv.prototype.un=Uv.prototype.K;cw.prototype.setMap=cw.prototype.setMap;cw.prototype.setSource=cw.prototype.Wc;
+cw.prototype.getExtent=cw.prototype.G;cw.prototype.getMaxResolution=cw.prototype.fc;cw.prototype.getMinResolution=cw.prototype.gc;cw.prototype.getOpacity=cw.prototype.hc;cw.prototype.getVisible=cw.prototype.Mb;cw.prototype.getZIndex=cw.prototype.Ba;cw.prototype.setExtent=cw.prototype.vc;cw.prototype.setMaxResolution=cw.prototype.Ac;cw.prototype.setMinResolution=cw.prototype.Bc;cw.prototype.setOpacity=cw.prototype.wc;cw.prototype.setVisible=cw.prototype.xc;cw.prototype.setZIndex=cw.prototype.Vb;
+cw.prototype.get=cw.prototype.get;cw.prototype.getKeys=cw.prototype.O;cw.prototype.getProperties=cw.prototype.N;cw.prototype.set=cw.prototype.set;cw.prototype.setProperties=cw.prototype.H;cw.prototype.unset=cw.prototype.P;cw.prototype.changed=cw.prototype.s;cw.prototype.dispatchEvent=cw.prototype.b;cw.prototype.getRevision=cw.prototype.L;cw.prototype.on=cw.prototype.J;cw.prototype.once=cw.prototype.once;cw.prototype.un=cw.prototype.K;W.prototype.getSource=W.prototype.ha;W.prototype.getStyle=W.prototype.D;
+W.prototype.getStyleFunction=W.prototype.C;W.prototype.setStyle=W.prototype.g;W.prototype.setMap=W.prototype.setMap;W.prototype.setSource=W.prototype.Wc;W.prototype.getExtent=W.prototype.G;W.prototype.getMaxResolution=W.prototype.fc;W.prototype.getMinResolution=W.prototype.gc;W.prototype.getOpacity=W.prototype.hc;W.prototype.getVisible=W.prototype.Mb;W.prototype.getZIndex=W.prototype.Ba;W.prototype.setExtent=W.prototype.vc;W.prototype.setMaxResolution=W.prototype.Ac;W.prototype.setMinResolution=W.prototype.Bc;
+W.prototype.setOpacity=W.prototype.wc;W.prototype.setVisible=W.prototype.xc;W.prototype.setZIndex=W.prototype.Vb;W.prototype.get=W.prototype.get;W.prototype.getKeys=W.prototype.O;W.prototype.getProperties=W.prototype.N;W.prototype.set=W.prototype.set;W.prototype.setProperties=W.prototype.H;W.prototype.unset=W.prototype.P;W.prototype.changed=W.prototype.s;W.prototype.dispatchEvent=W.prototype.b;W.prototype.getRevision=W.prototype.L;W.prototype.on=W.prototype.J;W.prototype.once=W.prototype.once;
+W.prototype.un=W.prototype.K;ng.prototype.get=ng.prototype.get;ng.prototype.getKeys=ng.prototype.O;ng.prototype.getProperties=ng.prototype.N;ng.prototype.set=ng.prototype.set;ng.prototype.setProperties=ng.prototype.H;ng.prototype.unset=ng.prototype.P;ng.prototype.changed=ng.prototype.s;ng.prototype.dispatchEvent=ng.prototype.b;ng.prototype.getRevision=ng.prototype.L;ng.prototype.on=ng.prototype.J;ng.prototype.once=ng.prototype.once;ng.prototype.un=ng.prototype.K;rg.prototype.getActive=rg.prototype.c;
+rg.prototype.getMap=rg.prototype.f;rg.prototype.setActive=rg.prototype.Ha;rg.prototype.get=rg.prototype.get;rg.prototype.getKeys=rg.prototype.O;rg.prototype.getProperties=rg.prototype.N;rg.prototype.set=rg.prototype.set;rg.prototype.setProperties=rg.prototype.H;rg.prototype.unset=rg.prototype.P;rg.prototype.changed=rg.prototype.s;rg.prototype.dispatchEvent=rg.prototype.b;rg.prototype.getRevision=rg.prototype.L;rg.prototype.on=rg.prototype.J;rg.prototype.once=rg.prototype.once;rg.prototype.un=rg.prototype.K;
+Rs.prototype.getActive=Rs.prototype.c;Rs.prototype.getMap=Rs.prototype.f;Rs.prototype.setActive=Rs.prototype.Ha;Rs.prototype.get=Rs.prototype.get;Rs.prototype.getKeys=Rs.prototype.O;Rs.prototype.getProperties=Rs.prototype.N;Rs.prototype.set=Rs.prototype.set;Rs.prototype.setProperties=Rs.prototype.H;Rs.prototype.unset=Rs.prototype.P;Rs.prototype.changed=Rs.prototype.s;Rs.prototype.dispatchEvent=Rs.prototype.b;Rs.prototype.getRevision=Rs.prototype.L;Rs.prototype.on=Rs.prototype.J;
+Rs.prototype.once=Rs.prototype.once;Rs.prototype.un=Rs.prototype.K;Us.prototype.type=Us.prototype.type;Us.prototype.target=Us.prototype.target;Us.prototype.preventDefault=Us.prototype.preventDefault;Us.prototype.stopPropagation=Us.prototype.stopPropagation;Dg.prototype.getActive=Dg.prototype.c;Dg.prototype.getMap=Dg.prototype.f;Dg.prototype.setActive=Dg.prototype.Ha;Dg.prototype.get=Dg.prototype.get;Dg.prototype.getKeys=Dg.prototype.O;Dg.prototype.getProperties=Dg.prototype.N;Dg.prototype.set=Dg.prototype.set;
+Dg.prototype.setProperties=Dg.prototype.H;Dg.prototype.unset=Dg.prototype.P;Dg.prototype.changed=Dg.prototype.s;Dg.prototype.dispatchEvent=Dg.prototype.b;Dg.prototype.getRevision=Dg.prototype.L;Dg.prototype.on=Dg.prototype.J;Dg.prototype.once=Dg.prototype.once;Dg.prototype.un=Dg.prototype.K;Rg.prototype.getActive=Rg.prototype.c;Rg.prototype.getMap=Rg.prototype.f;Rg.prototype.setActive=Rg.prototype.Ha;Rg.prototype.get=Rg.prototype.get;Rg.prototype.getKeys=Rg.prototype.O;
+Rg.prototype.getProperties=Rg.prototype.N;Rg.prototype.set=Rg.prototype.set;Rg.prototype.setProperties=Rg.prototype.H;Rg.prototype.unset=Rg.prototype.P;Rg.prototype.changed=Rg.prototype.s;Rg.prototype.dispatchEvent=Rg.prototype.b;Rg.prototype.getRevision=Rg.prototype.L;Rg.prototype.on=Rg.prototype.J;Rg.prototype.once=Rg.prototype.once;Rg.prototype.un=Rg.prototype.K;Wg.prototype.type=Wg.prototype.type;Wg.prototype.target=Wg.prototype.target;Wg.prototype.preventDefault=Wg.prototype.preventDefault;
+Wg.prototype.stopPropagation=Wg.prototype.stopPropagation;Gg.prototype.getActive=Gg.prototype.c;Gg.prototype.getMap=Gg.prototype.f;Gg.prototype.setActive=Gg.prototype.Ha;Gg.prototype.get=Gg.prototype.get;Gg.prototype.getKeys=Gg.prototype.O;Gg.prototype.getProperties=Gg.prototype.N;Gg.prototype.set=Gg.prototype.set;Gg.prototype.setProperties=Gg.prototype.H;Gg.prototype.unset=Gg.prototype.P;Gg.prototype.changed=Gg.prototype.s;Gg.prototype.dispatchEvent=Gg.prototype.b;Gg.prototype.getRevision=Gg.prototype.L;
+Gg.prototype.on=Gg.prototype.J;Gg.prototype.once=Gg.prototype.once;Gg.prototype.un=Gg.prototype.K;Kg.prototype.getActive=Kg.prototype.c;Kg.prototype.getMap=Kg.prototype.f;Kg.prototype.setActive=Kg.prototype.Ha;Kg.prototype.get=Kg.prototype.get;Kg.prototype.getKeys=Kg.prototype.O;Kg.prototype.getProperties=Kg.prototype.N;Kg.prototype.set=Kg.prototype.set;Kg.prototype.setProperties=Kg.prototype.H;Kg.prototype.unset=Kg.prototype.P;Kg.prototype.changed=Kg.prototype.s;Kg.prototype.dispatchEvent=Kg.prototype.b;
+Kg.prototype.getRevision=Kg.prototype.L;Kg.prototype.on=Kg.prototype.J;Kg.prototype.once=Kg.prototype.once;Kg.prototype.un=Kg.prototype.K;Ys.prototype.getActive=Ys.prototype.c;Ys.prototype.getMap=Ys.prototype.f;Ys.prototype.setActive=Ys.prototype.Ha;Ys.prototype.get=Ys.prototype.get;Ys.prototype.getKeys=Ys.prototype.O;Ys.prototype.getProperties=Ys.prototype.N;Ys.prototype.set=Ys.prototype.set;Ys.prototype.setProperties=Ys.prototype.H;Ys.prototype.unset=Ys.prototype.P;Ys.prototype.changed=Ys.prototype.s;
+Ys.prototype.dispatchEvent=Ys.prototype.b;Ys.prototype.getRevision=Ys.prototype.L;Ys.prototype.on=Ys.prototype.J;Ys.prototype.once=Ys.prototype.once;Ys.prototype.un=Ys.prototype.K;$g.prototype.getGeometry=$g.prototype.V;$g.prototype.getActive=$g.prototype.c;$g.prototype.getMap=$g.prototype.f;$g.prototype.setActive=$g.prototype.Ha;$g.prototype.get=$g.prototype.get;$g.prototype.getKeys=$g.prototype.O;$g.prototype.getProperties=$g.prototype.N;$g.prototype.set=$g.prototype.set;
+$g.prototype.setProperties=$g.prototype.H;$g.prototype.unset=$g.prototype.P;$g.prototype.changed=$g.prototype.s;$g.prototype.dispatchEvent=$g.prototype.b;$g.prototype.getRevision=$g.prototype.L;$g.prototype.on=$g.prototype.J;$g.prototype.once=$g.prototype.once;$g.prototype.un=$g.prototype.K;ju.prototype.getActive=ju.prototype.c;ju.prototype.getMap=ju.prototype.f;ju.prototype.setActive=ju.prototype.Ha;ju.prototype.get=ju.prototype.get;ju.prototype.getKeys=ju.prototype.O;
+ju.prototype.getProperties=ju.prototype.N;ju.prototype.set=ju.prototype.set;ju.prototype.setProperties=ju.prototype.H;ju.prototype.unset=ju.prototype.P;ju.prototype.changed=ju.prototype.s;ju.prototype.dispatchEvent=ju.prototype.b;ju.prototype.getRevision=ju.prototype.L;ju.prototype.on=ju.prototype.J;ju.prototype.once=ju.prototype.once;ju.prototype.un=ju.prototype.K;zu.prototype.type=zu.prototype.type;zu.prototype.target=zu.prototype.target;zu.prototype.preventDefault=zu.prototype.preventDefault;
+zu.prototype.stopPropagation=zu.prototype.stopPropagation;Au.prototype.getActive=Au.prototype.c;Au.prototype.getMap=Au.prototype.f;Au.prototype.setActive=Au.prototype.Ha;Au.prototype.get=Au.prototype.get;Au.prototype.getKeys=Au.prototype.O;Au.prototype.getProperties=Au.prototype.N;Au.prototype.set=Au.prototype.set;Au.prototype.setProperties=Au.prototype.H;Au.prototype.unset=Au.prototype.P;Au.prototype.changed=Au.prototype.s;Au.prototype.dispatchEvent=Au.prototype.b;Au.prototype.getRevision=Au.prototype.L;
+Au.prototype.on=Au.prototype.J;Au.prototype.once=Au.prototype.once;Au.prototype.un=Au.prototype.K;Lu.prototype.type=Lu.prototype.type;Lu.prototype.target=Lu.prototype.target;Lu.prototype.preventDefault=Lu.prototype.preventDefault;Lu.prototype.stopPropagation=Lu.prototype.stopPropagation;ah.prototype.getActive=ah.prototype.c;ah.prototype.getMap=ah.prototype.f;ah.prototype.setActive=ah.prototype.Ha;ah.prototype.get=ah.prototype.get;ah.prototype.getKeys=ah.prototype.O;ah.prototype.getProperties=ah.prototype.N;
+ah.prototype.set=ah.prototype.set;ah.prototype.setProperties=ah.prototype.H;ah.prototype.unset=ah.prototype.P;ah.prototype.changed=ah.prototype.s;ah.prototype.dispatchEvent=ah.prototype.b;ah.prototype.getRevision=ah.prototype.L;ah.prototype.on=ah.prototype.J;ah.prototype.once=ah.prototype.once;ah.prototype.un=ah.prototype.K;ch.prototype.getActive=ch.prototype.c;ch.prototype.getMap=ch.prototype.f;ch.prototype.setActive=ch.prototype.Ha;ch.prototype.get=ch.prototype.get;ch.prototype.getKeys=ch.prototype.O;
+ch.prototype.getProperties=ch.prototype.N;ch.prototype.set=ch.prototype.set;ch.prototype.setProperties=ch.prototype.H;ch.prototype.unset=ch.prototype.P;ch.prototype.changed=ch.prototype.s;ch.prototype.dispatchEvent=ch.prototype.b;ch.prototype.getRevision=ch.prototype.L;ch.prototype.on=ch.prototype.J;ch.prototype.once=ch.prototype.once;ch.prototype.un=ch.prototype.K;Nu.prototype.getActive=Nu.prototype.c;Nu.prototype.getMap=Nu.prototype.f;Nu.prototype.setActive=Nu.prototype.Ha;Nu.prototype.get=Nu.prototype.get;
+Nu.prototype.getKeys=Nu.prototype.O;Nu.prototype.getProperties=Nu.prototype.N;Nu.prototype.set=Nu.prototype.set;Nu.prototype.setProperties=Nu.prototype.H;Nu.prototype.unset=Nu.prototype.P;Nu.prototype.changed=Nu.prototype.s;Nu.prototype.dispatchEvent=Nu.prototype.b;Nu.prototype.getRevision=Nu.prototype.L;Nu.prototype.on=Nu.prototype.J;Nu.prototype.once=Nu.prototype.once;Nu.prototype.un=Nu.prototype.K;Vu.prototype.type=Vu.prototype.type;Vu.prototype.target=Vu.prototype.target;
+Vu.prototype.preventDefault=Vu.prototype.preventDefault;Vu.prototype.stopPropagation=Vu.prototype.stopPropagation;eh.prototype.getActive=eh.prototype.c;eh.prototype.getMap=eh.prototype.f;eh.prototype.setActive=eh.prototype.Ha;eh.prototype.get=eh.prototype.get;eh.prototype.getKeys=eh.prototype.O;eh.prototype.getProperties=eh.prototype.N;eh.prototype.set=eh.prototype.set;eh.prototype.setProperties=eh.prototype.H;eh.prototype.unset=eh.prototype.P;eh.prototype.changed=eh.prototype.s;
+eh.prototype.dispatchEvent=eh.prototype.b;eh.prototype.getRevision=eh.prototype.L;eh.prototype.on=eh.prototype.J;eh.prototype.once=eh.prototype.once;eh.prototype.un=eh.prototype.K;ih.prototype.getActive=ih.prototype.c;ih.prototype.getMap=ih.prototype.f;ih.prototype.setActive=ih.prototype.Ha;ih.prototype.get=ih.prototype.get;ih.prototype.getKeys=ih.prototype.O;ih.prototype.getProperties=ih.prototype.N;ih.prototype.set=ih.prototype.set;ih.prototype.setProperties=ih.prototype.H;ih.prototype.unset=ih.prototype.P;
+ih.prototype.changed=ih.prototype.s;ih.prototype.dispatchEvent=ih.prototype.b;ih.prototype.getRevision=ih.prototype.L;ih.prototype.on=ih.prototype.J;ih.prototype.once=ih.prototype.once;ih.prototype.un=ih.prototype.K;mh.prototype.getActive=mh.prototype.c;mh.prototype.getMap=mh.prototype.f;mh.prototype.setActive=mh.prototype.Ha;mh.prototype.get=mh.prototype.get;mh.prototype.getKeys=mh.prototype.O;mh.prototype.getProperties=mh.prototype.N;mh.prototype.set=mh.prototype.set;
+mh.prototype.setProperties=mh.prototype.H;mh.prototype.unset=mh.prototype.P;mh.prototype.changed=mh.prototype.s;mh.prototype.dispatchEvent=mh.prototype.b;mh.prototype.getRevision=mh.prototype.L;mh.prototype.on=mh.prototype.J;mh.prototype.once=mh.prototype.once;mh.prototype.un=mh.prototype.K;cv.prototype.getActive=cv.prototype.c;cv.prototype.getMap=cv.prototype.f;cv.prototype.setActive=cv.prototype.Ha;cv.prototype.get=cv.prototype.get;cv.prototype.getKeys=cv.prototype.O;
+cv.prototype.getProperties=cv.prototype.N;cv.prototype.set=cv.prototype.set;cv.prototype.setProperties=cv.prototype.H;cv.prototype.unset=cv.prototype.P;cv.prototype.changed=cv.prototype.s;cv.prototype.dispatchEvent=cv.prototype.b;cv.prototype.getRevision=cv.prototype.L;cv.prototype.on=cv.prototype.J;cv.prototype.once=cv.prototype.once;cv.prototype.un=cv.prototype.K;fv.prototype.type=fv.prototype.type;fv.prototype.target=fv.prototype.target;fv.prototype.preventDefault=fv.prototype.preventDefault;
+fv.prototype.stopPropagation=fv.prototype.stopPropagation;hv.prototype.getActive=hv.prototype.c;hv.prototype.getMap=hv.prototype.f;hv.prototype.setActive=hv.prototype.Ha;hv.prototype.get=hv.prototype.get;hv.prototype.getKeys=hv.prototype.O;hv.prototype.getProperties=hv.prototype.N;hv.prototype.set=hv.prototype.set;hv.prototype.setProperties=hv.prototype.H;hv.prototype.unset=hv.prototype.P;hv.prototype.changed=hv.prototype.s;hv.prototype.dispatchEvent=hv.prototype.b;hv.prototype.getRevision=hv.prototype.L;
+hv.prototype.on=hv.prototype.J;hv.prototype.once=hv.prototype.once;hv.prototype.un=hv.prototype.K;mv.prototype.getActive=mv.prototype.c;mv.prototype.getMap=mv.prototype.f;mv.prototype.setActive=mv.prototype.Ha;mv.prototype.get=mv.prototype.get;mv.prototype.getKeys=mv.prototype.O;mv.prototype.getProperties=mv.prototype.N;mv.prototype.set=mv.prototype.set;mv.prototype.setProperties=mv.prototype.H;mv.prototype.unset=mv.prototype.P;mv.prototype.changed=mv.prototype.s;mv.prototype.dispatchEvent=mv.prototype.b;
+mv.prototype.getRevision=mv.prototype.L;mv.prototype.on=mv.prototype.J;mv.prototype.once=mv.prototype.once;mv.prototype.un=mv.prototype.K;sv.prototype.type=sv.prototype.type;sv.prototype.target=sv.prototype.target;sv.prototype.preventDefault=sv.prototype.preventDefault;sv.prototype.stopPropagation=sv.prototype.stopPropagation;of.prototype.get=of.prototype.get;of.prototype.getKeys=of.prototype.O;of.prototype.getProperties=of.prototype.N;of.prototype.set=of.prototype.set;
+of.prototype.setProperties=of.prototype.H;of.prototype.unset=of.prototype.P;of.prototype.changed=of.prototype.s;of.prototype.dispatchEvent=of.prototype.b;of.prototype.getRevision=of.prototype.L;of.prototype.on=of.prototype.J;of.prototype.once=of.prototype.once;of.prototype.un=of.prototype.K;rf.prototype.getClosestPoint=rf.prototype.Ab;rf.prototype.intersectsCoordinate=rf.prototype.sb;rf.prototype.getExtent=rf.prototype.G;rf.prototype.rotate=rf.prototype.rotate;rf.prototype.scale=rf.prototype.scale;
+rf.prototype.simplify=rf.prototype.Rb;rf.prototype.transform=rf.prototype.tb;rf.prototype.get=rf.prototype.get;rf.prototype.getKeys=rf.prototype.O;rf.prototype.getProperties=rf.prototype.N;rf.prototype.set=rf.prototype.set;rf.prototype.setProperties=rf.prototype.H;rf.prototype.unset=rf.prototype.P;rf.prototype.changed=rf.prototype.s;rf.prototype.dispatchEvent=rf.prototype.b;rf.prototype.getRevision=rf.prototype.L;rf.prototype.on=rf.prototype.J;rf.prototype.once=rf.prototype.once;rf.prototype.un=rf.prototype.K;
+ys.prototype.getFirstCoordinate=ys.prototype.ac;ys.prototype.getLastCoordinate=ys.prototype.bc;ys.prototype.getLayout=ys.prototype.cc;ys.prototype.rotate=ys.prototype.rotate;ys.prototype.scale=ys.prototype.scale;ys.prototype.getClosestPoint=ys.prototype.Ab;ys.prototype.intersectsCoordinate=ys.prototype.sb;ys.prototype.getExtent=ys.prototype.G;ys.prototype.simplify=ys.prototype.Rb;ys.prototype.get=ys.prototype.get;ys.prototype.getKeys=ys.prototype.O;ys.prototype.getProperties=ys.prototype.N;
+ys.prototype.set=ys.prototype.set;ys.prototype.setProperties=ys.prototype.H;ys.prototype.unset=ys.prototype.P;ys.prototype.changed=ys.prototype.s;ys.prototype.dispatchEvent=ys.prototype.b;ys.prototype.getRevision=ys.prototype.L;ys.prototype.on=ys.prototype.J;ys.prototype.once=ys.prototype.once;ys.prototype.un=ys.prototype.K;tm.prototype.getClosestPoint=tm.prototype.Ab;tm.prototype.intersectsCoordinate=tm.prototype.sb;tm.prototype.getExtent=tm.prototype.G;tm.prototype.rotate=tm.prototype.rotate;
+tm.prototype.scale=tm.prototype.scale;tm.prototype.simplify=tm.prototype.Rb;tm.prototype.transform=tm.prototype.tb;tm.prototype.get=tm.prototype.get;tm.prototype.getKeys=tm.prototype.O;tm.prototype.getProperties=tm.prototype.N;tm.prototype.set=tm.prototype.set;tm.prototype.setProperties=tm.prototype.H;tm.prototype.unset=tm.prototype.P;tm.prototype.changed=tm.prototype.s;tm.prototype.dispatchEvent=tm.prototype.b;tm.prototype.getRevision=tm.prototype.L;tm.prototype.on=tm.prototype.J;
+tm.prototype.once=tm.prototype.once;tm.prototype.un=tm.prototype.K;Jf.prototype.getFirstCoordinate=Jf.prototype.ac;Jf.prototype.getLastCoordinate=Jf.prototype.bc;Jf.prototype.getLayout=Jf.prototype.cc;Jf.prototype.rotate=Jf.prototype.rotate;Jf.prototype.scale=Jf.prototype.scale;Jf.prototype.getClosestPoint=Jf.prototype.Ab;Jf.prototype.intersectsCoordinate=Jf.prototype.sb;Jf.prototype.getExtent=Jf.prototype.G;Jf.prototype.simplify=Jf.prototype.Rb;Jf.prototype.transform=Jf.prototype.tb;
+Jf.prototype.get=Jf.prototype.get;Jf.prototype.getKeys=Jf.prototype.O;Jf.prototype.getProperties=Jf.prototype.N;Jf.prototype.set=Jf.prototype.set;Jf.prototype.setProperties=Jf.prototype.H;Jf.prototype.unset=Jf.prototype.P;Jf.prototype.changed=Jf.prototype.s;Jf.prototype.dispatchEvent=Jf.prototype.b;Jf.prototype.getRevision=Jf.prototype.L;Jf.prototype.on=Jf.prototype.J;Jf.prototype.once=Jf.prototype.once;Jf.prototype.un=Jf.prototype.K;O.prototype.getFirstCoordinate=O.prototype.ac;
+O.prototype.getLastCoordinate=O.prototype.bc;O.prototype.getLayout=O.prototype.cc;O.prototype.rotate=O.prototype.rotate;O.prototype.scale=O.prototype.scale;O.prototype.getClosestPoint=O.prototype.Ab;O.prototype.intersectsCoordinate=O.prototype.sb;O.prototype.getExtent=O.prototype.G;O.prototype.simplify=O.prototype.Rb;O.prototype.transform=O.prototype.tb;O.prototype.get=O.prototype.get;O.prototype.getKeys=O.prototype.O;O.prototype.getProperties=O.prototype.N;O.prototype.set=O.prototype.set;
+O.prototype.setProperties=O.prototype.H;O.prototype.unset=O.prototype.P;O.prototype.changed=O.prototype.s;O.prototype.dispatchEvent=O.prototype.b;O.prototype.getRevision=O.prototype.L;O.prototype.on=O.prototype.J;O.prototype.once=O.prototype.once;O.prototype.un=O.prototype.K;P.prototype.getFirstCoordinate=P.prototype.ac;P.prototype.getLastCoordinate=P.prototype.bc;P.prototype.getLayout=P.prototype.cc;P.prototype.rotate=P.prototype.rotate;P.prototype.scale=P.prototype.scale;
+P.prototype.getClosestPoint=P.prototype.Ab;P.prototype.intersectsCoordinate=P.prototype.sb;P.prototype.getExtent=P.prototype.G;P.prototype.simplify=P.prototype.Rb;P.prototype.transform=P.prototype.tb;P.prototype.get=P.prototype.get;P.prototype.getKeys=P.prototype.O;P.prototype.getProperties=P.prototype.N;P.prototype.set=P.prototype.set;P.prototype.setProperties=P.prototype.H;P.prototype.unset=P.prototype.P;P.prototype.changed=P.prototype.s;P.prototype.dispatchEvent=P.prototype.b;
+P.prototype.getRevision=P.prototype.L;P.prototype.on=P.prototype.J;P.prototype.once=P.prototype.once;P.prototype.un=P.prototype.K;Q.prototype.getFirstCoordinate=Q.prototype.ac;Q.prototype.getLastCoordinate=Q.prototype.bc;Q.prototype.getLayout=Q.prototype.cc;Q.prototype.rotate=Q.prototype.rotate;Q.prototype.scale=Q.prototype.scale;Q.prototype.getClosestPoint=Q.prototype.Ab;Q.prototype.intersectsCoordinate=Q.prototype.sb;Q.prototype.getExtent=Q.prototype.G;Q.prototype.simplify=Q.prototype.Rb;
+Q.prototype.transform=Q.prototype.tb;Q.prototype.get=Q.prototype.get;Q.prototype.getKeys=Q.prototype.O;Q.prototype.getProperties=Q.prototype.N;Q.prototype.set=Q.prototype.set;Q.prototype.setProperties=Q.prototype.H;Q.prototype.unset=Q.prototype.P;Q.prototype.changed=Q.prototype.s;Q.prototype.dispatchEvent=Q.prototype.b;Q.prototype.getRevision=Q.prototype.L;Q.prototype.on=Q.prototype.J;Q.prototype.once=Q.prototype.once;Q.prototype.un=Q.prototype.K;R.prototype.getFirstCoordinate=R.prototype.ac;
+R.prototype.getLastCoordinate=R.prototype.bc;R.prototype.getLayout=R.prototype.cc;R.prototype.rotate=R.prototype.rotate;R.prototype.scale=R.prototype.scale;R.prototype.getClosestPoint=R.prototype.Ab;R.prototype.intersectsCoordinate=R.prototype.sb;R.prototype.getExtent=R.prototype.G;R.prototype.simplify=R.prototype.Rb;R.prototype.transform=R.prototype.tb;R.prototype.get=R.prototype.get;R.prototype.getKeys=R.prototype.O;R.prototype.getProperties=R.prototype.N;R.prototype.set=R.prototype.set;
+R.prototype.setProperties=R.prototype.H;R.prototype.unset=R.prototype.P;R.prototype.changed=R.prototype.s;R.prototype.dispatchEvent=R.prototype.b;R.prototype.getRevision=R.prototype.L;R.prototype.on=R.prototype.J;R.prototype.once=R.prototype.once;R.prototype.un=R.prototype.K;C.prototype.getFirstCoordinate=C.prototype.ac;C.prototype.getLastCoordinate=C.prototype.bc;C.prototype.getLayout=C.prototype.cc;C.prototype.rotate=C.prototype.rotate;C.prototype.scale=C.prototype.scale;
+C.prototype.getClosestPoint=C.prototype.Ab;C.prototype.intersectsCoordinate=C.prototype.sb;C.prototype.getExtent=C.prototype.G;C.prototype.simplify=C.prototype.Rb;C.prototype.transform=C.prototype.tb;C.prototype.get=C.prototype.get;C.prototype.getKeys=C.prototype.O;C.prototype.getProperties=C.prototype.N;C.prototype.set=C.prototype.set;C.prototype.setProperties=C.prototype.H;C.prototype.unset=C.prototype.P;C.prototype.changed=C.prototype.s;C.prototype.dispatchEvent=C.prototype.b;
+C.prototype.getRevision=C.prototype.L;C.prototype.on=C.prototype.J;C.prototype.once=C.prototype.once;C.prototype.un=C.prototype.K;D.prototype.getFirstCoordinate=D.prototype.ac;D.prototype.getLastCoordinate=D.prototype.bc;D.prototype.getLayout=D.prototype.cc;D.prototype.rotate=D.prototype.rotate;D.prototype.scale=D.prototype.scale;D.prototype.getClosestPoint=D.prototype.Ab;D.prototype.intersectsCoordinate=D.prototype.sb;D.prototype.getExtent=D.prototype.G;D.prototype.simplify=D.prototype.Rb;
+D.prototype.transform=D.prototype.tb;D.prototype.get=D.prototype.get;D.prototype.getKeys=D.prototype.O;D.prototype.getProperties=D.prototype.N;D.prototype.set=D.prototype.set;D.prototype.setProperties=D.prototype.H;D.prototype.unset=D.prototype.P;D.prototype.changed=D.prototype.s;D.prototype.dispatchEvent=D.prototype.b;D.prototype.getRevision=D.prototype.L;D.prototype.on=D.prototype.J;D.prototype.once=D.prototype.once;D.prototype.un=D.prototype.K;Sm.prototype.readFeatures=Sm.prototype.Oa;
+an.prototype.readFeatures=an.prototype.Oa;Sm.prototype.readFeatures=Sm.prototype.Oa;md.prototype.get=md.prototype.get;md.prototype.getKeys=md.prototype.O;md.prototype.getProperties=md.prototype.N;md.prototype.set=md.prototype.set;md.prototype.setProperties=md.prototype.H;md.prototype.unset=md.prototype.P;md.prototype.changed=md.prototype.s;md.prototype.dispatchEvent=md.prototype.b;md.prototype.getRevision=md.prototype.L;md.prototype.on=md.prototype.J;md.prototype.once=md.prototype.once;
+md.prototype.un=md.prototype.K;nd.prototype.getMap=nd.prototype.g;nd.prototype.setMap=nd.prototype.setMap;nd.prototype.setTarget=nd.prototype.f;nd.prototype.get=nd.prototype.get;nd.prototype.getKeys=nd.prototype.O;nd.prototype.getProperties=nd.prototype.N;nd.prototype.set=nd.prototype.set;nd.prototype.setProperties=nd.prototype.H;nd.prototype.unset=nd.prototype.P;nd.prototype.changed=nd.prototype.s;nd.prototype.dispatchEvent=nd.prototype.b;nd.prototype.getRevision=nd.prototype.L;nd.prototype.on=nd.prototype.J;
+nd.prototype.once=nd.prototype.once;nd.prototype.un=nd.prototype.K;yd.prototype.getMap=yd.prototype.g;yd.prototype.setMap=yd.prototype.setMap;yd.prototype.setTarget=yd.prototype.f;yd.prototype.get=yd.prototype.get;yd.prototype.getKeys=yd.prototype.O;yd.prototype.getProperties=yd.prototype.N;yd.prototype.set=yd.prototype.set;yd.prototype.setProperties=yd.prototype.H;yd.prototype.unset=yd.prototype.P;yd.prototype.changed=yd.prototype.s;yd.prototype.dispatchEvent=yd.prototype.b;
+yd.prototype.getRevision=yd.prototype.L;yd.prototype.on=yd.prototype.J;yd.prototype.once=yd.prototype.once;yd.prototype.un=yd.prototype.K;Dd.prototype.getMap=Dd.prototype.g;Dd.prototype.setMap=Dd.prototype.setMap;Dd.prototype.setTarget=Dd.prototype.f;Dd.prototype.get=Dd.prototype.get;Dd.prototype.getKeys=Dd.prototype.O;Dd.prototype.getProperties=Dd.prototype.N;Dd.prototype.set=Dd.prototype.set;Dd.prototype.setProperties=Dd.prototype.H;Dd.prototype.unset=Dd.prototype.P;Dd.prototype.changed=Dd.prototype.s;
+Dd.prototype.dispatchEvent=Dd.prototype.b;Dd.prototype.getRevision=Dd.prototype.L;Dd.prototype.on=Dd.prototype.J;Dd.prototype.once=Dd.prototype.once;Dd.prototype.un=Dd.prototype.K;Bk.prototype.getMap=Bk.prototype.g;Bk.prototype.setMap=Bk.prototype.setMap;Bk.prototype.setTarget=Bk.prototype.f;Bk.prototype.get=Bk.prototype.get;Bk.prototype.getKeys=Bk.prototype.O;Bk.prototype.getProperties=Bk.prototype.N;Bk.prototype.set=Bk.prototype.set;Bk.prototype.setProperties=Bk.prototype.H;Bk.prototype.unset=Bk.prototype.P;
+Bk.prototype.changed=Bk.prototype.s;Bk.prototype.dispatchEvent=Bk.prototype.b;Bk.prototype.getRevision=Bk.prototype.L;Bk.prototype.on=Bk.prototype.J;Bk.prototype.once=Bk.prototype.once;Bk.prototype.un=Bk.prototype.K;ud.prototype.getMap=ud.prototype.g;ud.prototype.setMap=ud.prototype.setMap;ud.prototype.setTarget=ud.prototype.f;ud.prototype.get=ud.prototype.get;ud.prototype.getKeys=ud.prototype.O;ud.prototype.getProperties=ud.prototype.N;ud.prototype.set=ud.prototype.set;
+ud.prototype.setProperties=ud.prototype.H;ud.prototype.unset=ud.prototype.P;ud.prototype.changed=ud.prototype.s;ud.prototype.dispatchEvent=ud.prototype.b;ud.prototype.getRevision=ud.prototype.L;ud.prototype.on=ud.prototype.J;ud.prototype.once=ud.prototype.once;ud.prototype.un=ud.prototype.K;Gk.prototype.getMap=Gk.prototype.g;Gk.prototype.setMap=Gk.prototype.setMap;Gk.prototype.setTarget=Gk.prototype.f;Gk.prototype.get=Gk.prototype.get;Gk.prototype.getKeys=Gk.prototype.O;
+Gk.prototype.getProperties=Gk.prototype.N;Gk.prototype.set=Gk.prototype.set;Gk.prototype.setProperties=Gk.prototype.H;Gk.prototype.unset=Gk.prototype.P;Gk.prototype.changed=Gk.prototype.s;Gk.prototype.dispatchEvent=Gk.prototype.b;Gk.prototype.getRevision=Gk.prototype.L;Gk.prototype.on=Gk.prototype.J;Gk.prototype.once=Gk.prototype.once;Gk.prototype.un=Gk.prototype.K;wd.prototype.getMap=wd.prototype.g;wd.prototype.setMap=wd.prototype.setMap;wd.prototype.setTarget=wd.prototype.f;wd.prototype.get=wd.prototype.get;
+wd.prototype.getKeys=wd.prototype.O;wd.prototype.getProperties=wd.prototype.N;wd.prototype.set=wd.prototype.set;wd.prototype.setProperties=wd.prototype.H;wd.prototype.unset=wd.prototype.P;wd.prototype.changed=wd.prototype.s;wd.prototype.dispatchEvent=wd.prototype.b;wd.prototype.getRevision=wd.prototype.L;wd.prototype.on=wd.prototype.J;wd.prototype.once=wd.prototype.once;wd.prototype.un=wd.prototype.K;Lk.prototype.getMap=Lk.prototype.g;Lk.prototype.setMap=Lk.prototype.setMap;
+Lk.prototype.setTarget=Lk.prototype.f;Lk.prototype.get=Lk.prototype.get;Lk.prototype.getKeys=Lk.prototype.O;Lk.prototype.getProperties=Lk.prototype.N;Lk.prototype.set=Lk.prototype.set;Lk.prototype.setProperties=Lk.prototype.H;Lk.prototype.unset=Lk.prototype.P;Lk.prototype.changed=Lk.prototype.s;Lk.prototype.dispatchEvent=Lk.prototype.b;Lk.prototype.getRevision=Lk.prototype.L;Lk.prototype.on=Lk.prototype.J;Lk.prototype.once=Lk.prototype.once;Lk.prototype.un=Lk.prototype.K;Qk.prototype.getMap=Qk.prototype.g;
+Qk.prototype.setMap=Qk.prototype.setMap;Qk.prototype.setTarget=Qk.prototype.f;Qk.prototype.get=Qk.prototype.get;Qk.prototype.getKeys=Qk.prototype.O;Qk.prototype.getProperties=Qk.prototype.N;Qk.prototype.set=Qk.prototype.set;Qk.prototype.setProperties=Qk.prototype.H;Qk.prototype.unset=Qk.prototype.P;Qk.prototype.changed=Qk.prototype.s;Qk.prototype.dispatchEvent=Qk.prototype.b;Qk.prototype.getRevision=Qk.prototype.L;Qk.prototype.on=Qk.prototype.J;Qk.prototype.once=Qk.prototype.once;
+Qk.prototype.un=Qk.prototype.K;
   return OPENLAYERS.ol;
 }));
 
diff --git a/VIPSWeb/static/js/forecastmap.js b/VIPSWeb/static/js/forecastmap.js
index c3b420d8..09b71c88 100755
--- a/VIPSWeb/static/js/forecastmap.js
+++ b/VIPSWeb/static/js/forecastmap.js
@@ -198,7 +198,7 @@ function initForecastMap(lonLat, zoomLevel, mapAttribution)
           		trigger: 'manual',
           		html: true,
           		placement: "auto top",
-          		title: feature.get("name") + (feature.get("infoUri") != undefined ? 
+          		title: feature.get("stationName") + (feature.get("infoUri") != undefined ? 
           				" <a href='" + feature.get("infoUri") + "' target='new'><span class='wi wi-day-cloudy'></span></a>"
           				:""),
           		content: summariesHTML + externalResourceHTML
@@ -206,73 +206,7 @@ function initForecastMap(lonLat, zoomLevel, mapAttribution)
           	
           	poiDetails.popover('show');
 
-	    	  /*
-	    	  // Fetching information asynchronously from server 
-	    	  var request = $.ajax({
-                    type:"GET",
-                    url: settings.vipslogicProtocol + "://" + settings.vipslogicServerName + "/rest/forecastresults/latest/poi/" + feature.getId(),
-                    statusCode:{
-                        200: function(data,textStatus, jqXHR){
-                        	// Building result HTML
-                        	var resultHTML = []
-                        	for(index in data.results)
-                    		{
-                        		var forecastConfiguration = data.forecastConfigurations[data.results[index].forecastConfigurationId];
-                        		var alertClass = "";
-                        		switch(data.results[index].warningStatus)
-                        		{
-                        			case 1:
-                        			  alertClass = "alert-info";
-                        			  break;
-                        			case 2:
-                        			  alertClass = "alert-success";
-                        			  break;
-                        			case 3:
-                        				alertClass = "alert-warning";
-                        			  break;
-                        			case 4:
-                        				alertClass = "alert-danger";
-                        			  break;
-                        			default:
-                        				alertClass = "alert-nowarning";
-                        		}
-                        		
-                        		resultHTML.push("<div class=\"" + alertClass + "\">" + moment(data.results[index].resultValidTime).format("YYYY-MM-DD") + ": <a href=\"/forecasts/" + forecastConfiguration.forecastConfigurationId + "\" class=\"alert-link\">" + modelLocalNames[forecastConfiguration.modelId] + "</a></div>");
-                    		}
-                        	if(data.pointOfInterest.pointOfInterestExternalResourceSet.length > 0)
-                    		{
-                        		var resourceLink = data.pointOfInterest.pointOfInterestExternalResourceSet[0].externalResourceUrl;
-                				var resourceName = data.pointOfInterest.pointOfInterestExternalResourceSet[0].externalResource.name;
-                        		resultHTML.push("<div><a href=\"" + resourceLink + "\" target=\"new\">" + resourceName + "</div>");
-                        		
-                    		}
-                        	
-                        	if(resultHTML.length == 0)
-                    		{
-                        		resultHTML.push(gettext("No forecasts found for") + " " + feature.get("name"));
-                    		}
-
-                        	// Create the popup, showing it
-                        	poiDetails.popover({
-                        		animation: true,
-                        		trigger: 'manual',
-                        		html: true,
-                        		placement: "auto top",
-                        		title: feature.get("name"),
-                        		content: resultHTML.join("")
-                        	});
-                        	
-                        	poiDetails.popover('show');
-
-                    	},
-                        400: handleAjaxError,
-                        401: handleAjaxError,
-                        404: handleAjaxError,
-                        500: handleAjaxError
-                    }
-                });
-	    	 */
-	    	
+	    	  	    	
 	      } else {
 	    	  currentClickedFeature = null;
 	    	  poiDetails.popover('destroy');
@@ -329,6 +263,7 @@ function getCurrentMapZoomLevel()
 }
 
 
+
 /**
  * Loads new KML info from VIPSLogic
  */
@@ -355,6 +290,7 @@ function updateForecastLayers()
 			projection: ol.proj.get('EPSG:3857')
 		})
 	});
+	//forecastLayer.setStyle(pointStyleFunction);
 	map.addLayer(forecastLayer);
 }
 
diff --git a/applefruitmoth/static/applefruitmoth/js/map.js b/applefruitmoth/static/applefruitmoth/js/map.js
index 4ee73a1d..32af327d 100755
--- a/applefruitmoth/static/applefruitmoth/js/map.js
+++ b/applefruitmoth/static/applefruitmoth/js/map.js
@@ -83,7 +83,7 @@ var initMap = function(container, mapAttribution)
 	          		trigger: 'manual',
 	          		html: true,
 	          		placement: "auto top",
-	          		title: feature.get("name"),
+	          		title: feature.get("siteName"),
 	          		content: generatePopupContents(feature.get("warningStatus"), feature.get("description"))
 	          	});
 	          	
diff --git a/applefruitmoth/templates/applefruitmoth/index.html b/applefruitmoth/templates/applefruitmoth/index.html
index 2b91160d..4915d1f1 100755
--- a/applefruitmoth/templates/applefruitmoth/index.html
+++ b/applefruitmoth/templates/applefruitmoth/index.html
@@ -73,7 +73,7 @@
 <link rel="stylesheet" href="{% static "css/3rdparty/ol.css" %}" type="text/css">
 {% endblock %}
 {% block customJS %}
-<script type="text/javascript" src="{% static "js/3rdparty/ol-debug.js" %}"></script>
+<script type="text/javascript" src="{% static "js/3rdparty/ol.js" %}"></script>
 <script type="text/javascript" src="{% url "django.views.i18n.javascript_catalog" %}"></script>
 <script type="text/javascript" src="{% url "views.settings_js" %}"></script>
 <script type="text/javascript" src="{% static "js/util.js" %}"></script>
diff --git a/observations/templates/observations/detail.html b/observations/templates/observations/detail.html
index 06888ba6..cdf35566 100755
--- a/observations/templates/observations/detail.html
+++ b/observations/templates/observations/detail.html
@@ -56,7 +56,7 @@
 {% block customJS %}
 <script type="text/javascript" src="{% url "django.views.i18n.javascript_catalog" %}"></script>
 <script type="text/javascript" src="{% static "js/3rdparty/moment.min.js" %}"></script>
-<script type="text/javascript" src="{% static "js/3rdparty/ol-debug.js" %}"></script>
+<script type="text/javascript" src="{% static "js/3rdparty/ol.js" %}"></script>
 <script type="text/javascript" src="{% url "views.settings_js" %}"></script>
 <script type="text/javascript" src="{% static "js/util.js" %}"></script>
 <script type="text/javascript" src="{% static "observations/js/observationViewMap.js" %}"></script>
diff --git a/roughage/templates/roughage/nutrition.html b/roughage/templates/roughage/nutrition.html
index 14528470..3eb9322a 100755
--- a/roughage/templates/roughage/nutrition.html
+++ b/roughage/templates/roughage/nutrition.html
@@ -355,7 +355,7 @@
 {% endblock %}
 {% block customJS %}
 <script type="text/javascript" src="{% url "django.views.i18n.javascript_catalog" %}"></script>
-<script type="text/javascript" src="{% static "js/3rdparty/ol-debug.js" %}"></script>
+<script type="text/javascript" src="{% static "js/3rdparty/ol.js" %}"></script>
 <script type="text/javascript" src="{% url "views.settings_js" %}"></script>
 <script type="text/javascript" src="{% static "js/3rdparty/moment.min.js" %}"></script>
 <script type="text/javascript" src="{% static "js/3rdparty/moment-timezone-with-data.min.js" %}"></script>
-- 
GitLab